arch/mips/kernel/relocate.c | 11 +++++++++++ 1 file changed, 11 insertions(+)
On MIPS, __current_thread_info is defined as global register variable
locating in $gp, and is simply assigned with new address during kernel
relocation.
This however is broken with LLVM, which always restores $gp if it finds
$gp is clobbered in any form, including when intentionally through a
global register variable. This is against GCC's documentation[1], which
requires a callee-saved register used as global register variable not to
be restored if it's clobbered.
As a result, $gp will continue to point to the unrelocated kernel after
the epilog of relocate_kernel(), leading to an early crash in init_idle,
[ 0.000000] CPU 0 Unable to handle kernel paging request at virtual address 0000000000000000, epc == ffffffff81afada8, ra == ffffffff81afad90
[ 0.000000] Oops[#1]:
[ 0.000000] CPU: 0 UID: 0 PID: 0 Comm: swapper Tainted: G W 6.19.0-rc5-00262-gd3eeb99bbc99-dirty #188 VOLUNTARY
[ 0.000000] Tainted: [W]=WARN
[ 0.000000] Hardware name: loongson,loongson64v-4core-virtio
[ 0.000000] $ 0 : 0000000000000000 0000000000000000 0000000000000001 0000000000000000
[ 0.000000] $ 4 : ffffffff80b80ec0 ffffffff80b53d48 0000000000000000 00000000000f4240
[ 0.000000] $ 8 : 0000000000000100 ffffffff81d82f80 ffffffff81d82f80 0000000000000001
[ 0.000000] $12 : 0000000000000000 ffffffff81776f58 00000000000005da 0000000000000002
[ 0.000000] $16 : ffffffff80b80e40 0000000000000000 ffffffff80b81614 9800000005dfbe80
[ 0.000000] $20 : 00000000540000e0 ffffffff81980000 0000000000000000 ffffffff80f81c80
[ 0.000000] $24 : 0000000000000a26 ffffffff8114fb90
[ 0.000000] $28 : ffffffff80b50000 ffffffff80b53d40 0000000000000000 ffffffff81afad90
[ 0.000000] Hi : 0000000000000000
[ 0.000000] Lo : 0000000000000000
[ 0.000000] epc : ffffffff81afada8 init_idle+0x130/0x270
[ 0.000000] ra : ffffffff81afad90 init_idle+0x118/0x270
[ 0.000000] Status: 540000e2 KX SX UX KERNEL EXL
[ 0.000000] Cause : 00000008 (ExcCode 02)
[ 0.000000] BadVA : 0000000000000000
[ 0.000000] PrId : 00006305 (ICT Loongson-3)
[ 0.000000] Process swapper (pid: 0, threadinfo=(____ptrval____), task=(____ptrval____), tls=0000000000000000)
[ 0.000000] Stack : 9800000005dfbf00 ffffffff8178e950 0000000000000000 0000000000000000
[ 0.000000] 0000000000000000 ffffffff81970000 000000000000003f ffffffff810a6528
[ 0.000000] 0000000000000001 9800000005dfbe80 9800000005dfbf00 ffffffff81980000
[ 0.000000] ffffffff810a6450 ffffffff81afb6c0 0000000000000000 ffffffff810a2258
[ 0.000000] ffffffff81d82ec8 ffffffff8198d010 ffffffff81b67e80 ffffffff8197dd98
[ 0.000000] ffffffff81d81c80 ffffffff81930000 0000000000000040 0000000000000000
[ 0.000000] 0000000000000000 0000000000000000 0000000000000000 0000000000000000
[ 0.000000] 0000000000000000 000000000000009e ffffffff9fc01000 0000000000000000
[ 0.000000] 0000000000000000 0000000000000000 0000000000000000 0000000000000000
[ 0.000000] 0000000000000000 ffffffff81ae86dc ffffffff81b3c741 0000000000000002
[ 0.000000] ...
[ 0.000000] Call Trace:
[ 0.000000] [<ffffffff81afada8>] init_idle+0x130/0x270
[ 0.000000] [<ffffffff81afb6c0>] sched_init+0x5c8/0x6c0
[ 0.000000] [<ffffffff81ae86dc>] start_kernel+0x27c/0x7a8
This bug has been reported to LLVM[2] and affects version from (at
least) 18 to 21. Let's work around this by using inline assembly to
assign $gp before a fix is widely available.
Cc: stable@vger.kernel.org
Link: https://gcc.gnu.org/onlinedocs/gcc-15.2.0/gcc/Global-Register-Variables.html # [1]
Link: https://github.com/llvm/llvm-project/issues/176546 # [2]
Signed-off-by: Yao Zi <me@ziyao.cc>
---
arch/mips/kernel/relocate.c | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/arch/mips/kernel/relocate.c b/arch/mips/kernel/relocate.c
index 7f1c136ad850..12aa0bbdd65e 100644
--- a/arch/mips/kernel/relocate.c
+++ b/arch/mips/kernel/relocate.c
@@ -420,7 +420,18 @@ void *__init relocate_kernel(void)
goto out;
/* The current thread is now within the relocated image */
+#ifndef CONFIG_CC_IS_CLANG
__current_thread_info = RELOCATED(&init_thread_union);
+#else
+ /*
+ * LLVM may wrongly restore $gp ($28) in epilog even if it's
+ * intentionally modified. Work around this by using inline
+ * assembly to assign $gp. $gp couldn't be listed as output or
+ * clobber, or LLVM will still restore its original value.
+ */
+ asm volatile("move $28, %0" : :
+ "r" (RELOCATED(&init_thread_union)));
+#endif
/* Return the new kernel's entry point */
kernel_entry = RELOCATED(start_kernel);
--
2.52.0
On Sun, Jan 18, 2026 at 09:02:35AM +0000, Yao Zi wrote:
> On MIPS, __current_thread_info is defined as global register variable
> locating in $gp, and is simply assigned with new address during kernel
> relocation.
>
> This however is broken with LLVM, which always restores $gp if it finds
> $gp is clobbered in any form, including when intentionally through a
> global register variable. This is against GCC's documentation[1], which
> requires a callee-saved register used as global register variable not to
> be restored if it's clobbered.
>
> As a result, $gp will continue to point to the unrelocated kernel after
> the epilog of relocate_kernel(), leading to an early crash in init_idle,
>
> [ 0.000000] CPU 0 Unable to handle kernel paging request at virtual address 0000000000000000, epc == ffffffff81afada8, ra == ffffffff81afad90
> [ 0.000000] Oops[#1]:
> [ 0.000000] CPU: 0 UID: 0 PID: 0 Comm: swapper Tainted: G W 6.19.0-rc5-00262-gd3eeb99bbc99-dirty #188 VOLUNTARY
> [ 0.000000] Tainted: [W]=WARN
> [ 0.000000] Hardware name: loongson,loongson64v-4core-virtio
> [ 0.000000] $ 0 : 0000000000000000 0000000000000000 0000000000000001 0000000000000000
> [ 0.000000] $ 4 : ffffffff80b80ec0 ffffffff80b53d48 0000000000000000 00000000000f4240
> [ 0.000000] $ 8 : 0000000000000100 ffffffff81d82f80 ffffffff81d82f80 0000000000000001
> [ 0.000000] $12 : 0000000000000000 ffffffff81776f58 00000000000005da 0000000000000002
> [ 0.000000] $16 : ffffffff80b80e40 0000000000000000 ffffffff80b81614 9800000005dfbe80
> [ 0.000000] $20 : 00000000540000e0 ffffffff81980000 0000000000000000 ffffffff80f81c80
> [ 0.000000] $24 : 0000000000000a26 ffffffff8114fb90
> [ 0.000000] $28 : ffffffff80b50000 ffffffff80b53d40 0000000000000000 ffffffff81afad90
> [ 0.000000] Hi : 0000000000000000
> [ 0.000000] Lo : 0000000000000000
> [ 0.000000] epc : ffffffff81afada8 init_idle+0x130/0x270
> [ 0.000000] ra : ffffffff81afad90 init_idle+0x118/0x270
> [ 0.000000] Status: 540000e2 KX SX UX KERNEL EXL
> [ 0.000000] Cause : 00000008 (ExcCode 02)
> [ 0.000000] BadVA : 0000000000000000
> [ 0.000000] PrId : 00006305 (ICT Loongson-3)
> [ 0.000000] Process swapper (pid: 0, threadinfo=(____ptrval____), task=(____ptrval____), tls=0000000000000000)
> [ 0.000000] Stack : 9800000005dfbf00 ffffffff8178e950 0000000000000000 0000000000000000
> [ 0.000000] 0000000000000000 ffffffff81970000 000000000000003f ffffffff810a6528
> [ 0.000000] 0000000000000001 9800000005dfbe80 9800000005dfbf00 ffffffff81980000
> [ 0.000000] ffffffff810a6450 ffffffff81afb6c0 0000000000000000 ffffffff810a2258
> [ 0.000000] ffffffff81d82ec8 ffffffff8198d010 ffffffff81b67e80 ffffffff8197dd98
> [ 0.000000] ffffffff81d81c80 ffffffff81930000 0000000000000040 0000000000000000
> [ 0.000000] 0000000000000000 0000000000000000 0000000000000000 0000000000000000
> [ 0.000000] 0000000000000000 000000000000009e ffffffff9fc01000 0000000000000000
> [ 0.000000] 0000000000000000 0000000000000000 0000000000000000 0000000000000000
> [ 0.000000] 0000000000000000 ffffffff81ae86dc ffffffff81b3c741 0000000000000002
> [ 0.000000] ...
> [ 0.000000] Call Trace:
> [ 0.000000] [<ffffffff81afada8>] init_idle+0x130/0x270
> [ 0.000000] [<ffffffff81afb6c0>] sched_init+0x5c8/0x6c0
> [ 0.000000] [<ffffffff81ae86dc>] start_kernel+0x27c/0x7a8
>
> This bug has been reported to LLVM[2] and affects version from (at
> least) 18 to 21. Let's work around this by using inline assembly to
> assign $gp before a fix is widely available.
>
> Cc: stable@vger.kernel.org
> Link: https://gcc.gnu.org/onlinedocs/gcc-15.2.0/gcc/Global-Register-Variables.html # [1]
> Link: https://github.com/llvm/llvm-project/issues/176546 # [2]
> Signed-off-by: Yao Zi <me@ziyao.cc>
Acked-by: Nathan Chancellor <nathan@kernel.org>
Hopefully the MIPS LLVM folks can look into this (even though I think
the MIPS backend is one of the less maintained backends in LLVM).
> ---
> arch/mips/kernel/relocate.c | 11 +++++++++++
> 1 file changed, 11 insertions(+)
>
> diff --git a/arch/mips/kernel/relocate.c b/arch/mips/kernel/relocate.c
> index 7f1c136ad850..12aa0bbdd65e 100644
> --- a/arch/mips/kernel/relocate.c
> +++ b/arch/mips/kernel/relocate.c
> @@ -420,7 +420,18 @@ void *__init relocate_kernel(void)
> goto out;
>
> /* The current thread is now within the relocated image */
> +#ifndef CONFIG_CC_IS_CLANG
I find
#ifdef FOO
<FOO block>
#else
<!FOO block>
#endif
to be easier to read and process
#ifndef FOO
<!FOO block>
#else
<FOO block>
#endif
but maybe that it is just personal preference.
> __current_thread_info = RELOCATED(&init_thread_union);
> +#else
> + /*
> + * LLVM may wrongly restore $gp ($28) in epilog even if it's
> + * intentionally modified. Work around this by using inline
> + * assembly to assign $gp. $gp couldn't be listed as output or
> + * clobber, or LLVM will still restore its original value.
This comment should likely include a link to the LLVM upstream report to
make it easier to version restrict this workaround when fixed in the
future.
> + */
> + asm volatile("move $28, %0" : :
> + "r" (RELOCATED(&init_thread_union)));
> +#endif
>
> /* Return the new kernel's entry point */
> kernel_entry = RELOCATED(start_kernel);
> --
> 2.52.0
>
On Mon, Feb 02, 2026 at 04:23:09PM -0700, Nathan Chancellor wrote: > On Sun, Jan 18, 2026 at 09:02:35AM +0000, Yao Zi wrote: > > On MIPS, __current_thread_info is defined as global register variable > > locating in $gp, and is simply assigned with new address during kernel > > relocation. > > > > This however is broken with LLVM, which always restores $gp if it finds > > $gp is clobbered in any form, including when intentionally through a > > global register variable. This is against GCC's documentation[1], which > > requires a callee-saved register used as global register variable not to > > be restored if it's clobbered. > > > > As a result, $gp will continue to point to the unrelocated kernel after > > the epilog of relocate_kernel(), leading to an early crash in init_idle, > > > > [ 0.000000] CPU 0 Unable to handle kernel paging request at virtual address 0000000000000000, epc == ffffffff81afada8, ra == ffffffff81afad90 > > [ 0.000000] Oops[#1]: > > [ 0.000000] CPU: 0 UID: 0 PID: 0 Comm: swapper Tainted: G W 6.19.0-rc5-00262-gd3eeb99bbc99-dirty #188 VOLUNTARY > > [ 0.000000] Tainted: [W]=WARN > > [ 0.000000] Hardware name: loongson,loongson64v-4core-virtio > > [ 0.000000] $ 0 : 0000000000000000 0000000000000000 0000000000000001 0000000000000000 > > [ 0.000000] $ 4 : ffffffff80b80ec0 ffffffff80b53d48 0000000000000000 00000000000f4240 > > [ 0.000000] $ 8 : 0000000000000100 ffffffff81d82f80 ffffffff81d82f80 0000000000000001 > > [ 0.000000] $12 : 0000000000000000 ffffffff81776f58 00000000000005da 0000000000000002 > > [ 0.000000] $16 : ffffffff80b80e40 0000000000000000 ffffffff80b81614 9800000005dfbe80 > > [ 0.000000] $20 : 00000000540000e0 ffffffff81980000 0000000000000000 ffffffff80f81c80 > > [ 0.000000] $24 : 0000000000000a26 ffffffff8114fb90 > > [ 0.000000] $28 : ffffffff80b50000 ffffffff80b53d40 0000000000000000 ffffffff81afad90 > > [ 0.000000] Hi : 0000000000000000 > > [ 0.000000] Lo : 0000000000000000 > > [ 0.000000] epc : ffffffff81afada8 init_idle+0x130/0x270 > > [ 0.000000] ra : ffffffff81afad90 init_idle+0x118/0x270 > > [ 0.000000] Status: 540000e2 KX SX UX KERNEL EXL > > [ 0.000000] Cause : 00000008 (ExcCode 02) > > [ 0.000000] BadVA : 0000000000000000 > > [ 0.000000] PrId : 00006305 (ICT Loongson-3) > > [ 0.000000] Process swapper (pid: 0, threadinfo=(____ptrval____), task=(____ptrval____), tls=0000000000000000) > > [ 0.000000] Stack : 9800000005dfbf00 ffffffff8178e950 0000000000000000 0000000000000000 > > [ 0.000000] 0000000000000000 ffffffff81970000 000000000000003f ffffffff810a6528 > > [ 0.000000] 0000000000000001 9800000005dfbe80 9800000005dfbf00 ffffffff81980000 > > [ 0.000000] ffffffff810a6450 ffffffff81afb6c0 0000000000000000 ffffffff810a2258 > > [ 0.000000] ffffffff81d82ec8 ffffffff8198d010 ffffffff81b67e80 ffffffff8197dd98 > > [ 0.000000] ffffffff81d81c80 ffffffff81930000 0000000000000040 0000000000000000 > > [ 0.000000] 0000000000000000 0000000000000000 0000000000000000 0000000000000000 > > [ 0.000000] 0000000000000000 000000000000009e ffffffff9fc01000 0000000000000000 > > [ 0.000000] 0000000000000000 0000000000000000 0000000000000000 0000000000000000 > > [ 0.000000] 0000000000000000 ffffffff81ae86dc ffffffff81b3c741 0000000000000002 > > [ 0.000000] ... > > [ 0.000000] Call Trace: > > [ 0.000000] [<ffffffff81afada8>] init_idle+0x130/0x270 > > [ 0.000000] [<ffffffff81afb6c0>] sched_init+0x5c8/0x6c0 > > [ 0.000000] [<ffffffff81ae86dc>] start_kernel+0x27c/0x7a8 > > > > This bug has been reported to LLVM[2] and affects version from (at > > least) 18 to 21. Let's work around this by using inline assembly to > > assign $gp before a fix is widely available. > > > > Cc: stable@vger.kernel.org > > Link: https://gcc.gnu.org/onlinedocs/gcc-15.2.0/gcc/Global-Register-Variables.html # [1] > > Link: https://github.com/llvm/llvm-project/issues/176546 # [2] > > Signed-off-by: Yao Zi <me@ziyao.cc> > > Acked-by: Nathan Chancellor <nathan@kernel.org> > > Hopefully the MIPS LLVM folks can look into this (even though I think > the MIPS backend is one of the less maintained backends in LLVM). Sigh... > > --- > > arch/mips/kernel/relocate.c | 11 +++++++++++ > > 1 file changed, 11 insertions(+) > > > > diff --git a/arch/mips/kernel/relocate.c b/arch/mips/kernel/relocate.c > > index 7f1c136ad850..12aa0bbdd65e 100644 > > --- a/arch/mips/kernel/relocate.c > > +++ b/arch/mips/kernel/relocate.c > > @@ -420,7 +420,18 @@ void *__init relocate_kernel(void) > > goto out; > > > > /* The current thread is now within the relocated image */ > > +#ifndef CONFIG_CC_IS_CLANG > > I find > > #ifdef FOO > <FOO block> > #else > <!FOO block> > #endif > > to be easier to read and process > > #ifndef FOO > <!FOO block> > #else > <FOO block> > #endif > > but maybe that it is just personal preference. I preferred to put Clang's workaround later, since > > __current_thread_info = RELOCATED(&init_thread_union); this simple assignment is easier to read and more clear than the inline assembly, and matching the comment /* The current thread is now within the relocated image */ better. But yes, this is basically a personal preference, and I'm happy with both. Please tell me if you do prefer the other. > > +#else > > + /* > > + * LLVM may wrongly restore $gp ($28) in epilog even if it's > > + * intentionally modified. Work around this by using inline > > + * assembly to assign $gp. $gp couldn't be listed as output or > > + * clobber, or LLVM will still restore its original value. > > This comment should likely include a link to the LLVM upstream report to > make it easier to version restrict this workaround when fixed in the > future. Sure, will do it in v2. Thanks for the feedback! Best regards, Yao Zi
On Sun, Jan 18, 2026 at 09:02:35AM +0000, Yao Zi wrote: > On MIPS, __current_thread_info is defined as global register variable > locating in $gp, and is simply assigned with new address during kernel > relocation. > > This however is broken with LLVM, which always restores $gp if it finds > $gp is clobbered in any form, including when intentionally through a > global register variable. This is against GCC's documentation[1], which > requires a callee-saved register used as global register variable not to > be restored if it's clobbered. > > As a result, $gp will continue to point to the unrelocated kernel after > the epilog of relocate_kernel(), leading to an early crash in init_idle, > > [ 0.000000] CPU 0 Unable to handle kernel paging request at virtual address 0000000000000000, epc == ffffffff81afada8, ra == ffffffff81afad90 > [ 0.000000] Oops[#1]: > [ 0.000000] CPU: 0 UID: 0 PID: 0 Comm: swapper Tainted: G W 6.19.0-rc5-00262-gd3eeb99bbc99-dirty #188 VOLUNTARY > [ 0.000000] Tainted: [W]=WARN > [ 0.000000] Hardware name: loongson,loongson64v-4core-virtio > [ 0.000000] $ 0 : 0000000000000000 0000000000000000 0000000000000001 0000000000000000 > [ 0.000000] $ 4 : ffffffff80b80ec0 ffffffff80b53d48 0000000000000000 00000000000f4240 > [ 0.000000] $ 8 : 0000000000000100 ffffffff81d82f80 ffffffff81d82f80 0000000000000001 > [ 0.000000] $12 : 0000000000000000 ffffffff81776f58 00000000000005da 0000000000000002 > [ 0.000000] $16 : ffffffff80b80e40 0000000000000000 ffffffff80b81614 9800000005dfbe80 > [ 0.000000] $20 : 00000000540000e0 ffffffff81980000 0000000000000000 ffffffff80f81c80 > [ 0.000000] $24 : 0000000000000a26 ffffffff8114fb90 > [ 0.000000] $28 : ffffffff80b50000 ffffffff80b53d40 0000000000000000 ffffffff81afad90 > [ 0.000000] Hi : 0000000000000000 > [ 0.000000] Lo : 0000000000000000 > [ 0.000000] epc : ffffffff81afada8 init_idle+0x130/0x270 > [ 0.000000] ra : ffffffff81afad90 init_idle+0x118/0x270 > [ 0.000000] Status: 540000e2 KX SX UX KERNEL EXL > [ 0.000000] Cause : 00000008 (ExcCode 02) > [ 0.000000] BadVA : 0000000000000000 > [ 0.000000] PrId : 00006305 (ICT Loongson-3) > [ 0.000000] Process swapper (pid: 0, threadinfo=(____ptrval____), task=(____ptrval____), tls=0000000000000000) > [ 0.000000] Stack : 9800000005dfbf00 ffffffff8178e950 0000000000000000 0000000000000000 > [ 0.000000] 0000000000000000 ffffffff81970000 000000000000003f ffffffff810a6528 > [ 0.000000] 0000000000000001 9800000005dfbe80 9800000005dfbf00 ffffffff81980000 > [ 0.000000] ffffffff810a6450 ffffffff81afb6c0 0000000000000000 ffffffff810a2258 > [ 0.000000] ffffffff81d82ec8 ffffffff8198d010 ffffffff81b67e80 ffffffff8197dd98 > [ 0.000000] ffffffff81d81c80 ffffffff81930000 0000000000000040 0000000000000000 > [ 0.000000] 0000000000000000 0000000000000000 0000000000000000 0000000000000000 > [ 0.000000] 0000000000000000 000000000000009e ffffffff9fc01000 0000000000000000 > [ 0.000000] 0000000000000000 0000000000000000 0000000000000000 0000000000000000 > [ 0.000000] 0000000000000000 ffffffff81ae86dc ffffffff81b3c741 0000000000000002 > [ 0.000000] ... > [ 0.000000] Call Trace: > [ 0.000000] [<ffffffff81afada8>] init_idle+0x130/0x270 > [ 0.000000] [<ffffffff81afb6c0>] sched_init+0x5c8/0x6c0 > [ 0.000000] [<ffffffff81ae86dc>] start_kernel+0x27c/0x7a8 > > This bug has been reported to LLVM[2] and affects version from (at > least) 18 to 21. Let's work around this by using inline assembly to > assign $gp before a fix is widely available. > > Cc: stable@vger.kernel.org > Link: https://gcc.gnu.org/onlinedocs/gcc-15.2.0/gcc/Global-Register-Variables.html # [1] > Link: https://github.com/llvm/llvm-project/issues/176546 # [2] > Signed-off-by: Yao Zi <me@ziyao.cc> Gently ping on this patch. Thanks for your time and review. Regards, Yao Zi
© 2016 - 2026 Red Hat, Inc.