]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
arm64: stacktrace: report recovered PCs
authorMark Rutland <mark.rutland@arm.com>
Thu, 17 Oct 2024 09:25:36 +0000 (10:25 +0100)
committerCatalin Marinas <catalin.marinas@arm.com>
Thu, 17 Oct 2024 17:06:25 +0000 (18:06 +0100)
When analysing a stacktrace it can be useful to know whether an unwound
PC has been rewritten by fgraph or kretprobes, as in some situations
these may be suspect or be known to be unreliable.

This patch adds flags to track when an unwind entry has recovered the PC
from fgraph and/or kretprobes, and updates dump_backtrace() to log when
this is the case.

The flags recorded are:

 "F" - the PC was recovered from fgraph
 "K" - the PC was recovered from kretprobes

These flags are recorded and logged in addition to the original source
of the unwound PC.

For example, with the ftrace_graph profiler enabled globally, and
kretprobes installed on generic_handle_domain_irq() and
do_interrupt_handler(), a backtrace triggered by magic-sysrq + L
reports:

| Call trace:
|  show_stack+0x20/0x40 (CF)
|  dump_stack_lvl+0x60/0x80 (F)
|  dump_stack+0x18/0x28
|  nmi_cpu_backtrace+0xfc/0x140
|  nmi_trigger_cpumask_backtrace+0x1c8/0x200
|  arch_trigger_cpumask_backtrace+0x20/0x40
|  sysrq_handle_showallcpus+0x24/0x38 (F)
|  __handle_sysrq+0xa8/0x1b0 (F)
|  handle_sysrq+0x38/0x50 (F)
|  pl011_int+0x460/0x5a8 (F)
|  __handle_irq_event_percpu+0x60/0x220 (F)
|  handle_irq_event+0x54/0xc0 (F)
|  handle_fasteoi_irq+0xa8/0x1d0 (F)
|  generic_handle_domain_irq+0x34/0x58 (F)
|  gic_handle_irq+0x54/0x140 (FK)
|  call_on_irq_stack+0x24/0x58 (F)
|  do_interrupt_handler+0x88/0xa0
|  el1_interrupt+0x34/0x68 (FK)
|  el1h_64_irq_handler+0x18/0x28
|  el1h_64_irq+0x64/0x68
|  default_idle_call+0x34/0x180
|  do_idle+0x204/0x268
|  cpu_startup_entry+0x40/0x50 (F)
|  rest_init+0xe4/0xf0
|  start_kernel+0x744/0x750
|  __primary_switched+0x80/0x90

Note that as these flags are reported next to the recovered PC value,
they appear on the callers of instrumented functions. For example
gic_handle_irq() has a "K" marker because generic_handle_domain_irq()
was instrumented with kretprobes and had its return address rewritten.

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-9-mark.rutland@arm.com
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
arch/arm64/kernel/stacktrace.c

index 83ab37e13159e87530b244546e9f717b610b9c2b..f8e231683dad92d8a7a03d1ba5c4f42d8eb8790c 100644 (file)
@@ -28,6 +28,14 @@ enum kunwind_source {
        KUNWIND_SOURCE_REGS_PC,
 };
 
+union unwind_flags {
+       unsigned long   all;
+       struct {
+               unsigned long   fgraph : 1,
+                               kretprobe : 1;
+       };
+};
+
 /*
  * Kernel unwind state
  *
@@ -46,6 +54,7 @@ struct kunwind_state {
        struct llist_node *kr_cur;
 #endif
        enum kunwind_source source;
+       union unwind_flags flags;
 };
 
 static __always_inline void
@@ -55,6 +64,7 @@ kunwind_init(struct kunwind_state *state,
        unwind_init_common(&state->common);
        state->task = task;
        state->source = KUNWIND_SOURCE_UNKNOWN;
+       state->flags.all = 0;
 }
 
 /*
@@ -127,6 +137,7 @@ kunwind_recover_return_address(struct kunwind_state *state)
                if (WARN_ON_ONCE(state->common.pc == orig_pc))
                        return -EINVAL;
                state->common.pc = orig_pc;
+               state->flags.fgraph = 1;
        }
 #endif /* CONFIG_FUNCTION_GRAPH_TRACER */
 
@@ -137,6 +148,7 @@ kunwind_recover_return_address(struct kunwind_state *state)
                                                  (void *)state->common.fp,
                                                  &state->kr_cur);
                state->common.pc = orig_pc;
+               state->flags.kretprobe = 1;
        }
 #endif /* CONFIG_KRETPROBES */
 
@@ -157,6 +169,8 @@ kunwind_next(struct kunwind_state *state)
        unsigned long fp = state->common.fp;
        int err;
 
+       state->flags.all = 0;
+
        /* Final frame; nothing to unwind */
        if (fp == (unsigned long)&task_pt_regs(tsk)->stackframe)
                return -ENOENT;
@@ -331,12 +345,18 @@ static const char *state_source_string(const struct kunwind_state *state)
 static bool dump_backtrace_entry(const struct kunwind_state *state, void *arg)
 {
        const char *source = state_source_string(state);
+       union unwind_flags flags = state->flags;
+       bool has_info = source || flags.all;
        char *loglvl = arg;
-       printk("%s %pSb%s%s%s\n", loglvl,
+
+       printk("%s %pSb%s%s%s%s%s\n", loglvl,
                (void *)state->common.pc,
-               source ? " (" : "",
+               has_info ? " (" : "",
                source ? source : "",
-               source ? ")" : "");
+               flags.fgraph ? "F" : "",
+               flags.kretprobe ? "K" : "",
+               has_info ? ")" : "");
+
        return true;
 }