[rfc/rfa] Use ARM exception tables as GDB unwinder

Ulrich Weigand
2010-10-20T00:01:14Z

Hello,

as suggested by Dan Jacobowitz, this patch adds support for using the frame
unwinding instructions found in certain records of the ARM-specific exception
handling data (in .ARM.exidx and .ARM.extbl sections) for unwinding in GDB.

There are two main parts needed to implement this support:

1. A new_objfile observer that checks every newly loaded objfile for the
presence of ARM exception handling records, and if found, records all frame
unwinding instructions present there. This data is stored indexed by section
and offset, analogous to how mapping symbols are handled.

2. An unwind sniffer that checks whether the PC is covered by an exception
table entry with unwinding instructions. If so, those instructions are
decoded and used to construct a prologue unwinder cache record. This is
then used with the existing prologue unwinder's this_is and prev_register
routines to perform actual unwinding.

In addition, one minor change was necessary to the prologue unwinder:
the this_id routine no longer uses get_frame_func to retrieve the
function start address; instead, this value is stored in the cache.
This allows the exception unwinder to provide this information from
the exception table, instead of having to rely on symbol data.

Tested on armv7l-linux-gnueabi with no regressions. The patch fixes
the following testsuite failures:
FAIL: gdb.base/break-interp.exp: LDprelinkNOdebugNO: BINprelinkNOdebugNOpieNO: core: core main bt
FAIL: gdb.base/break-interp.exp: LDprelinkNOdebugNO: BINprelinkNOdebugNOpieYES: core: core main bt
FAIL: gdb.base/break-interp.exp: LDprelinkNOdebugNO: BINprelinkNOdebugINpieNO: core: core main bt
FAIL: gdb.base/break-interp.exp: LDprelinkNOdebugNO: BINprelinkNOdebugINpieYES: core: core main bt
FAIL: gdb.base/break-interp.exp: LDprelinkNOdebugNO: BINprelinkNOdebugSEPpieNO: core: core main bt
FAIL: gdb.base/break-interp.exp: LDprelinkNOdebugNO: BINprelinkNOdebugSEPpieYES: core: core main bt
FAIL: gdb.base/break-interp.exp: LDprelinkNOdebugIN: BINprelinkNOdebugNOpieNO: core: core main bt
FAIL: gdb.base/break-interp.exp: LDprelinkNOdebugIN: BINprelinkNOdebugNOpieYES: core: core main bt
FAIL: gdb.base/break-interp.exp: LDprelinkNOdebugIN: BINprelinkNOdebugINpieNO: core: core main bt
FAIL: gdb.base/break-interp.exp: LDprelinkNOdebugIN: BINprelinkNOdebugINpieYES: core: core main bt
FAIL: gdb.base/break-interp.exp: LDprelinkNOdebugIN: BINprelinkNOdebugSEPpieNO: core: core main bt
FAIL: gdb.base/break-interp.exp: LDprelinkNOdebugIN: BINprelinkNOdebugSEPpieYES: core: core main bt

On systems where no libc debug/symbol information is available, the
following additional testsuite failures are fixed as well:
FAIL: gdb.gdb/selftest.exp: backtrace through signal handler
FAIL: gdb.threads/attach-stopped.exp: threaded: attach2 to stopped bt
FAIL: gdb.threads/attachstop-mt.exp: attach0 to sleeping bt
FAIL: gdb.threads/attachstop-mt.exp: attach3 to stopped bt
FAIL: gdb.threads/attachstop-mt.exp: attach4 to stopped bt
FAIL: gdb.threads/pthreads.exp: check backtrace from thread 1
FAIL: gdb.threads/pthreads.exp: check backtrace from thread 2
FAIL: gdb.threads/pthreads.exp: apply backtrace command to all three threads

OK for mainline?

Bye,
Ulrich

ChangeLog:

* arm-tdep.c: Include "observer.h".
(struct arm_prologue_cache): Add THIS_FUNC member.
(arm_make_prologue_cache): Set it.
(arm_prologue_this_id): Use it instead of calling get_frame_func.
(arm_exidx_data_key): New static variable.
(struct arm_exidx_entry, arm_exidx_entry_s): New data types.
(struct arm_exidx_data): Likewise.
(arm_exidx_data_free ): New function.
(arm_compare_exidx_entries): Likewise.
(section_covers_vma): Likewise.
(arm_exidx_new_objfile): Likewise.
(arm_find_exidx_entry): Likewise.
(arm_exidx_fill_cache): Likewise.
(arm_exidx_unwind_sniffer): Likewise.
(arm_exidx_unwind): New global variable.
(arm_gdbarch_init): Append unwinder arm_exidx_unwind.
(_initialize_arm_tdep): Attach arm_exidx_new_objfile to new_objfile
observer. Register arm_exidx_data_key as objfile data.

Index: gdb/arm-tdep.c
===================================================================
RCS file: /cvs/src/src/gdb/arm-tdep.c,v
retrieving revision 1.310
diff -u -p -r1.310 arm-tdep.c
--- gdb/arm-tdep.c 12 Oct 2010 08:46:15 -0000 1.310
+++ gdb/arm-tdep.c 18 Oct 2010 17:14:31 -0000
@@ -42,6 +42,7 @@
#include "prologue-value.h"
#include "target-descriptions.h"
#include "user-regs.h"
+#include "observer.h"

#include "arm-tdep.h"
#include "gdb/sim-arm.h"
@@ -225,6 +226,9 @@ struct arm_prologue_cache
to identify this frame. */
CORE_ADDR prev_sp;

+ /* Starting address of the current function. */
+ CORE_ADDR this_func;
+
/* The frame base for this frame is just prev_sp - frame size.
FRAMESIZE is the distance from the frame pointer to the
initial stack pointer. */
@@ -1793,6 +1797,7 @@ arm_make_prologue_cache (struct frame_in
return cache;

cache->prev_sp = unwound_fp + cache->framesize;
+ cache->this_func = get_frame_func (this_frame);

/* Calculate actual addresses of saved registers using offsets
determined by arm_scan_prologue. */
@@ -1813,7 +1818,7 @@ arm_prologue_this_id (struct frame_info
{
struct arm_prologue_cache *cache;
struct frame_id id;
- CORE_ADDR pc, func;
+ CORE_ADDR pc;

if (*this_cache == NULL)
*this_cache = arm_make_prologue_cache (this_frame);
@@ -1828,8 +1833,7 @@ arm_prologue_this_id (struct frame_info
if (cache->prev_sp == 0)
return;

- func = get_frame_func (this_frame);
- id = frame_id_build (cache->prev_sp, func);
+ id = frame_id_build (cache->prev_sp, cache->this_func);
*this_id = id;
}

@@ -1899,6 +1903,629 @@ struct frame_unwind arm_prologue_unwind
default_frame_sniffer
};

+/* Maintain a list of ARM exception table entries per objfile, similar to the
+ list of mapping symbols. We only cache entries for standard ARM-defined
+ personality routines; the cache will contains only the frame unwinding
+ instructions associated with the entry (not the descriptors). */
+
+static const struct objfile_data *arm_exidx_data_key;
+
+struct arm_exidx_entry
+{
+ bfd_vma addr;
+ gdb_byte *entry;
+};
+typedef struct arm_exidx_entry arm_exidx_entry_s;
+DEF_VEC_O(arm_exidx_entry_s);
+
+struct arm_exidx_data
+{
+ VEC(arm_exidx_entry_s) **section_maps;
+};
+
+static void
+arm_exidx_data_free (struct objfile *objfile, void *arg)
+{
+ struct arm_exidx_data *data = arg;
+ unsigned int i;
+
+ for (i = 0; i < objfile->obfd->section_count; i++)
+ VEC_free (arm_exidx_entry_s, data->section_maps[i]);
+}
+
+static inline int
+arm_compare_exidx_entries (const struct arm_exidx_entry *lhs,
+ const struct arm_exidx_entry *rhs)
+{
+ return lhs->addr < rhs->addr;
+}
+
+static bfd_boolean
+section_covers_vma (bfd *abfd, asection *section, void *ptr)
+{
+ bfd_vma vma = *(bfd_vma *) ptr;
+ return ((section->flags & SEC_ALLOC) != 0
+ && section->vma <= vma
+ && vma < section->vma + section->size);
+}
+
+/* Parse contents of exception table and exception index sections
+ of OBJFILE, and fill in the exception table entry cache.
+
+ For each entry that refers to a standard ARM-defined personality
+ routine, extract the frame unwinding instructions (from either
+ the index or the table section). The unwinding instructions
+ are normalized by:
+ - extracting them from the rest of the table data
+ - converting to host endianness
+ - appending the implicit 0xb0 ("Finish") code
+
+ The extracted and normalized instructions are stored for later
+ retrieval by the arm_find_exidx_entry routine. */
+
+static void
+arm_exidx_new_objfile (struct objfile *objfile)
+{
+ struct cleanup *cleanups = make_cleanup (null_cleanup, NULL);
+ struct arm_exidx_data *data;
+ asection *exidx, *extab;
+ bfd_vma exidx_vma = 0, extab_vma = 0;
+ bfd_size_type exidx_size = 0, extab_size = 0;
+ gdb_byte *exidx_data = NULL, *extab_data = NULL;
+ LONGEST i;
+
+ /* If we've already touched this file, do nothing. */
+ if (!objfile || objfile_data (objfile, arm_exidx_data_key) != NULL)
+ return;
+
+ /* Read contents of exception table and index. */
+ exidx = bfd_get_section_by_name (objfile->obfd, ".ARM.exidx");
+ if (exidx)
+ {
+ exidx_vma = bfd_section_vma (objfile->obfd, exidx);
+ exidx_size = bfd_get_section_size (exidx);
+ exidx_data = xmalloc (exidx_size);
+ make_cleanup (xfree, exidx_data);
+
+ if (!bfd_get_section_contents (objfile->obfd, exidx,
+ exidx_data, 0, exidx_size))
+ {
+ do_cleanups (cleanups);
+ return;
+ }
+ }
+
+ extab = bfd_get_section_by_name (objfile->obfd, ".ARM.extab");
+ if (extab)
+ {
+ extab_vma = bfd_section_vma (objfile->obfd, extab);
+ extab_size = bfd_get_section_size (extab);
+ extab_data = xmalloc (extab_size);
+ make_cleanup (xfree, extab_data);
+
+ if (!bfd_get_section_contents (objfile->obfd, extab,
+ extab_data, 0, extab_size))
+ {
+ do_cleanups (cleanups);
+ return;
+ }
+ }
+
+ /* Allocate exception table data structure. */
+ data = OBSTACK_ZALLOC (&objfile->objfile_obstack, struct arm_exidx_data);
+ set_objfile_data (objfile, arm_exidx_data_key, data);
+ data->section_maps = OBSTACK_CALLOC (&objfile->objfile_obstack,
+ objfile->obfd->section_count,
+ VEC(arm_exidx_entry_s) *);
+
+ /* Fill in exception table. */
+ for (i = 0; i < exidx_size / 8; i++)
+ {
+ struct arm_exidx_entry new_exidx_entry;
+ bfd_vma idx = bfd_h_get_32 (objfile->obfd, exidx_data + i * 8);
+ bfd_vma val = bfd_h_get_32 (objfile->obfd, exidx_data + i * 8 + 4);
+ asection *sect;
+ gdb_byte *p, *entry = NULL;
+
+ /* Extract address of start of function. */
+ idx = ((idx & 0x7fffffff) ^ 0x40000000) - 0x40000000;
+ idx += exidx_vma + i * 8;
+
+ /* Find section containing function and compute section offset. */
+ sect = bfd_sections_find_if (objfile->obfd, section_covers_vma, &idx);
+ if (sect == NULL)
+ continue;
+ idx -= bfd_get_section_vma (objfile->obfd, sect);
+
+ /* Determine address of exception table entry. */
+ if (val == 1)
+ {
+ /* EXIDX_CANTUNWIND -- no exception table entry present. */
+ }
+ else if ((val & 0xff000000) == 0x80000000)
+ {
+ /* Exception table entry embedded in .ARM.exidx
+ -- must be short form. */
+ p = entry = obstack_alloc (&objfile->objfile_obstack, 4);
+ *p++ = (gdb_byte) ((val >> 16) & 0xff);
+ *p++ = (gdb_byte) ((val >> 8) & 0xff);
+ *p++ = (gdb_byte) (val & 0xff);
+ *p++ = 0xb0; /* implied "Finish" to terminate the list */
+ }
+ else if (!(val & 0x80000000))
+ {
+ /* Exception table entry in .ARM.extab. */
+ val = ((val & 0x7fffffff) ^ 0x40000000) - 0x40000000;
+ val += exidx_vma + i * 8 + 4;
+
+ if (val >= extab_vma && val + 4 <= extab_vma + extab_size)
+ {
+ bfd_vma data = bfd_h_get_32 (objfile->obfd,
+ extab_data + val - extab_vma);
+
+ if ((data & 0xff000000) == 0x80000000)
+ {
+ /* Short form. */
+ p = entry = obstack_alloc (&objfile->objfile_obstack, 4);
+ *p++ = (gdb_byte) ((data >> 16) & 0xff);
+ *p++ = (gdb_byte) ((data >> 8) & 0xff);
+ *p++ = (gdb_byte) (data & 0xff);
+ *p++ = 0xb0; /* implied "Finish" to terminate the list */
+ }
+ else if ((data & 0xff000000) == 0x81000000
+ || (data & 0xff000000) == 0x82000000)
+ {
+ /* Long form. */
+ int n_words = ((data >> 16) & 0xff) + 1;
+ if (val + 4 * n_words <= extab_vma + extab_size)
+ {
+ p = entry = obstack_alloc (&objfile->objfile_obstack,
+ 4 * n_words);
+ *p++ = (gdb_byte) ((data >> 8) & 0xff);
+ *p++ = (gdb_byte) (data & 0xff);
+
+ while (--n_words)
+ {
+ val += 4;
+ data = bfd_h_get_32 (objfile->obfd,
+ extab_data + val - extab_vma);
+
+ *p++ = (gdb_byte) ((data >> 24) & 0xff);
+ *p++ = (gdb_byte) ((data >> 16) & 0xff);
+ *p++ = (gdb_byte) ((data >> 8) & 0xff);
+ *p++ = (gdb_byte) (data & 0xff);
+ }
+
+ *p++ = 0xb0; /* implied "Finish" to terminate the list */
+ *p++ = 0xb0; /* one more for 4-byte alignment */
+ }
+ }
+ }
+ }
+
+ /* Push entry onto vector. They are guaranteed to always
+ appear in order of increasing addresses. */
+ new_exidx_entry.addr = idx;
+ new_exidx_entry.entry = entry;
+ VEC_safe_push (arm_exidx_entry_s, data->section_maps[sect->index],
+ &new_exidx_entry);
+ }
+
+ do_cleanups (cleanups);
+}
+
+/* Search for the exception table entry covering MEMADDR. If one is found,
+ return a pointer to its data. Otherwise, return 0. If START is non-NULL,
+ set *START to the start of the function covered by this entry. */
+
+static gdb_byte *
+arm_find_exidx_entry (CORE_ADDR memaddr, CORE_ADDR *start)
+{
+ struct obj_section *sec;
+
+ sec = find_pc_section (memaddr);
+ if (sec != NULL)
+ {
+ struct arm_exidx_data *data;
+ VEC(arm_exidx_entry_s) *map;
+ struct arm_exidx_entry map_key = { memaddr - obj_section_addr (sec), 0 };
+ unsigned int idx;
+
+ data = objfile_data (sec->objfile, arm_exidx_data_key);
+ if (data != NULL)
+ {
+ map = data->section_maps[sec->the_bfd_section->index];
+ if (!VEC_empty (arm_exidx_entry_s, map))
+ {
+ struct arm_exidx_entry *map_sym;
+
+ idx = VEC_lower_bound (arm_exidx_entry_s, map, &map_key,
+ arm_compare_exidx_entries);
+
+ /* VEC_lower_bound finds the earliest ordered insertion
+ point. If the following symbol starts at this exact
+ address, we use that; otherwise, the preceding
+ exception table entry covers this address. */
+ if (idx < VEC_length (arm_exidx_entry_s, map))
+ {
+ map_sym = VEC_index (arm_exidx_entry_s, map, idx);
+ if (map_sym->addr == map_key.addr)
+ {
+ if (start)
+ *start = map_sym->addr + obj_section_addr (sec);
+ return map_sym->entry;
+ }
+ }
+
+ if (idx > 0)
+ {
+ map_sym = VEC_index (arm_exidx_entry_s, map, idx - 1);
+ if (start)
+ *start = map_sym->addr + obj_section_addr (sec);
+ return map_sym->entry;
+ }
+ }
+ }
+ }
+
+ return NULL;
+}
+
+/* Given the current frame THIS_FRAME, and its associated frame unwinding
+ instruction list from the ARM exception table entry ENTRY, allocate and
+ return a prologue cache structure describing how to unwind this frame.
+
+ Return NULL if the unwinding instruction list contains a "spare",
+ "reserved" or "refuse to unwind" instruction as defined in section
+ "9.3 Frame unwinding instructions" of the "Exception Handling ABI
+ for the ARM Architecture" document. */
+
+static struct arm_prologue_cache *
+arm_exidx_fill_cache (struct frame_info *this_frame, gdb_byte *entry)
+{
+ CORE_ADDR vsp;
+ int vsp_valid = 0;
+
+ struct arm_prologue_cache *cache;
+ cache = FRAME_OBSTACK_ZALLOC (struct arm_prologue_cache);
+ cache->saved_regs = trad_frame_alloc_saved_regs (this_frame);
+
+ for (;;)
+ {
+ gdb_byte insn;
+
+ /* Whenever we reload SP, we actually have to retrieve its
+ actual value in the current frame. */
+ if (!vsp_valid)
+ {
+ if (trad_frame_realreg_p (cache->saved_regs, ARM_SP_REGNUM))
+ {
+ int reg = cache->saved_regs[ARM_SP_REGNUM].realreg;
+ vsp = get_frame_register_unsigned (this_frame, reg);
+ }
+ else
+ {
+ CORE_ADDR addr = cache->saved_regs[ARM_SP_REGNUM].addr;
+ vsp = get_frame_memory_unsigned (this_frame, addr, 4);
+ }
+
+ vsp_valid = 1;
+ }
+
+ /* Decode next unwind instruction. */
+ insn = *entry++;
+
+ if ((insn & 0xc0) == 0)
+ {
+ int offset = insn & 0x3f;
+ vsp += (offset << 2) + 4;
+ }
+ else if ((insn & 0xc0) == 0x40)
+ {
+ int offset = insn & 0x3f;
+ vsp -= (offset << 2) + 4;
+ }
+ else if ((insn & 0xf0) == 0x80)
+ {
+ int mask = ((insn & 0xf) << 8) | *entry++;
+ int i;
+
+ /* The special case of an all-zero mask identifies
+ "Refuse to unwind". We return NULL to fall back
+ to the prologue analyzer. */
+ if (mask == 0)
+ return NULL;
+
+ /* Pop registers r4..r15 under mask. */
+ for (i = 0; i < 12; i++)
+ if (mask & (1 << i))
+ {
+ cache->saved_regs[4 + i].addr = vsp;
+ vsp += 4;
+ }
+
+ /* Special-case popping SP -- we need to reload vsp. */
+ if (mask & (1 << (ARM_SP_REGNUM - 4)))
+ vsp_valid = 0;
+ }
+ else if ((insn & 0xf0) == 0x90)
+ {
+ int reg = insn & 0xf;
+
+ /* Reserved cases. */
+ if (reg == ARM_SP_REGNUM || reg == ARM_PC_REGNUM)
+ return NULL;
+
+ /* Set SP from another register and mark VSP for reload. */
+ cache->saved_regs[ARM_SP_REGNUM] = cache->saved_regs[reg];
+ vsp_valid = 0;
+ }
+ else if ((insn & 0xf0) == 0xa0)
+ {
+ int count = insn & 0x7;
+ int pop_lr = (insn & 0x8) != 0;
+ int i;
+
+ /* Pop r4..r[4+count]. */
+ for (i = 0; i <= count; i++)
+ {
+ cache->saved_regs[4 + i].addr = vsp;
+ vsp += 4;
+ }
+
+ /* If indicated by flag, pop LR as well. */
+ if (pop_lr)
+ {
+ cache->saved_regs[ARM_LR_REGNUM].addr = vsp;
+ vsp += 4;
+ }
+ }
+ else if (insn == 0xb0)
+ {
+ /* We could only have updated PC by popping into it; if so, it
+ will show up as address. Otherwise, copy LR into PC. */
+ if (!trad_frame_addr_p (cache->saved_regs, ARM_PC_REGNUM))
+ cache->saved_regs[ARM_PC_REGNUM]
+ = cache->saved_regs[ARM_LR_REGNUM];
+
+ /* We're done. */
+ break;
+ }
+ else if (insn == 0xb1)
+ {
+ int mask = *entry++;
+ int i;
+
+ /* All-zero mask and mask >= 16 is "spare". */
+ if (mask == 0 || mask >= 16)
+ return NULL;
+
+ /* Pop r0..r3 under mask. */
+ for (i = 0; i < 4; i++)
+ if (mask & (1 << i))
+ {
+ cache->saved_regs[i].addr = vsp;
+ vsp += 4;
+ }
+ }
+ else if (insn == 0xb2)
+ {
+ ULONGEST offset = 0;
+ unsigned shift = 0;
+
+ do
+ {
+ offset |= (*entry & 0x7f) << shift;
+ shift += 7;
+ }
+ while (*entry++ & 0x80);
+
+ vsp += 0x204 + (offset << 2);
+ }
+ else if (insn == 0xb3)
+ {
+ int start = *entry >> 4;
+ int count = (*entry++) & 0xf;
+ int i;
+
+ /* Only registers D0..D15 are valid here. */
+ if (start + count >= 16)
+ return NULL;
+
+ /* Pop VFP double-precision registers D[start]..D[start+count]. */
+ for (i = 0; i <= count; i++)
+ {
+ cache->saved_regs[ARM_D0_REGNUM + start + i].addr = vsp;
+ vsp += 8;
+ }
+
+ /* Add an extra 4 bytes for FSTMFDX-style stack. */
+ vsp += 4;
+ }
+ else if ((insn & 0xf8) == 0xb8)
+ {
+ int count = insn & 0x7;
+ int i;
+
+ /* Pop VFP double-precision registers D[8]..D[8+count]. */
+ for (i = 0; i <= count; i++)
+ {
+ cache->saved_regs[ARM_D0_REGNUM + 8 + i].addr = vsp;
+ vsp += 8;
+ }
+
+ /* Add an extra 4 bytes for FSTMFDX-style stack. */
+ vsp += 4;
+ }
+ else if (insn == 0xc6)
+ {
+ int start = *entry >> 4;
+ int count = (*entry++) & 0xf;
+ int i;
+
+ /* Only registers WR0..WR15 are valid. */
+ if (start + count >= 16)
+ return NULL;
+
+ /* Pop iwmmx registers WR[start]..WR[start+count]. */
+ for (i = 0; i <= count; i++)
+ {
+ cache->saved_regs[ARM_WR0_REGNUM + start + i].addr = vsp;
+ vsp += 8;
+ }
+ }
+ else if (insn == 0xc7)
+ {
+ int mask = *entry++;
+ int i;
+
+ /* All-zero mask and mask >= 16 is "spare". */
+ if (mask == 0 || mask >= 16)
+ return NULL;
+
+ /* Pop iwmmx general-purpose registers WCGR0..WCGR3 under mask. */
+ for (i = 0; i < 4; i++)
+ if (mask & (1 << i))
+ {
+ cache->saved_regs[ARM_WCGR0_REGNUM + i].addr = vsp;
+ vsp += 4;
+ }
+ }
+ else if ((insn & 0xf8) == 0xc0)
+ {
+ int count = insn & 0x7;
+ int i;
+
+ /* Pop iwmmx registers WR[10]..WR[10+count]. */
+ for (i = 0; i <= count; i++)
+ {
+ cache->saved_regs[ARM_WR0_REGNUM + 10 + i].addr = vsp;
+ vsp += 8;
+ }
+ }
+ else if (insn == 0xc8)
+ {
+ int start = *entry >> 4;
+ int count = (*entry++) & 0xf;
+ int i;
+
+ /* Only registers D0..D31 are valid. */
+ if (start + count >= 16)
+ return NULL;
+
+ /* Pop VFP double-precision registers
+ D[16+start]..D[16+start+count]. */
+ for (i = 0; i <= count; i++)
+ {
+ cache->saved_regs[ARM_D0_REGNUM + 16 + start + i].addr = vsp;
+ vsp += 8;
+ }
+ }
+ else if (insn == 0xc9)
+ {
+ int start = *entry >> 4;
+ int count = (*entry++) & 0xf;
+ int i;
+
+ /* Pop VFP double-precision registers D[start]..D[start+count]. */
+ for (i = 0; i <= count; i++)
+ {
+ cache->saved_regs[ARM_D0_REGNUM + start + i].addr = vsp;
+ vsp += 8;
+ }
+ }
+ else if ((insn & 0xf8) == 0xd0)
+ {
+ int count = insn & 0x7;
+ int i;
+
+ /* Pop VFP double-precision registers D[8]..D[8+count]. */
+ for (i = 0; i <= count; i++)
+ {
+ cache->saved_regs[ARM_D0_REGNUM + 8 + i].addr = vsp;
+ vsp += 8;
+ }
+ }
+ else
+ {
+ /* Everything else is "spare". */
+ return NULL;
+ }
+ }
+
+ /* If we restore SP from a register, assume this was the frame register.
+ Otherwise just fall back to SP as frame register. */
+ if (trad_frame_realreg_p (cache->saved_regs, ARM_SP_REGNUM))
+ cache->framereg = cache->saved_regs[ARM_SP_REGNUM].realreg;
+ else
+ cache->framereg = ARM_SP_REGNUM;
+
+ /* Determine offset to previous frame. */
+ cache->framesize
+ = vsp - get_frame_register_unsigned (this_frame, cache->framereg);
+
+ /* We already got the previous SP. */
+ cache->prev_sp = vsp;
+
+ return cache;
+}
+
+/* Unwinding via ARM exception table entries. Note that the sniffer
+ already computes a filled-in prologue cache, which is then used
+ with the same arm_prologue_this_id and arm_prologue_prev_register
+ routines also used for prologue-parsing based unwinding. */
+
+static int
+arm_exidx_unwind_sniffer (const struct frame_unwind *self,
+ struct frame_info *this_frame,
+ void **this_prologue_cache)
+{
+ CORE_ADDR addr_in_block, func_exidx, func_symtab;
+ struct arm_prologue_cache *cache;
+ gdb_byte *entry;
+
+ /* See if we have an ARM exception table entry covering this address. */
+ addr_in_block = get_frame_address_in_block (this_frame);
+ entry = arm_find_exidx_entry (addr_in_block, &func_exidx);
+ if (!entry)
+ return 0;
+
+ /* The ARM exception index does not mark the *end* of the function
+ covered by the entry, and some functions will not have any entry.
+ Therefore, the entry we found above may not actually be correct
+ for this PC. As a sanity check, also try to determine the function
+ covering PC via the symbol table. If this finds a function start
+ address *closer* to PC than the one found via the exception table,
+ ignore the exception record, and fall back to prologue parsing.
+
+ (Note that if we don't find a function via the symbol table, the
+ exception table may still be wrong. But in this case, prologue
+ parsing wouldn't work anyway, so we use the exception table and
+ hope for the best.) */
+ if (find_pc_partial_function (addr_in_block, NULL, &func_symtab, NULL)
+ && func_symtab > func_exidx)
+ return 0;
+
+ /* Decode the list of unwinding instructions into a prologue cache.
+ Note that this may fail due to e.g. a "refuse to unwind" code. */
+ cache = arm_exidx_fill_cache (this_frame, entry);
+ if (!cache)
+ return 0;
+
+ /* Fill in the function start, and remember the cache. */
+ cache->this_func = func_exidx;
+ *this_prologue_cache = cache;
+ return 1;
+}
+
+struct frame_unwind arm_exidx_unwind = {
+ NORMAL_FRAME,
+ arm_prologue_this_id,
+ arm_prologue_prev_register,
+ NULL,
+ arm_exidx_unwind_sniffer
+};
+
static struct arm_prologue_cache *
arm_make_stub_cache (struct frame_info *this_frame)
{
@@ -7488,6 +8115,7 @@ arm_gdbarch_init (struct gdbarch_info in
/* Add some default predicates. */
frame_unwind_append_unwinder (gdbarch, &arm_stub_unwind);
dwarf2_append_unwinders (gdbarch);
+ frame_unwind_append_unwinder (gdbarch, &arm_exidx_unwind);
frame_unwind_append_unwinder (gdbarch, &arm_prologue_unwind);

/* Now we have tuned the configuration, set a few final things,
@@ -7590,6 +8218,11 @@ _initialize_arm_tdep (void)
arm_objfile_data_key
= register_objfile_data_with_cleanup (NULL, arm_objfile_data_free);

+ /* Add ourselves to objfile event chain. */
+ observer_attach_new_objfile (arm_exidx_new_objfile);
+ arm_exidx_data_key
+ = register_objfile_data_with_cleanup (NULL, arm_exidx_data_free);
+
/* Register an ELF OS ABI sniffer for ARM binaries. */
gdbarch_register_osabi_sniffer (bfd_arch_arm,
bfd_target_elf_flavour,