Using the framepointer register and optional symbol lookup support, we can now walk through the stack frames and print the link register values (branch location) as well as the name of the function we branched from.
When symbol lookup support is disabled, we simply print the LR value to still allow for manual lookups in a decompiled U-Boot binary. In addition, print the address of _start and CONFIG_TEXT_BASE since they are typically required to convert the runtime addresses to the addresses in the U-Boot binary/ELF. _start is used instead of relocaddr since it will be correct both pre and post relocation. Co-developed-by: Heinrich Schuchardt <heinrich.schucha...@canonical.com> Signed-off-by: Heinrich Schuchardt <heinrich.schucha...@canonical.com> Signed-off-by: Casey Connolly <casey.conno...@linaro.org> --- arch/arm/Makefile | 9 ++++- arch/arm/lib/interrupts_64.c | 82 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/arch/arm/Makefile b/arch/arm/Makefile index 5ecadb2ef1bc46ffa68b04658168647eaad9bc97..41e75b5bcb1cd068501f118d72bdb1c486b6784f 100644 --- a/arch/arm/Makefile +++ b/arch/arm/Makefile @@ -32,8 +32,15 @@ endif # Evaluate arch cc-option calls now arch-y := $(arch-y) +ifeq ($(CONFIG_$(PHASE_)FRAMEPOINTER),y) + ARCH_FLAGS += -fno-omit-frame-pointer +endif + +PLATFORM_CPPFLAGS += $(ARCH_FLAGS) +CFLAGS_EFI += $(ARCH_FLAGS) + # This selects how we optimise for the processor. tune-$(CONFIG_CPU_ARM720T) =-mtune=arm7tdmi tune-$(CONFIG_CPU_ARM920T) = tune-$(CONFIG_CPU_ARM926EJS) = @@ -46,9 +53,9 @@ tune-$(CONFIG_ARM64) = # Evaluate tune cc-option calls now tune-y := $(tune-y) -PLATFORM_CPPFLAGS += $(arch-y) $(tune-y) +PLATFORM_CPPFLAGS += $(ARCH_FLAGS) $(arch-y) $(tune-y) # Machine directory name. This list is sorted alphanumerically # by CONFIG_* macro name. machine-$(CONFIG_ARCH_AIROHA) += airoha diff --git a/arch/arm/lib/interrupts_64.c b/arch/arm/lib/interrupts_64.c index d13ee8d9f81dc8c41ef4732167663b223ab6ab3b..0f8ca7d343156bd53688bf4a0acd9437f041f8f5 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) @@ -97,8 +99,84 @@ void show_regs(struct pt_regs *regs) printf("\n"); dump_instr(regs); } +/* Stack frames automatically generated by compiler emitted code */ +struct stack_frame { + struct stack_frame *prev; + unsigned long lr; + char data[]; +}; + +static void print_sym(unsigned long lr) +{ + char symname[KSYM_NAME_LEN + 1] = { 0 }; + unsigned long offset; + + symbol_lookup(lr, NULL, &offset, symname); + + /* Print the symbol address and the offset to the LR */ + printf("\t<%#016lx> %s+%#lx\n", lr - (gd->flags & GD_FLG_RELOC ? gd->reloc_off : 0), + symname, offset); +} + +/** + * show_backtrace - show backtrace using frame pointers + * @elr: exception link register when called from an exception context + * @lr: link register (x30) of the current function + * @x29: frame pointer (x29) of the current function + */ +static void show_backtrace(unsigned long elr, unsigned long lr, unsigned long x29) +{ + struct stack_frame *fp = (struct stack_frame *)x29; + + /* If we're pre-relocation then _start and text base might be the same */ + if (CONFIG_TEXT_BASE && CONFIG_TEXT_BASE != (unsigned long)_start) + printf("\nCONFIG_TEXT_BASE : %#lx\n", (unsigned long)CONFIG_TEXT_BASE); + printf("Relocated base addr: %#lx\n", (unsigned long)_start); + printf("Backtrace:\n"); + + /* ELR tells us where the exception occurred */ + if (elr) { + printf("elr:"); + print_sym(elr); + } + + /* + * LR tells us the calling function, for exceptions this is the function that called + * the function where the exception occurred. For unwind_stack() case this is the caller + * of unwind_stack(). + */ + printf("lr:"); + print_sym(lr); + + /* Now we get the rest of the backtrace from the frame pointers */ + while (fp) { + print_sym(fp->lr); + fp = fp->prev; + } + printf("\n"); +} + +/** + * 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)); + + show_backtrace(0, x30, x29); +} + /* * Try to "emulate" a semihosting call in the event that we don't have a * debugger attached. */ @@ -152,8 +230,10 @@ static void except_msg(const char *msg, struct pt_regs *pt_regs) { efi_restore_gd(); printf("%s handler, esr 0x%016lx\n", msg, pt_regs->esr); show_regs(pt_regs); + if (CONFIG_IS_ENABLED(FRAMEPOINTER)) + show_backtrace(pt_regs->elr, pt_regs->regs[30], pt_regs->regs[29]); show_efi_loaded_images(pt_regs); panic("Resetting CPU ...\n"); } @@ -201,8 +281,10 @@ void do_sync(struct pt_regs *pt_regs) printf("\"Synchronous Abort\" handler, esr 0x%016lx", pt_regs->esr); dump_far(pt_regs->esr); printf("\n"); show_regs(pt_regs); + if (CONFIG_IS_ENABLED(FRAMEPOINTER)) + show_backtrace(pt_regs->elr, pt_regs->regs[30], pt_regs->regs[29]); show_efi_loaded_images(pt_regs); panic("Resetting CPU ...\n"); } -- 2.50.0