[PATCH v5 0/7] riscv: Add reliable stack unwinding for livepatch
Wang Han <[email protected]> Tue, 4 Aug 2026 18:36:19 +0800
| Newsgroups | org.kernel.vger.linux-trace-kernel,org.infradead.lists.linux-riscv,org.kernel.vger.linux-kernel,org.kernel.vger.linux-kselftest,org.kernel.vger.linux-perf-users,org.kernel.vger.live-patching |
|---|---|
| Message-ID | <[email protected]> |
Hi,
This is v5 of the RISC-V reliable stack unwinding series for livepatch.
The series adds explicit frame-record metadata, preserves the frame
pointer through dynamic ftrace, and replaces the open-coded stack walker
with a conservative frame-pointer based unwinder. It then enables the
RISC-V livepatch capability and adds the syscall-wrapper selftest prefix.
Changes since v4:
* Use sizeof(struct frame_record) for STACKFRAME_RECORD_SIZE, as
requested by Shuai Xue.
* Guard IRQ stack accessors and IRQ stack entries with
CONFIG_IRQ_STACKS, addressing the link-time issue reported by Shuai
Xue and Sashiko.
* Add the corresponding Reviewed-by tag from Shuai Xue to 1/7.
* Rebase onto the latest riscv/for-next; no other functional changes.
The review replies have been sent in the v4 RESEND thread.
Base:
riscv/for-next ff492e321c47 ("Revert \"riscv: hwprobe: Fix stale vDSO data for late-initialized keys at boot\"")
Previous versions:
v4 RESEND: https://lore.kernel.org/r/[email protected]
v3: https://lore.kernel.org/r/[email protected]
v2: https://lore.kernel.org/r/[email protected]
v1: https://lore.kernel.org/r/[email protected]
Validation:
* Built Linux 7.2.0-rc6 with riscv64-unknown-linux-gnu-gcc 15.2.0
and binutils 2.46.
* Standard livepatch configuration built successfully, including
Image, vmlinux, modules, compile_commands.json and GDB scripts.
* CONFIG_IRQ_STACKS=n variant linked successfully with GCC.
* QEMU 8.2.2, 1 CPU: livepatch selftests PASS: 7, SKIP: 1,
FAIL: 0; ftrace function graph 3 passed, 0 failed, 3 unsupported.
* QEMU 8.2.2, 4 CPUs: livepatch selftests PASS: 7, SKIP: 1,
FAIL: 0; ftrace function graph 3 passed, 0 failed, 3 unsupported.
* No BUG, WARNING, Oops, panic, unreliable stack or Call Trace found
by the validation log scans. Expected negative livepatch errors are
part of the selftests.
Wang Han (7):
riscv: stacktrace: Add frame record metadata
riscv: stacktrace: disable KASAN and KCOV instrumentation for
stacktrace.o
riscv: ftrace: always preserve s0 in dynamic ftrace register frame
riscv: stacktrace: introduce stack-bound tracking helpers
riscv: stacktrace: switch to frame-pointer based unwinder
riscv: Kconfig: enable HAVE_RELIABLE_STACKTRACE and HAVE_LIVEPATCH
selftests/livepatch: Add RISC-V syscall wrapper prefix
arch/riscv/Kconfig | 4 +
arch/riscv/include/asm/ptrace.h | 9 +
arch/riscv/include/asm/stacktrace.h | 67 ++-
arch/riscv/include/asm/stacktrace/common.h | 159 +++++
arch/riscv/include/asm/stacktrace/frame.h | 53 ++
arch/riscv/kernel/Makefile | 6 +
arch/riscv/kernel/asm-offsets.c | 6 +
arch/riscv/kernel/entry.S | 39 +-
arch/riscv/kernel/ftrace.c | 6 +-
arch/riscv/kernel/head.S | 23 +
arch/riscv/kernel/mcount-dyn.S | 4 -
arch/riscv/kernel/perf_callchain.c | 2 +-
arch/riscv/kernel/process.c | 33 +-
arch/riscv/kernel/stacktrace.c | 561 +++++++++++++++---
.../livepatch/test_modules/test_klp_syscall.c | 2 +
15 files changed, 868 insertions(+), 106 deletions(-)
create mode 100644 arch/riscv/include/asm/stacktrace/common.h
create mode 100644 arch/riscv/include/asm/stacktrace/frame.h
Range-diff against v4:
1: 8cef363cfed7 ! 1: cbb1dc004d0f riscv: stacktrace: Add frame record metadata
@@ Commit message
the secondary CPU path sets up s0 before smp_callin() so idle-task
unwinding does not inherit an undefined caller frame;
* copy_thread creates matching final metadata records for new kernel
- and user tasks, and keeps s0 available for the frame-pointer chain;
- * call_on_irq_stack still reserves an aligned stack slot, but links the
- saved {fp, ra} with the raw frame-record size so s0 points at the
- RISC-V frame record rather than past the alignment padding.
+ and user tasks, and keeps s0 available for the frame-pointer chain.
- The call_on_irq_stack adjustment fixes a latent RV32 issue. On RV64,
- sizeof(struct stackframe) is equal to the stack alignment, so the old
- s0 value happened to point just above the saved {fp, ra}. On RV32, the
- raw frame record is 8 bytes while the reserved stack slot is 16-byte
- aligned, so the old s0 value pointed into the padding. Using the raw
- record size makes s0 point above the saved frame record on both RV32
- and RV64 while still reserving the aligned slot.
+ Keep the embedded metadata-record field offsets distinct from the
+ s0-relative STACKFRAME_* offsets used by call_on_irq_stack(), because
+ the latter describe a frame record relative to s0 rather than to the
+ record base.
These changes keep s0 reserved for the frame-pointer chain at task and
- stack-switch boundaries.
+ exception boundaries.
+ Reviewed-by: Shuai Xue <[email protected]>
Signed-off-by: Wang Han <[email protected]>
## arch/riscv/include/asm/ptrace.h ##
@@ arch/riscv/kernel/asm-offsets.c: void asm_offsets(void)
OFFSET(HIBERN_PBE_ADDR, pbe, address);
@@ arch/riscv/kernel/asm-offsets.c: void asm_offsets(void)
- OFFSET(SBI_HART_BOOT_STACK_PTR_OFFSET, sbi_hart_boot_data, stack_ptr);
-
DEFINE(STACKFRAME_SIZE_ON_STACK, ALIGN(sizeof(struct stackframe), STACK_ALIGN));
-+ DEFINE(STACKFRAME_RECORD_SIZE, sizeof(struct stackframe));
- OFFSET(STACKFRAME_FP, stackframe, fp);
- OFFSET(STACKFRAME_RA, stackframe, ra);
+ DEFINE(STACKFRAME_FP, offsetof(struct stackframe, fp) - sizeof(struct stackframe));
+ DEFINE(STACKFRAME_RA, offsetof(struct stackframe, ra) - sizeof(struct stackframe));
++ DEFINE(STACKFRAME_RECORD_SIZE, sizeof(struct frame_record));
++ OFFSET(FRAME_RECORD_FP, frame_record, fp);
++ OFFSET(FRAME_RECORD_RA, frame_record, ra);
#ifdef CONFIG_FUNCTION_TRACER
+ DEFINE(FTRACE_OPS_FUNC, offsetof(struct ftrace_ops, func));
+ #ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
## arch/riscv/kernel/entry.S ##
@@
@@ arch/riscv/kernel/entry.S: SYM_CODE_START(handle_exception)
+ * Create a metadata frame record. The unwinder will use this to
+ * identify and unwind exception boundaries.
+ */
-+ REG_S zero, (S_STACKFRAME + STACKFRAME_FP)(sp) /* stackframe.record.fp = 0 */
-+ REG_S zero, (S_STACKFRAME + STACKFRAME_RA)(sp) /* stackframe.record.ra = 0 */
++ REG_S zero, (S_STACKFRAME + FRAME_RECORD_FP)(sp) /* stackframe.record.fp = 0 */
++ REG_S zero, (S_STACKFRAME + FRAME_RECORD_RA)(sp) /* stackframe.record.ra = 0 */
+#ifdef CONFIG_RISCV_M_MODE
+ li t0, SR_MPP
+ and t0, s1, t0
@@ arch/riscv/kernel/entry.S: SYM_CODE_START_LOCAL(handle_kernel_stack_overflow)
+ * pt_regs boundary and the unwinder can resume from the pre-overflow
+ * frame pointer saved in PT_S0.
+ */
-+ REG_S zero, (S_STACKFRAME + STACKFRAME_FP)(sp)
-+ REG_S zero, (S_STACKFRAME + STACKFRAME_RA)(sp)
++ REG_S zero, (S_STACKFRAME + FRAME_RECORD_FP)(sp)
++ REG_S zero, (S_STACKFRAME + FRAME_RECORD_RA)(sp)
+ li t0, FRAME_META_TYPE_PT_REGS
+ REG_S t0, S_STACKFRAME_TYPE(sp)
+ addi s0, sp, S_STACKFRAME + STACKFRAME_RECORD_SIZE
@@ arch/riscv/kernel/entry.S: ASM_NOKPROBE(handle_kernel_stack_overflow)
move a2, sp /* pt_regs */
call ret_from_fork_kernel
j ret_from_exception
-@@ arch/riscv/kernel/entry.S: SYM_FUNC_START(call_on_irq_stack)
- addi sp, sp, -STACKFRAME_SIZE_ON_STACK
- REG_S ra, STACKFRAME_RA(sp)
- REG_S s0, STACKFRAME_FP(sp)
-- addi s0, sp, STACKFRAME_SIZE_ON_STACK
-+ addi s0, sp, STACKFRAME_RECORD_SIZE
-
- /* Switch to the per-CPU shadow call stack */
- scs_save_current
-@@ arch/riscv/kernel/entry.S: SYM_FUNC_START(call_on_irq_stack)
- scs_load_current
-
- /* Switch back to the thread stack and restore ra and s0 */
-- addi sp, s0, -STACKFRAME_SIZE_ON_STACK
-+ addi sp, s0, -STACKFRAME_RECORD_SIZE
- REG_L ra, STACKFRAME_RA(sp)
- REG_L s0, STACKFRAME_FP(sp)
- addi sp, sp, STACKFRAME_SIZE_ON_STACK
## arch/riscv/kernel/head.S ##
@@
@@ arch/riscv/kernel/head.S: SYM_CODE_START(_start_kernel)
+ * fp/s0 points above the metadata record (RISC-V
+ * convention).
+ */
-+ REG_S zero, (S_STACKFRAME + STACKFRAME_FP)(sp)
-+ REG_S zero, (S_STACKFRAME + STACKFRAME_RA)(sp)
++ REG_S zero, (S_STACKFRAME + FRAME_RECORD_FP)(sp)
++ REG_S zero, (S_STACKFRAME + FRAME_RECORD_RA)(sp)
+ li t0, FRAME_META_TYPE_FINAL
+ REG_S t0, S_STACKFRAME_TYPE(sp)
+ addi s0, sp, S_STACKFRAME + STACKFRAME_RECORD_SIZE
2: 237864b66d78 = 2: c63daa8759a6 riscv: stacktrace: disable KASAN and KCOV instrumentation for stacktrace.o
3: e6035966a35a = 3: 352269306907 riscv: ftrace: always preserve s0 in dynamic ftrace register frame
4: d132087ea01e ! 4: 2cc86f5728c1 riscv: stacktrace: introduce stack-bound tracking helpers
@@ arch/riscv/include/asm/stacktrace.h: extern void notrace walk_stackframe(struct
+/*
+ * IRQ stack accessors
+ */
++#ifdef CONFIG_IRQ_STACKS
+static inline struct stack_info stackinfo_get_irq(void)
+{
+ unsigned long low = (unsigned long)raw_cpu_read(irq_stack_ptr);
@@ arch/riscv/include/asm/stacktrace.h: extern void notrace walk_stackframe(struct
+
+ return stackinfo_on_stack(&info, sp, size);
+}
++#endif /* CONFIG_IRQ_STACKS */
+
+/*
+ * Task stack accessors
5: 02adea3ece82 ! 5: 57c5e7d08cf8 riscv: stacktrace: switch to frame-pointer based unwinder
@@ arch/riscv/kernel/stacktrace.c: unsigned long __get_wchan(struct task_struct *ta
+ struct task_struct *tsk = task ?: current;
+ struct stack_info stacks[] = {
+ stackinfo_get_task(tsk),
++#ifdef CONFIG_IRQ_STACKS
+ STACKINFO_CPU(tsk, irq),
++#endif
+#ifdef CONFIG_VMAP_STACK
+ STACKINFO_CPU(tsk, overflow),
+#endif
6: c7d7dbe7a8a1 = 6: 9b3379fe9320 riscv: Kconfig: enable HAVE_RELIABLE_STACKTRACE and HAVE_LIVEPATCH
7: ae94a234b34a < -: ------------ selftests/livepatch: Add RISC-V syscall wrapper prefix
-: ------------ > 7: 07dddd25aae1 selftests/livepatch: Add RISC-V syscall wrapper prefix
base-commit: ff492e321c477f0c8899ac2b875635a84e928d78
--
2.43.0