From 0b6ae52290bac3fee2f4ce2e435b70b52c932d69 Mon Sep 17 00:00:00 2001 From: Daniel Jacobowitz Date: Tue, 2 Mar 2010 16:44:34 +0000 Subject: [PATCH] * 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. --- binutils/ChangeLog | 16 ++ binutils/NEWS | 3 + binutils/dwarf.c | 2 +- binutils/dwarf.h | 3 + binutils/readelf.c | 645 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 5 files changed, 638 insertions(+), 31 deletions(-) diff --git a/binutils/ChangeLog b/binutils/ChangeLog index 1068de9..3d47c3c 100644 --- a/binutils/ChangeLog +++ b/binutils/ChangeLog @@ -1,3 +1,19 @@ +2010-03-02 Daniel Jacobowitz + + * 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 * MAINTAINERS: Update my email address. diff --git a/binutils/NEWS b/binutils/NEWS index 351ffdf..7f8c61d 100644 --- a/binutils/NEWS +++ b/binutils/NEWS @@ -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 diff --git a/binutils/dwarf.c b/binutils/dwarf.c index 0e14332..e51aa64 100644 --- a/binutils/dwarf.c +++ b/binutils/dwarf.c @@ -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; diff --git a/binutils/dwarf.h b/binutils/dwarf.h index 3f68d47..1ad96ab 100644 --- a/binutils/dwarf.h +++ b/binutils/dwarf.h @@ -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); diff --git a/binutils/readelf.c b/binutils/readelf.c index c5e9b4f..2c67759 100644 --- a/binutils/readelf.c +++ b/binutils/readelf.c @@ -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) 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) { -- 2.7.4