2c79bd34af
Make arch_kunwind_consume_entry() as __always_inline otherwise the compiler might not inline it and allow attaching probes to it. Without this, just probing arch_kunwind_consume_entry() via <tracefs>/kprobe_events will crash the kernel on arm64. The crash can be reproduced using the following compiler and kernel combination: clang version 19.0.0git (https://github.com/llvm/llvm-project.git d68d29516102252f6bf6dc23fb22cef144ca1cb3) commit87adedeba5("Merge tag 'net-6.8-rc7' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net") [root@localhost ~]# echo 'p arch_kunwind_consume_entry' > /sys/kernel/debug/tracing/kprobe_events [root@localhost ~]# echo 1 > /sys/kernel/debug/tracing/events/kprobes/enable Modules linked in: aes_ce_blk aes_ce_cipher ghash_ce sha2_ce virtio_net sha256_arm64 sha1_ce arm_smccc_trng net_failover failover virtio_mmio uio_pdrv_genirq uio sch_fq_codel dm_mod dax configfs CPU: 3 PID: 1405 Comm: bash Not tainted 6.8.0-rc6+ #14 Hardware name: linux,dummy-virt (DT) pstate: 604003c5 (nZCv DAIF +PAN -UAO -TCO -DIT -SSBS BTYPE=--) pc : kprobe_breakpoint_handler+0x17c/0x258 lr : kprobe_breakpoint_handler+0x17c/0x258 sp : ffff800085d6ab60 x29: ffff800085d6ab60 x28: ffff0000066f0040 x27: ffff0000066f0b20 x26: ffff800081fa7b0c x25: 0000000000000002 x24: ffff00000b29bd18 x23: ffff00007904c590 x22: ffff800081fa6590 x21: ffff800081fa6588 x20: ffff00000b29bd18 x19: ffff800085d6ac40 x18: 0000000000000079 x17: 0000000000000001 x16: ffffffffffffffff x15: 0000000000000004 x14: ffff80008277a940 x13: 0000000000000003 x12: 0000000000000003 x11: 00000000fffeffff x10: c0000000fffeffff x9 : aa95616fdf80cc00 x8 : aa95616fdf80cc00 x7 : 205d343137373231 x6 : ffff800080fb48ec x5 : 0000000000000000 x4 : 0000000000000001 x3 : 0000000000000000 x2 : 0000000000000000 x1 : ffff800085d6a910 x0 : 0000000000000079 Call trace: kprobes: Failed to recover from reentered kprobes. kprobes: Dump kprobe: .symbol_name = arch_kunwind_consume_entry, .offset = 0, .addr = arch_kunwind_consume_entry+0x0/0x40 ------------[ cut here ]------------ kernel BUG at arch/arm64/kernel/probes/kprobes.c:241! kprobes: Failed to recover from reentered kprobes. kprobes: Dump kprobe: .symbol_name = arch_kunwind_consume_entry, .offset = 0, .addr = arch_kunwind_consume_entry+0x0/0x40 Fixes:1aba06e7b2("arm64: stacktrace: factor out kunwind_stack_walk()") Signed-off-by: Puranjay Mohan <puranjay12@gmail.com> Reviewed-by: Mark Rutland <mark.rutland@arm.com> Link: https://lore.kernel.org/r/20240229231620.24846-1-puranjay12@gmail.com Signed-off-by: Will Deacon <will@kernel.org>
301 lines
7.1 KiB
C
301 lines
7.1 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Stack tracing support
|
|
*
|
|
* Copyright (C) 2012 ARM Ltd.
|
|
*/
|
|
#include <linux/kernel.h>
|
|
#include <linux/efi.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/efi.h>
|
|
#include <asm/irq.h>
|
|
#include <asm/stack_pointer.h>
|
|
#include <asm/stacktrace.h>
|
|
|
|
/*
|
|
* Kernel unwind state
|
|
*
|
|
* @common: Common unwind state.
|
|
* @task: The task being unwound.
|
|
* @kr_cur: When KRETPROBES is selected, holds the kretprobe instance
|
|
* associated with the most recently encountered replacement lr
|
|
* value.
|
|
*/
|
|
struct kunwind_state {
|
|
struct unwind_state common;
|
|
struct task_struct *task;
|
|
#ifdef CONFIG_KRETPROBES
|
|
struct llist_node *kr_cur;
|
|
#endif
|
|
};
|
|
|
|
static __always_inline void
|
|
kunwind_init(struct kunwind_state *state,
|
|
struct task_struct *task)
|
|
{
|
|
unwind_init_common(&state->common);
|
|
state->task = task;
|
|
}
|
|
|
|
/*
|
|
* Start an unwind from a pt_regs.
|
|
*
|
|
* The unwind will begin at the PC within the regs.
|
|
*
|
|
* The regs must be on a stack currently owned by the calling task.
|
|
*/
|
|
static __always_inline void
|
|
kunwind_init_from_regs(struct kunwind_state *state,
|
|
struct pt_regs *regs)
|
|
{
|
|
kunwind_init(state, current);
|
|
|
|
state->common.fp = regs->regs[29];
|
|
state->common.pc = regs->pc;
|
|
}
|
|
|
|
/*
|
|
* Start an unwind from a caller.
|
|
*
|
|
* The unwind will begin at the caller of whichever function this is inlined
|
|
* into.
|
|
*
|
|
* The function which invokes this must be noinline.
|
|
*/
|
|
static __always_inline void
|
|
kunwind_init_from_caller(struct kunwind_state *state)
|
|
{
|
|
kunwind_init(state, current);
|
|
|
|
state->common.fp = (unsigned long)__builtin_frame_address(1);
|
|
state->common.pc = (unsigned long)__builtin_return_address(0);
|
|
}
|
|
|
|
/*
|
|
* Start an unwind from a blocked task.
|
|
*
|
|
* The unwind will begin at the blocked tasks saved PC (i.e. the caller of
|
|
* cpu_switch_to()).
|
|
*
|
|
* The caller should ensure the task is blocked in cpu_switch_to() for the
|
|
* duration of the unwind, or the unwind will be bogus. It is never valid to
|
|
* call this for the current task.
|
|
*/
|
|
static __always_inline void
|
|
kunwind_init_from_task(struct kunwind_state *state,
|
|
struct task_struct *task)
|
|
{
|
|
kunwind_init(state, task);
|
|
|
|
state->common.fp = thread_saved_fp(task);
|
|
state->common.pc = thread_saved_pc(task);
|
|
}
|
|
|
|
static __always_inline int
|
|
kunwind_recover_return_address(struct kunwind_state *state)
|
|
{
|
|
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
|
|
if (state->task->ret_stack &&
|
|
(state->common.pc == (unsigned long)return_to_handler)) {
|
|
unsigned long orig_pc;
|
|
orig_pc = ftrace_graph_ret_addr(state->task, NULL,
|
|
state->common.pc,
|
|
(void *)state->common.fp);
|
|
if (WARN_ON_ONCE(state->common.pc == orig_pc))
|
|
return -EINVAL;
|
|
state->common.pc = orig_pc;
|
|
}
|
|
#endif /* CONFIG_FUNCTION_GRAPH_TRACER */
|
|
|
|
#ifdef CONFIG_KRETPROBES
|
|
if (is_kretprobe_trampoline(state->common.pc)) {
|
|
unsigned long orig_pc;
|
|
orig_pc = kretprobe_find_ret_addr(state->task,
|
|
(void *)state->common.fp,
|
|
&state->kr_cur);
|
|
state->common.pc = orig_pc;
|
|
}
|
|
#endif /* CONFIG_KRETPROBES */
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
static __always_inline int
|
|
kunwind_next(struct kunwind_state *state)
|
|
{
|
|
struct task_struct *tsk = state->task;
|
|
unsigned long fp = state->common.fp;
|
|
int err;
|
|
|
|
/* Final frame; nothing to unwind */
|
|
if (fp == (unsigned long)task_pt_regs(tsk)->stackframe)
|
|
return -ENOENT;
|
|
|
|
err = unwind_next_frame_record(&state->common);
|
|
if (err)
|
|
return err;
|
|
|
|
state->common.pc = ptrauth_strip_kernel_insn_pac(state->common.pc);
|
|
|
|
return kunwind_recover_return_address(state);
|
|
}
|
|
|
|
typedef bool (*kunwind_consume_fn)(const struct kunwind_state *state, void *cookie);
|
|
|
|
static __always_inline void
|
|
do_kunwind(struct kunwind_state *state, kunwind_consume_fn consume_state,
|
|
void *cookie)
|
|
{
|
|
if (kunwind_recover_return_address(state))
|
|
return;
|
|
|
|
while (1) {
|
|
int ret;
|
|
|
|
if (!consume_state(state, cookie))
|
|
break;
|
|
ret = kunwind_next(state);
|
|
if (ret < 0)
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Per-cpu stacks are only accessible when unwinding the current task in a
|
|
* non-preemptible context.
|
|
*/
|
|
#define STACKINFO_CPU(name) \
|
|
({ \
|
|
((task == current) && !preemptible()) \
|
|
? stackinfo_get_##name() \
|
|
: stackinfo_get_unknown(); \
|
|
})
|
|
|
|
/*
|
|
* SDEI stacks are only accessible when unwinding the current task in an NMI
|
|
* context.
|
|
*/
|
|
#define STACKINFO_SDEI(name) \
|
|
({ \
|
|
((task == current) && in_nmi()) \
|
|
? stackinfo_get_sdei_##name() \
|
|
: stackinfo_get_unknown(); \
|
|
})
|
|
|
|
#define STACKINFO_EFI \
|
|
({ \
|
|
((task == current) && current_in_efi()) \
|
|
? stackinfo_get_efi() \
|
|
: stackinfo_get_unknown(); \
|
|
})
|
|
|
|
static __always_inline void
|
|
kunwind_stack_walk(kunwind_consume_fn consume_state,
|
|
void *cookie, struct task_struct *task,
|
|
struct pt_regs *regs)
|
|
{
|
|
struct stack_info stacks[] = {
|
|
stackinfo_get_task(task),
|
|
STACKINFO_CPU(irq),
|
|
#if defined(CONFIG_VMAP_STACK)
|
|
STACKINFO_CPU(overflow),
|
|
#endif
|
|
#if defined(CONFIG_VMAP_STACK) && defined(CONFIG_ARM_SDE_INTERFACE)
|
|
STACKINFO_SDEI(normal),
|
|
STACKINFO_SDEI(critical),
|
|
#endif
|
|
#ifdef CONFIG_EFI
|
|
STACKINFO_EFI,
|
|
#endif
|
|
};
|
|
struct kunwind_state state = {
|
|
.common = {
|
|
.stacks = stacks,
|
|
.nr_stacks = ARRAY_SIZE(stacks),
|
|
},
|
|
};
|
|
|
|
if (regs) {
|
|
if (task != current)
|
|
return;
|
|
kunwind_init_from_regs(&state, regs);
|
|
} else if (task == current) {
|
|
kunwind_init_from_caller(&state);
|
|
} else {
|
|
kunwind_init_from_task(&state, task);
|
|
}
|
|
|
|
do_kunwind(&state, consume_state, cookie);
|
|
}
|
|
|
|
struct kunwind_consume_entry_data {
|
|
stack_trace_consume_fn consume_entry;
|
|
void *cookie;
|
|
};
|
|
|
|
static __always_inline bool
|
|
arch_kunwind_consume_entry(const struct kunwind_state *state, void *cookie)
|
|
{
|
|
struct kunwind_consume_entry_data *data = cookie;
|
|
return data->consume_entry(data->cookie, state->common.pc);
|
|
}
|
|
|
|
noinline noinstr void arch_stack_walk(stack_trace_consume_fn consume_entry,
|
|
void *cookie, struct task_struct *task,
|
|
struct pt_regs *regs)
|
|
{
|
|
struct kunwind_consume_entry_data data = {
|
|
.consume_entry = consume_entry,
|
|
.cookie = cookie,
|
|
};
|
|
|
|
kunwind_stack_walk(arch_kunwind_consume_entry, &data, task, regs);
|
|
}
|
|
|
|
static bool dump_backtrace_entry(void *arg, unsigned long where)
|
|
{
|
|
char *loglvl = arg;
|
|
printk("%s %pSb\n", loglvl, (void *)where);
|
|
return true;
|
|
}
|
|
|
|
void dump_backtrace(struct pt_regs *regs, struct task_struct *tsk,
|
|
const char *loglvl)
|
|
{
|
|
pr_debug("%s(regs = %p tsk = %p)\n", __func__, regs, tsk);
|
|
|
|
if (regs && user_mode(regs))
|
|
return;
|
|
|
|
if (!tsk)
|
|
tsk = current;
|
|
|
|
if (!try_get_task_stack(tsk))
|
|
return;
|
|
|
|
printk("%sCall trace:\n", loglvl);
|
|
arch_stack_walk(dump_backtrace_entry, (void *)loglvl, tsk, regs);
|
|
|
|
put_task_stack(tsk);
|
|
}
|
|
|
|
void show_stack(struct task_struct *tsk, unsigned long *sp, const char *loglvl)
|
|
{
|
|
dump_backtrace(NULL, tsk, loglvl);
|
|
barrier();
|
|
}
|