summaryrefslogtreecommitdiffstats
path: root/arch/arm/lib64/stacktrace.c
diff options
context:
space:
mode:
authorSascha Hauer <s.hauer@pengutronix.de>2018-03-14 09:21:31 +0100
committerSascha Hauer <s.hauer@pengutronix.de>2018-03-29 08:16:40 +0200
commitfa8c3c03987f5e60df735583414d9a355b43ac5a (patch)
tree771966d3e7879903a3e4232693372144dff56808 /arch/arm/lib64/stacktrace.c
parentfa0d6412bf1d77e383f23f120688ecf495463d1e (diff)
downloadbarebox-fa8c3c03987f5e60df735583414d9a355b43ac5a.tar.gz
barebox-fa8c3c03987f5e60df735583414d9a355b43ac5a.tar.xz
ARM: aarch64: implement stacktraces
Implement stacktraces as a great debugging aid. On aarch64 this is cheap enough to be enabled unconditionally. Unwinding code is taken from the Kernel. Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Diffstat (limited to 'arch/arm/lib64/stacktrace.c')
-rw-r--r--arch/arm/lib64/stacktrace.c86
1 files changed, 86 insertions, 0 deletions
diff --git a/arch/arm/lib64/stacktrace.c b/arch/arm/lib64/stacktrace.c
new file mode 100644
index 0000000000..b8352c1454
--- /dev/null
+++ b/arch/arm/lib64/stacktrace.c
@@ -0,0 +1,86 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; version 2.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+
+#include <common.h>
+#include <asm/stacktrace.h>
+
+#define THREAD_SIZE 16384
+
+/*
+ * AArch64 PCS assigns the frame pointer to x29.
+ *
+ * A simple function prologue looks like this:
+ * sub sp, sp, #0x10
+ * stp x29, x30, [sp]
+ * mov x29, sp
+ *
+ * A simple function epilogue looks like this:
+ * mov sp, x29
+ * ldp x29, x30, [sp]
+ * add sp, sp, #0x10
+ */
+int unwind_frame(struct stackframe *frame)
+{
+ unsigned long high, low;
+ unsigned long fp = frame->fp;
+
+ low = frame->sp;
+ high = ALIGN(low, THREAD_SIZE);
+
+ if (fp < low || fp > high - 0x18 || fp & 0xf)
+ return -EINVAL;
+
+ frame->sp = fp + 0x10;
+ frame->fp = *(unsigned long *)(fp);
+ frame->pc = *(unsigned long *)(fp + 8);
+
+ return 0;
+}
+
+void dump_backtrace_entry(unsigned long where, unsigned long from)
+{
+#ifdef CONFIG_KALLSYMS
+ printf("[<%08lx>] (%pS) from [<%08lx>] (%pS)\n", where, (void *)where, from, (void *)from);
+#else
+ printf("Function entered at [<%08lx>] from [<%08lx>]\n", where, from);
+#endif
+}
+
+void unwind_backtrace(struct pt_regs *regs)
+{
+ struct stackframe frame = {};
+ register unsigned long current_sp asm ("sp");
+
+ if (regs) {
+ frame.fp = regs->regs[29];
+ frame.pc = regs->elr;
+ } else {
+ frame.fp = (unsigned long)__builtin_frame_address(0);
+ frame.sp = current_sp;
+ frame.pc = (unsigned long)unwind_backtrace;
+ }
+
+ printf("Call trace:\n");
+ while (1) {
+ unsigned long where = frame.pc;
+ int ret;
+
+ ret = unwind_frame(&frame);
+ if (ret < 0)
+ break;
+ dump_backtrace_entry(where, frame.pc);
+ }
+}
+
+void dump_stack(void)
+{
+ unwind_backtrace(NULL);
+}