arch/risc-v: preserve ra across ecall for fp-chain backtrace

sys_callN() wraps a bare ecall and calls no other function, so the
compiler treats it as a leaf function: with frame pointers enabled,
it only needs to spill the caller's s0, which it places in what
up_backtrace()'s fp-chain walk assumes is ra's stack slot, while the
real ra slot is never written. sched_backtrace() then misreads that
slot as the return address for this frame, either resolving to a
bogus symbol or, if the adjacent garbage happens to look
out-of-range, terminating the backtrace early.

Add "ra" to the ecall clobber list so the compiler spills/reloads ra
around the ecall like a normal call site, keeping ra and the saved
s0 in their expected slots. Gate this on
CONFIG_FRAME_POINTER && CONFIG_SCHED_BACKTRACE, the only combination
where up_backtrace()'s fp-chain walk is both valid (FRAME_POINTER)
and actually exercised (SCHED_BACKTRACE); other configurations keep
the original "memory"-only clobber and pay no extra cost.

This only fixes the syscall boundary. Leaf functions that do not
cross a syscall (e.g. up_idle()) can still lose their ra slot the
same way and are not addressed here.

Signed-off-by: liang.huang <liang.huang@houmo.ai>
This commit is contained in:
liang.huang 2026-07-15 12:13:31 +08:00 committed by Xiang Xiao
parent 73b3487e70
commit cbe4ceb158

View file

@ -139,6 +139,23 @@ uintptr_t sys_call6(unsigned int nbr, uintptr_t parm1, uintptr_t parm2,
uintptr_t parm3, uintptr_t parm4, uintptr_t parm5,
uintptr_t parm6);
#else
/* ecall is a leaf "call" as far as the compiler can tell: sys_callN()
* itself never calls another function, so with frame pointers enabled
* the compiler stores the caller's saved fp (s0) at what up_backtrace()
* assumes is ra's slot, leaving ra's own slot never written. Clobbering
* "ra" forces it to be spilled/reloaded around the ecall so the fp-chain
* backtrace sched_backtrace() relies on can find it. Only worth the
* extra spill when both frame pointers and backtrace support are built
* in; otherwise there is no fp-chain consumer to fix up for.
*/
#if defined(CONFIG_FRAME_POINTER) && defined(CONFIG_SCHED_BACKTRACE)
# define RISCV_ECALL_CLOBBERS "memory", "ra"
#else
# define RISCV_ECALL_CLOBBERS "memory"
#endif
/****************************************************************************
* Name: sys_call0
*
@ -155,7 +172,7 @@ static inline uintptr_t sys_call0(unsigned int nbr)
(
"ecall"
:: "r"(r0)
: "memory"
: RISCV_ECALL_CLOBBERS
);
asm volatile("nop" : "=r"(r0));
@ -180,7 +197,7 @@ static inline uintptr_t sys_call1(unsigned int nbr, uintptr_t parm1)
(
"ecall"
:: "r"(r0), "r"(r1)
: "memory"
: RISCV_ECALL_CLOBBERS
);
asm volatile("nop" : "=r"(r0));
@ -207,7 +224,7 @@ static inline uintptr_t sys_call2(unsigned int nbr, uintptr_t parm1,
(
"ecall"
:: "r"(r0), "r"(r1), "r"(r2)
: "memory"
: RISCV_ECALL_CLOBBERS
);
asm volatile("nop" : "=r"(r0));
@ -235,7 +252,7 @@ static inline uintptr_t sys_call3(unsigned int nbr, uintptr_t parm1,
(
"ecall"
:: "r"(r0), "r"(r1), "r"(r2), "r"(r3)
: "memory"
: RISCV_ECALL_CLOBBERS
);
asm volatile("nop" : "=r"(r0));
@ -265,7 +282,7 @@ static inline uintptr_t sys_call4(unsigned int nbr, uintptr_t parm1,
(
"ecall"
:: "r"(r0), "r"(r1), "r"(r2), "r"(r3), "r"(r4)
: "memory"
: RISCV_ECALL_CLOBBERS
);
asm volatile("nop" : "=r"(r0));
@ -296,7 +313,7 @@ static inline uintptr_t sys_call5(unsigned int nbr, uintptr_t parm1,
(
"ecall"
:: "r"(r0), "r"(r1), "r"(r2), "r"(r3), "r"(r4), "r"(r5)
: "memory"
: RISCV_ECALL_CLOBBERS
);
asm volatile("nop" : "=r"(r0));
@ -329,7 +346,7 @@ static inline uintptr_t sys_call6(unsigned int nbr, uintptr_t parm1,
(
"ecall"
:: "r"(r0), "r"(r1), "r"(r2), "r"(r3), "r"(r4), "r"(r5), "r"(r6)
: "memory"
: RISCV_ECALL_CLOBBERS
);
asm volatile("nop" : "=r"(r0));