[PATCH v2 2/2] riscv: stacktrace: Use %pB for return addresses and %pS for exact IPs

Rui Qi qirui.001 at bytedance.com
Fri Jun 5 01:20:34 PDT 2026


The print_trace_address callback uses print_ip_sym which formats
addresses with %pS. This does not adjust the address before symbol
lookup, so when a noreturn function (e.g. panic, do_exit) is the last
call in a function, the saved return address can point to the start of
the next function, and kallsyms resolves it to the wrong symbol.

The kernel provides %pB (sprint_backtrace) specifically for this
purpose: it subtracts 1 from the address before symbol lookup, which
is sufficient to fall back into the calling function range.

However, %pB must only be used for return addresses. walk_stackframe
passes two kinds of addresses to its callback:
- Exact instruction pointers: instruction_pointer(regs) and the epc
  from exception frames on the stack. These should use %pS since they
  are not return addresses and subtracting 1 could cause kallsyms to
  resolve them to the preceding function.
- Return addresses: frame->ra, regs->ra, and task->thread.ra. These
  should use %pB for correct symbol resolution, especially after
  noreturn calls.

Add an 'is_ra' parameter to the walk_stackframe callback to indicate
whether the address is a return address, then use %pB for return
addresses and %pS for exact instruction pointers. This matches the x86
approach which uses %pS for regs->ip via show_ip() and %pB for stack
return addresses via printk_stack_address().

Also update the walk_stackframe() declaration in asm/stacktrace.h and
the fill_callchain() callback in perf_callchain.c to match the new
signature.

Signed-off-by: Rui Qi <qirui.001 at bytedance.com>
---
 arch/riscv/include/asm/stacktrace.h |  2 +-
 arch/riscv/kernel/perf_callchain.c  |  7 +++-
 arch/riscv/kernel/stacktrace.c      | 54 ++++++++++++++++++++++++-----
 3 files changed, 52 insertions(+), 11 deletions(-)

diff --git a/arch/riscv/include/asm/stacktrace.h b/arch/riscv/include/asm/stacktrace.h
index b1495a7e06ce..6771d0e0f656 100644
--- a/arch/riscv/include/asm/stacktrace.h
+++ b/arch/riscv/include/asm/stacktrace.h
@@ -12,7 +12,7 @@ struct stackframe {
 };
 
 extern void notrace walk_stackframe(struct task_struct *task, struct pt_regs *regs,
-				    bool (*fn)(void *, unsigned long), void *arg);
+				    bool (*fn)(void *, unsigned long, bool), void *arg);
 extern void dump_backtrace(struct pt_regs *regs, struct task_struct *task,
 			   const char *loglvl);
 
diff --git a/arch/riscv/kernel/perf_callchain.c b/arch/riscv/kernel/perf_callchain.c
index b465bc9eb870..0a5678f0d400 100644
--- a/arch/riscv/kernel/perf_callchain.c
+++ b/arch/riscv/kernel/perf_callchain.c
@@ -11,6 +11,11 @@ static bool fill_callchain(void *entry, unsigned long pc)
 	return perf_callchain_store(entry, pc) == 0;
 }
 
+static bool fill_callchain_walk(void *entry, unsigned long pc, bool is_ra)
+{
+	return fill_callchain(entry, pc);
+}
+
 /*
  * This will be called when the target is in user mode
  * This function will only be called when we use
@@ -44,5 +49,5 @@ void perf_callchain_kernel(struct perf_callchain_entry_ctx *entry,
 		return;
 	}
 
-	walk_stackframe(NULL, regs, fill_callchain, entry);
+	walk_stackframe(NULL, regs, fill_callchain_walk, entry);
 }
diff --git a/arch/riscv/kernel/stacktrace.c b/arch/riscv/kernel/stacktrace.c
index c7555447149b..502729ab257b 100644
--- a/arch/riscv/kernel/stacktrace.c
+++ b/arch/riscv/kernel/stacktrace.c
@@ -46,32 +46,36 @@ static inline int fp_is_valid(unsigned long fp, unsigned long sp)
 }
 
 void notrace walk_stackframe(struct task_struct *task, struct pt_regs *regs,
-			     bool (*fn)(void *, unsigned long), void *arg)
+			     bool (*fn)(void *, unsigned long, bool), void *arg)
 {
 	unsigned long fp, sp, pc;
 	int graph_idx = 0;
 	int level = 0;
+	bool is_ra;
 
 	if (regs) {
 		fp = frame_pointer(regs);
 		sp = user_stack_pointer(regs);
 		pc = instruction_pointer(regs);
+		is_ra = false;	/* exact instruction pointer */
 	} else if (task == NULL || task == current) {
 		fp = (unsigned long)__builtin_frame_address(0);
 		sp = current_stack_pointer;
 		pc = (unsigned long)walk_stackframe;
+		is_ra = false;	/* function entry address */
 		level = -1;
 	} else {
 		/* task blocked in __switch_to */
 		fp = task->thread.s[0];
 		sp = task->thread.sp;
 		pc = task->thread.ra;
+		is_ra = true;	/* return address */
 	}
 
 	for (;;) {
 		struct stackframe *frame;
 
-		if (unlikely(!__kernel_text_address(pc) || (level++ >= 0 && !fn(arg, pc))))
+		if (unlikely(!__kernel_text_address(pc) || (level++ >= 0 && !fn(arg, pc, is_ra))))
 			break;
 
 		if (unlikely(!fp_is_valid(fp, sp)))
@@ -84,18 +88,21 @@ void notrace walk_stackframe(struct task_struct *task, struct pt_regs *regs,
 			/* We hit function where ra is not saved on the stack */
 			fp = frame->ra;
 			pc = regs->ra;
+			is_ra = true;	/* return address */
 		} else {
 			fp = READ_ONCE_TASK_STACK(task, frame->fp);
 			pc = READ_ONCE_TASK_STACK(task, frame->ra);
 			pc = ftrace_graph_ret_addr(task, &graph_idx, pc,
 						   &frame->ra);
+			is_ra = true;	/* return address */
 			if (pc >= (unsigned long)handle_exception &&
 			    pc < (unsigned long)&ret_from_exception_end) {
-				if (unlikely(!fn(arg, pc)))
+				if (unlikely(!fn(arg, pc, is_ra)))
 					break;
 
 				pc = ((struct pt_regs *)sp)->epc;
 				fp = ((struct pt_regs *)sp)->s0;
+				is_ra = false;	/* exact instruction pointer */
 			}
 		}
 
@@ -105,41 +112,58 @@ void notrace walk_stackframe(struct task_struct *task, struct pt_regs *regs,
 #else /* !CONFIG_FRAME_POINTER */
 
 void notrace walk_stackframe(struct task_struct *task,
-	struct pt_regs *regs, bool (*fn)(void *, unsigned long), void *arg)
+	struct pt_regs *regs, bool (*fn)(void *, unsigned long, bool), void *arg)
 {
 	unsigned long sp, pc;
 	unsigned long *ksp;
+	bool is_ra;
 
 	if (regs) {
 		sp = user_stack_pointer(regs);
 		pc = instruction_pointer(regs);
+		is_ra = false;	/* exact instruction pointer */
 	} else if (task == NULL || task == current) {
 		sp = current_stack_pointer;
 		pc = (unsigned long)walk_stackframe;
+		is_ra = false;	/* function entry address */
 	} else {
 		/* task blocked in __switch_to */
 		sp = task->thread.sp;
 		pc = task->thread.ra;
+		is_ra = true;	/* return address */
 	}
 
 	if (unlikely(sp & 0x7))
 		return;
 
+	/*
+	 * Scan the stack for kernel text addresses.  Without frame pointers
+	 * we cannot distinguish return addresses from other saved text
+	 * addresses (e.g. pt_regs->epc), so all stack values are treated
+	 * as return addresses (is_ra = true).  This means pt_regs->epc
+	 * values found during the scan may be displayed with %pB instead
+	 * of the correct %pS, which can cause misattribution when the
+	 * exact IP is at offset 0 of a function.
+	 */
 	ksp = (unsigned long *)sp;
 	while (!kstack_end(ksp)) {
-		if (__kernel_text_address(pc) && unlikely(!fn(arg, pc)))
+		if (__kernel_text_address(pc) && unlikely(!fn(arg, pc, is_ra)))
 			break;
 		pc = READ_ONCE_NOCHECK(*ksp++);
+		is_ra = true;
 	}
 }
 
 #endif /* CONFIG_FRAME_POINTER */
 
-static bool print_trace_address(void *arg, unsigned long pc)
+static bool print_trace_address(void *arg, unsigned long pc, bool is_ra)
 {
 	const char *loglvl = arg;
 
-	print_ip_sym(loglvl, pc);
+	if (is_ra)
+		printk("%s[<%px>] %pB\n", loglvl, (void *)pc, (void *)pc);
+	else
+		printk("%s[<%px>] %pS\n", loglvl, (void *)pc, (void *)pc);
 	return true;
 }
 
@@ -155,7 +179,7 @@ void show_stack(struct task_struct *task, unsigned long *sp, const char *loglvl)
 	dump_backtrace(NULL, task, loglvl);
 }
 
-static bool save_wchan(void *arg, unsigned long pc)
+static bool save_wchan(void *arg, unsigned long pc, bool is_ra)
 {
 	if (!in_sched_functions(pc)) {
 		unsigned long *p = arg;
@@ -176,10 +200,22 @@ unsigned long __get_wchan(struct task_struct *task)
 	return pc;
 }
 
+struct stack_walk_cookie {
+	stack_trace_consume_fn consume;
+	void *cookie;
+};
+
+static bool stack_walk_wrapper(void *arg, unsigned long pc, bool is_ra)
+{
+	struct stack_walk_cookie *ctx = arg;
+	return ctx->consume(ctx->cookie, pc);
+}
+
 noinline noinstr void arch_stack_walk(stack_trace_consume_fn consume_entry, void *cookie,
 		     struct task_struct *task, struct pt_regs *regs)
 {
-	walk_stackframe(task, regs, consume_entry, cookie);
+	struct stack_walk_cookie ctx = { .consume = consume_entry, .cookie = cookie };
+	walk_stackframe(task, regs, stack_walk_wrapper, &ctx);
 }
 
 /*
-- 
2.20.1



More information about the linux-riscv mailing list