DWARF Package files (.dwp) might contain a .debug_cu_index and/or
.debug_tu_index section. Print the contents of these sections. Handles
version 2 (DWARF4 + GNU Debugfission extension), version 5
(standardized) and version 6 (prelimenary, adds 64bit offsets). Use
the existing dwp4 and dwp5 testfiles.
* src/readelf.c (options): Add cu_index to debug-dump docs.
(section_e): Add section_cu_index.
(section_all): Likewise.
(parse_opt): handle "cu_index" arg for debug-dump.
(dwarf_section_short_string): New static function.
(print_cu_index_section): Likewise.
(print_debug): Move up implicit_info and explicit_info. Only
search for skeleton file if .debug_info is needed. Handle both
.debug_cu_index and .debug_tu_index through
print_cu_index_section.
* tests/run-readelf-cu-index.sh: New test.
* tests/Makefile.am (TESTS): Add run-readelf-cu-index.sh.
(EXTRA_DIST): Likewise.
Signed-off-by: Mark Wielaard <[email protected]>
---
src/readelf.c | 276 +++++++++++++++++++++++++++++++++-
tests/Makefile.am | 2 +
tests/run-readelf-cu-index.sh | 99 ++++++++++++
3 files changed, 369 insertions(+), 8 deletions(-)
create mode 100755 tests/run-readelf-cu-index.sh
diff --git a/src/readelf.c b/src/readelf.c
index 6d4cde9f59c5..5a897e6cd33d 100644
--- a/src/readelf.c
+++ b/src/readelf.c
@@ -1,6 +1,6 @@
/* Print information from ELF file in human-readable form.
Copyright (C) 1999-2018 Red Hat, Inc.
- Copyright (C) 2023, 2025 Mark J. Wielaard <[email protected]>
+ Copyright (C) 2023, 2025, 2026 Mark J. Wielaard <[email protected]>
This file is part of elfutils.
This file is free software; you can redistribute it and/or modify
@@ -141,8 +141,9 @@ 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, addr, "
- "aranges, decodedaranges, frame, gdb_index, info, info+, loc, line, "
- "decodedline, ranges, pubnames, str, macinfo, macro or exception"), 0 },
+ "aranges, cu_index, decodedaranges, frame, gdb_index, info, info+, "
+ "loc, line, decodedline, ranges, pubnames, str, macinfo, macro or "
+ "exception"), 0 },
{ "hex-dump", 'x', "SECTION", 0,
N_("Dump the uninterpreted contents of SECTION, by number or name"), 0 },
{ "strings", 'p', "SECTION", OPTION_ARG_OPTIONAL,
@@ -288,11 +289,13 @@ static enum section_e
section_macro = 4096, /* .debug_macro */
section_addr = 8192, /* .debug_addr */
section_types = 16384, /* .debug_types (implied by .debug_info) */
+ section_cu_index = 32768, /* .debug_cu_index (include .debug_tu_index) */
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_addr | section_types)
+ | section_macro | section_addr | section_types
+ | section_cu_index)
} print_debug_sections, implicit_debug_sections;
/* Select hex dumping of sections. */
@@ -789,6 +792,8 @@ parse_opt (int key, char *arg,
print_debug_sections |= section_exception;
else if (strcmp (arg, "gdb_index") == 0)
print_debug_sections |= section_gdb_index;
+ else if (strcmp (arg, "cu_index") == 0)
+ print_debug_sections |= section_cu_index;
else
{
fprintf (stderr, _("Unknown DWARF debug section `%s'.\n"),
@@ -4874,6 +4879,48 @@ dwarf_line_content_description_string (unsigned int kind)
}
+/* Doesn't use dwarf_section_name because we want short (max 6 chars)
+ lowercase strings and result depends on version. */
+static const char *
+dwarf_section_short_string (unsigned int vers, unsigned kind)
+{
+ const char *sec_str = NULL;
+ if (vers == 2 && (kind == 7 || kind == 8)) /* macinfo or macro */
+ sec_str = "macro";
+ else
+ {
+ switch (kind)
+ {
+ case DW_SECT_INFO:
+ sec_str = "info";
+ break;
+ case DW_SECT_TYPES:
+ sec_str = "types"; /* Only really valid for version 2. */
+ break;
+ case DW_SECT_ABBREV:
+ sec_str = "abbrv";
+ break;
+ case DW_SECT_LINE:
+ sec_str = "line";
+ break;
+ case DW_SECT_LOCLISTS:
+ sec_str = "locs";
+ break;
+ case DW_SECT_STR_OFFSETS:
+ sec_str = "stroff";
+ break;
+ case DW_SECT_MACRO:
+ sec_str = "macro";
+ break;
+ case DW_SECT_RNGLISTS:
+ sec_str = "rngs";
+ break;
+ }
+ }
+
+ return sec_str;
+}
+
/* Used by all dwarf_foo_name functions. */
static const char *
string_or_unknown (const char *known, unsigned int code,
@@ -12176,6 +12223,214 @@ print_gdb_index_section (Dwfl_Module *dwflmod, Ebl
*ebl,
fprintf (out, "<unknown>\n");
}
+/* Print the content of the '.debug_cu_index' or '.debug_tu_index'
+ sections. */
+static void
+print_cu_index_section (Dwfl_Module *dwflmod __attribute__ ((unused)),
+ Ebl *ebl, GElf_Ehdr *ehdr __attribute__ ((unused)),
+ Elf_Scn *scn, GElf_Shdr *shdr, Dwarf *dbg,
+ FILE *out)
+{
+ const char *sname = section_name (ebl, shdr);
+ fprintf (out, _("\nDWARF section [%2zu] '%s' at offset %#" PRIx64
+ " contains %" PRId64 " bytes :\n"),
+ elf_ndxscn (scn), sname,
+ (uint64_t) shdr->sh_offset, (uint64_t) shdr->sh_size);
+
+ Elf_Data *data = elf_rawdata (scn, NULL);
+
+ if (unlikely (data == NULL))
+ {
+ error (0, 0, _("cannot get %s content: %s"), sname, elf_errmsg (-1));
+ return;
+ }
+
+ bool is_tu = sname != NULL && strcmp (sname, ".debug_tu_index") == 0;
+
+ const unsigned char *readp = data->d_buf;
+ const unsigned char *const dataend = readp + data->d_size;
+
+ if (unlikely (readp > dataend - 4))
+ {
+ invalid_data:
+ error (0, 0, _("invalid data"));
+ return;
+ }
+
+ /* If read as 4 bytes the version is 2, it is GNU DebugFission for
+ DWARF 5, otherwise the version is in the first 2 bytes, with 2
+ bytes padding. */
+ int32_t vers = read_4ubyte_unaligned (dbg, readp);
+ if (vers != 2)
+ vers = read_2ubyte_unaligned (dbg, readp);
+ fprintf (out, _(" Version: %8" PRId32 "\n"), vers);
+
+ /* There used to be a version 1, which we don't support, but is
+ described at https://gcc.gnu.org/wiki/DebugFissionDWP
+ Version 2 is GNU DebugFission for DWARF4.
+ Version 5 is the standardized DWARF5 index.
+ Version 6 supports DWARF64 as described in
+ https://dwarfstd.org/issues/220708.2.html */
+ if (vers != 2 && vers != 5 && vers != 6)
+ {
+ error (0, 0, _("unknown version, cannot parse section"));
+ return;
+ }
+
+ uint8_t offset_size_flag = 0;
+ if (vers < 2)
+ readp += 4;
+ else
+ {
+ readp += 3;
+ offset_size_flag = *readp;
+ fprintf (out, _(" Offset Size: %4" PRIu8 "\n"),
+ offset_size_flag == 0 ? 32 : 64);
+ readp++;
+ }
+
+ const unsigned char off_bytes = offset_size_flag == 0 ? 4 : 8;
+
+ if (unlikely (readp > dataend - 4))
+ goto invalid_data;
+ uint32_t section_count = read_4ubyte_unaligned (dbg, readp);
+ fprintf (out, _(" Columns: %8" PRId32 "\n"), section_count);
+
+ readp += 4;
+ if (unlikely (readp > dataend - 4))
+ goto invalid_data;
+ uint32_t unit_count = read_4ubyte_unaligned (dbg, readp);
+ fprintf (out, _(" Entries: %8" PRId32 "\n"), unit_count);
+
+ readp += 4;
+ if (unlikely (readp > dataend - 4))
+ goto invalid_data;
+ uint32_t slot_count = read_4ubyte_unaligned (dbg, readp);
+ fprintf (out, _(" Slots: %8" PRId32 "\n"), slot_count);
+
+ /* Really should be slot_count > 3 * unit_count / 2, but accept as
+ long as slot_count is at least unit_count. */
+ if (slot_count < unit_count)
+ {
+ error (0, 0, _("Must have at least as many slots as entries"));
+ return;
+ }
+
+ readp += 4;
+
+ /* Hash table starts directly after header (16 bytes). */
+ const unsigned char *hash_table = readp;
+ /* Indices (which slot is used for each hash id entry) start after
+ the hash table (ids of 8 bytes). */
+ const unsigned char *indices = hash_table + slot_count * 8;
+ /* Sections used starts after the indices, indices and hash table
+ have the same number of slots, indeces are 4 bytes each, */
+ const unsigned char *sections = indices + slot_count * 4;
+ /* Offset slots for each section follow the one row of sections. */
+ const unsigned char *offsets = sections + section_count * 4;
+ /* Size slots for each section follow the offsets (used rows). */
+ const unsigned char *lengths = (offsets +
+ unit_count * section_count * off_bytes);
+ const unsigned char *lengths_end = lengths + section_count * 4;
+
+ /* Sanity check the above against dataend. */
+ if ((slot_count > UINT32_MAX / 8)
+ || (section_count > SIZE_MAX / off_bytes)
+ || (unit_count > SIZE_MAX / off_bytes)
+ || ((unit_count != 0) && (section_count > SIZE_MAX / unit_count))
+ || ((section_count != 0) && (unit_count > SIZE_MAX / section_count))
+ || (indices > dataend)
+ || (sections > dataend)
+ || (offsets > dataend)
+ || (lengths > dataend)
+ || (lengths_end > dataend))
+ goto invalid_data;
+
+ fprintf (out, _("\n Offset table\n"));
+ fprintf (out, " slot %s", is_tu ? "tu sig" : "dwo id");
+ fprintf (out, " ");
+ for (size_t i = 0; i < section_count; i++)
+ {
+ uint32_t section = read_4ubyte_unaligned (dbg, sections + i * 4);
+ const char *sec_str = dwarf_section_short_string (vers, section);
+ if (sec_str == NULL)
+ fprintf (out, " ??? %2x", section);
+ else
+ fprintf (out, " %6s", sec_str);
+ }
+ fprintf (out, "\n");
+
+ for (size_t i = 0; i < slot_count; i++)
+ {
+ uint64_t id;
+ uint32_t row;
+ id = read_8ubyte_unaligned (dbg, hash_table + i * 8);
+ row = read_4ubyte_unaligned (dbg, indices + i * 4);
+ /* Only print used rows. */
+ if (id != 0 && row != 0)
+ {
+ fprintf (out, " [%3zd] %016" PRIx64 " ", i, id);
+ if (row > unit_count)
+ {
+ error (0, 0, _("Row (%" PRIu32 ") larger than "
+ "unit count (%" PRIu32 ")"), row, unit_count);
+ continue;
+ }
+ /* Note row is one based, not zero based. */
+ const unsigned char *prow = (offsets
+ + ((row - 1) * section_count
+ * off_bytes));
+ for (size_t j = 0; j < section_count; j++)
+ {
+ uint32_t off = read_4ubyte_unaligned (dbg, prow + j * 4);
+ printf (" %6" PRIu32, off);
+ }
+ fprintf (out, "\n");
+ }
+ }
+
+ fprintf (out, _("\n Size table\n"));
+ fprintf (out, " slot %s", is_tu ? "tu sig" : "dwo id");
+ fprintf (out, " ");
+ for (size_t i = 0; i < section_count; i++)
+ {
+ uint32_t section = read_4ubyte_unaligned (dbg, sections + i * 4);
+ const char *sec_str = dwarf_section_short_string (vers, section);
+ if (sec_str == NULL)
+ fprintf (out, " ??? %2x", section);
+ else
+ fprintf (out, " %6s", sec_str);
+ }
+ fprintf (out, "\n");
+
+ for (size_t i = 0; i < slot_count; i++)
+ {
+ uint64_t id;
+ uint32_t row;
+ id = read_8ubyte_unaligned (dbg, hash_table + i * 8);
+ row = read_4ubyte_unaligned (dbg, indices + i * 4);
+ /* Only print used rows. */
+ if (id != 0 && row != 0)
+ {
+ fprintf (out, " [%3zd] %016" PRIx64 " ", i, id);
+ if (row > unit_count)
+ {
+ error (0, 0, _("Row (%" PRIu32 ") larger than "
+ "unit count (%" PRIu32 ")"), row, unit_count);
+ continue;
+ }
+ /* Note row is one based, not zero based. */
+ const unsigned char *prow = lengths + (row - 1) * section_count * 4;
+ for (size_t j = 0; j < section_count; j++)
+ {
+ uint32_t off = read_4ubyte_unaligned (dbg, prow + j * 4);
+ printf (" %6" PRIu32, off);
+ }
+ fprintf (out, "\n");
+ }
+ }
+}
+
/* Returns true and sets split DWARF CU id if there is a split compile
unit in the given Dwarf, and no non-split units are found (before it). */
static bool
@@ -12293,6 +12548,11 @@ print_debug (Dwfl_Module *dwflmod, Ebl *ebl, GElf_Ehdr
*ehdr)
Dwarf *split_dbg = NULL;
Dwarf_CU *split_cu = NULL;
+ /* If we need to implicitly or explicitly scan the debug_info section
+ we might need a bit more info, like a skeleton for split dwarf file). */
+ bool implicit_info = (implicit_debug_sections & section_info) != 0;
+ bool explicit_info = (print_debug_sections & section_info) != 0;
+
/* Before we start the real work get a debug context descriptor. */
Dwarf_Addr dwbias;
Dwarf *dbg = dwfl_module_getdwarf (dwflmod, &dwbias);
@@ -12308,7 +12568,7 @@ print_debug (Dwfl_Module *dwflmod, Ebl *ebl, GElf_Ehdr
*ehdr)
dwfl_errmsg (-1));
dbg = &dummy_dbg;
}
- else
+ else if (implicit_info || explicit_info)
{
/* If we are asked about a split dwarf (.dwo) file, use the user
provided, or find the corresponding skeleton file. If we got
@@ -12461,8 +12721,6 @@ print_debug (Dwfl_Module *dwflmod, Ebl *ebl, GElf_Ehdr
*ehdr)
we must make sure to handle it before handling any other debug
section. Various other sections depend on the CU DIEs being
scanned (silently) first. */
- bool implicit_info = (implicit_debug_sections & section_info) != 0;
- bool explicit_info = (print_debug_sections & section_info) != 0;
if (implicit_info)
{
Elf_Scn *scn = NULL;
@@ -12545,7 +12803,9 @@ print_debug (Dwfl_Module *dwflmod, Ebl *ebl, GElf_Ehdr
*ehdr)
print_debug_frame_hdr_section },
{ ".gcc_except_table", section_frame | section_exception,
print_debug_exception_table },
- { ".gdb_index", section_gdb_index, print_gdb_index_section }
+ { ".gdb_index", section_gdb_index, print_gdb_index_section },
+ { ".debug_cu_index", section_cu_index, print_cu_index_section },
+ { ".debug_tu_index", section_cu_index, print_cu_index_section },
};
const int ndebug_sections = (sizeof (debug_sections)
/ sizeof (debug_sections[0]));
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 3702444d820f..9a005416a398 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -149,6 +149,7 @@ TESTS = run-arextract.sh run-arsymtest.sh run-ar.sh newfile
test-nlist \
run-nm-self.sh run-readelf-self.sh run-readelf-info-plus.sh \
run-readelf-compressed.sh \
run-readelf-const-values.sh \
+ run-readelf-cu-index.sh \
run-varlocs-self.sh run-exprlocs-self.sh \
run-readelf-test1.sh run-readelf-test2.sh run-readelf-test3.sh \
run-readelf-test4.sh run-readelf-twofiles.sh \
@@ -368,6 +369,7 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh run-ar.sh \
run-readelf-compressed.sh \
run-readelf-compressed-zstd.sh \
run-readelf-const-values.sh testfile-const-values.debug.bz2 \
+ run-readelf-cu-index.sh \
run-addrcfi.sh run-dwarfcfi.sh \
testfile11-debugframe.bz2 testfile12-debugframe.bz2 \
testfileaarch64-debugframe.bz2 testfilearm-debugframe.bz2 \
diff --git a/tests/run-readelf-cu-index.sh b/tests/run-readelf-cu-index.sh
new file mode 100755
index 000000000000..c92ba44420e3
--- /dev/null
+++ b/tests/run-readelf-cu-index.sh
@@ -0,0 +1,99 @@
+#! /bin/sh
+# Copyright (C) 2026 Mark J. Wielaard <[email protected]>
+# This file is part of elfutils.
+#
+# This file 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 3 of the License, or
+# (at your option) any later version.
+#
+# elfutils 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, see <http://www.gnu.org/licenses/>.
+
+. $srcdir/test-subr.sh
+
+# See testfile-dwp.source.
+testfiles testfile-dwp-4.dwp testfile-dwp-5.dwp
+
+testrun_compare ${abs_top_builddir}/src/readelf --debug-dump=cu_index
testfile-dwp-4.dwp <<\EOF
+
+DWARF section [ 9] '.debug_cu_index' at offset 0x6c80 contains 376 bytes :
+ Version: 2
+ Offset Size: 32
+ Columns: 6
+ Entries: 3
+ Slots: 16
+
+ Offset table
+ slot dwo id info abbrv line locs stroff macro
+ [ 0] 947a8b559fb59920 404 585 164 283 3748 3469
+ [ 5] afdbe8f5b7425c95 286 370 82 0 1876 1735
+ [ 9] 0682b8eb2e720699 0 0 0 0 0 0
+
+ Size table
+ slot dwo id info abbrv line locs stroff macro
+ [ 0] 947a8b559fb59920 453 414 83 241 1908 1735
+ [ 5] afdbe8f5b7425c95 118 215 82 0 1872 1734
+ [ 9] 0682b8eb2e720699 286 370 82 283 1876 1735
+
+DWARF section [10] '.debug_tu_index' at offset 0x6df8 contains 328 bytes :
+ Version: 2
+ Offset Size: 32
+ Columns: 6
+ Entries: 2
+ Slots: 16
+
+ Offset table
+ slot tu sig types abbrv line locs stroff macro
+ [ 12] 063b4ae40c9316fc 111 370 82 0 1876 1735
+ [ 14] f35612db645f377e 0 0 0 0 0 0
+
+ Size table
+ slot tu sig types abbrv line locs stroff macro
+ [ 12] 063b4ae40c9316fc 109 215 82 0 1872 1734
+ [ 14] f35612db645f377e 111 370 82 283 1876 1735
+EOF
+
+testrun_compare ${abs_top_builddir}/src/readelf --debug-dump=cu_index
testfile-dwp-5.dwp<<\EOF
+
+DWARF section [10] '.debug_tu_index' at offset 0x6ca1 contains 204 bytes :
+ Version: 5
+ Offset Size: 32
+ Columns: 7
+ Entries: 2
+ Slots: 4
+
+ Offset table
+ slot tu sig info abbrv line locs stroff macro rngs
+ [ 0] 063b4ae40c9316fc 376 352 127 0 1884 1734 0
+ [ 2] f35612db645f377e 0 0 0 0 0 0 0
+
+ Size table
+ slot tu sig info abbrv line locs stroff macro rngs
+ [ 0] 063b4ae40c9316fc 110 202 127 0 1880 1733 0
+ [ 2] f35612db645f377e 112 352 127 219 1884 1734 34
+
+DWARF section [11] '.debug_cu_index' at offset 0x6d6d contains 308 bytes :
+ Version: 5
+ Offset Size: 32
+ Columns: 7
+ Entries: 3
+ Slots: 8
+
+ Offset table
+ slot dwo id info abbrv line locs stroff macro rngs
+ [ 2] 2970268d42208082 606 554 254 219 3764 3467 34
+ [ 3] 225988b4ba73f4b3 112 0 0 0 0 0 0
+ [ 6] 1082731073ccdfbe 486 352 127 0 1884 1734 0
+
+ Size table
+ slot dwo id info abbrv line locs stroff macro rngs
+ [ 2] 2970268d42208082 403 394 129 201 1916 1734 67
+ [ 3] 225988b4ba73f4b3 264 352 127 219 1884 1734 34
+ [ 6] 1082731073ccdfbe 120 202 127 0 1880 1733 0
+EOF
--
2.53.0