diff mbox

[5/5] ARM: Initial support for remote unwinding using libunwind-ptrace

Message ID 1312373525-20741-6-git-send-email-ken.werner@linaro.org
State Superseded
Headers show

Commit Message

Ken Werner Aug. 3, 2011, 12:12 p.m. UTC
Change _UPTi_find_unwind_table to also look for the ARM specific unwind
information. Adjust the ARM unwind code to read memory using the accessor
routines.

Signed-off-by: Ken Werner <ken.werner@linaro.org>
---
 include/libunwind-arm.h          |    5 --
 include/tdep-arm/ex_tables.h     |    2 +-
 src/arm/Gex_tables.c             |  136 +++++++++++++++++++++++++++-----------
 src/arm/Gstep.c                  |    4 +-
 src/ptrace/_UPT_find_proc_info.c |   45 ++++++++++++-
 src/ptrace/_UPT_internal.h       |    3 +
 6 files changed, 144 insertions(+), 51 deletions(-)
diff mbox

Patch

diff --git a/include/libunwind-arm.h b/include/libunwind-arm.h
index 3338b42..63b0138 100644
--- a/include/libunwind-arm.h
+++ b/include/libunwind-arm.h
@@ -280,11 +280,6 @@  typedef ucontext_t unw_tdep_context_t;
 
 #include "libunwind-dynamic.h"
 
-struct arm_exidx_entry {
-  uint32_t addr;
-  uint32_t data;
-};
-
 typedef struct
   {
     /* no arm-specific auxiliary proc-info */
diff --git a/include/tdep-arm/ex_tables.h b/include/tdep-arm/ex_tables.h
index 6ea3414..8906f13 100644
--- a/include/tdep-arm/ex_tables.h
+++ b/include/tdep-arm/ex_tables.h
@@ -48,7 +48,7 @@  struct arm_exbuf_data
 #define arm_exidx_decode	UNW_OBJ(arm_exidx_decode)
 #define arm_exidx_apply_cmd	UNW_OBJ(arm_exidx_apply_cmd)
 
-int arm_exidx_extract (struct arm_exidx_entry *entry, uint8_t *buf);
+int arm_exidx_extract (struct dwarf_cursor *c, uint8_t *buf);
 int arm_exidx_decode (const uint8_t *buf, uint8_t len, struct dwarf_cursor *c);
 int arm_exidx_apply_cmd (struct arm_exbuf_data *edata, struct dwarf_cursor *c);
 
diff --git a/src/arm/Gex_tables.c b/src/arm/Gex_tables.c
index c90f675..88f800e 100644
--- a/src/arm/Gex_tables.c
+++ b/src/arm/Gex_tables.c
@@ -44,11 +44,19 @@  prel31_read (uint32_t prel31)
   return ((int32_t)prel31 << 1) >> 1;
 }
 
-static inline uint32_t
-prel31_to_addr (uint32_t *addr)
+static inline int
+prel31_to_addr (unw_addr_space_t as, void *arg, unw_word_t prel31,
+		unw_word_t *val)
 {
-  uint32_t offset = ((long)*addr << 1) >> 1;
-  return (uint32_t)addr + offset;
+  unw_word_t offset;
+
+  if ((*as->acc.access_mem)(as, prel31, &offset, 0, arg) < 0)
+    return -UNW_EINVAL;
+
+  offset = ((long)offset << 1) >> 1;
+  *val = prel31 + offset;
+
+  return 0;
 }
 
 /**
@@ -258,17 +266,31 @@  arm_exidx_decode (const uint8_t *buf, uint8_t len, struct dwarf_cursor *c)
 }
 
 /**
- * Reads the given entry and extracts the unwind instructions into buf.
- * Returns the number of the extracted unwind insns or -UNW_ESTOPUNWIND
- * if the special bit pattern ARM_EXIDX_CANT_UNWIND (0x1) was found.
+ * Reads the entry from the given cursor and extracts the unwind instructions
+ * into buf.  Returns the number of the extracted unwind insns or 
+ * -UNW_ESTOPUNWIND if the special bit pattern ARM_EXIDX_CANT_UNWIND (0x1) was
+ * found.
  */
 HIDDEN int
-arm_exidx_extract (struct arm_exidx_entry *entry, uint8_t *buf)
+arm_exidx_extract (struct dwarf_cursor *c, uint8_t *buf)
 {
   int nbuf = 0;
-  unw_word_t addr = prel31_to_addr (&entry->addr);
+  unw_word_t entry = (unw_word_t) c->pi.unwind_info;
+  unw_word_t addr;
+  uint32_t data;
+
+  /* An ARM unwind entry consists of a prel31 offset to the start of a
+     function followed by 31bits of data: 
+       * if set to 0x1: the function cannot be unwound (EXIDX_CANTUNWIND)
+       * if bit 31 is one: this is a table entry itself (ARM_EXIDX_COMPACT)
+       * if bit 31 is zero: this is a prel31 offset of the start of the
+	 table entry for this function  */
+  if (prel31_to_addr(c->as, c->as_arg, entry, &addr) < 0)
+    return -UNW_EINVAL;
+
+  if ((*c->as->acc.access_mem)(c->as, entry + 4, &data, 0, c->as_arg) < 0)
+    return -UNW_EINVAL;
 
-  uint32_t data = entry->data;
   if (data == ARM_EXIDX_CANT_UNWIND)
     {
       Debug (2, "0x1 [can't unwind]\n");
@@ -276,16 +298,23 @@  arm_exidx_extract (struct arm_exidx_entry *entry, uint8_t *buf)
     }
   else if (data & ARM_EXIDX_COMPACT)
     {
-      Debug (2, "%p compact model %d [%8.8x]\n", (void *)addr, (data >> 24) & 0x7f, data);
+      Debug (2, "%p compact model %d [%8.8x]\n", (void *)addr,
+	     (data >> 24) & 0x7f, data);
       buf[nbuf++] = data >> 16;
       buf[nbuf++] = data >> 8;
       buf[nbuf++] = data;
     }
   else
     {
-      uint32_t *extbl_data = (uint32_t *)prel31_to_addr (&entry->data);
-      data = (unw_word_t)extbl_data[0];
+      unw_word_t extbl_data;
       unsigned int n_table_words = 0;
+
+      if (prel31_to_addr(c->as, c->as_arg, entry + 4, &extbl_data) < 0)
+        return -UNW_EINVAL;
+
+      if ((*c->as->acc.access_mem)(c->as, extbl_data, &data, 0, c->as_arg) < 0)
+	return -UNW_EINVAL;
+
       if (data & ARM_EXIDX_COMPACT)
 	{
 	  int pers = (data >> 24) & 0x0f;
@@ -293,7 +322,7 @@  arm_exidx_extract (struct arm_exidx_entry *entry, uint8_t *buf)
 	  if (pers == 1 || pers == 2)
 	    {
 	      n_table_words = (data >> 16) & 0xff;
-	      extbl_data += 1;
+	      extbl_data += 4;
 	    }
 	  else
 	    buf[nbuf++] = data >> 16;
@@ -302,19 +331,28 @@  arm_exidx_extract (struct arm_exidx_entry *entry, uint8_t *buf)
 	}
       else
 	{
-	  unw_word_t pers = prel31_to_addr (extbl_data);
-	  Debug (2, "%p Personality routine: %8p\n", (void *)addr, (void *)pers);
-	  n_table_words = extbl_data[1] >> 24;
-	  buf[nbuf++] = extbl_data[1] >> 16;
-	  buf[nbuf++] = extbl_data[1] >> 8;
-	  buf[nbuf++] = extbl_data[1];
-	  extbl_data += 2;
+	  unw_word_t pers;
+ 	  if (prel31_to_addr (c->as, c->as_arg, extbl_data, &pers) < 0)
+	    return -UNW_EINVAL;
+	  Debug (2, "%p Personality routine: %8p\n", (void *)addr,
+		 (void *)pers);
+	  if ((*c->as->acc.access_mem)(c->as, extbl_data + 4, &data, 0,
+				       c->as_arg) < 0)
+	    return -UNW_EINVAL;
+	  n_table_words = data >> 24;
+	  buf[nbuf++] = data >> 16;
+	  buf[nbuf++] = data >> 8;
+	  buf[nbuf++] = data;
+	  extbl_data += 8;
 	}
       assert (n_table_words <= 5);
       unsigned j;
       for (j = 0; j < n_table_words; j++)
 	{
-	  data = *extbl_data++;
+	  if ((*c->as->acc.access_mem)(c->as, extbl_data, &data, 0,
+				       c->as_arg) < 0)
+	    return -UNW_EINVAL;
+	  extbl_data += 4;
 	  buf[nbuf++] = data >> 24;
 	  buf[nbuf++] = data >> 16;
 	  buf[nbuf++] = data >> 8;
@@ -336,41 +374,59 @@  unw_search_arm_unwind_table (unw_addr_space_t as, unw_word_t ip,
   if (UNW_TRY_METHOD (UNW_ARM_METHOD_EXIDX)
       && di->format == UNW_INFO_FORMAT_ARM_EXIDX)
     {
-      struct arm_exidx_entry *first =
-	(struct arm_exidx_entry *) di->u.rti.table_data;
-      struct arm_exidx_entry *last =
-	((struct arm_exidx_entry *) (di->u.rti.table_data +
-				     di->u.rti.table_len)) - 1;
-      struct arm_exidx_entry *entry;
-
-      if (ip < prel31_to_addr (&first->addr))
+      /* The .ARM.exidx section contains a sorted list of key-value pairs -
+	 the unwind entries.  The 'key' is a prel31 offset to the start of a
+	 function.  We binary search this section in order to find the
+	 appropriate unwind entry.  */
+      unw_word_t first = di->u.rti.table_data;
+      unw_word_t last = di->u.rti.table_data + di->u.rti.table_len - 8;
+      unw_word_t entry, val;
+
+      if (prel31_to_addr (as, arg, first, &val) < 0 && ip < val)
 	return -UNW_ENOINFO;
-      else if (ip >= prel31_to_addr (&last->addr))
+
+      if (prel31_to_addr (as, arg, last, &val) < 0)
+	return -UNW_EINVAL;
+
+      if (ip >= val)
 	{
-	   entry = last;
-	   pi->start_ip = prel31_to_addr (&last->addr);
-	   pi->end_ip = di->end_ip - 1;
+	  entry = last;
+
+	  if (prel31_to_addr (as, arg, last, &pi->start_ip) < 0)
+	    return -UNW_EINVAL;
+
+	  pi->end_ip = di->end_ip -1;
 	}
       else
 	{
-	  while (first < last - 1)
+	  while (first < last - 8)
 	    {
-	      entry = first + ((last - first + 1) >> 1);
-	      if (ip < prel31_to_addr (&entry->addr))
+	      entry = first + (((last - first) / 8 + 1) >> 1) * 8;
+
+	      if (prel31_to_addr (as, arg, entry, &val) < 0)
+		return -UNW_EINVAL;
+
+	      if (ip < val)
 		last = entry;
 	      else
 		first = entry;
 	    }
 
 	  entry = first;
-	  pi->start_ip = prel31_to_addr (&entry->addr);
-	  pi->end_ip = prel31_to_addr (&(entry + 1)->addr) - 1;
+
+	  if (prel31_to_addr (as, arg, entry, &pi->start_ip) < 0)
+	    return -UNW_EINVAL;
+
+	  if (prel31_to_addr (as, arg, entry + 8, &pi->end_ip) < 0)
+	    return -UNW_EINVAL;
+
+	  pi->end_ip--;
 	}
 
       if (need_unwind_info)
 	{
 	  pi->unwind_info_size = 8;
-	  pi->unwind_info = entry;
+	  pi->unwind_info = (void *) entry;
 	  pi->format = UNW_INFO_FORMAT_ARM_EXIDX;
 	}
       return 0;
diff --git a/src/arm/Gstep.c b/src/arm/Gstep.c
index 60cbf2c..270058b 100644
--- a/src/arm/Gstep.c
+++ b/src/arm/Gstep.c
@@ -35,7 +35,6 @@  static inline int
 arm_exidx_step (struct cursor *c)
 {
   unw_word_t old_ip, old_cfa;
-  struct arm_exidx_entry *entry;
   uint8_t buf[32];
   int ret;
 
@@ -51,8 +50,7 @@  arm_exidx_step (struct cursor *c)
   if (c->dwarf.pi.format != UNW_INFO_FORMAT_ARM_EXIDX)
     return -UNW_ENOINFO;
 
-  entry = (struct arm_exidx_entry *)c->dwarf.pi.unwind_info;
-  ret = arm_exidx_extract (entry, buf);
+  ret = arm_exidx_extract (&c->dwarf, buf);
   if (ret < 0)
     return ret;
 
diff --git a/src/ptrace/_UPT_find_proc_info.c b/src/ptrace/_UPT_find_proc_info.c
index e98a344..44feb34 100644
--- a/src/ptrace/_UPT_find_proc_info.c
+++ b/src/ptrace/_UPT_find_proc_info.c
@@ -176,6 +176,9 @@  _UPTi_find_unwind_table (struct UPT_info *ui, unw_addr_space_t as,
   unw_proc_info_t pi;
   unw_accessors_t *a;
   Elf_W(Ehdr) *ehdr;
+#if UNW_TARGET_ARM
+  const Elf_W(Phdr) *parm_exidx = NULL;
+#endif
   int i, ret, found = 0;
 
   /* XXX: Much of this code is Linux/LSB-specific.  */
@@ -205,6 +208,12 @@  _UPTi_find_unwind_table (struct UPT_info *ui, unw_addr_space_t as,
 	  pdyn = phdr + i;
 	  break;
 
+#if UNW_TARGET_ARM
+	case PT_ARM_EXIDX:
+	  parm_exidx = phdr + i;
+	  break;
+#endif
+
 	default:
 	  break;
 	}
@@ -316,6 +325,20 @@  _UPTi_find_unwind_table (struct UPT_info *ui, unw_addr_space_t as,
       found = 1;
     }
 
+#if UNW_TARGET_ARM
+  if (parm_exidx)
+    {
+      ui->di_arm.format = UNW_INFO_FORMAT_ARM_EXIDX;
+      ui->di_arm.start_ip = segbase;
+      ui->di_arm.end_ip = ui->di_arm.start_ip + ptxt->p_memsz;
+      ui->di_arm.u.rti.name_ptr = (unw_word_t) path;
+      ui->di_arm.u.rti.table_data = parm_exidx->p_vaddr + segbase -
+				    ptxt->p_vaddr;
+      ui->di_arm.u.rti.table_len = parm_exidx->p_memsz;
+      found = 1;
+    }
+#endif
+
 #ifdef CONFIG_DEBUG_FRAME
   {
       /* Try .debug_frame. */
@@ -362,6 +385,10 @@  get_unwind_info (struct UPT_info *ui, unw_addr_space_t as, unw_word_t ip)
 
   if ((ui->di_cache.format != -1
        && ip >= ui->di_cache.start_ip && ip < ui->di_cache.end_ip)
+#if UNW_TARGET_ARM
+      || (ui->di_debug.format != -1
+       && ip >= ui->di_arm.start_ip && ip < ui->di_arm.end_ip)
+#endif
       || (ui->di_debug.format != -1
        && ip >= ui->di_debug.start_ip && ip < ui->di_debug.end_ip))
     return 0;
@@ -377,6 +404,10 @@  get_unwind_info (struct UPT_info *ui, unw_addr_space_t as, unw_word_t ip)
       ui->di_debug.start_ip = ui->di_debug.end_ip = 0;
       ui->di_cache.format = -1;
       ui->di_debug.format = -1;
+#if UNW_TARGET_ARM
+      ui->di_arm.start_ip = ui->di_arm.end_ip = 0;
+      ui->di_arm.format = -1;
+#endif
     }
 
   if (tdep_get_elf_image (&ui->ei, ui->pid, ip, &segbase, &mapoff, path,
@@ -400,7 +431,11 @@  get_unwind_info (struct UPT_info *ui, unw_addr_space_t as, unw_word_t ip)
       && (ip < ui->di_debug.start_ip || ip >= ui->di_debug.end_ip))
      ui->di_debug.format = -1;
 
-  if (ui->di_cache.format == -1 && ui->di_debug.format == -1)
+  if (ui->di_cache.format == -1
+#if UNW_TARGET_ARM
+      && ui->di_arm.format == -1
+#endif
+      && ui->di_debug.format == -1)
     return -UNW_ENOINFO;
 
   return 0;
@@ -444,10 +479,16 @@  _UPT_find_proc_info (unw_addr_space_t as, unw_word_t ip, unw_proc_info_t *pi,
     }
 #endif
 
-  if (ret == -UNW_ENOINFO && ui->di_cache.format == -1)
+  if (ret == -UNW_ENOINFO && ui->di_cache.format != -1)
     ret = tdep_search_unwind_table (as, ip, &ui->di_cache,
 				    pi, need_unwind_info, arg);
 
+#if UNW_TARGET_ARM
+  if (ret == -UNW_ENOINFO && ui->di_arm.format != -1)
+    ret = tdep_search_unwind_table (as, ip, &ui->di_arm, pi,
+                                    need_unwind_info, arg);
+#endif
+
   if (ret == -UNW_ENOINFO && ui->di_debug.format != -1)
     ret = tdep_search_unwind_table (as, ip, &ui->di_debug, pi,
 				    need_unwind_info, arg);
diff --git a/src/ptrace/_UPT_internal.h b/src/ptrace/_UPT_internal.h
index 54d5fc9..a7a0481 100644
--- a/src/ptrace/_UPT_internal.h
+++ b/src/ptrace/_UPT_internal.h
@@ -57,6 +57,9 @@  struct UPT_info
 #if UNW_TARGET_IA64
     unw_dyn_info_t ktab;
 #endif
+#if UNW_TARGET_ARM
+    unw_dyn_info_t di_arm;	/* additional table info for .ARM.exidx */
+#endif
   };
 
 extern int _UPT_reg_offset[UNW_REG_LAST + 1];