Linux Trace Kernel
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Shuai Xue" <xueshuai@linux.alibaba.com>,
	palmer@dabbelt.com, pjw@kernel.org, aou@eecs.berkeley.edu
Cc: alex@ghiti.fr, oleg@redhat.com, rostedt@goodmis.org,
	mhiramat@kernel.org, mark.rutland@arm.com, peterz@infradead.org,
	mingo@redhat.com, acme@kernel.org, namhyung@kernel.org,
	alexander.shishkin@linux.intel.com, jolsa@kernel.org,
	irogers@google.com, adrian.hunter@intel.com,
	james.clark@linaro.org, jpoimboe@kernel.org, jikos@kernel.org,
	mbenes@suse.cz, pmladek@suse.com, joe.lawrence@redhat.com,
	shuah@kernel.org, mpdesouza@suse.com, martin@kaiser.cx,
	kmehltretter@gmail.com, oliver.yang@linux.alibaba.com,
	zhuo.song@linux.alibaba.com, jkchen@linux.alibaba.com,
	linux-trace-kernel@vger.kernel.org,
	linux-perf-users@vger.kernel.org, live-patching@vger.kernel.org
Subject: Re: [PATCH v7 08/10] riscv: stacktrace: switch to frame-pointer based unwinder
Date: Tue, 10 Nov 2026 02:20:51 +0000	[thread overview]
Message-ID: <sashiko-outbox-206040@kernel.org> (raw)
In-Reply-To: <20261110013750.36800-9-xueshuai@linux.alibaba.com>

Thank you for your contribution! Sashiko AI review found 4 potential
issues to consider:
- [High] riscv: stacktrace: broken leaf-function unwind in
  kunwind_next_frame_record()
- [High] riscv: stacktrace: broken unwind across ftrace_caller and
  return_to_handler
- [Medium] riscv: stacktrace: bogus walk_stackframe entry in
  __walk_stackframe()
- [Low] riscv: stacktrace: inaccurate metadata type offset comment
--

commit 263985113a916e8cd08cc4485092812e49e28303
Author: Wang Han <wanghan@linux.alibaba.com>

riscv: stacktrace: switch to frame-pointer based unwinder

This commit replaces the open-coded frame-pointer stack walker with a
kunwind state machine, adds arch_stack_walk_reliable(), and routes
perf_callchain_kernel(), dump_backtrace(), and __get_wchan() through
arch_stack_walk().

> diff --git a/arch/riscv/kernel/stacktrace.c b/arch/riscv/kernel/stacktrace.c
> index a5abb705d43ba..490092c28d612 100644
> --- a/arch/riscv/kernel/stacktrace.c
> +++ b/arch/riscv/kernel/stacktrace.c

[ ... ]

> +/*
> + * Unwind from one frame record to the next.
> + *
> + * On RISC-V, the frame record sits at fp - sizeof(struct frame_record),
> + * immediately below the address pointed to by fp/s0. This applies to both
> + * normal frame records and metadata frame records (embedded in pt_regs).
> + *
> + * A metadata record is identified by both fp and ra being zero in the
> + * frame_record portion, with a type value following at fp + 16.
> + */

[Severity: Low]
This isn't a bug, but since fp points immediately above struct
frame_record on RISC-V (meta_base = fp - sizeof(struct frame_record)),
meta->type is located at fp rather than fp + 16.

> +static __always_inline int
> +kunwind_next_frame_record(struct kunwind_state *state)
> +{
> +	unsigned long fp = state->common.fp;
> +	struct frame_record *record;
> +	struct stack_info *info;
> +	unsigned long new_fp, new_pc;
> +	unsigned long record_base;
> +
> +	if (fp & 0x7)
> +		return -EINVAL;
> +
> +	record_base = fp - sizeof(*record);
> +
> +	info = unwind_find_stack(&state->common, record_base, sizeof(*record));
> +	if (!info)
> +		return -EINVAL;
> +
> +	record = (struct frame_record *)record_base;
> +	new_fp = READ_ONCE(record->fp);
> +	new_pc = READ_ONCE(record->ra);
> +
> +	if (!new_fp && !new_pc)
> +		return kunwind_next_frame_record_meta(state);
> +
> +	unwind_consume_stack(&state->common, info, record_base,
> +			     sizeof(*record));
> +
> +	state->common.fp = new_fp;
> +	state->common.pc = new_pc;
> +	state->source = KUNWIND_SOURCE_FRAME;
> +
> +	return 0;
> +}

[Severity: High]
Does unwinding from KUNWIND_SOURCE_REGS_PC corrupt the unwind and leak
kernel stack addresses when an exception or interrupt hits a leaf
function?

On RISC-V with CONFIG_FRAME_POINTER=y, leaf functions do not push ra onto
the stack: they save only the caller's s0 at fp - sizeof(unsigned long)
(which aliases record->ra), leave the return address in regs->ra, and
leave fp - 2 * sizeof(unsigned long) (record->fp) as uninitialized stack
padding or local storage.

The previous unwinder checked for this case, whereas kunwind_init_from_regs()
and kunwind_next_regs_pc() store regs in state->regs without ever reading
it back, and kunwind_next_frame_record() unconditionally reads record->ra
as new_pc and record->fp as new_fp.  Because __kernel_text_address()
filtering is also removed and perf_callchain_kernel() now calls
arch_stack_walk(), a PMU sample in a leaf function passes the caller's
saved kernel stack pointer in record->ra to fill_callchain() ->
perf_callchain_store(), exposing it to userspace, while regs->ra is lost
and the uninitialized word in record->fp is used as the next frame
pointer.

[ ... ]

> +static __always_inline bool
> +arch_reliable_kunwind_consume_entry(const struct kunwind_state *state, void *cookie)
> +{
> +	/*
> +	 * At an exception boundary we can reliably consume the saved PC. We do
> +	 * not know whether ra was live when the exception was taken, and
> +	 * so we cannot perform the next unwind step reliably.
> +	 *
> +	 * All that matters is whether the *entire* unwind is reliable, so give
> +	 * up as soon as we hit an exception boundary.
> +	 */
> +	if (state->source == KUNWIND_SOURCE_REGS_PC)
> +		return false;
> +
> +	return arch_kunwind_consume_entry(state, cookie);
> +}

[Severity: High]
Can kunwind_next_frame_record() and arch_stack_walk_reliable() unwind
across ftrace_caller and return_to_handler?

Unlike arm64, RISC-V's ftrace trampolines do not maintain a valid struct
frame_record chain at s0 - sizeof(struct frame_record):

arch/riscv/kernel/mcount-dyn.S:SAVE_ABI_REGS {
	addi	sp, sp, -FREGS_SIZE_ON_STACK
	REG_S	t0,  FREGS_EPC(sp)
	REG_S	x1,  FREGS_RA(sp)
	REG_S	x8,  FREGS_S0(sp)
    ...
}

arch/riscv/kernel/mcount.S:SAVE_RET_ABI_STATE {
	addi	sp, sp, -FREGS_SIZE_ON_STACK
	REG_S	ra, FREGS_RA(sp)
	REG_S	s0, FREGS_S0(sp)
	REG_S	a0, FREGS_A0(sp)
	REG_S	a1, FREGS_A1(sp)
	addi	s0, sp, FREGS_SIZE_ON_STACK
}

In ftrace_caller, SAVE_ABI_REGS saves ftrace_regs without updating s0 or
creating a frame record, so a C tracer callback saves the traced
function's caller s0 alongside a return address in ftrace_caller.  Walking
past the callback jumps straight from ftrace_caller to the caller of the
traced function, silently skipping both the traced function and its
immediate caller while arch_stack_walk_reliable() still succeeds.  (Commit
"riscv: ftrace: always preserve s0 in dynamic ftrace register frame" noted
that the reliable unwinder would read ftrace_regs_get_frame_pointer(fregs)
directly, but kunwind has no ftrace_regs handling.)

In return_to_handler, SAVE_RET_ABI_STATE sets s0 to sp +
FREGS_SIZE_ON_STACK after saving only ra, s0, a0, and a1.  When unwinding
past ftrace_return_to_handler(), kunwind_next_frame_record() reads
s0 - sizeof(struct frame_record), which sits in the uninitialized upper
words of the __arch_ftrace_regs allocation rather than at FREGS_S0(sp).

[ ... ]

> +/*
>   * The generic code expects the first entry for the current task to be the
>   * return address of arch_stack_walk() itself, the way x86 and arm64 report
> - * it, and stack_trace_save() sizes its skip count accordingly.  Walking from
> - * inside a called walk_stackframe() adds that function's own frame on top,
> - * so inline the walker here and start from this frame instead.
> + * it, and stack_trace_save() sizes its skip count accordingly.  Walking
> + * from inside a called walk_stackframe() adds that function's own frame
> + * on top, so call the always-inlined walker here and start from this
> + * frame instead.
>   */
> -noinline noinstr void arch_stack_walk(stack_trace_consume_fn consume_entry, void *cookie,
> -		     struct task_struct *task, struct pt_regs *regs)
> +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);
>  }

[Severity: Medium]
On !CONFIG_FRAME_POINTER builds, doesn't __walk_stackframe() still report
walk_stackframe as the first entry when unwinding the current task?

arch/riscv/kernel/stacktrace.c:__walk_stackframe() {
    ...
	} else if (task == NULL || task == current) {
		sp = current_stack_pointer;
		pc = (unsigned long)walk_stackframe;
	} else {
    ...
	ksp = (unsigned long *)sp;
	while (!kstack_end(ksp)) {
		if (__kernel_text_address(pc) && unlikely(!fn(arg, pc)))
			break;
		pc = READ_ONCE_NOCHECK(*ksp++);
	}
}

Unlike the removed CONFIG_FRAME_POINTER version (which started at
level = -1 to skip the placeholder pc), the !CONFIG_FRAME_POINTER loop
calls fn(arg, pc) before reading *ksp++.  As a result, arch_stack_walk()
reports walk_stackframe before scanning its own stack frame, shifting
stack_trace_save() and return_address() off by one and printing a bogus
walk_stackframe entry in dump_backtrace().

Also, since all callers of walk_stackframe() are converted to
arch_stack_walk() by this patch, walk_stackframe() has no remaining
callers and its extern declaration in arch/riscv/include/asm/stacktrace.h
is left without a definition when CONFIG_FRAME_POINTER=y.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261110013750.36800-1-xueshuai@linux.alibaba.com?part=8

  reply	other threads:[~2026-11-10  2:20 UTC|newest]

Thread overview: 39+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-14  9:26 [PATCH v6 0/7] riscv: Add reliable stack unwinding for livepatch Shuai Xue
2026-09-14  9:26 ` [PATCH v6 1/7] riscv: stacktrace: Add frame record metadata Shuai Xue
2026-09-14  9:40   ` sashiko-bot
2026-09-14  9:26 ` [PATCH v6 2/7] riscv: stacktrace: disable KASAN and KCOV instrumentation for stacktrace.o Shuai Xue
2026-09-14  9:32   ` sashiko-bot
2026-09-14  9:26 ` [PATCH v6 3/7] riscv: ftrace: always preserve s0 in dynamic ftrace register frame Shuai Xue
2026-09-14  9:32   ` sashiko-bot
2026-09-14  9:26 ` [PATCH v6 4/7] riscv: stacktrace: introduce stack-bound tracking helpers Shuai Xue
2026-09-14  9:33   ` sashiko-bot
2026-09-14  9:26 ` [PATCH v6 5/7] riscv: stacktrace: switch to frame-pointer based unwinder Shuai Xue
2026-09-14  9:39   ` sashiko-bot
2026-10-09 18:59   ` Karl Mehltretter
2026-10-10  3:38     ` Shuai Xue
2026-10-13 20:18   ` Martin Kaiser
2026-10-14 13:09     ` Shuai Xue
2026-09-14  9:26 ` [PATCH v6 6/7] riscv: Kconfig: enable HAVE_RELIABLE_STACKTRACE and HAVE_LIVEPATCH Shuai Xue
2026-09-14  9:38   ` sashiko-bot
2026-09-14  9:26 ` [PATCH v6 7/7] selftests/livepatch: Add RISC-V syscall wrapper prefix Shuai Xue
2026-09-14  9:38   ` sashiko-bot
2026-09-29  7:28 ` [PATCH v6 0/7] riscv: Add reliable stack unwinding for livepatch Martin Kaiser
2026-10-08 11:42   ` Shuai Xue
2026-10-12  8:44     ` Martin Kaiser
2026-11-10  1:37 ` [PATCH v7 00/10] " Shuai Xue
2026-11-10  1:37   ` [PATCH v7 01/10] riscv: stacktrace: do not report the walker's own frame from arch_stack_walk() Shuai Xue
2026-11-10  1:52     ` sashiko-bot
2026-11-10  1:37   ` [PATCH v7 02/10] riscv: Add KUnit test for stack trace start frames Shuai Xue
2026-11-10  1:50     ` sashiko-bot
2026-11-10  1:37   ` [PATCH v7 03/10] riscv: ftrace: use the frame pointer as the function graph return-address cookie Shuai Xue
2026-11-10  1:37   ` [PATCH v7 04/10] riscv: stacktrace: Add frame record metadata Shuai Xue
2026-11-10  1:54     ` sashiko-bot
2026-11-10  1:37   ` [PATCH v7 05/10] riscv: stacktrace: disable KASAN and KCOV instrumentation for stacktrace.o Shuai Xue
2026-11-10  1:37   ` [PATCH v7 06/10] riscv: ftrace: always preserve s0 in dynamic ftrace register frame Shuai Xue
2026-11-10  1:37   ` [PATCH v7 07/10] riscv: stacktrace: introduce stack-bound tracking helpers Shuai Xue
2026-11-10  1:37   ` [PATCH v7 08/10] riscv: stacktrace: switch to frame-pointer based unwinder Shuai Xue
2026-11-10  2:20     ` sashiko-bot [this message]
2026-11-10  1:37   ` [PATCH v7 09/10] riscv: Kconfig: enable HAVE_RELIABLE_STACKTRACE and HAVE_LIVEPATCH Shuai Xue
2026-11-10  2:10     ` sashiko-bot
2026-11-10  1:37   ` [PATCH v7 10/10] selftests/livepatch: Add RISC-V syscall wrapper prefix Shuai Xue
2026-11-10  6:35   ` [PATCH v7 00/10] riscv: Add reliable stack unwinding for livepatch Karl Mehltretter

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-206040@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=acme@kernel.org \
    --cc=adrian.hunter@intel.com \
    --cc=alex@ghiti.fr \
    --cc=alexander.shishkin@linux.intel.com \
    --cc=aou@eecs.berkeley.edu \
    --cc=irogers@google.com \
    --cc=james.clark@linaro.org \
    --cc=jikos@kernel.org \
    --cc=jkchen@linux.alibaba.com \
    --cc=joe.lawrence@redhat.com \
    --cc=jolsa@kernel.org \
    --cc=jpoimboe@kernel.org \
    --cc=kmehltretter@gmail.com \
    --cc=linux-perf-users@vger.kernel.org \
    --cc=linux-trace-kernel@vger.kernel.org \
    --cc=live-patching@vger.kernel.org \
    --cc=mark.rutland@arm.com \
    --cc=martin@kaiser.cx \
    --cc=mbenes@suse.cz \
    --cc=mhiramat@kernel.org \
    --cc=mingo@redhat.com \
    --cc=mpdesouza@suse.com \
    --cc=namhyung@kernel.org \
    --cc=oleg@redhat.com \
    --cc=oliver.yang@linux.alibaba.com \
    --cc=palmer@dabbelt.com \
    --cc=peterz@infradead.org \
    --cc=pjw@kernel.org \
    --cc=pmladek@suse.com \
    --cc=rostedt@goodmis.org \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=shuah@kernel.org \
    --cc=xueshuai@linux.alibaba.com \
    --cc=zhuo.song@linux.alibaba.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox