summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorMark Wielaard <[email protected]>2018-04-28 00:10:41 +0200
committerMark Wielaard <[email protected]>2018-05-30 12:14:10 +0200
commit50dfd98101dde72b32ea2d4df89166ce148a28f2 (patch)
treeaf701df25251309b413b1435f48df532937280cd /src
parent4dc31fde711c69fed6acfe18e7e57dfd5665cebb (diff)
readelf handle .debug_addr section.
Add debug-dump=addr which will show the .debug_addr section tables. The only tricky bit is the fact that GNU DebugFission, a DWARF4 extension, didn't produce unit table headers. So if we see a mixed DWARF4/5 .debug_addr table we have to reconstruct the table length from the CU DIE DW_AT_[GNU_]_addr_base offsets. Signed-off-by: Mark Wielaard <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/ChangeLog12
-rw-r--r--src/readelf.c267
2 files changed, 277 insertions, 2 deletions
diff --git a/src/ChangeLog b/src/ChangeLog
index b6c27432..01ecc610 100644
--- a/src/ChangeLog
+++ b/src/ChangeLog
@@ -1,3 +1,15 @@
+2018-04-27 Mark Wielaard <[email protected]>
+
+ * readelf.c (options): Add addr.
+ (enum section_e): Add section_addr.
+ (section_all): Add section_addr.
+ (parse_opt): Parse "addr".
+ (known_addrbases): New static variable.
+ (get_listptr): New function.
+ (print_debug_addr_section): Likewise.
+ (attr_callback): Handle DW_AT_addr_base and DW_AT_GNU_addr_base.
+ (print_debug): Add NEW_SECTION (addr). Reset known_addrbases.
+
2018-04-07 Mark Wielaard <[email protected]>
* readelf.c (attr_callback): Handle DW_FORM_loclistx and
diff --git a/src/readelf.c b/src/readelf.c
index 54bf22c5..ab0223b8 100644
--- a/src/readelf.c
+++ b/src/readelf.c
@@ -121,7 +121,7 @@ static const struct argp_option options[] =
{ NULL, 0, NULL, 0, N_("Additional output selection:"), 0 },
{ "debug-dump", 'w', "SECTION", OPTION_ARG_OPTIONAL,
- N_("Display DWARF section content. SECTION can be one of abbrev, "
+ N_("Display DWARF section content. SECTION can be one of abbrev, addr, "
"aranges, decodedaranges, frame, gdb_index, info, info+, loc, line, "
"decodedline, ranges, pubnames, str, macinfo, macro or exception"), 0 },
{ "hex-dump", 'x', "SECTION", 0,
@@ -248,11 +248,12 @@ static enum section_e
section_exception = 1024, /* .eh_frame & al. */
section_gdb_index = 2048, /* .gdb_index */
section_macro = 4096, /* .debug_macro */
+ section_addr = 8192,
section_all = (section_abbrev | section_aranges | section_frame
| section_info | section_line | section_loc
| section_pubnames | section_str | section_macinfo
| section_ranges | section_exception | section_gdb_index
- | section_macro)
+ | section_macro | section_addr)
} print_debug_sections, implicit_debug_sections;
/* Select hex dumping of sections. */
@@ -442,6 +443,11 @@ parse_opt (int key, char *arg,
}
else if (strcmp (arg, "abbrev") == 0)
print_debug_sections |= section_abbrev;
+ else if (strcmp (arg, "addr") == 0)
+ {
+ print_debug_sections |= section_addr;
+ implicit_debug_sections |= section_info;
+ }
else if (strcmp (arg, "aranges") == 0)
print_debug_sections |= section_aranges;
else if (strcmp (arg, "decodedaranges") == 0)
@@ -4817,6 +4823,7 @@ static struct listptr_table known_locsptr;
static struct listptr_table known_loclistsptr;
static struct listptr_table known_rangelistptr;
static struct listptr_table known_rnglistptr;
+static struct listptr_table known_addrbases;
static void
reset_listptr (struct listptr_table *table)
@@ -4934,6 +4941,15 @@ next_listptr_offset (struct listptr_table *table, size_t idx)
return 0;
}
+/* Returns the listptr associated with the given index, or NULL. */
+static struct listptr *
+get_listptr (struct listptr_table *table, size_t idx)
+{
+ if (idx >= table->n)
+ return NULL;
+ return &table->table[idx];
+}
+
/* Returns the next index, base address and CU associated with the
list unit offsets. If there is none false is returned, otherwise
true. Assumes the table has been sorted. */
@@ -5033,6 +5049,235 @@ print_debug_abbrev_section (Dwfl_Module *dwflmod __attribute__ ((unused)),
}
+static void
+print_debug_addr_section (Dwfl_Module *dwflmod __attribute__ ((unused)),
+ Ebl *ebl, GElf_Ehdr *ehdr,
+ Elf_Scn *scn, GElf_Shdr *shdr, Dwarf *dbg)
+{
+ printf (gettext ("\
+\nDWARF section [%2zu] '%s' at offset %#" PRIx64 ":\n"),
+ elf_ndxscn (scn), section_name (ebl, ehdr, shdr),
+ (uint64_t) shdr->sh_offset);
+
+ if (shdr->sh_size == 0)
+ return;
+
+ /* We like to get the section from libdw to make sure they are relocated. */
+ Elf_Data *data = (dbg->sectiondata[IDX_debug_addr]
+ ?: elf_rawdata (scn, NULL));
+ if (unlikely (data == NULL))
+ {
+ error (0, 0, gettext ("cannot get .debug_addr section data: %s"),
+ elf_errmsg (-1));
+ return;
+ }
+
+ size_t idx = 0;
+ sort_listptr (&known_addrbases, "addr_base");
+
+ const unsigned char *start = (const unsigned char *) data->d_buf;
+ const unsigned char *readp = start;
+ const unsigned char *readendp = ((const unsigned char *) data->d_buf
+ + data->d_size);
+
+ while (readp < readendp)
+ {
+ /* We cannot really know whether or not there is an header. The
+ DebugFission extension to DWARF4 doesn't add one. The DWARF5
+ .debug_addr variant does. Whether or not we have an header,
+ DW_AT_[GNU_]addr_base points at "index 0". So if the current
+ offset equals the CU addr_base then we can just start
+ printing addresses. If there is no CU with an exact match
+ then we'll try to parse the header first. */
+ Dwarf_Off off = (Dwarf_Off) (readp
+ - (const unsigned char *) data->d_buf);
+
+ printf ("Table at offset %" PRIx64 " ", off);
+
+ struct listptr *listptr = get_listptr (&known_addrbases, idx++);
+ const unsigned char *next_unitp;
+
+ uint64_t unit_length;
+ uint16_t version;
+ uint8_t address_size;
+ uint8_t segment_size;
+ if (listptr == NULL)
+ {
+ error (0, 0, "Warning: No CU references .debug_addr after %" PRIx64,
+ off);
+
+ /* We will have to assume it is just addresses to the end... */
+ address_size = ehdr->e_ident[EI_CLASS] == ELFCLASS32 ? 4 : 8;
+ next_unitp = readendp;
+ printf ("Unknown CU:\n");
+ }
+ else
+ {
+ Dwarf_Die cudie;
+ if (dwarf_cu_die (listptr->cu, &cudie,
+ NULL, NULL, NULL, NULL,
+ NULL, NULL) == NULL)
+ printf ("Unknown CU (%s):\n", dwarf_errmsg (-1));
+ else
+ printf ("for CU [%6" PRIx64 "]:\n", dwarf_dieoffset (&cudie));
+
+ if (listptr->offset == off)
+ {
+ address_size = listptr_address_size (listptr);
+ segment_size = 0;
+ version = 4;
+
+ /* The addresses start here, but where do they end? */
+ listptr = get_listptr (&known_addrbases, idx);
+ if (listptr == NULL)
+ {
+ next_unitp = readendp;
+ unit_length = (uint64_t) (next_unitp - readp);
+ }
+ else if (listptr->cu->version < 5)
+ {
+ next_unitp = start + listptr->offset;
+ if (listptr->offset < off || listptr->offset > data->d_size)
+ {
+ error (0, 0,
+ "Warning: Bad address base for next unit at %"
+ PRIx64, off);
+ next_unitp = readendp;
+ }
+ unit_length = (uint64_t) (next_unitp - readp);
+ }
+ else
+ {
+ /* Tricky, we don't have a header for this unit, but
+ there is one for the next. We will have to
+ "guess" how big it is and subtract it from the
+ offset (because that points after the header). */
+ unsigned int offset_size = listptr_offset_size (listptr);
+ Dwarf_Off next_off = (listptr->offset
+ - (offset_size == 4 ? 4 : 12) /* len */
+ - 2 /* version */
+ - 1 /* address size */
+ - 1); /* segment selector size */
+ next_unitp = start + next_off;
+ if (next_off < off || next_off > data->d_size)
+ {
+ error (0, 0,
+ "Warning: Couldn't calculate .debug_addr "
+ " unit lenght at %" PRIx64, off);
+ next_unitp = readendp;
+ }
+ }
+
+ /* Pretend we have a header. */
+ printf ("\n");
+ printf (gettext (" Length: %8" PRIu64 "\n"),
+ unit_length);
+ printf (gettext (" DWARF version: %8" PRIu16 "\n"), version);
+ printf (gettext (" Address size: %8" PRIu64 "\n"),
+ (uint64_t) address_size);
+ printf (gettext (" Segment size: %8" PRIu64 "\n"),
+ (uint64_t) segment_size);
+ printf ("\n");
+ }
+ else
+ {
+ /* OK, we have to parse an header first. */
+ unit_length = read_4ubyte_unaligned_inc (dbg, readp);
+ if (unlikely (unit_length == 0xffffffff))
+ {
+ if (unlikely (readp > readendp - 8))
+ {
+ invalid_data:
+ error (0, 0, "Invalid data");
+ return;
+ }
+ unit_length = read_8ubyte_unaligned_inc (dbg, readp);
+ }
+ printf ("\n");
+ printf (gettext (" Length: %8" PRIu64 "\n"),
+ unit_length);
+
+ /* We need at least 2-bytes (version) + 1-byte
+ (addr_size) + 1-byte (segment_size) = 4 bytes to
+ complete the header. And this unit cannot go beyond
+ the section data. */
+ if (readp > readendp - 4
+ || unit_length < 4
+ || unit_length > (uint64_t) (readendp - readp))
+ goto invalid_data;
+
+ next_unitp = readp + unit_length;
+
+ version = read_2ubyte_unaligned_inc (dbg, readp);
+ printf (gettext (" DWARF version: %8" PRIu16 "\n"), version);
+
+ if (version != 5)
+ {
+ error (0, 0, gettext ("Unknown version"));
+ goto next_unit;
+ }
+
+ address_size = *readp++;
+ printf (gettext (" Address size: %8" PRIu64 "\n"),
+ (uint64_t) address_size);
+
+ if (address_size != 4 && address_size != 8)
+ {
+ error (0, 0, gettext ("unsupported address size"));
+ goto next_unit;
+ }
+
+ segment_size = *readp++;
+ printf (gettext (" Segment size: %8" PRIu64 "\n"),
+ (uint64_t) segment_size);
+ printf ("\n");
+
+ if (segment_size != 0)
+ {
+ error (0, 0, gettext ("unsupported segment size"));
+ goto next_unit;
+ }
+
+ if (listptr->offset != (Dwarf_Off) (readp - start))
+ {
+ error (0, 0, "Address index doesn't start after header");
+ goto next_unit;
+ }
+ }
+ }
+
+ int digits = 1;
+ size_t addresses = (next_unitp - readp) / address_size;
+ while (addresses >= 10)
+ {
+ ++digits;
+ addresses /= 10;
+ }
+
+ unsigned int index = 0;
+ size_t index_offset = readp - (const unsigned char *) data->d_buf;
+ printf (" Addresses start at offset 0x%zx:\n", index_offset);
+ while (readp <= next_unitp - address_size)
+ {
+ Dwarf_Addr addr = read_addr_unaligned_inc (address_size, dbg,
+ readp);
+ printf (" [%*u] ", digits, index++);
+ char *a = format_dwarf_addr (dwflmod, address_size,
+ addr, addr);
+ printf ("%s\n", a);
+ free (a);
+ }
+ printf ("\n");
+
+ if (readp != next_unitp)
+ error (0, 0, "extra %zd bytes at end of unit",
+ (size_t) (next_unitp - readp));
+
+ next_unit:
+ readp = next_unitp;
+ }
+}
+
/* Print content of DWARF .debug_aranges section. We fortunately do
not have to know a bit about the structure of the section, libdwarf
takes care of it. */
@@ -6931,6 +7176,22 @@ attr_callback (Dwarf_Attribute *attrp, void *arg)
}
return DWARF_CB_OK;
+ case DW_AT_addr_base:
+ case DW_AT_GNU_addr_base:
+ {
+ bool addrbase = notice_listptr (section_addr, &known_addrbases,
+ cbargs->addrsize,
+ cbargs->offset_size,
+ cbargs->cu, num, attr);
+ if (!cbargs->silent)
+ printf (" %*s%-20s (%s) address base [%6"
+ PRIxMAX "]%s\n",
+ (int) (level * 2), "", dwarf_attr_name (attr),
+ dwarf_form_name (form), (uintmax_t) num,
+ addrbase ? "" : " <WARNING offset too big>");
+ }
+ return DWARF_CB_OK;
+
case DW_AT_language:
valuestr = dwarf_lang_name (num);
break;
@@ -10476,6 +10737,7 @@ print_debug (Dwfl_Module *dwflmod, Ebl *ebl, GElf_Ehdr *ehdr)
#define NEW_SECTION(name) \
{ ".debug_" #name, section_##name, print_debug_##name##_section }
NEW_SECTION (abbrev),
+ NEW_SECTION (addr),
NEW_SECTION (aranges),
NEW_SECTION (frame),
NEW_SECTION (info),
@@ -10544,6 +10806,7 @@ print_debug (Dwfl_Module *dwflmod, Ebl *ebl, GElf_Ehdr *ehdr)
reset_listptr (&known_loclistsptr);
reset_listptr (&known_rangelistptr);
reset_listptr (&known_rnglistptr);
+ reset_listptr (&known_addrbases);
}