* NEWS: Document .ARM.exidx / .ARM.extab support.
authorDaniel Jacobowitz <drow@false.org>
Tue, 2 Mar 2010 16:44:34 +0000 (16:44 +0000)
committerDaniel Jacobowitz <drow@false.org>
Tue, 2 Mar 2010 16:44:34 +0000 (16:44 +0000)
* dwarf.c (read_leb128): Make non-static.
* dwarf.h (read_leb128): Declare.
* readelf.c (REMOVE_ARCH_BITS): Define.
(find_section_by_address): New.
(read_uleb128): Move higher.  Use read_leb128 from dwarf.c.
(find_symbol_for_address): Handle the Thumb bit for ARM, by
using REMOVE_ARCH_BITS.
(struct arm_section, struct arm_unw_aux_info, arm_print_vma_and_name)
(arm_free_section, arm_section_get_word, decode_arm_unwind)
(dump_arm_unwind, arm_process_unwind): New.
(process_unwind): Handle ARM.

binutils/ChangeLog
binutils/NEWS
binutils/dwarf.c
binutils/dwarf.h
binutils/readelf.c

index 1068de9b0663718747057142e80fa5c906bfa585..3d47c3c12e90582242ce0917a433a3078c836138 100644 (file)
@@ -1,3 +1,19 @@
+2010-03-02  Daniel Jacobowitz  <dan@codesourcery.com>
+
+       * NEWS: Document .ARM.exidx / .ARM.extab support.
+
+       * dwarf.c (read_leb128): Make non-static.
+       * dwarf.h (read_leb128): Declare.
+       * readelf.c (REMOVE_ARCH_BITS): Define.
+       (find_section_by_address): New.
+       (read_uleb128): Move higher.  Use read_leb128 from dwarf.c.
+       (find_symbol_for_address): Handle the Thumb bit for ARM, by
+       using REMOVE_ARCH_BITS.
+       (struct arm_section, struct arm_unw_aux_info, arm_print_vma_and_name)
+       (arm_free_section, arm_section_get_word, decode_arm_unwind)
+       (dump_arm_unwind, arm_process_unwind): New.
+       (process_unwind): Handle ARM.
+
 2010-02-26  Jie Zhang  <jie@codesourcery.com>
 
        * MAINTAINERS: Update my email address.
index 351ffdfe441f90c871259e1575ab0fa2afc97239..7f8c61d1f72217003eeae147ee49e3beedd829df 100644 (file)
@@ -1,5 +1,8 @@
 -*- text -*-
 
+* Readelf can now display ARM unwind tables (.ARM.exidx / .ARM.extab) using
+  the -u / --unwind option.
+
 * Add --dyn-syms to readelf to dump dynamic symbol table.
 
 * A new tool - elfedit - has been added to directly manipulate ELF format
index 0e143322712a952fdbf57b5a97cb57a0b62b788a..e51aa647e590da813d2f7fa4d68900b1822f60b8 100644 (file)
@@ -227,7 +227,7 @@ print_dwarf_vma (dwarf_vma val, unsigned byte_size)
   fputs (buff + (byte_size == 4 ? 8 : 0), stdout);
 }
 
-static unsigned long int
+unsigned long int
 read_leb128 (unsigned char *data, unsigned int *length_return, int sign)
 {
   unsigned long int result = 0;
index 3f68d4739aaa557273f6c0471f56481ff81a583f..1ad96abd9427f4177363fc218daf8e18119c7ea4 100644 (file)
@@ -133,3 +133,6 @@ void *xcrealloc (void *, size_t, size_t);
 
 void error (const char *, ...) ATTRIBUTE_PRINTF_1;
 void warn (const char *, ...) ATTRIBUTE_PRINTF_1;
+
+unsigned long int read_leb128 (unsigned char *data,
+                              unsigned int *length_return, int sign);
index c5e9b4feb250e35f96b5378f90011b1f434317df..2c677598f9b59534f64a9878d2034d5cfac091b0 100644 (file)
@@ -285,6 +285,11 @@ static void (* byte_put) (unsigned char *, bfd_vma, int);
 #define streq(a,b)       (strcmp ((a), (b)) == 0)
 #define strneq(a,b,n)    (strncmp ((a), (b), (n)) == 0)
 #define const_strneq(a,b) (strncmp ((a), (b), sizeof (b) - 1) == 0)
+
+#define REMOVE_ARCH_BITS(ADDR) do {            \
+    if (elf_header.e_machine == EM_ARM)                \
+      (ADDR) &= ~1;                            \
+  } while (0)
 \f
 static void *
 get_data (void * var, FILE * file, long offset, size_t size, size_t nmemb,
@@ -544,6 +549,33 @@ find_section (const char * name)
   return NULL;
 }
 
+/* Return a pointer to a section containing ADDR, or NULL if no such
+   section exists.  */
+
+static Elf_Internal_Shdr *
+find_section_by_address (bfd_vma addr)
+{
+  unsigned int i;
+
+  for (i = 0; i < elf_header.e_shnum; i++)
+    {
+      Elf_Internal_Shdr *sec = section_headers + i;
+      if (addr >= sec->sh_addr && addr < sec->sh_addr + sec->sh_size)
+       return sec;
+    }
+
+  return NULL;
+}
+
+/* Read an unsigned LEB128 encoded value from p.  Set *PLEN to the number of
+   bytes read.  */
+
+static unsigned long
+read_uleb128 (unsigned char *data, unsigned int *length_return)
+{
+  return read_leb128 (data, length_return, 0);
+}
+
 /* Guess the relocation size commonly used by the specific machines.  */
 
 static int
@@ -5089,16 +5121,22 @@ find_symbol_for_address (Elf_Internal_Sym * symtab,
   Elf_Internal_Sym * best = NULL;
   unsigned long i;
 
+  REMOVE_ARCH_BITS (addr.offset);
+
   for (i = 0, sym = symtab; i < nsyms; ++i, ++sym)
     {
+      bfd_vma value = sym->st_value;
+
+      REMOVE_ARCH_BITS (value);
+
       if (ELF_ST_TYPE (sym->st_info) == STT_FUNC
          && sym->st_name != 0
          && (addr.section == SHN_UNDEF || addr.section == sym->st_shndx)
-         && addr.offset >= sym->st_value
-         && addr.offset - sym->st_value < dist)
+         && addr.offset >= value
+         && addr.offset - value < dist)
        {
          best = sym;
-         dist = addr.offset - sym->st_value;
+         dist = addr.offset - value;
          if (!dist)
            break;
        }
@@ -5761,6 +5799,579 @@ hppa_process_unwind (FILE * file)
   return 1;
 }
 
+struct arm_section
+{
+  unsigned char *data;
+
+  Elf_Internal_Shdr *sec;
+  Elf_Internal_Rela *rela;
+  unsigned long nrelas;
+  unsigned int rel_type;
+
+  Elf_Internal_Rela *next_rela;
+};
+
+struct arm_unw_aux_info
+{
+  FILE *file;
+
+  Elf_Internal_Sym *symtab;    /* The symbol table.  */
+  unsigned long nsyms;         /* Number of symbols.  */
+  char *strtab;                        /* The string table.  */
+  unsigned long strtab_size;   /* Size of string table.  */
+};
+
+static const char *
+arm_print_vma_and_name (struct arm_unw_aux_info *aux,
+                       bfd_vma fn, struct absaddr addr)
+{
+  const char *procname;
+  bfd_vma sym_offset;
+
+  if (addr.section == SHN_UNDEF)
+    addr.offset = fn;
+
+  find_symbol_for_address (aux->symtab, aux->nsyms, aux->strtab,
+                          aux->strtab_size, addr, &procname,
+                          &sym_offset);
+
+  print_vma (fn, PREFIX_HEX);
+
+  if (procname)
+    {
+      fputs (" <", stdout);
+      fputs (procname, stdout);
+
+      if (sym_offset)
+       printf ("+0x%lx", (unsigned long) sym_offset);
+      fputc ('>', stdout);
+    }
+
+  return procname;
+}
+
+static void
+arm_free_section (struct arm_section *arm_sec)
+{
+  if (arm_sec->data != NULL)
+    free (arm_sec->data);
+
+  if (arm_sec->rela != NULL)
+    free (arm_sec->rela);
+}
+
+static int
+arm_section_get_word (struct arm_unw_aux_info *aux,
+                     struct arm_section *arm_sec,
+                     Elf_Internal_Shdr *sec, bfd_vma word_offset,
+                     unsigned int *wordp, struct absaddr *addr)
+{
+  Elf_Internal_Rela *rp;
+  Elf_Internal_Sym *sym;
+  const char * relname;
+  unsigned int word;
+  bfd_boolean wrapped;
+
+  addr->section = SHN_UNDEF;
+  addr->offset = 0;
+
+  if (sec != arm_sec->sec)
+    {
+      Elf_Internal_Shdr *relsec;
+
+      arm_free_section (arm_sec);
+
+      arm_sec->sec = sec;
+      arm_sec->data = get_data (NULL, aux->file, sec->sh_offset, 1,
+                               sec->sh_size, _("unwind data"));
+
+      arm_sec->rela = NULL;
+      arm_sec->nrelas = 0;
+
+      for (relsec = section_headers;
+          relsec < section_headers + elf_header.e_shnum;
+          ++relsec)
+       {
+         if (relsec->sh_info >= elf_header.e_shnum
+             || section_headers + relsec->sh_info != sec)
+           continue;
+
+         if (relsec->sh_type == SHT_REL)
+           {
+             if (!slurp_rel_relocs (aux->file, relsec->sh_offset,
+                                    relsec->sh_size,
+                                    & arm_sec->rela, & arm_sec->nrelas))
+               return 0;
+             break;
+           }
+         else if (relsec->sh_type == SHT_RELA)
+           {
+             if (!slurp_rela_relocs (aux->file, relsec->sh_offset,
+                                     relsec->sh_size,
+                                     & arm_sec->rela, & arm_sec->nrelas))
+               return 0;
+             break;
+           }
+       }
+
+      arm_sec->next_rela = arm_sec->rela;
+    }
+
+  if (arm_sec->data == NULL)
+    return 0;
+
+  word = byte_get (arm_sec->data + word_offset, 4);
+
+  wrapped = FALSE;
+  for (rp = arm_sec->next_rela; rp != arm_sec->rela + arm_sec->nrelas; rp++)
+    {
+      bfd_vma prelval, offset;
+
+      if (rp->r_offset > word_offset && !wrapped)
+       {
+         rp = arm_sec->rela;
+         wrapped = TRUE;
+       }
+      if (rp->r_offset > word_offset)
+       break;
+
+      if (rp->r_offset & 3)
+       {
+         warn (_("Skipping unexpected relocation at offset 0x%lx\n"),
+               (unsigned long) rp->r_offset);
+         continue;
+       }
+
+      if (rp->r_offset < word_offset)
+       continue;
+
+      relname = elf_arm_reloc_type (ELF32_R_TYPE (rp->r_info));
+
+      if (streq (relname, "R_ARM_NONE"))
+       continue;
+
+      if (! streq (relname, "R_ARM_PREL31"))
+       {
+         warn (_("Skipping unexpected relocation type %s\n"), relname);
+         continue;
+       }
+
+      sym = aux->symtab + ELF32_R_SYM (rp->r_info);
+
+      if (arm_sec->rel_type == SHT_REL)
+       {
+         offset = word & 0x7fffffff;
+         if (offset & 0x40000000)
+           offset |= ~ (bfd_vma) 0x7fffffff;
+       }
+      else
+       offset = rp->r_addend;
+
+      offset += sym->st_value;
+      prelval = offset - (arm_sec->sec->sh_addr + rp->r_offset);
+
+      word = (word & ~ (bfd_vma) 0x7fffffff) | (prelval & 0x7fffffff);
+      addr->section = sym->st_shndx;
+      addr->offset = offset;
+      break;
+    }
+
+  *wordp = word;
+  arm_sec->next_rela = rp;
+
+  return 1;
+}
+
+static void
+decode_arm_unwind (struct arm_unw_aux_info *aux,
+                  unsigned int word, unsigned int remaining,
+                  bfd_vma data_offset, Elf_Internal_Shdr *data_sec,
+                  struct arm_section *data_arm_sec)
+{
+  int per_index;
+  unsigned int more_words;
+  struct absaddr addr;
+
+#define ADVANCE                                                        \
+  if (remaining == 0 && more_words)                            \
+    {                                                          \
+      data_offset += 4;                                                \
+      if (!arm_section_get_word (aux, data_arm_sec, data_sec,  \
+                                data_offset, &word, &addr))    \
+       return;                                                 \
+      remaining = 4;                                           \
+      more_words--;                                            \
+    }                                                          \
+
+#define GET_OP(OP)                     \
+  ADVANCE;                             \
+  if (remaining)                       \
+    {                                  \
+      remaining--;                     \
+      (OP) = word >> 24;               \
+      word <<= 8;                      \
+    }                                  \
+  else                                 \
+    {                                  \
+      printf ("[Truncated opcode]\n"); \
+      return;                          \
+    }                                  \
+  printf (_("0x%02x "), OP)
+
+  if (remaining == 0)
+    {
+      /* Fetch the first word.  */
+      if (!arm_section_get_word (aux, data_arm_sec, data_sec, data_offset,
+                                &word, &addr))
+       return;
+      remaining = 4;
+    }
+
+  if ((word & 0x80000000) == 0)
+    {
+      /* Expand prel31 for personality routine.  */
+      bfd_vma fn;
+      const char *procname;
+
+      fn = word;
+      if (fn & 0x40000000)
+       fn |= ~ (bfd_vma) 0x7fffffff;
+      fn = fn + data_sec->sh_addr + data_offset;
+
+      printf (_("  Personality routine: "));
+      procname = arm_print_vma_and_name (aux, fn, addr);
+      fputc ('\n', stdout);
+
+      /* The GCC personality routines use the standard compact
+        encoding, starting with one byte giving the number of
+        words.  */
+      if (procname != NULL
+         && (const_strneq (procname, "__gcc_personality_v0")
+             || const_strneq (procname, "__gxx_personality_v0")
+             || const_strneq (procname, "__gcj_personality_v0")
+             || const_strneq (procname, "__gnu_objc_personality_v0")))
+       {
+         remaining = 0;
+         more_words = 1;
+         ADVANCE;
+         if (!remaining)
+           {
+             printf (_("  [Truncated data]\n"));
+             return;
+           }
+         more_words = word >> 24;
+         word <<= 8;
+         remaining--;
+       }
+      else
+       return;
+    }
+  else
+    {
+      
+      per_index = (word >> 24) & 0x7f;
+      if (per_index != 0 && per_index != 1 && per_index != 2)
+       {
+         printf (_("  [reserved compact index %d]\n"), per_index);
+         return;
+       }
+
+      printf (_("  Compact model %d\n"), per_index);
+      if (per_index == 0)
+       {
+         more_words = 0;
+         word <<= 8;
+         remaining--;
+       }
+      else
+       {
+         more_words = (word >> 16) & 0xff;
+         word <<= 16;
+         remaining -= 2;
+       }
+    }
+
+  /* Decode the unwinding instructions.  */
+  while (1)
+    {
+      unsigned int op, op2;
+
+      ADVANCE;
+      if (remaining == 0)
+       break;
+      remaining--;
+      op = word >> 24;
+      word <<= 8;
+
+      printf (_("  0x%02x "), op);
+
+      if ((op & 0xc0) == 0x00)
+       {
+         int offset = ((op & 0x3f) << 2) + 4;
+         printf (_("     vsp = vsp + %d"), offset);
+       }
+      else if ((op & 0xc0) == 0x40)
+       {
+         int offset = ((op & 0x3f) << 2) + 4;
+         printf (_("     vsp = vsp - %d"), offset);
+       }
+      else if ((op & 0xf0) == 0x80)
+       {
+         GET_OP (op2);
+         if (op == 0x80 && op2 == 0)
+           printf (_("Refuse to unwind"));
+         else
+           {
+             unsigned int mask = ((op & 0x0f) << 8) | op2;
+             int first = 1;
+             int i;
+             printf ("pop {");
+             for (i = 0; i < 12; i++)
+               if (mask & (1 << i))
+                 {
+                   if (first)
+                     first = 0;
+                   else
+                     printf (", ");
+                   printf ("r%d", 4 + i);
+                 }
+             printf ("}");
+           }
+       }
+      else if ((op & 0xf0) == 0x90)
+       {
+         if (op == 0x9d || op == 0x9f)
+           printf (_("     [Reserved]"));
+         else
+           printf (_("     vsp = r%d"), op & 0x0f);
+       }
+      else if ((op & 0xf0) == 0xa0)
+       {
+         int end = 4 + (op & 0x07);
+         int first = 1;
+         int i;
+         printf ("     pop {");
+         for (i = 4; i <= end; i++)
+           {
+             if (first)
+               first = 0;
+             else
+               printf (", ");
+             printf ("r%d", i);
+           }
+         if (op & 0x08)
+           {
+             if (first)
+               printf (", ");
+             printf ("r14");
+           }
+         printf ("}");
+       }
+      else if (op == 0xb0)
+       printf (_("     finish"));
+      else if (op == 0xb1)
+       {
+         GET_OP (op2);
+         if (op2 == 0 || (op2 & 0xf0) != 0)
+           printf (_("[Spare]"));
+         else
+           {
+             unsigned int mask = op2 & 0x0f;
+             int first = 1;
+             int i;
+             printf ("pop {");
+             for (i = 0; i < 12; i++)
+               if (mask & (1 << i))
+                 {
+                   if (first)
+                     first = 0;
+                   else
+                     printf (", ");
+                   printf ("r%d", i);
+                 }
+             printf ("}");
+           }
+       }
+      else if (op == 0xb2)
+       {
+         unsigned char buf[5];
+         unsigned int i, len;
+         unsigned long offset;
+         for (i = 0; i < 9; i++)
+           {
+             GET_OP (buf[i]);
+             if ((buf[i] & 0x80) == 0)
+               break;
+           }
+         assert (i < sizeof (buf));
+         offset = read_uleb128 (buf, &len);
+         assert (len == i + 1);
+         offset = offset * 4 + 0x204;
+         printf (_("vsp = vsp + %ld"), offset);
+       }
+      else
+       {
+         if (op == 0xb3 || op == 0xc6 || op == 0xc7 || op == 0xc8 || op == 0xc9)
+           {
+             GET_OP (op2);
+             printf (_("[unsupported two-byte opcode]"));
+           }
+         else
+           {
+             printf (_("     [unsupported opcode]"));
+           }
+       }
+      printf ("\n");
+    }
+
+  /* Decode the descriptors.  Not implemented.  */
+}
+
+static void
+dump_arm_unwind (struct arm_unw_aux_info *aux, Elf_Internal_Shdr *exidx_sec)
+{
+  struct arm_section exidx_arm_sec, extab_arm_sec;
+  unsigned int i, exidx_len;
+
+  memset (&exidx_arm_sec, 0, sizeof (exidx_arm_sec));
+  memset (&extab_arm_sec, 0, sizeof (extab_arm_sec));
+  exidx_len = exidx_sec->sh_size / 8;
+
+  for (i = 0; i < exidx_len; i++)
+    {
+      unsigned int exidx_fn, exidx_entry;
+      struct absaddr fn_addr, entry_addr;
+      bfd_vma fn;
+
+      fputc ('\n', stdout);
+
+      if (!arm_section_get_word (aux, &exidx_arm_sec, exidx_sec,
+                                8 * i, &exidx_fn, &fn_addr)
+         || !arm_section_get_word (aux, &exidx_arm_sec, exidx_sec,
+                                   8 * i + 4, &exidx_entry, &entry_addr))
+       {
+         arm_free_section (&exidx_arm_sec);
+         arm_free_section (&extab_arm_sec);
+         return;
+       }
+
+      fn = exidx_fn & 0x7fffffff;
+      if (fn & 0x40000000)
+       fn |= ~ (bfd_vma) 0x7fffffff;
+      fn = fn + exidx_sec->sh_addr + 8 * i;
+
+      arm_print_vma_and_name (aux, fn, entry_addr);
+      fputs (": ", stdout);
+
+      if (exidx_entry == 1)
+       {
+         print_vma (exidx_entry, PREFIX_HEX);
+         fputs (" [cantunwind]\n", stdout);
+       }
+      else if (exidx_entry & 0x80000000)
+       {
+         print_vma (exidx_entry, PREFIX_HEX);
+         fputc ('\n', stdout);
+         decode_arm_unwind (aux, exidx_entry, 4, 0, NULL, NULL);
+       }
+      else
+       {
+         bfd_vma table, table_offset;
+         Elf_Internal_Shdr *table_sec;
+
+         fputs ("@", stdout);
+         table = exidx_entry;
+         if (table & 0x40000000)
+           table |= ~ (bfd_vma) 0x7fffffff;
+         table = table + exidx_sec->sh_addr + 8 * i + 4;
+         print_vma (table, PREFIX_HEX);
+         printf ("\n");
+
+         /* Locate the matching .ARM.extab.  */
+         if (entry_addr.section != SHN_UNDEF
+             && entry_addr.section < elf_header.e_shnum)
+           {
+             table_sec = section_headers + entry_addr.section;
+             table_offset = entry_addr.offset;
+           }
+         else
+           {
+             table_sec = find_section_by_address (table);
+             if (table_sec != NULL)
+               table_offset = table - table_sec->sh_addr;
+           }
+         if (table_sec == NULL)
+           {
+             warn (_("Could not locate .ARM.extab section containing 0x%lx.\n"),
+                   (unsigned long) table);
+             continue;
+           }
+         decode_arm_unwind (aux, 0, 0, table_offset, table_sec,
+                            &extab_arm_sec);
+       }
+    }
+
+  printf ("\n");
+
+  arm_free_section (&exidx_arm_sec);
+  arm_free_section (&extab_arm_sec);
+}
+
+static int
+arm_process_unwind (FILE *file)
+{
+  struct arm_unw_aux_info aux;
+  Elf_Internal_Shdr *unwsec = NULL;
+  Elf_Internal_Shdr *strsec;
+  Elf_Internal_Shdr *sec;
+  unsigned long i;
+
+  memset (& aux, 0, sizeof (aux));
+  aux.file = file;
+
+  if (string_table == NULL)
+    return 1;
+
+  for (i = 0, sec = section_headers; i < elf_header.e_shnum; ++i, ++sec)
+    {
+      if (sec->sh_type == SHT_SYMTAB && sec->sh_link < elf_header.e_shnum)
+       {
+         aux.nsyms = sec->sh_size / sec->sh_entsize;
+         aux.symtab = GET_ELF_SYMBOLS (file, sec);
+
+         strsec = section_headers + sec->sh_link;
+         aux.strtab = get_data (NULL, file, strsec->sh_offset,
+                                1, strsec->sh_size, _("string table"));
+         aux.strtab_size = aux.strtab != NULL ? strsec->sh_size : 0;
+       }
+      else if (sec->sh_type == SHT_ARM_EXIDX)
+       unwsec = sec;
+    }
+
+  if (!unwsec)
+    printf (_("\nThere are no unwind sections in this file.\n"));
+
+  for (i = 0, sec = section_headers; i < elf_header.e_shnum; ++i, ++sec)
+    {
+      if (sec->sh_type == SHT_ARM_EXIDX)
+       {
+         printf (_("\nUnwind table index '%s' at offset 0x%lx contains %lu entries:\n"),
+                 SECTION_NAME (sec),
+                 (unsigned long) sec->sh_offset,
+                 (unsigned long) (sec->sh_size / (2 * eh_addr_size)));
+
+         dump_arm_unwind (&aux, sec);
+       }
+    }
+
+  if (aux.symtab)
+    free (aux.symtab);
+  if (aux.strtab)
+    free ((char *) aux.strtab);
+
+  return 1;
+}
+
 static int
 process_unwind (FILE * file)
 {
@@ -5770,6 +6381,7 @@ process_unwind (FILE * file)
     int (* handler)(FILE *);
   } handlers[] =
   {
+    { EM_ARM, arm_process_unwind },
     { EM_IA_64, ia64_process_unwind },
     { EM_PARISC, hppa_process_unwind },
     { 0, 0 }
@@ -9177,33 +9789,6 @@ static arm_attr_public_tag arm_attr_public_tags[] =
 };
 #undef LOOKUP
 
-/* Read an unsigned LEB128 encoded value from p.  Set *PLEN to the number of
-   bytes read.  */
-
-static unsigned int
-read_uleb128 (unsigned char * p, unsigned int * plen)
-{
-  unsigned char c;
-  unsigned int val;
-  int shift;
-  int len;
-
-  val = 0;
-  shift = 0;
-  len = 0;
-  do
-    {
-      c = *(p++);
-      len++;
-      val |= ((unsigned int)c & 0x7f) << shift;
-      shift += 7;
-    }
-  while (c & 0x80);
-
-  *plen = len;
-  return val;
-}
-
 static unsigned char *
 display_arm_attribute (unsigned char * p)
 {