Hi Mark,

On Wed, Apr 1, 2026 at 8:55 AM Mark Wielaard <[email protected]> wrote:
>
> 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]>
> ---
>
> v2 changes:
> - The offset size field is only available in version 6.
> - print_cu_index_section actually use off_bytes to print 32/64 bit offsets.
> - Use fprint (out, ...) consistently.
> - Remove Offset Size: headers in tests.

LGTM.

Aaron

>
>  src/readelf.c                 | 284 +++++++++++++++++++++++++++++++++-
>  tests/Makefile.am             |   2 +
>  tests/run-readelf-cu-index.sh |  95 ++++++++++++
>  3 files changed, 373 insertions(+), 8 deletions(-)
>  create mode 100755 tests/run-readelf-cu-index.sh
>
> diff --git a/src/readelf.c b/src/readelf.c
> index 6d4cde9f59c5..0258d41ae16d 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,222 @@ 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;
> +    }
> +
> +  /* The offset size field is only available in version 6.  */
> +  uint8_t offset_size_flag = 0;
> +  if (vers < 6)
> +    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));
> +         if (off_bytes == 4)
> +           for (size_t j = 0; j < section_count; j++)
> +             {
> +               uint32_t off = read_4ubyte_unaligned (dbg, prow + j * 4);
> +               fprintf (out, " %6" PRIu32, off);
> +             }
> +         else
> +           for (size_t j = 0; j < section_count; j++)
> +             {
> +               uint64_t off = read_4ubyte_unaligned (dbg, prow + j * 8);
> +               fprintf (out, " %6" PRIu64, 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);
> +             fprintf (out, " %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 +12556,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 +12576,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 +12729,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 +12811,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..37642a11b0d7
> --- /dev/null
> +++ b/tests/run-readelf-cu-index.sh
> @@ -0,0 +1,95 @@
> +#! /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
> + 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
> + 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
> + 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
> + 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
>

Reply via email to