mirror of
https://github.com/torvalds/linux.git
synced 2024-11-25 21:51:40 +00:00
9fbed16c3f
Historically architectures have had duplicated code in their stack trace
implementations for filtering what gets traced. In order to avoid this
duplication some generic code has been provided using a new interface
arch_stack_walk(), enabled by selecting ARCH_STACKWALK in Kconfig, which
factors all this out into the generic stack trace code. Convert ARM to
use this common infrastructure.
When initializing the stack frame of the current task, arm64 uses
__builtin_frame_address(1) to initialize the frame pointer, skipping
arch_stack_walk(), see the commit c607ab4f91
("arm64: stacktrace:
don't trace arch_stack_walk()"). Since __builtin_frame_address(1) does
not work on ARM, unwind_frame() is used to unwind the stack one layer
forward before calling walk_stackframe().
Signed-off-by: Li Huafei <lihuafei1@huawei.com>
Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
198 lines
5.0 KiB
C
198 lines
5.0 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
#include <linux/export.h>
|
|
#include <linux/kprobes.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/sched/debug.h>
|
|
#include <linux/stacktrace.h>
|
|
|
|
#include <asm/sections.h>
|
|
#include <asm/stacktrace.h>
|
|
#include <asm/traps.h>
|
|
|
|
#include "reboot.h"
|
|
|
|
#if defined(CONFIG_FRAME_POINTER) && !defined(CONFIG_ARM_UNWIND)
|
|
/*
|
|
* Unwind the current stack frame and store the new register values in the
|
|
* structure passed as argument. Unwinding is equivalent to a function return,
|
|
* hence the new PC value rather than LR should be used for backtrace.
|
|
*
|
|
* With framepointer enabled, a simple function prologue looks like this:
|
|
* mov ip, sp
|
|
* stmdb sp!, {fp, ip, lr, pc}
|
|
* sub fp, ip, #4
|
|
*
|
|
* A simple function epilogue looks like this:
|
|
* ldm sp, {fp, sp, pc}
|
|
*
|
|
* When compiled with clang, pc and sp are not pushed. A simple function
|
|
* prologue looks like this when built with clang:
|
|
*
|
|
* stmdb {..., fp, lr}
|
|
* add fp, sp, #x
|
|
* sub sp, sp, #y
|
|
*
|
|
* A simple function epilogue looks like this when built with clang:
|
|
*
|
|
* sub sp, fp, #x
|
|
* ldm {..., fp, pc}
|
|
*
|
|
*
|
|
* Note that with framepointer enabled, even the leaf functions have the same
|
|
* prologue and epilogue, therefore we can ignore the LR value in this case.
|
|
*/
|
|
|
|
extern unsigned long call_with_stack_end;
|
|
|
|
static int frame_pointer_check(struct stackframe *frame)
|
|
{
|
|
unsigned long high, low;
|
|
unsigned long fp = frame->fp;
|
|
unsigned long pc = frame->pc;
|
|
|
|
/*
|
|
* call_with_stack() is the only place we allow SP to jump from one
|
|
* stack to another, with FP and SP pointing to different stacks,
|
|
* skipping the FP boundary check at this point.
|
|
*/
|
|
if (pc >= (unsigned long)&call_with_stack &&
|
|
pc < (unsigned long)&call_with_stack_end)
|
|
return 0;
|
|
|
|
/* only go to a higher address on the stack */
|
|
low = frame->sp;
|
|
high = ALIGN(low, THREAD_SIZE);
|
|
|
|
/* check current frame pointer is within bounds */
|
|
#ifdef CONFIG_CC_IS_CLANG
|
|
if (fp < low + 4 || fp > high - 4)
|
|
return -EINVAL;
|
|
#else
|
|
if (fp < low + 12 || fp > high - 4)
|
|
return -EINVAL;
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
int notrace unwind_frame(struct stackframe *frame)
|
|
{
|
|
unsigned long fp = frame->fp;
|
|
|
|
if (frame_pointer_check(frame))
|
|
return -EINVAL;
|
|
|
|
/*
|
|
* When we unwind through an exception stack, include the saved PC
|
|
* value into the stack trace.
|
|
*/
|
|
if (frame->ex_frame) {
|
|
struct pt_regs *regs = (struct pt_regs *)frame->sp;
|
|
|
|
/*
|
|
* We check that 'regs + sizeof(struct pt_regs)' (that is,
|
|
* ®s[1]) does not exceed the bottom of the stack to avoid
|
|
* accessing data outside the task's stack. This may happen
|
|
* when frame->ex_frame is a false positive.
|
|
*/
|
|
if ((unsigned long)®s[1] > ALIGN(frame->sp, THREAD_SIZE))
|
|
return -EINVAL;
|
|
|
|
frame->pc = regs->ARM_pc;
|
|
frame->ex_frame = false;
|
|
return 0;
|
|
}
|
|
|
|
/* restore the registers from the stack frame */
|
|
#ifdef CONFIG_CC_IS_CLANG
|
|
frame->sp = frame->fp;
|
|
frame->fp = READ_ONCE_NOCHECK(*(unsigned long *)(fp));
|
|
frame->pc = READ_ONCE_NOCHECK(*(unsigned long *)(fp + 4));
|
|
#else
|
|
frame->fp = READ_ONCE_NOCHECK(*(unsigned long *)(fp - 12));
|
|
frame->sp = READ_ONCE_NOCHECK(*(unsigned long *)(fp - 8));
|
|
frame->pc = READ_ONCE_NOCHECK(*(unsigned long *)(fp - 4));
|
|
#endif
|
|
#ifdef CONFIG_KRETPROBES
|
|
if (is_kretprobe_trampoline(frame->pc))
|
|
frame->pc = kretprobe_find_ret_addr(frame->tsk,
|
|
(void *)frame->fp, &frame->kr_cur);
|
|
#endif
|
|
|
|
if (in_entry_text(frame->pc))
|
|
frame->ex_frame = true;
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
void notrace walk_stackframe(struct stackframe *frame,
|
|
bool (*fn)(void *, unsigned long), void *data)
|
|
{
|
|
while (1) {
|
|
int ret;
|
|
|
|
if (!fn(data, frame->pc))
|
|
break;
|
|
ret = unwind_frame(frame);
|
|
if (ret < 0)
|
|
break;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(walk_stackframe);
|
|
|
|
#ifdef CONFIG_STACKTRACE
|
|
static void start_stack_trace(struct stackframe *frame, struct task_struct *task,
|
|
unsigned long fp, unsigned long sp,
|
|
unsigned long lr, unsigned long pc)
|
|
{
|
|
frame->fp = fp;
|
|
frame->sp = sp;
|
|
frame->lr = lr;
|
|
frame->pc = pc;
|
|
#ifdef CONFIG_KRETPROBES
|
|
frame->kr_cur = NULL;
|
|
frame->tsk = task;
|
|
#endif
|
|
#ifdef CONFIG_UNWINDER_FRAME_POINTER
|
|
frame->ex_frame = in_entry_text(frame->pc);
|
|
#endif
|
|
}
|
|
|
|
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_stack_trace(&frame, NULL, regs->ARM_fp, regs->ARM_sp,
|
|
regs->ARM_lr, regs->ARM_pc);
|
|
} else if (task != current) {
|
|
#ifdef CONFIG_SMP
|
|
/*
|
|
* What guarantees do we have here that 'tsk' is not
|
|
* running on another CPU? For now, ignore it as we
|
|
* can't guarantee we won't explode.
|
|
*/
|
|
return;
|
|
#else
|
|
start_stack_trace(&frame, task, thread_saved_fp(task),
|
|
thread_saved_sp(task), 0,
|
|
thread_saved_pc(task));
|
|
#endif
|
|
} else {
|
|
here:
|
|
start_stack_trace(&frame, task,
|
|
(unsigned long)__builtin_frame_address(0),
|
|
current_stack_pointer,
|
|
(unsigned long)__builtin_return_address(0),
|
|
(unsigned long)&&here);
|
|
/* skip this function */
|
|
if (unwind_frame(&frame))
|
|
return;
|
|
}
|
|
|
|
walk_stackframe(&frame, consume_entry, cookie);
|
|
}
|
|
#endif
|