* 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.
This commit is contained in:
Daniel Jacobowitz 2010-03-02 16:44:34 +00:00
parent 1ba53b71ca
commit 0b6ae52290
5 changed files with 638 additions and 31 deletions

View 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.

View 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

View 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;

View 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);

View 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)
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)
{