summaryrefslogtreecommitdiffstats
path: root/backends
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2017-02-14 13:35:11 +0100
committerUlf Hermann <ulf.hermann@qt.io>2017-05-08 12:52:36 +0000
commit9f6ffdc1b75fd69ecda219c9e9f1b2551f542b26 (patch)
tree17560623af820de8723e268f4c36cb892202888d /backends
parent5c20064a9aff24b5e1120349e718c48bb7d9a028 (diff)
Add frame pointer unwinding as fallback on arm
If we don't find any debug information for a given frame, we usually cannot unwind any further. However, the binary in question might have been compiled with frame pointers, in which case we can look up the well known frame pointer locations in the stack snapshot and use them to bridge the frames without debug information. At the moment this works only for ARM code. THUMB code uses a different mechanism for unwinding. Also, in order to figure out if a function was compiled in ARM or in THUMB mode we need a symbol table which might not be available (e.g. with JIT-compiled code). Furthermore, there doesn't seem to be a fixed convention on the order in which LR and FP are pushed to the stack on a new frame. The code assumes FP is first and LR second. This is what QV4 does. Change-Id: Iac732e11b7434043cd613d95d8cb2ac753b93920 Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
Diffstat (limited to 'backends')
-rw-r--r--backends/ChangeLog6
-rw-r--r--backends/Makefile.am3
-rw-r--r--backends/arm_init.c1
-rw-r--r--backends/arm_unwind.c93
4 files changed, 102 insertions, 1 deletions
diff --git a/backends/ChangeLog b/backends/ChangeLog
index e2f05295..930854ba 100644
--- a/backends/ChangeLog
+++ b/backends/ChangeLog
@@ -1,3 +1,9 @@
+2017-04-26 Ulf Hermann <ulf.hermann@qt.io>
+
+ * arm_unwind.c: New file
+ * Makefile.am (arm_SRCS): Add arm_unwind.c
+ * arm_init.c (arm_init): Hook arm_unwind
+
2017-04-28 Ulf Hermann <ulf.hermann@qt.io>
* Makefile.am: Have the backend file names follow the platform's
diff --git a/backends/Makefile.am b/backends/Makefile.am
index 55ff871f..526fcb99 100644
--- a/backends/Makefile.am
+++ b/backends/Makefile.am
@@ -80,7 +80,8 @@ libebl_alpha_pic_a_SOURCES = $(alpha_SRCS)
am_libebl_alpha_pic_a_OBJECTS = $(alpha_SRCS:.c=.os)
arm_SRCS = arm_init.c arm_symbol.c arm_regs.c arm_corenote.c \
- arm_auxv.c arm_attrs.c arm_retval.c arm_cfi.c arm_initreg.c
+ arm_auxv.c arm_attrs.c arm_retval.c arm_cfi.c arm_initreg.c \
+ arm_unwind.c
libebl_arm_pic_a_SOURCES = $(arm_SRCS)
am_libebl_arm_pic_a_OBJECTS = $(arm_SRCS:.c=.os)
diff --git a/backends/arm_init.c b/backends/arm_init.c
index caadac65..4fa0601a 100644
--- a/backends/arm_init.c
+++ b/backends/arm_init.c
@@ -68,6 +68,7 @@ arm_init (Elf *elf __attribute__ ((unused)),
/* We only unwind the core integer registers. */
eh->frame_nregs = 16;
HOOK (eh, set_initial_registers_tid);
+ HOOK (eh, unwind);
/* Bit zero encodes whether an function address is THUMB or ARM. */
eh->func_addr_mask = ~(GElf_Addr)1;
diff --git a/backends/arm_unwind.c b/backends/arm_unwind.c
new file mode 100644
index 00000000..3d1e1f25
--- /dev/null
+++ b/backends/arm_unwind.c
@@ -0,0 +1,93 @@
+/* Get previous frame state for an existing frame state.
+ Copyright (C) 2016 The Qt Company Ltd.
+ This file is part of elfutils.
+
+ This file is free software; you can redistribute it and/or modify
+ it under the terms of either
+
+ * the GNU Lesser General Public License as published by the Free
+ Software Foundation; either version 3 of the License, or (at
+ your option) any later version
+
+ or
+
+ * the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at
+ your option) any later version
+
+ or both in parallel, as here.
+
+ elfutils 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.
+
+ You should have received copies of the GNU General Public License and
+ the GNU Lesser General Public License along with this program. If
+ not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <assert.h>
+
+#ifndef BACKEND
+#define BACKEND arm_
+
+// Actually, in THUMB mode it's r7. Let's ignore this.
+#define FP_REG 11
+#define LR_REG 14
+#define SP_REG 13
+
+// The offsets are a mess. gcc-generated code with -mapcs-frame has FP and LR the other way around.
+// We make it work with QV4.
+#define FP_OFFSET 0
+#define LR_OFFSET 4
+#define SP_OFFSET 8
+#endif
+
+#include "libebl_CPU.h"
+
+/* There was no CFI. Maybe we happen to have a frame pointer and can unwind from that? */
+
+bool
+EBLHOOK(unwind) (Ebl *ebl __attribute__ ((unused)), Dwarf_Addr pc __attribute__ ((unused)),
+ ebl_tid_registers_t *setfunc, ebl_tid_registers_get_t *getfunc,
+ ebl_pid_memory_read_t *readfunc, void *arg,
+ bool *signal_framep __attribute__ ((unused)))
+{
+ Dwarf_Word fp = 0, sp = 0; // have to be initialized because registers are 32bit only
+
+ if (!getfunc(FP_REG, 1, &fp, arg))
+ fp = 0;
+
+ if (!getfunc(SP_REG, 1, &sp, arg))
+ sp = 0;
+
+ Dwarf_Word newLr, newFp, newSp;
+
+ if (!readfunc(fp + LR_OFFSET, &newLr, arg))
+ newLr = 0;
+
+ if (!readfunc(fp + FP_OFFSET, &newFp, arg))
+ newFp = 0;
+
+ newSp = fp + SP_OFFSET;
+
+ if (!setfunc(-1, 1, &newLr, arg))
+ return false;
+
+ // unset the "thumb" bit. We get LR without thumb bit, so let's also pass it on that way.
+ newLr &= 0xfffffffe;
+
+ // These are not fatal if they don't work. They will just prevent unwinding at the next frame.
+ setfunc(LR_REG, 1, &newLr, arg);
+ setfunc(FP_REG, 1, &newFp, arg);
+ setfunc(SP_REG, 1, &newSp, arg);
+
+ // If the fp is invalid, we might still have a valid lr.
+ // But if the fp is valid, then the stack should be moving in the right direction.
+ return newLr != 0 && (fp == 0 || newSp > sp);
+}