
commit c607ab4f916d4d5259072eca34055d3f5a795c21 upstream. We recently converted arm64 to use arch_stack_walk() in commit:5fc57df2f6
("arm64: stacktrace: Convert to ARCH_STACKWALK") The core stacktrace code expects that (when tracing the current task) arch_stack_walk() starts a trace at its caller, and does not include itself in the trace. However, arm64's arch_stack_walk() includes itself, and so traces include one more entry than callers expect. The core stacktrace code which calls arch_stack_walk() tries to skip a number of entries to prevent itself appearing in a trace, and the additional entry prevents skipping one of the core stacktrace functions, leaving this in the trace unexpectedly. We can fix this by having arm64's arch_stack_walk() begin the trace with its caller. The first value returned by the trace will be __builtin_return_address(0), i.e. the caller of arch_stack_walk(). The first frame record to be unwound will be __builtin_frame_address(1), i.e. the caller's frame record. To prevent surprises, arch_stack_walk() is also marked noinline. While __builtin_frame_address(1) is not safe in portable code, local GCC developers have confirmed that it is safe on arm64. To find the caller's frame record, the builtin can safely dereference the current function's frame record or (in theory) could stash the original FP into another GPR at function entry time, neither of which are problematic. Prior to this patch, the tracing code would unexpectedly show up in traces of the current task, e.g. | # cat /proc/self/stack | [<0>] stack_trace_save_tsk+0x98/0x100 | [<0>] proc_pid_stack+0xb4/0x130 | [<0>] proc_single_show+0x60/0x110 | [<0>] seq_read_iter+0x230/0x4d0 | [<0>] seq_read+0xdc/0x130 | [<0>] vfs_read+0xac/0x1e0 | [<0>] ksys_read+0x6c/0xfc | [<0>] __arm64_sys_read+0x20/0x30 | [<0>] el0_svc_common.constprop.0+0x60/0x120 | [<0>] do_el0_svc+0x24/0x90 | [<0>] el0_svc+0x2c/0x54 | [<0>] el0_sync_handler+0x1a4/0x1b0 | [<0>] el0_sync+0x170/0x180 After this patch, the tracing code will not show up in such traces: | # cat /proc/self/stack | [<0>] proc_pid_stack+0xb4/0x130 | [<0>] proc_single_show+0x60/0x110 | [<0>] seq_read_iter+0x230/0x4d0 | [<0>] seq_read+0xdc/0x130 | [<0>] vfs_read+0xac/0x1e0 | [<0>] ksys_read+0x6c/0xfc | [<0>] __arm64_sys_read+0x20/0x30 | [<0>] el0_svc_common.constprop.0+0x60/0x120 | [<0>] do_el0_svc+0x24/0x90 | [<0>] el0_svc+0x2c/0x54 | [<0>] el0_sync_handler+0x1a4/0x1b0 | [<0>] el0_sync+0x170/0x180 Erring on the side of caution, I've given this a spin with a bunch of toolchains, verifying the output of /proc/self/stack and checking that the assembly looked sound. For GCC (where we require version 5.1.0 or later) I tested with the kernel.org crosstool binares for versions 5.5.0, 6.4.0, 6.5.0, 7.3.0, 7.5.0, 8.1.0, 8.3.0, 8.4.0, 9.2.0, and 10.1.0. For clang (where we require version 10.0.1 or later) I tested with the llvm.org binary releases of 11.0.0, and 11.0.1. Fixes:5fc57df2f6
("arm64: stacktrace: Convert to ARCH_STACKWALK") Signed-off-by: Mark Rutland <mark.rutland@arm.com> Cc: Catalin Marinas <catalin.marinas@arm.com> Cc: Chen Jun <chenjun102@huawei.com> Cc: Marco Elver <elver@google.com> Cc: Mark Brown <broonie@kernel.org> Cc: Will Deacon <will@kernel.org> Cc: <stable@vger.kernel.org> # 5.10.x Reviewed-by: Catalin Marinas <catalin.marinas@arm.com> Reviewed-by: Mark Brown <broonie@kernel.org> Link: https://lore.kernel.org/r/20210319184106.5688-1-mark.rutland@arm.com Signed-off-by: Will Deacon <will@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
222 lines
5.3 KiB
C
222 lines
5.3 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Stack tracing support
|
|
*
|
|
* Copyright (C) 2012 ARM Ltd.
|
|
*/
|
|
#include <linux/kernel.h>
|
|
#include <linux/export.h>
|
|
#include <linux/ftrace.h>
|
|
#include <linux/kprobes.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/sched/debug.h>
|
|
#include <linux/sched/task_stack.h>
|
|
#include <linux/stacktrace.h>
|
|
|
|
#include <asm/irq.h>
|
|
#include <asm/pointer_auth.h>
|
|
#include <asm/stack_pointer.h>
|
|
#include <asm/stacktrace.h>
|
|
|
|
/*
|
|
* AArch64 PCS assigns the frame pointer to x29.
|
|
*
|
|
* A simple function prologue looks like this:
|
|
* sub sp, sp, #0x10
|
|
* stp x29, x30, [sp]
|
|
* mov x29, sp
|
|
*
|
|
* A simple function epilogue looks like this:
|
|
* mov sp, x29
|
|
* ldp x29, x30, [sp]
|
|
* add sp, sp, #0x10
|
|
*/
|
|
|
|
/*
|
|
* Unwind from one frame record (A) to the next frame record (B).
|
|
*
|
|
* We terminate early if the location of B indicates a malformed chain of frame
|
|
* records (e.g. a cycle), determined based on the location and fp value of A
|
|
* and the location (but not the fp value) of B.
|
|
*/
|
|
int notrace unwind_frame(struct task_struct *tsk, struct stackframe *frame)
|
|
{
|
|
unsigned long fp = frame->fp;
|
|
struct stack_info info;
|
|
|
|
if (fp & 0xf)
|
|
return -EINVAL;
|
|
|
|
if (!tsk)
|
|
tsk = current;
|
|
|
|
if (!on_accessible_stack(tsk, fp, &info))
|
|
return -EINVAL;
|
|
|
|
if (test_bit(info.type, frame->stacks_done))
|
|
return -EINVAL;
|
|
|
|
/*
|
|
* As stacks grow downward, any valid record on the same stack must be
|
|
* at a strictly higher address than the prior record.
|
|
*
|
|
* Stacks can nest in several valid orders, e.g.
|
|
*
|
|
* TASK -> IRQ -> OVERFLOW -> SDEI_NORMAL
|
|
* TASK -> SDEI_NORMAL -> SDEI_CRITICAL -> OVERFLOW
|
|
*
|
|
* ... but the nesting itself is strict. Once we transition from one
|
|
* stack to another, it's never valid to unwind back to that first
|
|
* stack.
|
|
*/
|
|
if (info.type == frame->prev_type) {
|
|
if (fp <= frame->prev_fp)
|
|
return -EINVAL;
|
|
} else {
|
|
set_bit(frame->prev_type, frame->stacks_done);
|
|
}
|
|
|
|
/*
|
|
* Record this frame record's values and location. The prev_fp and
|
|
* prev_type are only meaningful to the next unwind_frame() invocation.
|
|
*/
|
|
frame->fp = READ_ONCE_NOCHECK(*(unsigned long *)(fp));
|
|
frame->pc = READ_ONCE_NOCHECK(*(unsigned long *)(fp + 8));
|
|
frame->prev_fp = fp;
|
|
frame->prev_type = info.type;
|
|
|
|
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
|
|
if (tsk->ret_stack &&
|
|
(ptrauth_strip_insn_pac(frame->pc) == (unsigned long)return_to_handler)) {
|
|
struct ftrace_ret_stack *ret_stack;
|
|
/*
|
|
* This is a case where function graph tracer has
|
|
* modified a return address (LR) in a stack frame
|
|
* to hook a function return.
|
|
* So replace it to an original value.
|
|
*/
|
|
ret_stack = ftrace_graph_get_ret_stack(tsk, frame->graph++);
|
|
if (WARN_ON_ONCE(!ret_stack))
|
|
return -EINVAL;
|
|
frame->pc = ret_stack->ret;
|
|
}
|
|
#endif /* CONFIG_FUNCTION_GRAPH_TRACER */
|
|
|
|
frame->pc = ptrauth_strip_insn_pac(frame->pc);
|
|
|
|
/*
|
|
* Frames created upon entry from EL0 have NULL FP and PC values, so
|
|
* don't bother reporting these. Frames created by __noreturn functions
|
|
* might have a valid FP even if PC is bogus, so only terminate where
|
|
* both are NULL.
|
|
*/
|
|
if (!frame->fp && !frame->pc)
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
NOKPROBE_SYMBOL(unwind_frame);
|
|
|
|
void notrace walk_stackframe(struct task_struct *tsk, struct stackframe *frame,
|
|
bool (*fn)(void *, unsigned long), void *data)
|
|
{
|
|
while (1) {
|
|
int ret;
|
|
|
|
if (!fn(data, frame->pc))
|
|
break;
|
|
ret = unwind_frame(tsk, frame);
|
|
if (ret < 0)
|
|
break;
|
|
}
|
|
}
|
|
NOKPROBE_SYMBOL(walk_stackframe);
|
|
|
|
static void dump_backtrace_entry(unsigned long where, const char *loglvl)
|
|
{
|
|
printk("%s %pS\n", loglvl, (void *)where);
|
|
}
|
|
|
|
void dump_backtrace(struct pt_regs *regs, struct task_struct *tsk,
|
|
const char *loglvl)
|
|
{
|
|
struct stackframe frame;
|
|
int skip = 0;
|
|
|
|
pr_debug("%s(regs = %p tsk = %p)\n", __func__, regs, tsk);
|
|
|
|
if (regs) {
|
|
if (user_mode(regs))
|
|
return;
|
|
skip = 1;
|
|
}
|
|
|
|
if (!tsk)
|
|
tsk = current;
|
|
|
|
if (!try_get_task_stack(tsk))
|
|
return;
|
|
|
|
if (tsk == current) {
|
|
start_backtrace(&frame,
|
|
(unsigned long)__builtin_frame_address(0),
|
|
(unsigned long)dump_backtrace);
|
|
} else {
|
|
/*
|
|
* task blocked in __switch_to
|
|
*/
|
|
start_backtrace(&frame,
|
|
thread_saved_fp(tsk),
|
|
thread_saved_pc(tsk));
|
|
}
|
|
|
|
printk("%sCall trace:\n", loglvl);
|
|
do {
|
|
/* skip until specified stack frame */
|
|
if (!skip) {
|
|
dump_backtrace_entry(frame.pc, loglvl);
|
|
} else if (frame.fp == regs->regs[29]) {
|
|
skip = 0;
|
|
/*
|
|
* Mostly, this is the case where this function is
|
|
* called in panic/abort. As exception handler's
|
|
* stack frame does not contain the corresponding pc
|
|
* at which an exception has taken place, use regs->pc
|
|
* instead.
|
|
*/
|
|
dump_backtrace_entry(regs->pc, loglvl);
|
|
}
|
|
} while (!unwind_frame(tsk, &frame));
|
|
|
|
put_task_stack(tsk);
|
|
}
|
|
|
|
void show_stack(struct task_struct *tsk, unsigned long *sp, const char *loglvl)
|
|
{
|
|
dump_backtrace(NULL, tsk, loglvl);
|
|
barrier();
|
|
}
|
|
|
|
#ifdef CONFIG_STACKTRACE
|
|
|
|
noinline void arch_stack_walk(stack_trace_consume_fn consume_entry,
|
|
void *cookie, struct task_struct *task,
|
|
struct pt_regs *regs)
|
|
{
|
|
struct stackframe frame;
|
|
|
|
if (regs)
|
|
start_backtrace(&frame, regs->regs[29], regs->pc);
|
|
else if (task == current)
|
|
start_backtrace(&frame,
|
|
(unsigned long)__builtin_frame_address(1),
|
|
(unsigned long)__builtin_return_address(0));
|
|
else
|
|
start_backtrace(&frame, thread_saved_fp(task),
|
|
thread_saved_pc(task));
|
|
|
|
walk_stackframe(task, &frame, consume_entry, cookie);
|
|
}
|
|
|
|
#endif
|