We already build arm64 images with frame pointers. Let's finally make use of them in tandem with the new symbol lookup support by unwinding the stack when an exception occurs, producing a backtrace similar to those emitted by Linux.
In addition, introduce a dedicated unwind_stack() function which can be called from anywhere to print a backtrace. Signed-off-by: Caleb Connolly <caleb.conno...@linaro.org> --- arch/arm/include/asm/ptrace.h | 2 ++ arch/arm/lib/interrupts_64.c | 75 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/arch/arm/include/asm/ptrace.h b/arch/arm/include/asm/ptrace.h index a836f6cc60db..d4a971211843 100644 --- a/arch/arm/include/asm/ptrace.h +++ b/arch/arm/include/asm/ptrace.h @@ -22,8 +22,10 @@ #ifdef __KERNEL__ extern void show_regs(struct pt_regs *); +void unwind_stack(void); + #define predicate(x) (x & 0xf0000000) #define PREDICATE_ALWAYS 0xe0000000 #endif diff --git a/arch/arm/lib/interrupts_64.c b/arch/arm/lib/interrupts_64.c index b3024ba514ec..bae823cb2feb 100644 --- a/arch/arm/lib/interrupts_64.c +++ b/arch/arm/lib/interrupts_64.c @@ -3,15 +3,17 @@ * (C) Copyright 2013 * David Feng <feng...@phytium.com.cn> */ +#include <asm-generic/sections.h> #include <asm/esr.h> #include <asm/global_data.h> #include <asm/ptrace.h> #include <irq_func.h> #include <linux/compiler.h> #include <efi_loader.h> #include <semihosting.h> +#include <symbols.h> DECLARE_GLOBAL_DATA_PTR; int interrupt_init(void) @@ -80,8 +82,76 @@ static void dump_instr(struct pt_regs *regs) printf(i == 0 ? "(%08x) " : "%08x ", addr[i]); printf("\n"); } +#if IS_ENABLED(CONFIG_SYMBOL_LOOKUP) + +static void print_sym(unsigned long symaddr, const char *symname, unsigned long offset) +{ + if (symaddr > (unsigned long)__bss_end) + printf("\t<%#016lx> ???\n", symaddr); + else + printf("\t<%#016lx> %s+%#lx\n", symaddr, symname, offset); +} + +/* Stack frames automatically generated by compiler emitted code */ +struct stack_frame { + struct stack_frame *prev_ptr; /* Alays a bigger address on ARM64 */ + unsigned long lr; + char data[]; +}; + +static void __unwind_stack(unsigned long lr, unsigned long x29) +{ + char symname[KSYM_NAME_LEN+1] = { 0 }; + unsigned long addr, offset; + struct stack_frame *fp; + + /* + * Either the place where unwind_stack() was called, or the + * instruction that caused an exception + */ + symbols_lookup(lr, &addr, &offset, symname); + print_sym(lr, symname, offset); + + fp = (struct stack_frame *)x29; + while (fp && fp->prev_ptr > fp) { + symbols_lookup(fp->lr, &addr, &offset, symname); + /* + * _start is used over gd->relocaddr because it will be correct + * if U-Boot was built as relocatable and loaded at an arbitrary + * address (both pre and post-relocation). + */ + print_sym(fp->lr, symname, offset); + fp = (struct stack_frame *)(u64)fp->prev_ptr; + } +} + +/** + * unwind_stack() - Unwind the callstack and print a backtrace. + * + * This function can be called for debugging purposes to walk backwards through + * stack frames, printing the function name and offset of the branch instruction. + * + * It reads out the link register (x30) which contains the return address of the + * caller, and x29 which contains the frame pointer of the caller. + */ +void unwind_stack(void) +{ + unsigned long x29, x30; + + asm("mov %0, x29" : "=r" (x29)); + asm("mov %0, x30" : "=r" (x30)); + + __unwind_stack(x30, x29); +} + +#else + +#define __unwind_stack(lr, x29) do {} while (0) + +#endif + void show_regs(struct pt_regs *regs) { int i; @@ -95,8 +165,13 @@ void show_regs(struct pt_regs *regs) printf("x%-2d: %016lx x%-2d: %016lx\n", i, regs->regs[i], i+1, regs->regs[i+1]); printf("\n"); dump_instr(regs); + + if (IS_ENABLED(CONFIG_SYMBOL_LOOKUP)) { + printf("\nBacktrace:\n"); + __unwind_stack(regs->elr, regs->regs[29]); + } } /* * Try to "emulate" a semihosting call in the event that we don't have a -- 2.46.0