[PATCH v2 6/7] arm64: implement printing backtraces with symbols
Casey Connolly
casey.connolly at linaro.org
Fri Jul 25 14:57:10 CEST 2025
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.schuchardt at canonical.com>
Signed-off-by: Heinrich Schuchardt <heinrich.schuchardt at canonical.com>
Signed-off-by: Casey Connolly <casey.connolly at linaro.org>
---
arch/arm/Makefile | 9 ++++-
arch/arm/lib/interrupts_64.c | 83 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 91 insertions(+), 1 deletion(-)
diff --git a/arch/arm/Makefile b/arch/arm/Makefile
index 5ecadb2ef1bc..41e75b5bcb1c 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 b3024ba514ec..6cb16811566a 100644
--- a/arch/arm/lib/interrupts_64.c
+++ b/arch/arm/lib/interrupts_64.c
@@ -3,15 +3,17 @@
* (C) Copyright 2013
* David Feng <fenghua at 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,73 @@ 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 symaddr, const char *symname, unsigned long offset)
+{
+ if (!CONFIG_IS_ENABLED(SYMBOL_LOOKUP) || symname[0] == '?')
+ /* If the symbol is not found, symaddr will just be the link register address */
+ printf("\t(%#016lx)\n", symaddr);
+ else
+ /* Print the symbol address and the offset to the LR */
+ printf("\t<%#016lx> %s+%#lx\n", symaddr, symname, offset);
+}
+
+/**
+ * show_backtrace - show backtrace using frame pointers
+ * @lr: link register (x30) of the current function
+ * @x29: frame pointer (x29) of the current function
+ */
+static void show_backtrace(unsigned long lr, unsigned long x29)
+{
+ struct stack_frame *fp = (struct stack_frame *)x29;
+ char symname[KSYM_NAME_LEN + 1] = { 0 };
+ unsigned long symaddr, offset;
+
+ /* 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");
+
+ /* Before walking the framepointers, print the calling function from the link register */
+ symbol_lookup((unsigned long)lr, &symaddr, &offset, symname);
+ print_sym(symaddr, symname, offset);
+
+ while (fp) {
+ symbol_lookup((unsigned long)fp->lr, &symaddr, &offset, symname);
+ print_sym(symaddr, symname, offset);
+ 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(x30, x29);
+}
+
/*
* Try to "emulate" a semihosting call in the event that we don't have a
* debugger attached.
*/
@@ -148,8 +215,10 @@ void do_bad_sync(struct pt_regs *pt_regs)
efi_restore_gd();
printf("Bad mode in \"Synchronous Abort\" handler, esr 0x%08lx\n",
pt_regs->esr);
show_regs(pt_regs);
+ if (CONFIG_IS_ENABLED(FRAMEPOINTER))
+ show_backtrace(pt_regs->regs[30], pt_regs->regs[29]);
show_efi_loaded_images(pt_regs);
panic("Resetting CPU ...\n");
}
@@ -160,8 +229,10 @@ void do_bad_irq(struct pt_regs *pt_regs)
{
efi_restore_gd();
printf("Bad mode in \"Irq\" handler, esr 0x%08lx\n", pt_regs->esr);
show_regs(pt_regs);
+ if (CONFIG_IS_ENABLED(FRAMEPOINTER))
+ show_backtrace(pt_regs->regs[30], pt_regs->regs[29]);
show_efi_loaded_images(pt_regs);
panic("Resetting CPU ...\n");
}
@@ -172,8 +243,10 @@ void do_bad_fiq(struct pt_regs *pt_regs)
{
efi_restore_gd();
printf("Bad mode in \"Fiq\" handler, esr 0x%08lx\n", pt_regs->esr);
show_regs(pt_regs);
+ if (CONFIG_IS_ENABLED(FRAMEPOINTER))
+ show_backtrace(pt_regs->regs[30], pt_regs->regs[29]);
show_efi_loaded_images(pt_regs);
panic("Resetting CPU ...\n");
}
@@ -184,8 +257,10 @@ void do_bad_error(struct pt_regs *pt_regs)
{
efi_restore_gd();
printf("Bad mode in \"Error\" handler, esr 0x%08lx\n", pt_regs->esr);
show_regs(pt_regs);
+ if (CONFIG_IS_ENABLED(FRAMEPOINTER))
+ show_backtrace(pt_regs->regs[30], pt_regs->regs[29]);
show_efi_loaded_images(pt_regs);
panic("Resetting CPU ...\n");
}
@@ -201,8 +276,10 @@ void do_sync(struct pt_regs *pt_regs)
printf("\"Synchronous Abort\" handler, esr 0x%08lx", pt_regs->esr);
dump_far(pt_regs->esr);
printf("\n");
show_regs(pt_regs);
+ if (CONFIG_IS_ENABLED(FRAMEPOINTER))
+ show_backtrace(pt_regs->regs[30], pt_regs->regs[29]);
show_efi_loaded_images(pt_regs);
panic("Resetting CPU ...\n");
}
@@ -213,8 +290,10 @@ void do_irq(struct pt_regs *pt_regs)
{
efi_restore_gd();
printf("\"Irq\" handler, esr 0x%08lx\n", pt_regs->esr);
show_regs(pt_regs);
+ if (CONFIG_IS_ENABLED(FRAMEPOINTER))
+ show_backtrace(pt_regs->regs[30], pt_regs->regs[29]);
show_efi_loaded_images(pt_regs);
panic("Resetting CPU ...\n");
}
@@ -225,8 +304,10 @@ void do_fiq(struct pt_regs *pt_regs)
{
efi_restore_gd();
printf("\"Fiq\" handler, esr 0x%08lx\n", pt_regs->esr);
show_regs(pt_regs);
+ if (CONFIG_IS_ENABLED(FRAMEPOINTER))
+ show_backtrace(pt_regs->regs[30], pt_regs->regs[29]);
show_efi_loaded_images(pt_regs);
panic("Resetting CPU ...\n");
}
@@ -240,7 +321,9 @@ void __weak do_error(struct pt_regs *pt_regs)
{
efi_restore_gd();
printf("\"Error\" handler, esr 0x%08lx\n", pt_regs->esr);
show_regs(pt_regs);
+ if (CONFIG_IS_ENABLED(FRAMEPOINTER))
+ show_backtrace(pt_regs->regs[30], pt_regs->regs[29]);
show_efi_loaded_images(pt_regs);
panic("Resetting CPU ...\n");
}
--
2.50.1
More information about the U-Boot
mailing list