diff options
Diffstat (limited to 'src/dwarf2.c')
-rw-r--r-- | src/dwarf2.c | 1388 |
1 files changed, 1388 insertions, 0 deletions
diff --git a/src/dwarf2.c b/src/dwarf2.c new file mode 100644 index 0000000..b9588b1 --- /dev/null +++ b/src/dwarf2.c @@ -0,0 +1,1388 @@ +/* Copyright (C) 2001, 2002, 2003, 2005, 2006, 2009, 2010, 2011, 2012 + Red Hat, Inc. + Written by Jakub Jelinek <jakub@redhat.com>, 2001. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#include <config.h> +#include <assert.h> +#include <byteswap.h> +#include <endian.h> +#include <errno.h> +#include <error.h> +#include <limits.h> +#include <string.h> +#include <sys/types.h> + +#include "dwarf2.h" +#include "hashtab.h" +#include "prelink.h" + +#define read_uleb128(ptr) ({ \ + unsigned int ret = 0; \ + unsigned int c; \ + int shift = 0; \ + do \ + { \ + c = *ptr++; \ + ret |= (c & 0x7f) << shift; \ + shift += 7; \ + } while (c & 0x80); \ + \ + if (shift >= 35) \ + ret = UINT_MAX; \ + ret; \ +}) + +static uint16_t (*do_read_16) (unsigned char *ptr); +static uint32_t (*do_read_32) (unsigned char *ptr); +static uint64_t (*do_read_32_64) (unsigned char *ptr); +static uint64_t (*do_read_64) (unsigned char *ptr); +static uint64_t (*do_read_ptr) (unsigned char *ptr); +static void (*write_32) (unsigned char *ptr, GElf_Addr val); +static void (*write_64) (unsigned char *ptr, GElf_Addr val); +static void (*write_ptr) (unsigned char *ptr, GElf_Addr val); + +static int ptr_size; + +#define read_1(ptr) *ptr++ + +#define read_16(ptr) ({ \ + uint16_t ret = do_read_16 (ptr); \ + ptr += 2; \ + ret; \ +}) + +#define read_32(ptr) ({ \ + uint32_t ret = do_read_32 (ptr); \ + ptr += 4; \ + ret; \ +}) + +#define read_64(ptr) ({ \ + uint64_t ret = do_read_64 (ptr); \ + ptr += 8; \ + ret; \ +}) + +#define read_ptr(ptr) ({ \ + uint64_t ret = do_read_ptr (ptr); \ + ptr += ptr_size; \ + ret; \ +}) + +static uint64_t +buf_read_ule32_64 (unsigned char *p) +{ + return buf_read_ule32 (p); +} + +static uint64_t +buf_read_ube32_64 (unsigned char *p) +{ + return buf_read_ube32 (p); +} + +static void +dwarf2_write_le32 (unsigned char *p, GElf_Addr val) +{ + uint32_t v = (uint32_t) val; + + p[0] = v; + p[1] = v >> 8; + p[2] = v >> 16; + p[3] = v >> 24; +} + +static void +dwarf2_write_le64 (unsigned char *p, GElf_Addr val) +{ + p[0] = val; + p[1] = val >> 8; + p[2] = val >> 16; + p[3] = val >> 24; + p[4] = val >> 32; + p[5] = val >> 40; + p[6] = val >> 48; + p[7] = val >> 56; +} + +static void +dwarf2_write_be32 (unsigned char *p, GElf_Addr val) +{ + uint32_t v = (uint32_t) val; + + p[3] = v; + p[2] = v >> 8; + p[1] = v >> 16; + p[0] = v >> 24; +} + +static void +dwarf2_write_be64 (unsigned char *p, GElf_Addr val) +{ + p[7] = val; + p[6] = val >> 8; + p[5] = val >> 16; + p[4] = val >> 24; + p[3] = val >> 32; + p[2] = val >> 40; + p[1] = val >> 48; + p[0] = val >> 56; +} + +static struct + { + const char *name; + unsigned char *data; + size_t size; + int sec; + } debug_sections[] = + { +#define DEBUG_INFO 0 +#define DEBUG_ABBREV 1 +#define DEBUG_LINE 2 +#define DEBUG_ARANGES 3 +#define DEBUG_PUBNAMES 4 +#define DEBUG_PUBTYPES 5 +#define DEBUG_MACINFO 6 +#define DEBUG_LOC 7 +#define DEBUG_STR 8 +#define DEBUG_FRAME 9 +#define DEBUG_RANGES 10 +#define DEBUG_TYPES 11 +#define DEBUG_MACRO 12 + { ".debug_info", NULL, 0, 0 }, + { ".debug_abbrev", NULL, 0, 0 }, + { ".debug_line", NULL, 0, 0 }, + { ".debug_aranges", NULL, 0, 0 }, + { ".debug_pubnames", NULL, 0, 0 }, + { ".debug_pubtypes", NULL, 0, 0 }, + { ".debug_macinfo", NULL, 0, 0 }, + { ".debug_loc", NULL, 0, 0 }, + { ".debug_str", NULL, 0, 0 }, + { ".debug_frame", NULL, 0, 0 }, + { ".debug_ranges", NULL, 0, 0 }, + { ".debug_types", NULL, 0, 0 }, + { ".debug_macro", NULL, 0, 0 }, + { NULL, NULL, 0 } + }; + +struct abbrev_attr + { + unsigned int attr; + unsigned int form; + }; + +struct abbrev_tag + { + unsigned int entry; + unsigned int tag; + int nattr; + struct abbrev_attr attr[0]; + }; + +struct cu_data + { + GElf_Addr cu_entry_pc; + GElf_Addr cu_low_pc; + unsigned char cu_version; + }; + +static hashval_t +abbrev_hash (const void *p) +{ + struct abbrev_tag *t = (struct abbrev_tag *)p; + + return t->entry; +} + +static int +abbrev_eq (const void *p, const void *q) +{ + struct abbrev_tag *t1 = (struct abbrev_tag *)p; + struct abbrev_tag *t2 = (struct abbrev_tag *)q; + + return t1->entry == t2->entry; +} + +static void +abbrev_del (void *p) +{ + free (p); +} + +static htab_t +read_abbrev (DSO *dso, unsigned char *ptr) +{ + htab_t h = htab_try_create (50, abbrev_hash, abbrev_eq, abbrev_del); + unsigned int attr, form; + struct abbrev_tag *t; + int size; + void **slot; + + if (h == NULL) + { +no_memory: + error (0, ENOMEM, "%s: Could not read .debug_abbrev", dso->filename); + if (h) + htab_delete (h); + return NULL; + } + + while ((attr = read_uleb128 (ptr)) != 0) + { + size = 10; + t = malloc (sizeof (*t) + size * sizeof (struct abbrev_attr)); + if (t == NULL) + goto no_memory; + t->entry = attr; + t->nattr = 0; + slot = htab_find_slot (h, t, INSERT); + if (slot == NULL) + { + free (t); + goto no_memory; + } + if (*slot != NULL) + { + error (0, 0, "%s: Duplicate DWARF abbreviation %d", dso->filename, + t->entry); + free (t); + htab_delete (h); + return NULL; + } + t->tag = read_uleb128 (ptr); + ++ptr; /* skip children flag. */ + while ((attr = read_uleb128 (ptr)) != 0) + { + if (t->nattr == size) + { + size += 10; + t = realloc (t, sizeof (*t) + size * sizeof (struct abbrev_attr)); + if (t == NULL) + goto no_memory; + } + form = read_uleb128 (ptr); + if (form == 2 + || (form > DW_FORM_flag_present + && form != DW_FORM_ref_sig8 + && form != DW_FORM_GNU_ref_alt + && form != DW_FORM_GNU_strp_alt)) + { + error (0, 0, "%s: Unknown DWARF DW_FORM_%d", dso->filename, form); + htab_delete (h); + return NULL; + } + + t->attr[t->nattr].attr = attr; + t->attr[t->nattr++].form = form; + } + if (read_uleb128 (ptr) != 0) + { + error (0, 0, "%s: DWARF abbreviation does not end with 2 zeros", + dso->filename); + htab_delete (h); + return NULL; + } + *slot = t; + } + + return h; +} + +static int +adjust_location_list (DSO *dso, struct cu_data *cu, unsigned char *ptr, + size_t len, GElf_Addr start, GElf_Addr adjust) +{ + unsigned char *end = ptr + len; + unsigned char op; + GElf_Addr addr; + + while (ptr < end) + { + op = *ptr++; + switch (op) + { + case DW_OP_addr: + addr = read_ptr (ptr); + if (addr >= start && addr_to_sec (dso, addr) != -1) + write_ptr (ptr - ptr_size, addr + adjust); + break; + case DW_OP_deref: + case DW_OP_dup: + case DW_OP_drop: + case DW_OP_over: + case DW_OP_swap: + case DW_OP_rot: + case DW_OP_xderef: + case DW_OP_abs: + case DW_OP_and: + case DW_OP_div: + case DW_OP_minus: + case DW_OP_mod: + case DW_OP_mul: + case DW_OP_neg: + case DW_OP_not: + case DW_OP_or: + case DW_OP_plus: + case DW_OP_shl: + case DW_OP_shr: + case DW_OP_shra: + case DW_OP_xor: + case DW_OP_eq: + case DW_OP_ge: + case DW_OP_gt: + case DW_OP_le: + case DW_OP_lt: + case DW_OP_ne: + case DW_OP_lit0 ... DW_OP_lit31: + case DW_OP_reg0 ... DW_OP_reg31: + case DW_OP_nop: + case DW_OP_push_object_address: + case DW_OP_form_tls_address: + case DW_OP_call_frame_cfa: + case DW_OP_stack_value: + case DW_OP_GNU_push_tls_address: + case DW_OP_GNU_uninit: + break; + case DW_OP_const1u: + case DW_OP_pick: + case DW_OP_deref_size: + case DW_OP_xderef_size: + case DW_OP_const1s: + ++ptr; + break; + case DW_OP_const2u: + case DW_OP_const2s: + case DW_OP_skip: + case DW_OP_bra: + case DW_OP_call2: + ptr += 2; + break; + case DW_OP_const4u: + case DW_OP_const4s: + case DW_OP_call4: + case DW_OP_GNU_parameter_ref: + ptr += 4; + break; + case DW_OP_call_ref: + if (cu == NULL) + { + error (0, 0, "%s: DWARF DW_OP_call_ref shouldn't appear" + " in .debug_frame", dso->filename); + return 1; + } + if (cu->cu_version == 2) + ptr += ptr_size; + else + ptr += 4; + break; + case DW_OP_const8u: + case DW_OP_const8s: + ptr += 8; + break; + case DW_OP_constu: + case DW_OP_plus_uconst: + case DW_OP_regx: + case DW_OP_piece: + case DW_OP_consts: + case DW_OP_breg0 ... DW_OP_breg31: + case DW_OP_fbreg: + case DW_OP_GNU_convert: + case DW_OP_GNU_reinterpret: + read_uleb128 (ptr); + break; + case DW_OP_bregx: + case DW_OP_bit_piece: + case DW_OP_GNU_regval_type: + read_uleb128 (ptr); + read_uleb128 (ptr); + break; + case DW_OP_implicit_value: + { + uint32_t leni = read_uleb128 (ptr); + ptr += leni; + } + break; + case DW_OP_GNU_implicit_pointer: + if (cu == NULL) + { + error (0, 0, "%s: DWARF DW_OP_GNU_implicit_pointer shouldn't" + " appear in .debug_frame", dso->filename); + return 1; + } + if (cu->cu_version == 2) + ptr += ptr_size; + else + ptr += 4; + read_uleb128 (ptr); + break; + case DW_OP_GNU_entry_value: + { + uint32_t leni = read_uleb128 (ptr); + if ((end - ptr) < leni) + { + error (0, 0, "%s: DWARF DW_OP_GNU_entry_value with too large" + " length", dso->filename); + return 1; + } + if (adjust_location_list (dso, cu, ptr, leni, start, adjust)) + return 1; + ptr += leni; + } + break; + case DW_OP_GNU_const_type: + read_uleb128 (ptr); + ptr += *ptr + 1; + break; + case DW_OP_GNU_deref_type: + ++ptr; + read_uleb128 (ptr); + break; + default: + error (0, 0, "%s: Unknown DWARF DW_OP_%d", dso->filename, op); + return 1; + } + } + return 0; +} + +static int +adjust_dwarf2_ranges (DSO *dso, GElf_Addr offset, GElf_Addr base, + GElf_Addr start, GElf_Addr adjust) +{ + unsigned char *ptr, *endsec; + GElf_Addr low, high; + int adjusted_base; + + ptr = debug_sections[DEBUG_RANGES].data; + if (ptr == NULL) + { + error (0, 0, "%s: DW_AT_ranges attribute, yet no .debug_ranges section", + dso->filename); + return 1; + } + if (offset >= debug_sections[DEBUG_RANGES].size) + { + error (0, 0, + "%s: DW_AT_ranges offset %Ld outside of .debug_ranges section", + dso->filename, (long long) offset); + return 1; + } + endsec = ptr + debug_sections[DEBUG_RANGES].size; + ptr += offset; + adjusted_base = (base && base >= start && addr_to_sec (dso, base) != -1); + while (ptr < endsec) + { + low = read_ptr (ptr); + high = read_ptr (ptr); + if (low == 0 && high == 0) + break; + + if (low == ~ (GElf_Addr) 0 || (ptr_size == 4 && low == 0xffffffff)) + { + base = high; + adjusted_base = (base && base >= start + && addr_to_sec (dso, base) != -1); + if (adjusted_base) + write_ptr (ptr - ptr_size, base + adjust); + } + else if (! adjusted_base) + { + if (base + low >= start && addr_to_sec (dso, base + low) != -1) + { + write_ptr (ptr - 2 * ptr_size, low + adjust); + if (high == low) + write_ptr (ptr - ptr_size, high + adjust); + } + if (low != high && base + high >= start + && addr_to_sec (dso, base + high - 1) != -1) + write_ptr (ptr - ptr_size, high + adjust); + } + } + + elf_flagscn (dso->scn[debug_sections[DEBUG_RANGES].sec], ELF_C_SET, + ELF_F_DIRTY); + return 0; +} + +static int +adjust_dwarf2_loc (DSO *dso, struct cu_data *cu, GElf_Addr offset, + GElf_Addr base, GElf_Addr start, GElf_Addr adjust) +{ + unsigned char *ptr, *endsec; + GElf_Addr low, high; + int adjusted_base; + size_t len; + + ptr = debug_sections[DEBUG_LOC].data; + if (ptr == NULL) + { + error (0, 0, "%s: loclistptr attribute, yet no .debug_loc section", + dso->filename); + return 1; + } + if (offset >= debug_sections[DEBUG_LOC].size) + { + error (0, 0, + "%s: loclistptr offset %Ld outside of .debug_loc section", + dso->filename, (long long) offset); + return 1; + } + endsec = ptr + debug_sections[DEBUG_LOC].size; + ptr += offset; + adjusted_base = (base && base >= start && addr_to_sec (dso, base) != -1); + while (ptr < endsec) + { + low = read_ptr (ptr); + high = read_ptr (ptr); + if (low == 0 && high == 0) + break; + + if (low == ~ (GElf_Addr) 0 || (ptr_size == 4 && low == 0xffffffff)) + { + base = high; + adjusted_base = (base && base >= start + && addr_to_sec (dso, base) != -1); + if (adjusted_base) + write_ptr (ptr - ptr_size, base + adjust); + continue; + } + len = read_16 (ptr); + assert (ptr + len <= endsec); + + if (adjust_location_list (dso, cu, ptr, len, start, adjust)) + return 1; + + ptr += len; + } + + elf_flagscn (dso->scn[debug_sections[DEBUG_LOC].sec], ELF_C_SET, + ELF_F_DIRTY); + return 0; +} + +static unsigned char * +adjust_attributes (DSO *dso, unsigned char *ptr, struct abbrev_tag *t, + struct cu_data *cu, + GElf_Addr start, GElf_Addr adjust, htab_t offset_hash) +{ + int i; + GElf_Addr addr; + + for (i = 0; i < t->nattr; ++i) + { + uint32_t form = t->attr[i].form; + uint32_t len = 0; + + while (1) + { + switch (t->attr[i].attr) + { + case DW_AT_data_member_location: + /* In DWARF4+ DW_AT_data_member_location + with DW_FORM_data[48] is just very high + constant, rather than loclistptr. */ + if (cu->cu_version >= 4 && form != DW_FORM_sec_offset) + break; + /* FALLTHRU */ + case DW_AT_location: + case DW_AT_string_length: + case DW_AT_return_addr: + case DW_AT_frame_base: + case DW_AT_segment: + case DW_AT_static_link: + case DW_AT_use_location: + case DW_AT_vtable_elem_location: + case DW_AT_ranges: + if (form == DW_FORM_data4 || form == DW_FORM_sec_offset) + addr = read_32 (ptr), ptr -= 4; + else if (form == DW_FORM_data8) + addr = read_64 (ptr), ptr -= 8; + else + break; + { + GElf_Addr base; + + if (cu->cu_entry_pc != ~ (GElf_Addr) 0) + base = cu->cu_entry_pc; + else if (cu->cu_low_pc != ~ (GElf_Addr) 0) + base = cu->cu_low_pc; + else + base = 0; + if (t->attr[i].attr == DW_AT_ranges) + { + if (adjust_dwarf2_ranges (dso, addr, base, start, adjust)) + return NULL; + } + else + { + GElf_Addr *offsetp = malloc (sizeof (addr)); + void **slot; + if (offsetp == NULL) + return NULL; + *offsetp = addr; + slot = htab_find_slot (offset_hash, offsetp, INSERT); + if (slot == NULL) + { + free (offsetp); + return NULL; + } + if (*slot == NULL) + { + *slot = offsetp; + if (adjust_dwarf2_loc (dso, cu, addr, base, + start, adjust)) + return NULL; + } + else + free (offsetp); + } + } + break; + } + switch (form) + { + case DW_FORM_addr: + addr = read_ptr (ptr); + if (t->tag == DW_TAG_compile_unit + || t->tag == DW_TAG_partial_unit) + { + if (t->attr[i].attr == DW_AT_entry_pc) + cu->cu_entry_pc = addr; + else if (t->attr[i].attr == DW_AT_low_pc) + cu->cu_low_pc = addr; + if (addr == 0) + break; + } + if (addr >= start + && addr_to_sec (dso, + ((t->attr[i].attr == DW_AT_high_pc + && addr > start) + ? addr - 1 + : addr)) != -1) + write_ptr (ptr - ptr_size, addr + adjust); + break; + case DW_FORM_flag_present: + break; + case DW_FORM_ref1: + case DW_FORM_flag: + case DW_FORM_data1: + ++ptr; + break; + case DW_FORM_ref2: + case DW_FORM_data2: + ptr += 2; + break; + case DW_FORM_ref4: + case DW_FORM_GNU_ref_alt: + case DW_FORM_data4: + case DW_FORM_sec_offset: + ptr += 4; + break; + case DW_FORM_ref8: + case DW_FORM_data8: + case DW_FORM_ref_sig8: + ptr += 8; + break; + case DW_FORM_sdata: + case DW_FORM_ref_udata: + case DW_FORM_udata: + read_uleb128 (ptr); + break; + case DW_FORM_ref_addr: + if (cu->cu_version == 2) + ptr += ptr_size; + else + ptr += 4; + break; + case DW_FORM_strp: + case DW_FORM_GNU_strp_alt: + ptr += 4; + break; + case DW_FORM_string: + ptr = strchr (ptr, '\0') + 1; + break; + case DW_FORM_indirect: + form = read_uleb128 (ptr); + continue; + case DW_FORM_block1: + len = *ptr++; + break; + case DW_FORM_block2: + len = read_16 (ptr); + form = DW_FORM_block1; + break; + case DW_FORM_block4: + len = read_32 (ptr); + form = DW_FORM_block1; + break; + case DW_FORM_block: + len = read_uleb128 (ptr); + form = DW_FORM_block1; + assert (len < UINT_MAX); + break; + case DW_FORM_exprloc: + len = read_uleb128 (ptr); + assert (len < UINT_MAX); + break; + default: + error (0, 0, "%s: Unknown DWARF DW_FORM_%d", dso->filename, + form); + return NULL; + } + + if (form == DW_FORM_block1) + { + switch (t->attr[i].attr) + { + case DW_AT_frame_base: + case DW_AT_location: + case DW_AT_data_member_location: + case DW_AT_vtable_elem_location: + case DW_AT_byte_size: + case DW_AT_bit_offset: + case DW_AT_bit_size: + case DW_AT_string_length: + case DW_AT_lower_bound: + case DW_AT_return_addr: + case DW_AT_bit_stride: + case DW_AT_upper_bound: + case DW_AT_count: + case DW_AT_segment: + case DW_AT_static_link: + case DW_AT_use_location: + case DW_AT_allocated: + case DW_AT_associated: + case DW_AT_data_location: + case DW_AT_byte_stride: + case DW_AT_GNU_call_site_value: + case DW_AT_GNU_call_site_data_value: + case DW_AT_GNU_call_site_target: + case DW_AT_GNU_call_site_target_clobbered: + if (adjust_location_list (dso, cu, ptr, len, start, adjust)) + return NULL; + break; + default: + if (t->attr[i].attr <= DW_AT_linkage_name + || (t->attr[i].attr >= DW_AT_MIPS_fde + && t->attr[i].attr <= DW_AT_MIPS_has_inlines) + || (t->attr[i].attr >= DW_AT_sf_names + && t->attr[i].attr <= DW_AT_body_end)) + break; + error (0, 0, "%s: Unknown DWARF DW_AT_%d with block DW_FORM", + dso->filename, t->attr[i].attr); + return NULL; + } + ptr += len; + } + else if (form == DW_FORM_exprloc) + { + if (adjust_location_list (dso, cu, ptr, len, start, adjust)) + return NULL; + ptr += len; + } + + break; + } + } + + return ptr; +} + +static int +adjust_dwarf2_line (DSO *dso, GElf_Addr start, GElf_Addr adjust) +{ + unsigned char *ptr = debug_sections[DEBUG_LINE].data; + unsigned char *endsec = ptr + debug_sections[DEBUG_LINE].size; + unsigned char *endcu, *endprol; + unsigned char opcode_base, *opcode_lengths, op; + uint32_t value; + GElf_Addr addr; + int i; + + while (ptr < endsec) + { + endcu = ptr + 4; + endcu += read_32 (ptr); + if (endcu == ptr + 0xffffffff) + { + error (0, 0, "%s: 64-bit DWARF not supported", dso->filename); + return 1; + } + + if (endcu > endsec) + { + error (0, 0, "%s: .debug_line CU does not fit into section", + dso->filename); + return 1; + } + + value = read_16 (ptr); + if (value != 2 && value != 3 && value != 4) + { + error (0, 0, "%s: DWARF version %d unhandled", dso->filename, + value); + return 1; + } + + endprol = ptr + 4; + endprol += read_32 (ptr); + if (endprol > endcu) + { + error (0, 0, "%s: .debug_line CU prologue does not fit into CU", + dso->filename); + return 1; + } + + opcode_base = ptr[4 + (value >= 4)]; + opcode_lengths = ptr + 4 + (value >= 4); + + ptr = endprol; + while (ptr < endcu) + { + op = *ptr++; + if (op >= opcode_base) + continue; + if (op == DW_LNS_extended_op) + { + unsigned int len = read_uleb128 (ptr); + + assert (len < UINT_MAX); + op = *ptr++; + switch (op) + { + case DW_LNE_set_address: + addr = read_ptr (ptr); + if (addr >= start && addr_to_sec (dso, addr) != -1) + write_ptr (ptr - ptr_size, addr + adjust); + break; + case DW_LNE_end_sequence: + case DW_LNE_define_file: + case DW_LNE_set_discriminator: + default: + ptr += len - 1; + break; + } + } + else if (op == DW_LNS_fixed_advance_pc) + ptr += 2; + else + for (i = 0; i < opcode_lengths[op]; ++i) + read_uleb128 (ptr); + } + } + + elf_flagscn (dso->scn[debug_sections[DEBUG_LINE].sec], ELF_C_SET, + ELF_F_DIRTY); + return 0; +} + +static int +adjust_dwarf2_aranges (DSO *dso, GElf_Addr start, GElf_Addr adjust) +{ + unsigned char *ptr = debug_sections[DEBUG_ARANGES].data; + unsigned char *endsec = ptr + debug_sections[DEBUG_ARANGES].size; + unsigned char *endcu; + GElf_Addr addr, len; + uint32_t value; + + while (ptr < endsec) + { + endcu = ptr + 4; + endcu += read_32 (ptr); + if (endcu == ptr + 0xffffffff) + { + error (0, 0, "%s: 64-bit DWARF not supported", dso->filename); + return 1; + } + + if (endcu > endsec) + { + error (0, 0, "%s: .debug_line CU does not fit into section", + dso->filename); + return 1; + } + + value = read_16 (ptr); + if (value != 2) + { + error (0, 0, "%s: DWARF version %d unhandled", dso->filename, + value); + return 1; + } + + ptr += 4; + if (ptr[0] != ptr_size || ptr[1]) + { + error (0, 0, "%s: Unsupported .debug_aranges address size %d or segment size %d", + dso->filename, ptr[0], ptr[1]); + return 1; + } + + ptr += 6; + while (ptr < endcu) + { + addr = read_ptr (ptr); + len = read_ptr (ptr); + if (addr == 0 && len == 0) + break; + if (addr >= start && addr_to_sec (dso, addr) != -1) + write_ptr (ptr - 2 * ptr_size, addr + adjust); + } + assert (ptr == endcu); + } + + elf_flagscn (dso->scn[debug_sections[DEBUG_LINE].sec], ELF_C_SET, + ELF_F_DIRTY); + return 0; +} + +static int +adjust_dwarf2_frame (DSO *dso, GElf_Addr start, GElf_Addr adjust) +{ + unsigned char *ptr = debug_sections[DEBUG_FRAME].data; + unsigned char *endsec = ptr + debug_sections[DEBUG_FRAME].size; + unsigned char *endie; + GElf_Addr addr, len; + uint32_t value; + + while (ptr < endsec) + { + endie = ptr + 4; + endie += read_32 (ptr); + if (endie == ptr + 0xffffffff) + { + error (0, 0, "%s: 64-bit DWARF not supported", dso->filename); + return 1; + } + + if (endie > endsec) + { + error (0, 0, "%s: .debug_frame CIE/FDE does not fit into section", + dso->filename); + return 1; + } + + value = read_32 (ptr); + if (value == 0xffffffff) + { + /* CIE. */ + uint32_t version = *ptr++; + if (version != 1 && version != 3 && version != 4) + { + error (0, 0, "%s: unhandled .debug_frame version %d", + dso->filename, version); + return 1; + } + if (*ptr == 'S') + { + /* This is a signal frame. We don't care. */ + ptr++; + } + if (*ptr != '\0') + { + error (0, 0, "%s: .debug_frame unhandled augmentation \"%s\"", + dso->filename, ptr); + return 1; + } + ptr++; /* Skip augmentation. */ + if (version >= 4) + { + if (ptr[0] != ptr_size) + { + error (0, 0, "%s: .debug_frame unhandled pointer size %d", + dso->filename, ptr[0]); + return 1; + } + if (ptr[1] != 0) + { + error (0, 0, "%s: .debug_frame unhandled non-zero segment size", + dso->filename); + return 1; + } + ptr += 2; + } + read_uleb128 (ptr); /* Skip code_alignment factor. */ + read_uleb128 (ptr); /* Skip data_alignment factor. */ + if (version >= 3) + read_uleb128 (ptr); /* Skip return_address_register. */ + else + ptr++; + } + else + { + addr = read_ptr (ptr); + if (addr >= start && addr_to_sec (dso, addr) != -1) + write_ptr (ptr - ptr_size, addr + adjust); + read_ptr (ptr); /* Skip address range. */ + } + + while (ptr < endie) + { + unsigned char insn = *ptr++; + + if ((insn & 0xc0) == DW_CFA_advance_loc + || (insn & 0xc0) == DW_CFA_restore) + continue; + else if ((insn & 0xc0) == DW_CFA_offset) + { + read_uleb128 (ptr); + continue; + } + switch (insn) + { + case DW_CFA_nop: + case DW_CFA_remember_state: + case DW_CFA_restore_state: + case DW_CFA_GNU_window_save: + break; + case DW_CFA_offset_extended: + case DW_CFA_register: + case DW_CFA_def_cfa: + case DW_CFA_offset_extended_sf: + case DW_CFA_def_cfa_sf: + case DW_CFA_GNU_negative_offset_extended: + case DW_CFA_val_offset: + case DW_CFA_val_offset_sf: + read_uleb128 (ptr); + /* FALLTHROUGH */ + case DW_CFA_restore_extended: + case DW_CFA_undefined: + case DW_CFA_same_value: + case DW_CFA_def_cfa_register: + case DW_CFA_def_cfa_offset: + case DW_CFA_def_cfa_offset_sf: + case DW_CFA_GNU_args_size: + read_uleb128 (ptr); + break; + case DW_CFA_set_loc: + addr = read_ptr (ptr); + if (addr >= start && addr_to_sec (dso, addr) != -1) + write_ptr (ptr - ptr_size, addr + adjust); + break; + case DW_CFA_advance_loc1: + ptr++; + break; + case DW_CFA_advance_loc2: + ptr += 2; + break; + case DW_CFA_advance_loc4: + ptr += 4; + break; + case DW_CFA_expression: + case DW_CFA_val_expression: + read_uleb128 (ptr); + /* FALLTHROUGH */ + case DW_CFA_def_cfa_expression: + len = read_uleb128 (ptr); + if (adjust_location_list (dso, NULL, ptr, len, start, adjust)) + return 1; + ptr += len; + break; + default: + error (0, 0, "%s: Unhandled DW_CFA_%02x operation", + dso->filename, insn); + return 1; + } + } + } + + elf_flagscn (dso->scn[debug_sections[DEBUG_FRAME].sec], ELF_C_SET, + ELF_F_DIRTY); + return 0; +} + +static hashval_t +loclistoffset_hash (const void *p) +{ + GElf_Addr *offset = (GElf_Addr *)p; + + return *offset; +} + +static int +loclistoffset_eq (const void *p, const void *q) +{ + GElf_Addr *offset1 = (GElf_Addr *)p; + GElf_Addr *offset2 = (GElf_Addr *)q; + + return *offset1 = *offset2; +} + +static void +loclistoffset_del (void *p) +{ + free (p); +} + +static int +adjust_dwarf2_info (DSO *dso, GElf_Addr start, GElf_Addr adjust, int type) +{ + unsigned char *ptr, *endcu, *endsec; + uint32_t value; + htab_t abbrev; + struct abbrev_tag tag, *t; + struct cu_data cu; + htab_t offset_hash = htab_try_create (50, loclistoffset_hash, + loclistoffset_eq, loclistoffset_del); + + if (offset_hash == NULL) + { + error (0, ENOMEM, "%s: Could not create hash for attributes", + dso->filename); + return 1; + } + + memset (&cu, 0, sizeof(cu)); + ptr = debug_sections[type].data; + endsec = ptr + debug_sections[type].size; + while (ptr < endsec) + { + if (ptr + 11 > endsec) + { + error (0, 0, "%s: .debug_info CU header too small", dso->filename); + htab_delete (offset_hash); + return 1; + } + + endcu = ptr + 4; + endcu += read_32 (ptr); + if (endcu == ptr + 0xffffffff) + { + error (0, 0, "%s: 64-bit DWARF not supported", dso->filename); + htab_delete (offset_hash); + return 1; + } + + if (endcu > endsec) + { + error (0, 0, "%s: .debug_info too small", dso->filename); + htab_delete (offset_hash); + return 1; + } + + value = read_16 (ptr); + if (value != 2 && value != 3 && value != 4) + { + error (0, 0, "%s: DWARF version %d unhandled", dso->filename, value); + htab_delete (offset_hash); + return 1; + } + cu.cu_version = value; + + value = read_32 (ptr); + if (value >= debug_sections[DEBUG_ABBREV].size) + { + if (debug_sections[DEBUG_ABBREV].data == NULL) + error (0, 0, "%s: .debug_abbrev not present", dso->filename); + else + error (0, 0, "%s: DWARF CU abbrev offset too large", + dso->filename); + htab_delete (offset_hash); + return 1; + } + + if (ptr_size == 0) + { + ptr_size = read_1 (ptr); + if (ptr_size == 4) + { + do_read_ptr = do_read_32_64; + write_ptr = write_32; + } + else if (ptr_size == 8) + { + do_read_ptr = do_read_64; + write_ptr = write_64; + } + else + { + error (0, 0, "%s: Invalid DWARF pointer size %d", + dso->filename, ptr_size); + htab_delete (offset_hash); + return 1; + } + } + else if (read_1 (ptr) != ptr_size) + { + error (0, 0, "%s: DWARF pointer size differs between CUs", + dso->filename); + htab_delete (offset_hash); + return 1; + } + + abbrev = read_abbrev (dso, debug_sections[DEBUG_ABBREV].data + value); + if (abbrev == NULL) + { + htab_delete (offset_hash); + return 1; + } + + cu.cu_entry_pc = ~ (GElf_Addr) 0; + cu.cu_low_pc = ~ (GElf_Addr) 0; + + if (type == DEBUG_TYPES) + { + ptr += 8; /* Skip type_signature. */ + ptr += 4; /* Skip type_offset. */ + } + + while (ptr < endcu) + { + tag.entry = read_uleb128 (ptr); + if (tag.entry == 0) + continue; + t = htab_find_with_hash (abbrev, &tag, tag.entry); + if (t == NULL) + { + error (0, 0, "%s: Could not find DWARF abbreviation %d", + dso->filename, tag.entry); + htab_delete (abbrev); + htab_delete (offset_hash); + return 1; + } + + ptr = adjust_attributes (dso, ptr, t, &cu, start, adjust, + offset_hash); + if (ptr == NULL) + { + htab_delete (abbrev); + htab_delete (offset_hash); + return 1; + } + } + + htab_delete (abbrev); + } + htab_delete (offset_hash); + return 0; +} + +int +adjust_dwarf2 (DSO *dso, int n, GElf_Addr start, GElf_Addr adjust) +{ + Elf_Data *data; + Elf_Scn *scn; + int i, j; + + for (i = 0; debug_sections[i].name; ++i) + { + debug_sections[i].data = NULL; + debug_sections[i].size = 0; + debug_sections[i].sec = 0; + } + ptr_size = 0; + + for (i = 1; i < dso->ehdr.e_shnum; ++i) + if (! (dso->shdr[i].sh_flags & (SHF_ALLOC | SHF_WRITE | SHF_EXECINSTR)) + && dso->shdr[i].sh_size) + { + const char *name = strptr (dso, dso->ehdr.e_shstrndx, + dso->shdr[i].sh_name); + + if (strncmp (name, ".debug_", sizeof (".debug_") - 1) == 0) + { + for (j = 0; debug_sections[j].name; ++j) + if (strcmp (name, debug_sections[j].name) == 0) + { + if (debug_sections[j].data) + { + error (0, 0, "%s: Found two copies of %s section", + dso->filename, name); + return 1; + } + + scn = dso->scn[i]; + data = elf_getdata (scn, NULL); + assert (data != NULL && data->d_buf != NULL); + assert (elf_getdata (scn, data) == NULL); + assert (data->d_off == 0); + assert (data->d_size == dso->shdr[i].sh_size); + debug_sections[j].data = data->d_buf; + debug_sections[j].size = data->d_size; + debug_sections[j].sec = i; + break; + } + + if (debug_sections[j].name == NULL) + { + error (0, 0, "%s: Unknown debugging section %s", + dso->filename, name); + return 1; + } + } + } + + if (dso->ehdr.e_ident[EI_DATA] == ELFDATA2LSB) + { + do_read_16 = buf_read_ule16; + do_read_32 = buf_read_ule32; + do_read_32_64 = buf_read_ule32_64; + do_read_64 = buf_read_ule64; + write_32 = dwarf2_write_le32; + write_64 = dwarf2_write_le64; + } + else if (dso->ehdr.e_ident[EI_DATA] == ELFDATA2MSB) + { + do_read_16 = buf_read_ube16; + do_read_32 = buf_read_ube32; + do_read_32_64 = buf_read_ube32_64; + do_read_64 = buf_read_ube64; + write_32 = dwarf2_write_be32; + write_64 = dwarf2_write_be64; + } + else + { + error (0, 0, "%s: Wrong ELF data enconding", dso->filename); + return 1; + } + + if (debug_sections[DEBUG_INFO].data != NULL + && adjust_dwarf2_info (dso, start, adjust, DEBUG_INFO)) + return 1; + + if (debug_sections[DEBUG_TYPES].data != NULL + && adjust_dwarf2_info (dso, start, adjust, DEBUG_TYPES)) + return 1; + + if (ptr_size == 0) + /* Should not happen. */ + ptr_size = dso->ehdr.e_ident[EI_CLASS] == ELFCLASS64 ? 8 : 4; + + if (debug_sections[DEBUG_LINE].data != NULL + && adjust_dwarf2_line (dso, start, adjust)) + return 1; + + if (debug_sections[DEBUG_ARANGES].data != NULL + && adjust_dwarf2_aranges (dso, start, adjust)) + return 1; + + if (debug_sections[DEBUG_FRAME].data != NULL + && adjust_dwarf2_frame (dso, start, adjust)) + return 1; + + /* .debug_abbrev requires no adjustement. */ + /* .debug_pubnames requires no adjustement. */ + /* .debug_pubtypes requires no adjustement. */ + /* .debug_macinfo requires no adjustement. */ + /* .debug_str requires no adjustement. */ + /* .debug_ranges adjusted for each DW_AT_ranges pointing into it. */ + /* .debug_loc adjusted for each loclistptr pointing into it. */ + + elf_flagscn (dso->scn[n], ELF_C_SET, ELF_F_DIRTY); + return 0; +} |