1
0
Fork 0
mirror of synced 2025-03-06 20:59:54 +01:00

arm64: stacktrace: report source of unwind data

When analysing a stacktrace it can be useful to know where an unwound PC
came from, as in some situations certain sources may be suspect or known
to be unreliable. In future it would also be useful to track this so
that certain unwind steps can be performed in a stateful manner. For
example when unwinding across an exception boundary, we'd ideally unwind
pt_regs::pc, then pt_regs::lr, then the next frame record.

This patch adds an enumerated set of unwind sources, tracks this during
the unwind, and updates dump_backtrace() to log these for interesting
unwind steps.

The interesting sources recorded are:

 "C" - the PC came from the caller of an unwind function.
 "T" - the PC came from thread_saved_pc() for a blocked task.
 "P" - the PC came from a pt_regs::pc.
 "U" - the PC came from an unknown source (indicates an unwinder error).

... with nothing recorded when the PC came from a frame_record::pc as
this is the vastly common case and logging this would make it difficult
to spot the more interesting cases.

For example, when triggering a backtrace via magic-sysrq + L, the CPU
handling the sysrq will have a backtrace whose first element is the
caller (C) of dump_backtrace():

| Call trace:
|  show_stack+0x18/0x30 (C)
|  dump_stack_lvl+0x60/0x80
|  dump_stack+0x18/0x24
|  nmi_cpu_backtrace+0xfc/0x140
| ...

... and other CPUs will have a backtrace whose first element is their
pt_regs::pc (P) at the instant the backtrace IPI was taken:

| Call trace:
|  _raw_spin_unlock_irqrestore+0x8/0x50 (P)
|  wake_up_process+0x18/0x24
|  process_timeout+0x14/0x20
|  call_timer_fn.isra.0+0x24/0x80
| ...

Signed-off-by: Mark Rutland <mark.rutland@arm.com>
Reviewed-by: Mark Brown <broonie@kernel.org>
Reviewed-by: Miroslav Benes <mbenes@suse.cz>
Reviewed-by: Puranjay Mohan <puranjay12@gmail.com>
Cc: Ard Biesheuvel <ardb@kernel.org>
Cc: Josh Poimboeuf <jpoimboe@kernel.org>
Cc: Kalesh Singh <kaleshsingh@google.com>
Cc: Madhavan T. Venkataraman <madvenka@linux.microsoft.com>
Cc: Marc Zyngier <maz@kernel.org>
Cc: Will Deacon <will@kernel.org>
Link: https://lore.kernel.org/r/20241017092538.1859841-8-mark.rutland@arm.com
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
This commit is contained in:
Mark Rutland 2024-10-17 10:25:35 +01:00 committed by Catalin Marinas
parent b7794795c9
commit bdf8eafbf7

View file

@ -20,6 +20,14 @@
#include <asm/stack_pointer.h> #include <asm/stack_pointer.h>
#include <asm/stacktrace.h> #include <asm/stacktrace.h>
enum kunwind_source {
KUNWIND_SOURCE_UNKNOWN,
KUNWIND_SOURCE_FRAME,
KUNWIND_SOURCE_CALLER,
KUNWIND_SOURCE_TASK,
KUNWIND_SOURCE_REGS_PC,
};
/* /*
* Kernel unwind state * Kernel unwind state
* *
@ -37,6 +45,7 @@ struct kunwind_state {
#ifdef CONFIG_KRETPROBES #ifdef CONFIG_KRETPROBES
struct llist_node *kr_cur; struct llist_node *kr_cur;
#endif #endif
enum kunwind_source source;
}; };
static __always_inline void static __always_inline void
@ -45,6 +54,7 @@ kunwind_init(struct kunwind_state *state,
{ {
unwind_init_common(&state->common); unwind_init_common(&state->common);
state->task = task; state->task = task;
state->source = KUNWIND_SOURCE_UNKNOWN;
} }
/* /*
@ -62,6 +72,7 @@ kunwind_init_from_regs(struct kunwind_state *state,
state->common.fp = regs->regs[29]; state->common.fp = regs->regs[29];
state->common.pc = regs->pc; state->common.pc = regs->pc;
state->source = KUNWIND_SOURCE_REGS_PC;
} }
/* /*
@ -79,6 +90,7 @@ kunwind_init_from_caller(struct kunwind_state *state)
state->common.fp = (unsigned long)__builtin_frame_address(1); state->common.fp = (unsigned long)__builtin_frame_address(1);
state->common.pc = (unsigned long)__builtin_return_address(0); state->common.pc = (unsigned long)__builtin_return_address(0);
state->source = KUNWIND_SOURCE_CALLER;
} }
/* /*
@ -99,6 +111,7 @@ kunwind_init_from_task(struct kunwind_state *state,
state->common.fp = thread_saved_fp(task); state->common.fp = thread_saved_fp(task);
state->common.pc = thread_saved_pc(task); state->common.pc = thread_saved_pc(task);
state->source = KUNWIND_SOURCE_TASK;
} }
static __always_inline int static __always_inline int
@ -148,9 +161,19 @@ kunwind_next(struct kunwind_state *state)
if (fp == (unsigned long)&task_pt_regs(tsk)->stackframe) if (fp == (unsigned long)&task_pt_regs(tsk)->stackframe)
return -ENOENT; return -ENOENT;
err = unwind_next_frame_record(&state->common); switch (state->source) {
if (err) case KUNWIND_SOURCE_FRAME:
return err; case KUNWIND_SOURCE_CALLER:
case KUNWIND_SOURCE_TASK:
case KUNWIND_SOURCE_REGS_PC:
err = unwind_next_frame_record(&state->common);
if (err)
return err;
state->source = KUNWIND_SOURCE_FRAME;
break;
default:
return -EINVAL;
}
state->common.pc = ptrauth_strip_kernel_insn_pac(state->common.pc); state->common.pc = ptrauth_strip_kernel_insn_pac(state->common.pc);
@ -294,10 +317,26 @@ noinline noinstr void arch_bpf_stack_walk(bool (*consume_entry)(void *cookie, u6
kunwind_stack_walk(arch_bpf_unwind_consume_entry, &data, current, NULL); kunwind_stack_walk(arch_bpf_unwind_consume_entry, &data, current, NULL);
} }
static const char *state_source_string(const struct kunwind_state *state)
{
switch (state->source) {
case KUNWIND_SOURCE_FRAME: return NULL;
case KUNWIND_SOURCE_CALLER: return "C";
case KUNWIND_SOURCE_TASK: return "T";
case KUNWIND_SOURCE_REGS_PC: return "P";
default: return "U";
}
}
static bool dump_backtrace_entry(const struct kunwind_state *state, void *arg) static bool dump_backtrace_entry(const struct kunwind_state *state, void *arg)
{ {
const char *source = state_source_string(state);
char *loglvl = arg; char *loglvl = arg;
printk("%s %pSb\n", loglvl, (void *)state->common.pc); printk("%s %pSb%s%s%s\n", loglvl,
(void *)state->common.pc,
source ? " (" : "",
source ? source : "",
source ? ")" : "");
return true; return true;
} }