diff options
Diffstat (limited to 'src/dwarf')
-rw-r--r-- | src/dwarf/Gexpr.c | 60 | ||||
-rw-r--r-- | src/dwarf/Gfde.c | 86 | ||||
-rw-r--r-- | src/dwarf/Gfind_proc_info-lsb.c | 732 | ||||
-rw-r--r-- | src/dwarf/Gpe.c | 2 |
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, |