[PATCH] x86/paravirt: Force RIP-relative paravirt calls under low optimization levels

From: Zeliang Li

Date: Thu Aug 13 2026 - 15:17:38 EST


When compiling the kernel with non-standard lower optimization levels like
-O1 (e.g., during specific debugging or framework testing setups) using
newer toolchains like GCC 15.2.0, the compiler exhibits passive register
hoisting. In complex code paths like early_fixup_exception(), it caches the
base address of the global 'pv_ops' structure into a general-purpose regist=
er
instead of issuing direct RIP-relative memory loads, producing:

mov $0xffffffff82c3f180, %rbx
call *0x8(%rbx)

While this behavior is bypassed under aggressive -O2 optimizations, under -=
O1
it leaves a register-relative indirect call. This violates the strict forma=
t
assertion in the x86 alternative text-patching engine (alt_replace_call),
which expects an 'ALT_FLAG_DIRECT_CALL' site to be a standard 6-byte
RIP-relative indirect call (ff 15), leading to a boot-time kernel BUG.

Fix this by changing the x86_64 paravirt inline assembly to use an "i"
(immediate) constraint for the function pointer address, and explicitly
reference it via (%rip) in the assembly template. This removes the
toolchain's ability to select any other addressing mode, guaranteeing the
emission of compliant 'call *pv_ops+offset(%rip)' sequences on x86_64
regardless of the active compiler -O flag.

For i386, the original "m" constraint is retained since RIP-relative
addressing does not exist on 32-bit x86.

Signed-off-by: Zeliang Li <lizeliang.linux@xxxxxxxxx>
---
arch/x86/include/asm/paravirt_types.h | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/arch/x86/include/asm/paravirt_types.h
b/arch/x86/include/asm/paravirt_types.h
index b4c4a23e77a1..e8047bdbed3a 100644
--- a/arch/x86/include/asm/paravirt_types.h
+++ b/arch/x86/include/asm/paravirt_types.h
@@ -184,8 +184,6 @@ struct paravirt_patch_template {

extern struct paravirt_patch_template pv_ops;

-#define paravirt_ptr(array, op) [paravirt_opptr] "m" (array.op)
-
/*
* This generates an indirect call based on the operation type number.
*
@@ -197,9 +195,20 @@ extern struct paravirt_patch_template pv_ops;
* OTOH since this is effectively a __nocfi indirect call, the paravirt st=
ubs
* don't need to bother with CFI prefixes.
*/
+#ifdef CONFIG_X86_64
+
+#define paravirt_ptr(array, op) [paravirt_opptr] "i" (&(array.op))
#define PARAVIRT_CALL \
ANNOTATE_RETPOLINE_SAFE "\n\t" \
- "call *%[paravirt_opptr]"
+ "call *%c[paravirt_opptr](%%rip);"
+#else /* CONFIG_X86_32 */
+
+#define paravirt_ptr(array, op) [paravirt_opptr] "m" (array.op)
+#define PARAVIRT_CALL \
+ ANNOTATE_RETPOLINE_SAFE "\n\t" \
+ "call *%[paravirt_opptr];"
+
+#endif /* CONFIG_X86_64 */

/*
* These macros are intended to wrap calls through one of the paravirt
--=20
2.53.0

=E6=9D=8E=E5=88=99=E8=89=AF <lizeliang.linux@xxxxxxxxx> =E4=BA=8E2026=E5=B9=
=B48=E6=9C=8814=E6=97=A5=E5=91=A8=E4=BA=94 03:35=E5=86=99=E9=81=93=EF=BC=9A
>
> >
> > That's not what I asked, now was it. I asked what opcodes it does
> > generate. Disassemble an affected function and show the difference
> > between the normal -O2 and your -O1 build that causes the failure.
> on vmlinux-O1:
> (gdb) until arch/x86/kernel/alternative.c:557
> alt_replace_call (instr=3D0xffffffff82f447e5 <early_fixup_exception+133>
> "\377S\b\220\220\353\371[A\\]\303\314\314\314\314\303\314\314\314\314f\01=
7\037D",
> insn_buff=3D0xffffffff82c03d78 <init_thread_union+15736>
> "\350\263\177\020\377",
> a=3D0xffffffff831dae0e) at arch/x86/kernel/alternative.c:557
> 557 pr_err("ALT_FLAG_DIRECT_CALL set for unrecognized indirect call\n");
> vmlinux-O1:
> 0xffffffff82f447e5 <+133>: ff 53 08 call QWORD PTR [rbx+0x8=
]
> 0xffffffff82f447e8 <+136>: 90 nop
> 0xffffffff82f447e9 <+137>: 90 nop
> 0xffffffff82f447ea <+138>: eb f9 jmp
> 0xffffffff82f447e5 <early_fixup_exception+133>
> 0xffffffff82f447ec <+140>: 5b pop rbx
> 0xffffffff82f447ed <+141>: 41 5c pop r12
>
> vmlinux-O2:
> 0xffffffff82f3bf4b <+123>: 74 b0 je
> 0xffffffff82f3befd <early_fixup_exception+45>
> 0xffffffff82f3bf4d <+125>: eb 08 jmp
> 0xffffffff82f3bf57 <early_fixup_exception+135>
> 0xffffffff82f3bf4f <+127>: ff 15 33 32 d0 ff call QWORD PTR
> [rip+0xffffffffffd03233] # 0xffffffff82c3f188 <pv_ops+8>
> 0xffffffff82f3bf55 <+133>: eb f8 jmp
> 0xffffffff82f3bf4f <early_fixup_exception+127>
> 0xffffffff82f3bf57 <+135>: 5b pop rbx
> 0xffffffff82f3bf58 <+136>: 41 5c pop r12
> 0xffffffff82f3bf5a <+138>: 5d pop rbp
>
> I use the patch below to bypass the problem while keeping all logic of
> alt_replace_call intact.
>
> From 2538684697e537298aa9082b582bc7dddd29b278 Mon Sep 17 00:00:00 2001
> From: Zeliang Li <lizeliang.linux@xxxxxxxxx>
> Date: Fri, 14 Aug 2026 02:32:19 +0800
> Subject: [PATCH] x86/mm: force O2 optimization for early_fixup_exception
>
> When the kernel is compiled with non-standard optimization flags
> (e.g., KCFLAGS=3D-O1), the function early_fixup_exception may be
> compiled to an indirect call instruction that is not recognized
> by alt_replace_call() (e.g., "call *0x8(%rbx)"). This triggers a
> BUG() during early boot, as seen in:
>
> ALT_FLAG_DIRECT_CALL: original instruction is not a 6-byte ...
> instruction at early_fixup_exception+0x85/0xa0, length=3D5
> instruction bytes: ff 53 08 90 90
>
> Force this function to be compiled with -O2 optimization level,
> which guarantees the standard 6-byte `ff 15 <disp32>` form used
> by alternatives patching.
>
> Signed-off-by: Zeliang Li <lizeliang.linux@xxxxxxxxx>
> ---
> arch/x86/mm/extable.c | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/arch/x86/mm/extable.c b/arch/x86/mm/extable.c
> index ceb8d03191ab..5d5c7510a817 100644
> --- a/arch/x86/mm/extable.c
> +++ b/arch/x86/mm/extable.c
> @@ -375,6 +375,7 @@ int fixup_exception(struct pt_regs *regs, int
> trapnr, unsigned long error_code,
> extern unsigned int early_recursion_flag;
>
> /* Restricted version used during very early boot */
> +__attribute__((optimize("O2")))
> void __init early_fixup_exception(struct pt_regs *regs, int trapnr)
> {
> /* Ignore early NMIs. */
> --
> 2.53.0
>
> --
> KISS =3D=3D Keep it simple,stupid~:-)
> http://lizeliang.org



--=20
KISS =3D=3D Keep it simple,stupid~:-)
http://lizeliang.org