diff mbox

[2/2] Add support for handling signal frames on ARM Linux.

Message ID 1304671887-696-3-git-send-email-ken.werner@linaro.org
State Accepted
Commit 36511d3d1f040bbf778094e907725ad0617326c8
Headers show

Commit Message

Ken Werner May 6, 2011, 8:51 a.m. UTC
This patch add support for resuming at a certain stack frame even if signal
frames are involved. For restoring the registers the trampoline (sigreturn)
is used. RT and non-RT signal frames are handled for both >=2.6.18 and
<2.6.18 kernels.

Signed-off-by: Ken Werner <ken.werner@linaro.org>
---
 include/tdep-arm/libunwind_i.h |   11 +++++
 src/arm/Gresume.c              |   74 +++++++++++++++++++++++--------
 src/arm/Gstep.c                |   95 ++++++++++++++++++++++++++++++++++++++++
 src/arm/init.h                 |    5 ++
 4 files changed, 166 insertions(+), 19 deletions(-)
diff mbox

Patch

diff --git a/include/tdep-arm/libunwind_i.h b/include/tdep-arm/libunwind_i.h
index 271e1d3..508f6c8 100644
--- a/include/tdep-arm/libunwind_i.h
+++ b/include/tdep-arm/libunwind_i.h
@@ -61,7 +61,18 @@  struct unw_addr_space
 struct cursor
   {
     struct dwarf_cursor dwarf;		/* must be first */
+    enum
+      {
+        ARM_SCF_NONE,                   /* no signal frame */
+        ARM_SCF_LINUX_SIGFRAME,         /* non-RT signal frame, kernel >=2.6.18 */
+        ARM_SCF_LINUX_RT_SIGFRAME,      /* RT signal frame, kernel >=2.6.18 */
+        ARM_SCF_LINUX_OLD_SIGFRAME,     /* non-RT signal frame, kernel < 2.6.18 */
+        ARM_SCF_LINUX_OLD_RT_SIGFRAME   /* RT signal frame, kernel < 2.6.18 */
+      }
+    sigcontext_format;
     unw_word_t sigcontext_addr;
+    unw_word_t sigcontext_sp;
+    unw_word_t sigcontext_pc;
   };
 
 #define DWARF_GET_LOC(l)	((l).val)
diff --git a/src/arm/Gresume.c b/src/arm/Gresume.c
index 4bb4701..c870f5f 100644
--- a/src/arm/Gresume.c
+++ b/src/arm/Gresume.c
@@ -24,6 +24,7 @@  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.  */
 
 #include "unwind_i.h"
+#include "offsets.h"
 
 #ifndef UNW_REMOTE_ONLY
 
@@ -33,27 +34,62 @@  arm_local_resume (unw_addr_space_t as, unw_cursor_t *cursor, void *arg)
 #ifdef __linux__
   struct cursor *c = (struct cursor *) cursor;
   ucontext_t *uc = c->dwarf.as_arg;
-  unsigned long regs[10];
-
-  /* Copy the register contents to be restored.  */
-  regs[0] = uc->uc_mcontext.arm_r4;
-  regs[1] = uc->uc_mcontext.arm_r5;
-  regs[2] = uc->uc_mcontext.arm_r6;
-  regs[3] = uc->uc_mcontext.arm_r7;
-  regs[4] = uc->uc_mcontext.arm_r8;
-  regs[5] = uc->uc_mcontext.arm_r9;
-  regs[6] = uc->uc_mcontext.arm_r10;
-  regs[7] = uc->uc_mcontext.arm_fp;
-  regs[8] = uc->uc_mcontext.arm_sp;
-  regs[9] = uc->uc_mcontext.arm_lr;
-
-  /* Restore the registers.  */
-  asm __volatile__ (
-    "ldmia %0, {r4-r12, lr}\n"
-    "mov sp, r12\n"
-    "bx lr\n"
-    : : "r" (regs) :
-  );
+
+  if (c->sigcontext_format == ARM_SCF_NONE)
+    {
+      /* Since there are no signals involved here we restore the non scratch
+	 registers only.  */
+      unsigned long regs[10];
+      regs[0] = uc->uc_mcontext.arm_r4;
+      regs[1] = uc->uc_mcontext.arm_r5;
+      regs[2] = uc->uc_mcontext.arm_r6;
+      regs[3] = uc->uc_mcontext.arm_r7;
+      regs[4] = uc->uc_mcontext.arm_r8;
+      regs[5] = uc->uc_mcontext.arm_r9;
+      regs[6] = uc->uc_mcontext.arm_r10;
+      regs[7] = uc->uc_mcontext.arm_fp;
+      regs[8] = uc->uc_mcontext.arm_sp;
+      regs[9] = uc->uc_mcontext.arm_lr;
+
+      asm __volatile__ (
+	"ldmia %0, {r4-r12, lr}\n"
+	"mov sp, r12\n"
+	"bx lr\n"
+	: : "r" (regs) :
+      );
+    }
+  else
+    {
+      /* In case a signal frame is involved, we're using its trampoline which
+	 calls sigreturn.  */
+      struct sigcontext *sc = (struct sigcontext *) c->sigcontext_addr;
+      sc->arm_r0 = uc->uc_mcontext.arm_r0;
+      sc->arm_r1 = uc->uc_mcontext.arm_r1;
+      sc->arm_r2 = uc->uc_mcontext.arm_r2;
+      sc->arm_r3 = uc->uc_mcontext.arm_r3;
+      sc->arm_r4 = uc->uc_mcontext.arm_r4;
+      sc->arm_r5 = uc->uc_mcontext.arm_r5;
+      sc->arm_r6 = uc->uc_mcontext.arm_r6;
+      sc->arm_r7 = uc->uc_mcontext.arm_r7;
+      sc->arm_r8 = uc->uc_mcontext.arm_r8;
+      sc->arm_r9 = uc->uc_mcontext.arm_r9;
+      sc->arm_r10 = uc->uc_mcontext.arm_r10;
+      sc->arm_fp = uc->uc_mcontext.arm_fp;
+      sc->arm_ip = uc->uc_mcontext.arm_ip;
+      sc->arm_sp = uc->uc_mcontext.arm_sp;
+      sc->arm_lr = uc->uc_mcontext.arm_lr;
+      sc->arm_pc = uc->uc_mcontext.arm_pc;
+      /* clear the ITSTATE bits.  */
+      sc->arm_cpsr &= 0xf9ff03ffUL;
+
+      /* Set the SP and the PC in order to continue execution at the modified
+	 trampoline which restores the signal mask and the registers.  */
+      asm __volatile__ (
+	"mov sp, %0\n"
+	"bx %1\n"
+	: : "r" (c->sigcontext_sp), "r" (c->sigcontext_pc) :
+      );
+   }
 #else
   printf ("%s: implement me\n", __FUNCTION__);
 #endif
diff --git a/src/arm/Gstep.c b/src/arm/Gstep.c
index 9164d78..f5fb441 100644
--- a/src/arm/Gstep.c
+++ b/src/arm/Gstep.c
@@ -27,6 +27,8 @@  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.  */
 #include "offsets.h"
 #include "ex_tables.h"
 
+#include <signal.h>
+
 #define arm_exidx_step	UNW_OBJ(arm_exidx_step)
 
 static inline int
@@ -71,6 +73,95 @@  arm_exidx_step (struct cursor *c)
 }
 
 PROTECTED int
+unw_handle_signal_frame (unw_cursor_t *cursor)
+{
+  struct cursor *c = (struct cursor *) cursor;
+  int ret;
+  unw_word_t sc_addr, sp, sp_addr = c->dwarf.cfa;
+  struct dwarf_loc sp_loc = DWARF_LOC (sp_addr, 0);
+
+  if ((ret = dwarf_get (&c->dwarf, sp_loc, &sp)) < 0)
+    return -UNW_EUNSPEC;
+
+  /* Obtain signal frame type (non-RT or RT). */
+  ret = unw_is_signal_frame (cursor);
+
+  /* Save the SP and PC to be able to return execution at this point
+     later in time (unw_resume).  */
+  c->sigcontext_sp = c->dwarf.cfa;
+  c->sigcontext_pc = c->dwarf.ip;
+
+  /* Since kernel version 2.6.18 the non-RT signal frame starts with a
+     ucontext while the RT signal frame starts with a siginfo, followed
+     by a sigframe whose first element is an ucontext.
+     Prior 2.6.18 the non-RT signal frame starts with a sigcontext while
+     the RT signal frame starts with two pointers followed by a siginfo
+     and an ucontext. The first pointer points to the start of the siginfo
+     structure and the second one to the ucontext structure.  */
+
+  if (ret == 1)
+    {
+      /* Handle non-RT signal frames. Check if the first word on the stack
+	 is the magic number.  */
+      if (sp == 0x5ac3c35a)
+	{
+	  c->sigcontext_format = ARM_SCF_LINUX_SIGFRAME;
+	  sc_addr = sp_addr + LINUX_UC_MCONTEXT_OFF;
+	}
+      else
+	{
+	  c->sigcontext_format = ARM_SCF_LINUX_OLD_SIGFRAME;
+	  sc_addr = sp_addr;
+	}
+      c->sigcontext_addr = sp_addr;
+    }
+  else if (ret == 2)
+    {
+      /* Handle RT signal frames. Check if the first word on the stack is a
+	 pointer to the siginfo structure.  */
+      if (sp == sp_addr + 8)
+	{
+	  c->sigcontext_format = ARM_SCF_LINUX_OLD_RT_SIGFRAME;
+	  c->sigcontext_addr = sp_addr + 8 + sizeof (siginfo_t); 
+	}
+      else
+	{
+	  c->sigcontext_format = ARM_SCF_LINUX_RT_SIGFRAME;
+	  c->sigcontext_addr = sp_addr + sizeof (siginfo_t);
+	}
+      sc_addr = c->sigcontext_addr + LINUX_UC_MCONTEXT_OFF;
+    }
+  else
+    return -UNW_EUNSPEC;
+
+  /* Update the dwarf cursor.
+     Set the location of the registers to the corresponding addresses of the
+     uc_mcontext / sigcontext structure contents.  */
+  c->dwarf.loc[UNW_ARM_R0] = DWARF_LOC (sc_addr + LINUX_SC_R0_OFF, 0);
+  c->dwarf.loc[UNW_ARM_R1] = DWARF_LOC (sc_addr + LINUX_SC_R1_OFF, 0);
+  c->dwarf.loc[UNW_ARM_R2] = DWARF_LOC (sc_addr + LINUX_SC_R2_OFF, 0);
+  c->dwarf.loc[UNW_ARM_R3] = DWARF_LOC (sc_addr + LINUX_SC_R3_OFF, 0);
+  c->dwarf.loc[UNW_ARM_R4] = DWARF_LOC (sc_addr + LINUX_SC_R4_OFF, 0);
+  c->dwarf.loc[UNW_ARM_R5] = DWARF_LOC (sc_addr + LINUX_SC_R5_OFF, 0);
+  c->dwarf.loc[UNW_ARM_R6] = DWARF_LOC (sc_addr + LINUX_SC_R6_OFF, 0);
+  c->dwarf.loc[UNW_ARM_R7] = DWARF_LOC (sc_addr + LINUX_SC_R7_OFF, 0);
+  c->dwarf.loc[UNW_ARM_R8] = DWARF_LOC (sc_addr + LINUX_SC_R8_OFF, 0);
+  c->dwarf.loc[UNW_ARM_R9] = DWARF_LOC (sc_addr + LINUX_SC_R9_OFF, 0);
+  c->dwarf.loc[UNW_ARM_R10] = DWARF_LOC (sc_addr + LINUX_SC_R10_OFF, 0);
+  c->dwarf.loc[UNW_ARM_R11] = DWARF_LOC (sc_addr + LINUX_SC_FP_OFF, 0);
+  c->dwarf.loc[UNW_ARM_R12] = DWARF_LOC (sc_addr + LINUX_SC_IP_OFF, 0);
+  c->dwarf.loc[UNW_ARM_R13] = DWARF_LOC (sc_addr + LINUX_SC_SP_OFF, 0);
+  c->dwarf.loc[UNW_ARM_R14] = DWARF_LOC (sc_addr + LINUX_SC_LR_OFF, 0);
+  c->dwarf.loc[UNW_ARM_R15] = DWARF_LOC (sc_addr + LINUX_SC_PC_OFF, 0);
+
+  /* Set SP/CFA and PC/IP.  */
+  dwarf_get (&c->dwarf, c->dwarf.loc[UNW_ARM_R13], &c->dwarf.cfa);
+  dwarf_get (&c->dwarf, c->dwarf.loc[UNW_ARM_R15], &c->dwarf.ip);
+
+  return 1;
+}
+
+PROTECTED int
 unw_step (unw_cursor_t *cursor)
 {
   struct cursor *c = (struct cursor *) cursor;
@@ -78,6 +169,10 @@  unw_step (unw_cursor_t *cursor)
 
   Debug (1, "(cursor=%p)\n", c);
 
+  /* Check if this is a signal frame. */
+  if (unw_is_signal_frame (cursor))
+     return unw_handle_signal_frame (cursor);
+
   /* First, try DWARF-based unwinding. */
   if (UNW_TRY_METHOD(UNW_ARM_METHOD_DWARF))
     {
diff --git a/src/arm/init.h b/src/arm/init.h
index 1f8d7c1..767dbdc 100644
--- a/src/arm/init.h
+++ b/src/arm/init.h
@@ -58,6 +58,11 @@  common_init (struct cursor *c, unsigned use_prev_instr)
   if (ret < 0)
     return ret;
 
+  c->sigcontext_format = ARM_SCF_NONE;
+  c->sigcontext_addr = 0;
+  c->sigcontext_sp = 0;
+  c->sigcontext_pc = 0;
+
   /* FIXME: Initialisation for other registers.  */
 
   c->dwarf.args_size = 0;