summaryrefslogtreecommitdiff
path: root/src/dwarf
diff options
context:
space:
mode:
Diffstat (limited to 'src/dwarf')
-rw-r--r--src/dwarf/Gexpr.c60
-rw-r--r--src/dwarf/Gfde.c86
-rw-r--r--src/dwarf/Gfind_proc_info-lsb.c732
-rw-r--r--src/dwarf/Gpe.c2
4 files changed, 726 insertions, 154 deletions
diff --git a/src/dwarf/Gexpr.c b/src/dwarf/Gexpr.c
index b62c4dfe..4a012158 100644
--- a/src/dwarf/Gexpr.c
+++ b/src/dwarf/Gexpr.c
@@ -47,10 +47,11 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
#define ULEB128 0x4
#define SLEB128 0x5
#define OFFSET 0x6 /* 32-bit offset for 32-bit DWARF, 64-bit otherwise */
+#define ADDR 0x7 /* Machine address. */
static uint8_t operands[256] =
{
- [DW_OP_addr] = OPND1 (sizeof (unw_word_t) == 4 ? VAL32 : VAL64),
+ [DW_OP_addr] = OPND1 (ADDR),
[DW_OP_const1u] = OPND1 (VAL8),
[DW_OP_const1s] = OPND1 (VAL8),
[DW_OP_const2u] = OPND1 (VAL16),
@@ -106,7 +107,18 @@ static uint8_t operands[256] =
[DW_OP_call_ref] = OPND1 (OFFSET)
};
-#define sword(X) ((unw_sword_t) (X))
+static inline unw_sword_t
+sword (unw_addr_space_t as, unw_word_t val)
+{
+ switch (dwarf_addr_size (as))
+ {
+ case 1: return (int8_t) val;
+ case 2: return (int16_t) val;
+ case 4: return (int32_t) val;
+ case 8: return (int64_t) val;
+ default: abort ();
+ }
+}
static inline unw_word_t
read_operand (unw_addr_space_t as, unw_accessors_t *a,
@@ -118,25 +130,43 @@ read_operand (unw_addr_space_t as, unw_accessors_t *a,
uint64_t u64;
int ret;
+ if (operand_type == ADDR)
+ switch (dwarf_addr_size (as))
+ {
+ case 1: operand_type = VAL8; break;
+ case 2: operand_type = VAL16; break;
+ case 4: operand_type = VAL32; break;
+ case 8: operand_type = VAL64; break;
+ default: abort ();
+ }
+
switch (operand_type)
{
case VAL8:
ret = dwarf_readu8 (as, a, addr, &u8, arg);
+ if (ret < 0)
+ return ret;
*val = u8;
break;
case VAL16:
ret = dwarf_readu16 (as, a, addr, &u16, arg);
+ if (ret < 0)
+ return ret;
*val = u16;
break;
case VAL32:
ret = dwarf_readu32 (as, a, addr, &u32, arg);
+ if (ret < 0)
+ return ret;
*val = u32;
break;
case VAL64:
ret = dwarf_readu64 (as, a, addr, &u64, arg);
+ if (ret < 0)
+ return ret;
*val = u64;
break;
@@ -345,8 +375,10 @@ do { \
tmp1 = pop ();
switch (operand1)
{
- case 0:
- break;
+ default:
+ Debug (1, "Unexpected DW_OP_deref_size size %d\n",
+ (int) operand1);
+ return -UNW_EINVAL;
case 1:
if ((ret = dwarf_readu8 (as, a, &tmp1, &u8, arg)) < 0)
@@ -379,7 +411,7 @@ do { \
case 8:
if ((ret = dwarf_readu64 (as, a, &tmp1, &u64, arg)) < 0)
return ret;
- tmp2 = u16;
+ tmp2 = u64;
if (operand1 != 8)
{
if (dwarf_is_big_endian (as))
@@ -433,7 +465,7 @@ do { \
case DW_OP_abs:
Debug (15, "OP_abs\n");
tmp1 = pop ();
- if (tmp1 & ((unw_word_t) 1 << (8 * sizeof (unw_word_t) - 1)))
+ if (tmp1 & ((unw_word_t) 1 << (8 * dwarf_addr_size (as) - 1)))
tmp1 = -tmp1;
push (tmp1);
break;
@@ -450,7 +482,7 @@ do { \
tmp1 = pop ();
tmp2 = pop ();
if (tmp1)
- tmp1 = sword (tmp2) / sword (tmp1);
+ tmp1 = sword (as, tmp2) / sword (as, tmp1);
push (tmp1);
break;
@@ -528,7 +560,7 @@ do { \
Debug (15, "OP_shra\n");
tmp1 = pop ();
tmp2 = pop ();
- push (sword (tmp2) >> tmp1);
+ push (sword (as, tmp2) >> tmp1);
break;
case DW_OP_xor:
@@ -542,42 +574,42 @@ do { \
Debug (15, "OP_le\n");
tmp1 = pop ();
tmp2 = pop ();
- push (sword (tmp1) <= sword (tmp2));
+ push (sword (as, tmp1) <= sword (as, tmp2));
break;
case DW_OP_ge:
Debug (15, "OP_ge\n");
tmp1 = pop ();
tmp2 = pop ();
- push (sword (tmp1) >= sword (tmp2));
+ push (sword (as, tmp1) >= sword (as, tmp2));
break;
case DW_OP_eq:
Debug (15, "OP_eq\n");
tmp1 = pop ();
tmp2 = pop ();
- push (sword (tmp1) == sword (tmp2));
+ push (sword (as, tmp1) == sword (as, tmp2));
break;
case DW_OP_lt:
Debug (15, "OP_lt\n");
tmp1 = pop ();
tmp2 = pop ();
- push (sword (tmp1) < sword (tmp2));
+ push (sword (as, tmp1) < sword (as, tmp2));
break;
case DW_OP_gt:
Debug (15, "OP_gt\n");
tmp1 = pop ();
tmp2 = pop ();
- push (sword (tmp1) > sword (tmp2));
+ push (sword (as, tmp1) > sword (as, tmp2));
break;
case DW_OP_ne:
Debug (15, "OP_ne\n");
tmp1 = pop ();
tmp2 = pop ();
- push (sword (tmp1) != sword (tmp2));
+ push (sword (as, tmp1) != sword (as, tmp2));
break;
case DW_OP_skip:
diff --git a/src/dwarf/Gfde.c b/src/dwarf/Gfde.c
index ecddef63..49e21db8 100644
--- a/src/dwarf/Gfde.c
+++ b/src/dwarf/Gfde.c
@@ -26,12 +26,15 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
#include "dwarf_i.h"
static inline int
-is_cie_id (unw_word_t val)
+is_cie_id (unw_word_t val, int is_debug_frame)
{
- /* DWARF spec says CIE_id is 0xffffffff (for 32-bit ELF) or
- 0xffffffffffffffff (for 64-bit ELF). However, the GNU toolchain
+ /* The CIE ID is normally 0xffffffff (for 32-bit ELF) or
+ 0xffffffffffffffff (for 64-bit ELF). However, .eh_frame
uses 0. */
- return (val == 0 || val == - (unw_word_t) 1);
+ if (is_debug_frame)
+ return (val == - (uint32_t) 1 || val == - (uint64_t) 1);
+ else
+ return (val == 0);
}
/* Note: we don't need to keep track of more than the first four
@@ -41,7 +44,8 @@ is_cie_id (unw_word_t val)
repeated. */
static inline int
parse_cie (unw_addr_space_t as, unw_accessors_t *a, unw_word_t addr,
- const unw_proc_info_t *pi, struct dwarf_cie_info *dci, void *arg)
+ const unw_proc_info_t *pi, struct dwarf_cie_info *dci,
+ unw_word_t base, void *arg)
{
uint8_t version, ch, augstr[5], fde_encoding, handler_encoding;
unw_word_t len, cie_end_addr, aug_size;
@@ -57,7 +61,7 @@ parse_cie (unw_addr_space_t as, unw_accessors_t *a, unw_word_t addr,
"address-unit sized constants". The `R' augmentation can be used
to override this, but by default, we pick an address-sized unit
for fde_encoding. */
- switch (sizeof (unw_word_t))
+ switch (dwarf_addr_size (as))
{
case 4: fde_encoding = DW_EH_PE_udata4; break;
case 8: fde_encoding = DW_EH_PE_udata8; break;
@@ -74,13 +78,14 @@ parse_cie (unw_addr_space_t as, unw_accessors_t *a, unw_word_t addr,
{
/* the CIE is in the 32-bit DWARF format */
uint32_t cie_id;
+ /* DWARF says CIE id should be 0xffffffff, but in .eh_frame, it's 0 */
+ const uint32_t expected_id = (base) ? 0xffffffff : 0;
len = u32val;
cie_end_addr = addr + len;
if ((ret = dwarf_readu32 (as, a, &addr, &cie_id, arg)) < 0)
return ret;
- /* DWARF says CIE id should be 0xffffffff, but in .eh_frame, it's 0 */
- if (cie_id != 0)
+ if (cie_id != expected_id)
{
Debug (1, "Unexpected CIE id %x\n", cie_id);
return -UNW_EINVAL;
@@ -90,6 +95,9 @@ parse_cie (unw_addr_space_t as, unw_accessors_t *a, unw_word_t addr,
{
/* the CIE is in the 64-bit DWARF format */
uint64_t cie_id;
+ /* DWARF says CIE id should be 0xffffffffffffffff, but in
+ .eh_frame, it's 0 */
+ const uint64_t expected_id = (base) ? 0xffffffffffffffffull : 0;
if ((ret = dwarf_readu64 (as, a, &addr, &u64val, arg)) < 0)
return ret;
@@ -97,9 +105,7 @@ parse_cie (unw_addr_space_t as, unw_accessors_t *a, unw_word_t addr,
cie_end_addr = addr + len;
if ((ret = dwarf_readu64 (as, a, &addr, &cie_id, arg)) < 0)
return ret;
- /* DWARF says CIE id should be 0xffffffffffffffff, but in
- .eh_frame, it's 0 */
- if (cie_id != 0)
+ if (cie_id != expected_id)
{
Debug (1, "Unexpected CIE id %llx\n", (long long) cie_id);
return -UNW_EINVAL;
@@ -146,14 +152,16 @@ parse_cie (unw_addr_space_t as, unw_accessors_t *a, unw_word_t addr,
arg)) < 0)
return ret;
+ i = 0;
if (augstr[0] == 'z')
{
dci->sized_augmentation = 1;
if ((ret = dwarf_read_uleb128 (as, a, &addr, &aug_size, arg)) < 0)
return ret;
+ i++;
}
- for (i = 1; i < sizeof (augstr) && augstr[i]; ++i)
+ for (; i < sizeof (augstr) && augstr[i]; ++i)
switch (augstr[i])
{
case 'L':
@@ -185,16 +193,15 @@ parse_cie (unw_addr_space_t as, unw_accessors_t *a, unw_word_t addr,
break;
default:
+ Debug (1, "Unexpected augmentation string `%s'\n", augstr);
if (dci->sized_augmentation)
/* If we have the size of the augmentation body, we can skip
over the parts that we don't understand, so we're OK. */
- return 0;
+ goto done;
else
- {
- Debug (1, "Unexpected augmentation string `%s'\n", augstr);
- return -UNW_EINVAL;
- }
+ return -UNW_EINVAL;
}
+ done:
dci->fde_encoding = fde_encoding;
dci->cie_instr_start = addr;
Debug (15, "CIE parsed OK, augmentation = \"%s\", handler=0x%lx\n",
@@ -202,12 +209,15 @@ parse_cie (unw_addr_space_t as, unw_accessors_t *a, unw_word_t addr,
return 0;
}
-/* Extract proc-info from the FDE starting at adress ADDR. */
+/* Extract proc-info from the FDE starting at adress ADDR.
+
+ Pass BASE as zero for eh_frame behaviour, or a pointer to
+ debug_frame base for debug_frame behaviour. */
HIDDEN int
dwarf_extract_proc_info_from_fde (unw_addr_space_t as, unw_accessors_t *a,
unw_word_t *addrp, unw_proc_info_t *pi,
- int need_unwind_info,
+ int need_unwind_info, unw_word_t base,
void *arg)
{
unw_word_t fde_end_addr, cie_addr, cie_offset_addr, aug_end_addr = 0;
@@ -226,7 +236,7 @@ dwarf_extract_proc_info_from_fde (unw_addr_space_t as, unw_accessors_t *a,
if (u32val != 0xffffffff)
{
- uint32_t cie_offset;
+ int32_t cie_offset;
/* In some configurations, an FDE with a 0 length indicates the
end of the FDE-table. */
@@ -241,19 +251,22 @@ dwarf_extract_proc_info_from_fde (unw_addr_space_t as, unw_accessors_t *a,
if ((ret = dwarf_reads32 (as, a, &addr, &cie_offset, arg)) < 0)
return ret;
- if (is_cie_id (cie_offset))
+ if (is_cie_id (cie_offset, base != 0))
/* ignore CIEs (happens during linear searches) */
return 0;
- /* DWARF says that the CIE_pointer in the FDE is a
- .debug_frame-relative offset, but the GCC-generated .eh_frame
- sections instead store a "pcrelative" offset, which is just
- as fine as it's self-contained. */
- cie_addr = cie_offset_addr - cie_offset;
+ if (base != 0)
+ cie_addr = base + cie_offset;
+ else
+ /* DWARF says that the CIE_pointer in the FDE is a
+ .debug_frame-relative offset, but the GCC-generated .eh_frame
+ sections instead store a "pcrelative" offset, which is just
+ as fine as it's self-contained. */
+ cie_addr = cie_offset_addr - cie_offset;
}
else
{
- uint64_t cie_offset;
+ int64_t cie_offset;
/* the FDE is in the 64-bit DWARF format */
@@ -266,18 +279,23 @@ dwarf_extract_proc_info_from_fde (unw_addr_space_t as, unw_accessors_t *a,
if ((ret = dwarf_reads64 (as, a, &addr, &cie_offset, arg)) < 0)
return ret;
- if (is_cie_id (cie_offset))
+ if (is_cie_id (cie_offset, base != 0))
/* ignore CIEs (happens during linear searches) */
return 0;
- /* DWARF says that the CIE_pointer in the FDE is a
- .debug_frame-relative offset, but the GCC-generated .eh_frame
- sections instead store a "pcrelative" offset, which is just
- as fine as it's self-contained. */
- cie_addr = (unw_word_t) ((uint64_t) cie_offset_addr - cie_offset);
+ if (base != 0)
+ cie_addr = base + cie_offset;
+ else
+ /* DWARF says that the CIE_pointer in the FDE is a
+ .debug_frame-relative offset, but the GCC-generated .eh_frame
+ sections instead store a "pcrelative" offset, which is just
+ as fine as it's self-contained. */
+ cie_addr = (unw_word_t) ((uint64_t) cie_offset_addr - cie_offset);
}
- if ((ret = parse_cie (as, a, cie_addr, pi, &dci, arg)) < 0)
+ Debug (15, "looking for CIE at address %x\n", (int) cie_addr);
+
+ if ((ret = parse_cie (as, a, cie_addr, pi, &dci, base, arg)) < 0)
return ret;
/* IP-range has same encoding as FDE pointers, except that it's
diff --git a/src/dwarf/Gfind_proc_info-lsb.c b/src/dwarf/Gfind_proc_info-lsb.c
index a8a3b69e..d3d5fe5f 100644
--- a/src/dwarf/Gfind_proc_info-lsb.c
+++ b/src/dwarf/Gfind_proc_info-lsb.c
@@ -28,6 +28,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
#include <link.h>
#include <stddef.h>
+#include <stdio.h>
+#include <limits.h>
#include "dwarf_i.h"
#include "dwarf-eh.h"
@@ -41,6 +43,10 @@ struct table_entry
#ifndef UNW_REMOTE_ONLY
+#ifdef __linux
+#include "os-linux.h"
+#endif
+
struct callback_data
{
/* in: */
@@ -50,6 +56,7 @@ struct callback_data
/* out: */
int single_fde; /* did we find a single FDE? (vs. a table) */
unw_dyn_info_t di; /* table info (if single_fde is false) */
+ unw_dyn_info_t di_debug; /* additional table info for .debug_frame */
};
static int
@@ -65,7 +72,7 @@ linear_search (unw_addr_space_t as, unw_word_t ip,
while (i++ < fde_count && addr < eh_frame_end)
{
fde_addr = addr;
- if ((ret = dwarf_extract_proc_info_from_fde (as, a, &addr, pi, 0, arg))
+ if ((ret = dwarf_extract_proc_info_from_fde (as, a, &addr, pi, 0, 0, arg))
< 0)
return ret;
@@ -75,7 +82,8 @@ linear_search (unw_addr_space_t as, unw_word_t ip,
return 1;
addr = fde_addr;
if ((ret = dwarf_extract_proc_info_from_fde (as, a, &addr, pi,
- need_unwind_info, arg))
+ need_unwind_info, 0,
+ arg))
< 0)
return ret;
return 1;
@@ -84,8 +92,316 @@ linear_search (unw_addr_space_t as, unw_word_t ip,
return -UNW_ENOINFO;
}
-/* Info is a pointer to a unw_dyn_info_t structure and, on entry,
- member u.rti.segbase contains the instruction-pointer we're looking
+/* Load .debug_frame section from FILE. Allocates and returns space
+ in *BUF, and sets *BUFSIZE to its size. IS_LOCAL is 1 if using the
+ local process, in which case we can search the system debug file
+ directory; 0 for other address spaces, in which case we do not; or
+ -1 for recursive calls following .gnu_debuglink. Returns 0 on
+ success, 1 on error. Succeeds even if the file contains no
+ .debug_frame. */
+/* XXX: Could use mmap; but elf_map_image keeps tons mapped in. */
+
+static int
+load_debug_frame (const char *file, char **buf, size_t *bufsize, int is_local)
+{
+ FILE *f;
+ Elf_W (Ehdr) ehdr;
+ Elf_W (Half) shstrndx;
+ Elf_W (Shdr) *sec_hdrs;
+ char *stringtab;
+ unsigned int i;
+ size_t linksize = 0;
+ char *linkbuf = NULL;
+
+ *buf = NULL;
+ *bufsize = 0;
+
+ f = fopen (file, "r");
+
+ if (!f)
+ return 1;
+
+ fread (&ehdr, sizeof (Elf_W (Ehdr)), 1, f);
+
+ shstrndx = ehdr.e_shstrndx;
+
+ Debug (4, "opened file '%s'. Section header at offset %d\n",
+ file, (int) ehdr.e_shoff);
+
+ fseek (f, ehdr.e_shoff, SEEK_SET);
+ sec_hdrs = calloc (ehdr.e_shnum, sizeof (Elf_W (Shdr)));
+ fread (sec_hdrs, sizeof (Elf_W (Shdr)), ehdr.e_shnum, f);
+
+ Debug (4, "loading string table of size %d\n",
+ sec_hdrs[shstrndx].sh_size);
+ stringtab = malloc (sec_hdrs[shstrndx].sh_size);
+ fseek (f, sec_hdrs[shstrndx].sh_offset, SEEK_SET);
+ fread (stringtab, 1, sec_hdrs[shstrndx].sh_size, f);
+
+ for (i = 1; i < ehdr.e_shnum && *buf == NULL; i++)
+ {
+ char *secname = &stringtab[sec_hdrs[i].sh_name];
+
+ if (strcmp (secname, ".debug_frame") == 0)
+ {
+ *bufsize = sec_hdrs[i].sh_size;
+ *buf = malloc (*bufsize);
+
+ fseek (f, sec_hdrs[i].sh_offset, SEEK_SET);
+ fread (*buf, 1, *bufsize, f);
+
+ Debug (4, "read %d bytes of .debug_frame from offset %d\n",
+ *bufsize, sec_hdrs[i].sh_offset);
+ }
+ else if (is_local >= 0 && strcmp (secname, ".gnu_debuglink") == 0)
+ {
+ linksize = sec_hdrs[i].sh_size;
+ linkbuf = malloc (linksize);
+
+ fseek (f, sec_hdrs[i].sh_offset, SEEK_SET);
+ fread (linkbuf, 1, linksize, f);
+
+ Debug (4, "read %d bytes of .gnu_debuglink from offset %d\n",
+ *bufsize, sec_hdrs[i].sh_offset);
+ }
+ }
+
+ free (stringtab);
+ free (sec_hdrs);
+
+ fclose (f);
+
+ if (*buf == NULL && linkbuf != NULL && memchr (linkbuf, 0, linksize) != NULL)
+ {
+ char *newname, *basedir, *p;
+ static const char *debugdir = "/usr/lib/debug";
+ int ret;
+
+ /* XXX: Don't bother with the checksum; just search for the file. */
+ basedir = malloc (strlen (file) + 1);
+ newname = malloc (strlen (linkbuf) + strlen (debugdir)
+ + strlen (file) + 9);
+
+ p = strrchr (file, '/');
+ if (p != NULL)
+ {
+ memcpy (basedir, file, p - file);
+ basedir[p - file] = '\0';
+ }
+ else
+ basedir[0] = 0;
+
+ strcpy (newname, basedir);
+ strcat (newname, "/");
+ strcat (newname, linkbuf);
+ ret = load_debug_frame (newname, buf, bufsize, -1);
+
+ if (ret == 1)
+ {
+ strcpy (newname, basedir);
+ strcat (newname, "/.debug/");
+ strcat (newname, linkbuf);
+ ret = load_debug_frame (newname, buf, bufsize, -1);
+ }
+
+ if (ret == 1 && is_local == 1)
+ {
+ strcpy (newname, debugdir);
+ strcat (newname, basedir);
+ strcat (newname, "/");
+ strcat (newname, linkbuf);
+ ret = load_debug_frame (newname, buf, bufsize, -1);
+ }
+
+ free (basedir);
+ free (newname);
+ }
+ free (linkbuf);
+
+ return 0;
+}
+
+/* Locate the binary which originated the contents of address ADDR. Return
+ the name of the binary in *name (which is allocated on the heap, and must
+ be freed by the caller). Returns 0 if a binary is successfully found, or 1
+ if an error occurs. */
+
+static int
+find_binary_for_address (unw_word_t ip, char **name)
+{
+#ifdef __linux
+ struct map_iterator mi;
+ char path[PATH_MAX];
+ int found = 0;
+ int pid = getpid ();
+ unsigned long segbase, mapoff, hi;
+
+ maps_init (&mi, pid);
+ while (maps_next (&mi, &segbase, &hi, &mapoff, path, sizeof (path)))
+ if (ip >= segbase && ip < hi)
+ {
+ found = 1;
+ break;
+ }
+ maps_close (&mi);
+
+ if (found)
+ {
+ *name = strdup (path);
+ return 0;
+ }
+#endif
+
+ return 1;
+}
+
+/* Locate and/or try to load a debug_frame section for address ADDR. Return
+ pointer to debug frame descriptor, or zero if not found. */
+
+static struct unw_debug_frame_list *
+locate_debug_info (unw_addr_space_t as, struct dl_phdr_info *info,
+ unw_word_t addr, const char *dlname)
+{
+ struct unw_debug_frame_list *w, *fdesc = 0;
+ char *name = 0;
+ int err;
+ uint64_t start = 0, end = 0;
+ char *buf;
+ size_t bufsize;
+ unsigned int i;
+
+ /* First, see if we loaded this frame already. */
+
+ for (w = as->debug_frames; w; w = w->next)
+ {
+ Debug (4, "checking %p: %x-%x\n", w, (int)w->start, (int)w->end);
+ if (addr >= w->start && addr < w->end)
+ return w;
+ }
+
+ /* If the object name we receive is blank, there's still a chance of locating
+ the file by parsing /proc/self/maps. */
+
+ if (strcmp (dlname, "") == 0)
+ {
+ err = find_binary_for_address (addr, &name);
+ if (err)
+ {
+ Debug (15, "tried to locate binary for 0x%" PRIx64 ", but no luck\n",
+ (uint64_t) addr);
+ return 0;
+ }
+ }
+ else
+ name = (char*) dlname;
+
+ /* Find the start/end of the described region by parsing the
+ dl_phdr_info structure. */
+
+ start = info->dlpi_addr + info->dlpi_phdr[0].p_vaddr;
+ end = start;
+
+ for (i = 0; i < info->dlpi_phnum; i++)
+ {
+ Elf_W (Addr) hdrbase = info->dlpi_addr + info->dlpi_phdr[i].p_vaddr;
+ Elf_W (Addr) hdrlimit = hdrbase + info->dlpi_phdr[i].p_memsz;
+
+ if (info->dlpi_phdr[i].p_type != PT_LOAD)
+ continue;
+
+ if (hdrbase < start)
+ start = hdrbase;
+ if (hdrlimit > end)
+ end = hdrlimit;
+ }
+
+ Debug (4, "calculated bounds of %x-%x for '%s'\n", (int)start, (int)end,
+ name);
+
+ err = load_debug_frame (name, &buf, &bufsize, as == unw_local_addr_space);
+
+ if (!err)
+ {
+ fdesc = malloc (sizeof (struct unw_debug_frame_list));
+
+ fdesc->start = start;
+ fdesc->end = end;
+ fdesc->debug_frame = buf;
+ fdesc->debug_frame_size = bufsize;
+ fdesc->index = NULL;
+ fdesc->next = as->debug_frames;
+
+ as->debug_frames = fdesc;
+ }
+
+ if (name && name != dlname)
+ free (name);
+
+ return fdesc;
+}
+
+struct debug_frame_tab
+ {
+ struct table_entry *tab;
+ uint32_t length;
+ uint32_t size;
+ };
+
+static struct debug_frame_tab *
+debug_frame_tab_new (unsigned int base_size)
+{
+ struct debug_frame_tab *tab = malloc (sizeof (struct debug_frame_tab));
+
+ tab->tab = calloc (base_size, sizeof (struct table_entry));
+ tab->length = 0;
+ tab->size = base_size;
+
+ return tab;
+}
+
+static void
+debug_frame_tab_append (struct debug_frame_tab *tab,
+ unw_word_t fde_offset, unw_word_t start_ip)
+{
+ unsigned int length = tab->length;
+
+ if (length == tab->size)
+ {
+ tab->size *= 2;
+ tab->tab = realloc (tab->tab, sizeof (struct table_entry) * tab->size);
+ }
+
+ tab->tab[length].fde_offset = fde_offset;
+ tab->tab[length].start_ip_offset = start_ip;
+
+ tab->length = length + 1;
+}
+
+static void
+debug_frame_tab_shrink (struct debug_frame_tab *tab)
+{
+ if (tab->size > tab->length)
+ {
+ tab->tab = realloc (tab->tab, sizeof (struct table_entry) * tab->length);
+ tab->size = tab->length;
+ }
+}
+
+static int
+debug_frame_tab_compare (const void *a, const void *b)
+{
+ const struct table_entry *fa = a, *fb = b;
+
+ if (fa->start_ip_offset > fb->start_ip_offset)
+ return 1;
+ else if (fa->start_ip_offset < fb->start_ip_offset)
+ return -1;
+ else
+ return 0;
+}
+
+/* ptr is a pointer to a callback_data structure and, on entry,
+ member ip contains the instruction-pointer we're looking
for. */
static int
callback (struct dl_phdr_info *info, size_t size, void *ptr)
@@ -100,6 +416,8 @@ callback (struct dl_phdr_info *info, size_t size, void *ptr)
struct dwarf_eh_frame_hdr *hdr;
unw_accessors_t *a;
long n;
+ struct unw_debug_frame_list *fdesc = 0;
+ int found = 0;
ip = cb_data->ip;
@@ -136,117 +454,261 @@ callback (struct dl_phdr_info *info, size_t size, void *ptr)
else if (phdr->p_type == PT_DYNAMIC)
p_dynamic = phdr;
}
- if (!p_text || !p_eh_hdr)
+
+ if (!p_text)
return 0;
- if (likely (p_eh_hdr->p_vaddr >= p_text->p_vaddr
- && p_eh_hdr->p_vaddr < p_text->p_vaddr + p_text->p_memsz))
- /* normal case: eh-hdr is inside text segment */
- segbase = p_text->p_vaddr + load_base;
- else
+ if (p_eh_hdr)
{
- /* Special case: eh-hdr is in some other segment; this may
- happen, e.g., for the Linux kernel's gate DSO, for
- example. */
- phdr = info->dlpi_phdr;
- for (n = info->dlpi_phnum; --n >= 0; phdr++)
+ if (likely (p_eh_hdr->p_vaddr >= p_text->p_vaddr
+ && p_eh_hdr->p_vaddr < p_text->p_vaddr + p_text->p_memsz))
+ /* normal case: eh-hdr is inside text segment */
+ segbase = p_text->p_vaddr + load_base;
+ else
{
- if (phdr->p_type == PT_LOAD && p_eh_hdr->p_vaddr >= phdr->p_vaddr
- && p_eh_hdr->p_vaddr < phdr->p_vaddr + phdr->p_memsz)
+ /* Special case: eh-hdr is in some other segment; this may
+ happen, e.g., for the Linux kernel's gate DSO, for
+ example. */
+ phdr = info->dlpi_phdr;
+ for (n = info->dlpi_phnum; --n >= 0; phdr++)
{
- segbase = phdr->p_vaddr + load_base;
- break;
+ if (phdr->p_type == PT_LOAD && p_eh_hdr->p_vaddr >= phdr->p_vaddr
+ && p_eh_hdr->p_vaddr < phdr->p_vaddr + phdr->p_memsz)
+ {
+ segbase = phdr->p_vaddr + load_base;
+ break;
+ }
}
}
+
+ if (p_dynamic)
+ {
+ /* For dynamicly linked executables and shared libraries,
+ DT_PLTGOT is the value that data-relative addresses are
+ relative to for that object. We call this the "gp". */
+ Elf_W(Dyn) *dyn = (Elf_W(Dyn) *)(p_dynamic->p_vaddr + load_base);
+ for (; dyn->d_tag != DT_NULL; ++dyn)
+ if (dyn->d_tag == DT_PLTGOT)
+ {
+ /* Assume that _DYNAMIC is writable and GLIBC has
+ relocated it (true for x86 at least). */
+ di->gp = dyn->d_un.d_ptr;
+ break;
+ }
+ }
+ else
+ /* Otherwise this is a static executable with no _DYNAMIC. Assume
+ that data-relative addresses are relative to 0, i.e.,
+ absolute. */
+ di->gp = 0;
+ pi->gp = di->gp;
+
+ hdr = (struct dwarf_eh_frame_hdr *) (p_eh_hdr->p_vaddr + load_base);
+ if (hdr->version != DW_EH_VERSION)
+ {
+ Debug (1, "table `%s' has unexpected version %d\n",
+ info->dlpi_name, hdr->version);
+ return 0;
+ }
+
+ a = unw_get_accessors (unw_local_addr_space);
+ addr = (unw_word_t) (uintptr_t) (hdr + 1);
+
+ /* (Optionally) read eh_frame_ptr: */
+ if ((ret = dwarf_read_encoded_pointer (unw_local_addr_space, a,
+ &addr, hdr->eh_frame_ptr_enc, pi,
+ &eh_frame_start, NULL)) < 0)
+ return ret;
+
+ /* (Optionally) read fde_count: */
+ if ((ret = dwarf_read_encoded_pointer (unw_local_addr_space, a,
+ &addr, hdr->fde_count_enc, pi,
+ &fde_count, NULL)) < 0)
+ return ret;
+
+ if (hdr->table_enc != (DW_EH_PE_datarel | DW_EH_PE_sdata4))
+ {
+ /* If there is no search table or it has an unsupported
+ encoding, fall back on linear search. */
+ if (hdr->table_enc == DW_EH_PE_omit)
+ Debug (4, "table `%s' lacks search table; doing linear search\n",
+ info->dlpi_name);
+ else
+ Debug (4, "table `%s' has encoding 0x%x; doing linear search\n",
+ info->dlpi_name, hdr->table_enc);
+
+ eh_frame_end = max_load_addr; /* XXX can we do better? */
+
+ if (hdr->fde_count_enc == DW_EH_PE_omit)
+ fde_count = ~0UL;
+ if (hdr->eh_frame_ptr_enc == DW_EH_PE_omit)
+ abort ();
+
+ /* XXX we know how to build a local binary search table for
+ .debug_frame, so we could do that here too. */
+ cb_data->single_fde = 1;
+ found = linear_search (unw_local_addr_space, ip,
+ eh_frame_start, eh_frame_end, fde_count,
+ pi, need_unwind_info, NULL);
+ if (found != 1)
+ found = 0;
+ }
+ else
+ {
+ di->format = UNW_INFO_FORMAT_REMOTE_TABLE;
+ di->start_ip = p_text->p_vaddr + load_base;
+ di->end_ip = p_text->p_vaddr + load_base + p_text->p_memsz;
+ di->u.rti.name_ptr = (unw_word_t) (uintptr_t) info->dlpi_name;
+ di->u.rti.table_data = addr;
+ assert (sizeof (struct table_entry) % sizeof (unw_word_t) == 0);
+ di->u.rti.table_len = (fde_count * sizeof (struct table_entry)
+ / sizeof (unw_word_t));
+ /* For the binary-search table in the eh_frame_hdr, data-relative
+ means relative to the start of that section... */
+ di->u.rti.segbase = (unw_word_t) (uintptr_t) hdr;
+
+ found = 1;
+ Debug (15, "found table `%s': segbase=0x%lx, len=%lu, gp=0x%lx, "
+ "table_data=0x%lx\n", (char *) (uintptr_t) di->u.rti.name_ptr,
+ (long) di->u.rti.segbase, (long) di->u.rti.table_len,
+ (long) di->gp, (long) di->u.rti.table_data);
+ }
}
- if (p_dynamic)
+ Debug (15, "Trying to find .debug_frame\n");
+ di = &cb_data->di_debug;
+ fdesc = locate_debug_info (unw_local_addr_space, info, ip, info->dlpi_name);
+
+ if (!fdesc)
{
- /* For dynamicly linked executables and shared libraries,
- DT_PLTGOT is the value that data-relative addresses are
- relative to for that object. We call this the "gp". */
- Elf_W(Dyn) *dyn = (Elf_W(Dyn) *)(p_dynamic->p_vaddr + load_base);
- for (; dyn->d_tag != DT_NULL; ++dyn)
- if (dyn->d_tag == DT_PLTGOT)
- {
- /* Assume that _DYNAMIC is writable and GLIBC has
- relocated it (true for x86 at least). */
- di->gp = dyn->d_un.d_ptr;
- break;
- }
+ Debug (15, "couldn't load .debug_frame\n");
+ return found;
}
else
- /* Otherwise this is a static executable with no _DYNAMIC. Assume
- that data-relative addresses are relative to 0, i.e.,
- absolute. */
- di->gp = 0;
- pi->gp = di->gp;
-
- hdr = (struct dwarf_eh_frame_hdr *) (p_eh_hdr->p_vaddr + load_base);
- if (hdr->version != DW_EH_VERSION)
{
- Debug (1, "table `%s' has unexpected version %d\n",
- info->dlpi_name, hdr->version);
- return 0;
- }
+ char *buf;
+ size_t bufsize;
+ unw_word_t item_start, item_end = 0;
+ uint32_t u32val = 0;
+ uint64_t cie_id = 0;
+ struct debug_frame_tab *tab;
- a = unw_get_accessors (unw_local_addr_space);
- addr = (unw_word_t) (hdr + 1);
+ Debug (15, "loaded .debug_frame\n");
- /* (Optionally) read eh_frame_ptr: */
- if ((ret = dwarf_read_encoded_pointer (unw_local_addr_space, a,
- &addr, hdr->eh_frame_ptr_enc, pi,
- &eh_frame_start, NULL)) < 0)
- return ret;
+ buf = fdesc->debug_frame;
+ bufsize = fdesc->debug_frame_size;
- /* (Optionally) read fde_count: */
- if ((ret = dwarf_read_encoded_pointer (unw_local_addr_space, a,
- &addr, hdr->fde_count_enc, pi,
- &fde_count, NULL)) < 0)
- return ret;
+ if (bufsize == 0)
+ {
+ Debug (15, "zero-length .debug_frame\n");
+ return found;
+ }
- if (hdr->table_enc != (DW_EH_PE_datarel | DW_EH_PE_sdata4))
- {
- /* If there is no search table or it has an unsupported
- encoding, fall back on linear search. */
- if (hdr->table_enc == DW_EH_PE_omit)
- Debug (4, "table `%s' lacks search table; doing linear search\n",
- info->dlpi_name);
- else
- Debug (4, "table `%s' has encoding 0x%x; doing linear search\n",
- info->dlpi_name, hdr->table_enc);
+ /* Now create a binary-search table, if it does not already exist. */
+ if (!fdesc->index)
+ {
+ addr = (unw_word_t) (uintptr_t) buf;
+
+ a = unw_get_accessors (unw_local_addr_space);
+
+ /* Find all FDE entries in debug_frame, and make into a sorted
+ index. */
- eh_frame_end = max_load_addr; /* XXX can we do better? */
+ tab = debug_frame_tab_new (16);
- if (hdr->fde_count_enc == DW_EH_PE_omit)
- fde_count = ~0UL;
- if (hdr->eh_frame_ptr_enc == DW_EH_PE_omit)
- abort ();
+ while (addr < (unw_word_t) (uintptr_t) (buf + bufsize))
+ {
+ uint64_t id_for_cie;
+ item_start = addr;
+
+ dwarf_readu32 (unw_local_addr_space, a, &addr, &u32val, NULL);
+
+ if (u32val == 0)
+ break;
+ else if (u32val != 0xffffffff)
+ {
+ uint32_t cie_id32 = 0;
+ item_end = addr + u32val;
+ dwarf_readu32 (unw_local_addr_space, a, &addr, &cie_id32,
+ NULL);
+ cie_id = cie_id32;
+ id_for_cie = 0xffffffff;
+ }
+ else
+ {
+ uint64_t u64val = 0;
+ /* Extended length. */
+ dwarf_readu64 (unw_local_addr_space, a, &addr, &u64val, NULL);
+ item_end = addr + u64val;
+
+ dwarf_readu64 (unw_local_addr_space, a, &addr, &cie_id, NULL);
+ id_for_cie = 0xffffffffffffffffull;
+ }
+
+ /*Debug (1, "CIE/FDE id = %.8x\n", (int) cie_id);*/
+
+ if (cie_id == id_for_cie)
+ ;
+ /*Debug (1, "Found CIE at %.8x.\n", item_start);*/
+ else
+ {
+ unw_word_t fde_addr = item_start;
+ unw_proc_info_t this_pi;
+ int err;
+
+ /*Debug (1, "Found FDE at %.8x\n", item_start);*/
+
+ err = dwarf_extract_proc_info_from_fde (unw_local_addr_space,
+ a, &fde_addr,
+ &this_pi, 0,
+ (uintptr_t) buf,
+ NULL);
+ if (err == 0)
+ {
+ Debug (15, "start_ip = %x, end_ip = %x\n",
+ (int) this_pi.start_ip, (int) this_pi.end_ip);
+ debug_frame_tab_append (tab,
+ item_start - (unw_word_t) (uintptr_t) buf,
+ this_pi.start_ip);
+ }
+ /*else
+ Debug (1, "FDE parse failed\n");*/
+ }
+
+ addr = item_end;
+ }
+
+ debug_frame_tab_shrink (tab);
+ qsort (tab->tab, tab->length, sizeof (struct table_entry),
+ debug_frame_tab_compare);
+ /* for (i = 0; i < tab->length; i++)
+ {
+ fprintf (stderr, "ip %x, fde offset %x\n",
+ (int) tab->tab[i].start_ip_offset,
+ (int) tab->tab[i].fde_offset);
+ }*/
+ fdesc->index = tab->tab;
+ fdesc->index_size = tab->length;
+ free (tab);
+ }
- cb_data->single_fde = 1;
- return linear_search (unw_local_addr_space, ip,
- eh_frame_start, eh_frame_end, fde_count,
- pi, need_unwind_info, NULL);
+ di->format = UNW_INFO_FORMAT_TABLE;
+ di->start_ip = fdesc->start;
+ di->end_ip = fdesc->end;
+ di->u.ti.name_ptr = (unw_word_t) (uintptr_t) info->dlpi_name;
+ di->u.ti.table_data = (unw_word_t *) fdesc;
+ di->u.ti.table_len = sizeof (*fdesc) / sizeof (unw_word_t);
+ di->u.ti.segbase = (unw_word_t) (uintptr_t) info->dlpi_addr;
+
+ found = 1;
+ Debug (15, "found debug_frame table `%s': segbase=0x%lx, len=%lu, "
+ "gp=0x%lx, table_data=0x%lx\n",
+ (char *) (uintptr_t) di->u.ti.name_ptr,
+ (long) di->u.ti.segbase, (long) di->u.ti.table_len,
+ (long) di->gp, (long) di->u.ti.table_data);
}
- cb_data->single_fde = 0;
- di->format = UNW_INFO_FORMAT_REMOTE_TABLE;
- di->start_ip = p_text->p_vaddr + load_base;
- di->end_ip = p_text->p_vaddr + load_base + p_text->p_memsz;
- di->u.rti.name_ptr = (unw_word_t) info->dlpi_name;
- di->u.rti.table_data = addr;
- assert (sizeof (struct table_entry) % sizeof (unw_word_t) == 0);
- di->u.rti.table_len = (fde_count * sizeof (struct table_entry)
- / sizeof (unw_word_t));
- /* For the binary-search table in the eh_frame_hdr, data-relative
- means relative to the start of that section... */
- di->u.rti.segbase = (unw_word_t) hdr;
-
- Debug (15, "found table `%s': segbase=0x%lx, len=%lu, gp=0x%lx, "
- "table_data=0x%lx\n", (char *) di->u.rti.name_ptr,
- (long) di->u.rti.segbase, (long) di->u.rti.table_len,
- (long) di->gp, (long) di->u.rti.table_data);
- return 1;
+ return found;
}
HIDDEN int
@@ -259,9 +721,12 @@ dwarf_find_proc_info (unw_addr_space_t as, unw_word_t ip,
Debug (14, "looking for IP=0x%lx\n", (long) ip);
+ memset (&cb_data, 0, sizeof (cb_data));
cb_data.ip = ip;
cb_data.pi = pi;
cb_data.need_unwind_info = need_unwind_info;
+ cb_data.di.format = -1;
+ cb_data.di_debug.format = -1;
sigprocmask (SIG_SETMASK, &unwi_full_mask, &saved_mask);
ret = dl_iterate_phdr (callback, &cb_data);
@@ -276,14 +741,22 @@ dwarf_find_proc_info (unw_addr_space_t as, unw_word_t ip,
if (cb_data.single_fde)
/* already got the result in *pi */
return 0;
- else
- /* search the table: */
- return dwarf_search_unwind_table (as, ip, &cb_data.di,
+
+ /* search the table: */
+ if (cb_data.di.format != -1)
+ ret = dwarf_search_unwind_table (as, ip, &cb_data.di,
pi, need_unwind_info, arg);
+ else
+ ret = -UNW_ENOINFO;
+
+ if (ret == -UNW_ENOINFO && cb_data.di_debug.format != -1)
+ ret = dwarf_search_unwind_table (as, ip, &cb_data.di_debug, pi,
+ need_unwind_info, arg);
+ return ret;
}
static inline const struct table_entry *
-lookup (struct table_entry *table, size_t table_size, int32_t rel_ip)
+lookup (const struct table_entry *table, size_t table_size, int32_t rel_ip)
{
unsigned long table_len = table_size / sizeof (struct table_entry);
const struct table_entry *e = 0;
@@ -294,6 +767,7 @@ lookup (struct table_entry *table, size_t table_size, int32_t rel_ip)
{
mid = (lo + hi) / 2;
e = table + mid;
+ Debug (1, "e->start_ip_offset = %x\n", (int) e->start_ip_offset);
if (rel_ip < e->start_ip_offset)
hi = mid;
else
@@ -353,16 +827,47 @@ dwarf_search_unwind_table (unw_addr_space_t as, unw_word_t ip,
unw_dyn_info_t *di, unw_proc_info_t *pi,
int need_unwind_info, void *arg)
{
- const struct table_entry *e = NULL;
+ const struct table_entry *e = NULL, *table;
unw_word_t segbase = 0, fde_addr;
unw_accessors_t *a;
#ifndef UNW_LOCAL_ONLY
struct table_entry ent;
#endif
int ret;
+ unw_word_t debug_frame_base;
+ size_t table_len;
+#ifdef UNW_REMOTE_ONLY
+ assert (di->format == UNW_INFO_FORMAT_REMOTE_TABLE);
+#else
assert (di->format == UNW_INFO_FORMAT_REMOTE_TABLE
- && (ip >= di->start_ip && ip < di->end_ip));
+ || di->format == UNW_INFO_FORMAT_TABLE);
+#endif
+ assert (ip >= di->start_ip && ip < di->end_ip);
+
+ if (di->format == UNW_INFO_FORMAT_REMOTE_TABLE)
+ {
+ table = (const struct table_entry *) (uintptr_t) di->u.rti.table_data;
+ table_len = di->u.rti.table_len * sizeof (unw_word_t);
+ debug_frame_base = 0;
+ }
+ else
+ {
+#ifndef UNW_REMOTE_ONLY
+ struct unw_debug_frame_list *fdesc = (void *) di->u.ti.table_data;
+
+ /* UNW_INFO_FORMAT_TABLE (i.e. .debug_frame) is currently only
+ supported for the local address space. Both the index and
+ the unwind tables live in local memory, but the address space
+ to check for properties like the address size and endianness
+ is the target one. When the ptrace code adds support for
+ .debug_frame something will have to change. */
+ assert (as == unw_local_addr_space);
+ table = fdesc->index;
+ table_len = fdesc->index_size * sizeof (struct table_entry);
+ debug_frame_base = (uintptr_t) fdesc->debug_frame;
+#endif
+ }
a = unw_get_accessors (as);
@@ -370,16 +875,14 @@ dwarf_search_unwind_table (unw_addr_space_t as, unw_word_t ip,
if (as == unw_local_addr_space)
{
segbase = di->u.rti.segbase;
- e = lookup ((struct table_entry *) di->u.rti.table_data,
- di->u.rti.table_len * sizeof (unw_word_t), ip - segbase);
+ e = lookup (table, table_len, ip - segbase);
}
else
#endif
{
#ifndef UNW_LOCAL_ONLY
segbase = di->u.rti.segbase;
- if ((ret = remote_lookup (as, di->u.rti.table_data,
- di->u.rti.table_len * sizeof (unw_word_t),
+ if ((ret = remote_lookup (as, (uintptr_t) table, table_len,
ip - segbase, &ent, arg)) < 0)
return ret;
if (ret)
@@ -390,17 +893,34 @@ dwarf_search_unwind_table (unw_addr_space_t as, unw_word_t ip,
}
if (!e)
{
+ Debug (1, "IP %x inside range %x-%x, but no explicit unwind info found\n",
+ (int) ip, (int) di->start_ip, (int) di->end_ip);
/* IP is inside this table's range, but there is no explicit
unwind info. */
return -UNW_ENOINFO;
}
Debug (15, "ip=0x%lx, start_ip=0x%lx\n",
- (long) ip, (long) (e->start_ip_offset + segbase));
- fde_addr = e->fde_offset + segbase;
+ (long) ip, (long) (e->start_ip_offset));
+ if (debug_frame_base)
+ fde_addr = e->fde_offset + debug_frame_base;
+ else
+ fde_addr = e->fde_offset + segbase;
+ Debug (1, "e->fde_offset = %x, segbase = %x, debug_frame_base = %x, "
+ "fde_addr = %x\n", (int) e->fde_offset, (int) segbase,
+ (int) debug_frame_base, (int) fde_addr);
if ((ret = dwarf_extract_proc_info_from_fde (as, a, &fde_addr, pi,
- need_unwind_info, arg)) < 0)
+ need_unwind_info,
+ debug_frame_base, arg)) < 0)
return ret;
+ /* .debug_frame uses an absolute encoding that does not know about any
+ shared library relocation. */
+ if (di->format == UNW_INFO_FORMAT_TABLE)
+ {
+ pi->start_ip += segbase;
+ pi->end_ip += segbase;
+ }
+
if (ip < pi->start_ip || ip >= pi->end_ip)
return -UNW_ENOINFO;
diff --git a/src/dwarf/Gpe.c b/src/dwarf/Gpe.c
index 4ca0d649..c271d763 100644
--- a/src/dwarf/Gpe.c
+++ b/src/dwarf/Gpe.c
@@ -26,6 +26,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
#include "dwarf_i.h"
#include "libunwind_i.h"
+#include <assert.h>
+
HIDDEN int
dwarf_read_encoded_pointer (unw_addr_space_t as, unw_accessors_t *a,
unw_word_t *addr, unsigned char encoding,