[binutils-gdb] gdb: refuse to produce .gdb_index when skeletonless type units are present

Simon Marchi via Gdb-cvs <[email protected]>
Newsgroups gmane.comp.gdb.cvs
Message-ID <[email protected]>
https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=d82821421b407254b2060c2244297cb03a5a88e2

commit d82821421b407254b2060c2244297cb03a5a88e2
Author: Simon Marchi <[email protected]>
Date:   Mon Nov 10 16:19:40 2025 -0500

    gdb: refuse to produce .gdb_index when skeletonless type units are present
    
    Running test gdb.dwarf2/fission-with-type-unit.exp with the
    cc-with-gdb-index target board fails with:
    
        (gdb) maint expand-symtabs
        /home/simark/src/binutils-gdb/gdb/dwarf2/read.c:3064: internal-error: cutu_reader: Assertion `sig_type->signature == cu->header.signature' failed.
        A problem internal to GDB has been detected,
        further debugging may prove unreliable.
        ----- Backtrace -----
        FAIL: gdb.dwarf2/fission-with-type-unit.exp: maint expand-symtabs (GDB internal error)
    
    This is a consequence of .gdb_index not supporting skeletonless type
    units in .dwo files.  That is, type units in .dwo files that don't have
    a corresponding skeleton (or stub) in the main file.
    
    For context: in DWARF 4, gcc 4.x used to create skeletons for type units
    in .dwo files, but subsequent versions don't.  DWARF 5 doesn't have
    support for type unit skeletons at all.  So skeletons for type units are
    mostly a historical curiosity at this point, the norm is to not have
    them.
    
    Here's what leads up to the crash.  First, this is what is in the main
    file's .debug_info section (the first and last CUs are dummy CUs added
    by the testsuite):
    
          Compilation Unit @ offset 0:
           Length:        0x8 (32-bit)
           Version:       4
           Abbrev Offset: 0
           Pointer Size:  8
         <0><b>: Abbrev Number: 1 (DW_TAG_compile_unit)
    
          Compilation Unit @ offset 0xc:
           Length:        0x15 (32-bit)
           Version:       5
           Unit Type:     DW_UT_skeleton (4)
           Abbrev Offset: 0x6
           Pointer Size:  8
           DWO ID:        0xf00d
         <0><20>: Abbrev Number: 1 (DW_TAG_compile_unit)
            <21>   DW_AT_dwo_name    : (strp) (offset: 0): fission-with-type-unit-dw.dwo
    
          Compilation Unit @ offset 0x25:
           Length:        0x8 (32-bit)
           Version:       4
           Abbrev Offset: 0xe
           Pointer Size:  8
         <0><30>: Abbrev Number: 1 (DW_TAG_compile_unit)
    
    And here is what is in the fission-with-type-unit-dw.dwo file (one TU
    and the CU):
    
        Contents of the .debug_info.dwo section:
    
          Compilation Unit @ offset 0:
           Length:        0x1d (32-bit)
           Version:       5
           Unit Type:     DW_UT_type (2)
           Abbrev Offset: 0
           Pointer Size:  8
           Signature:     0xcafe
           Type Offset:   0x19
         <0><18>: Abbrev Number: 1 (DW_TAG_type_unit)
         <1><19>: Abbrev Number: 2 (DW_TAG_base_type)
            <1a>   DW_AT_byte_size   : (sdata) 4
            <1b>   DW_AT_encoding    : (sdata) 5        (signed)
            <1c>   DW_AT_name        : (string) int
         <1><20>: Abbrev Number: 0
    
        Contents of the .debug_info.dwo section:
    
          Compilation Unit @ offset 0:
           Length:        0x2d (32-bit)
           Version:       5
           Unit Type:     DW_UT_split_compile (5)
           Abbrev Offset: 0
           Pointer Size:  8
           DWO ID:        0xf00d
         <0><14>: Abbrev Number: 3 (DW_TAG_compile_unit)
         <1><15>: Abbrev Number: 4 (DW_TAG_base_type)
            <16>   DW_AT_byte_size   : (sdata) 4
            <17>   DW_AT_encoding    : (sdata) 5        (signed)
            <18>   DW_AT_name        : (string) int
         <1><1c>: Abbrev Number: 5 (DW_TAG_variable)
            <1d>   DW_AT_name        : (string) global_var
            <28>   DW_AT_type        : (ref4) <0x15>, int
            <2c>   DW_AT_location    : (exprloc) 3 byte block: 8 c 9f   (DW_OP_const1u: 12; DW_OP_stack_value)
         <1><30>: Abbrev Number: 0
    
    After loading the above in GDB, here is what is in GDB's mind (contents
    of dwarf2_per_bfd::all_units):
    
     - CU at offset 0x0  of .debug_info     in fission-with-type-unit -- dummy
     - CU at offset 0xc  of .debug_info     in fission-with-type-unit
     - CU at offset 0x25 of .debug_info     in fission-with-type-unit -- dummy
     - TU at offset 0x0  of .debug_info.dwo in fission-with-type-unit-dw.dwo
    
    This is correct.  Then, this is the generated .gdb_index:
    
        Contents of the .gdb_index section:
    
        Version 9
    
        CU table:
        [  0] 0 - 0xb     -- dummy
        [  1] 0xc - 0x24
        [  2] 0x25 - 0x30 -- dummy
    
        TU table:
        [  0] 0 0x19 000000000000cafe
    
        Address table:
    
        Symbol table:
        [  3] global_var: 1 [static, variable]
        [754] int: 1 [static, type]
    
        Shortcut table:
        Language of main: unknown: 0
        Name of main: <unknown>
    
    The TU table says that there exists a TU at offset 0.  Unfortunately,
    there is no way for a reader of that index to know that this TU is
    really in a .dwo file, not in the main file.  So when GDB loads this
    index back (creating dwarf2_per_bfd::all_units from .gdb_index this
    time, rather than walking the debug info), this is what is in its mind:
    
     - CU at offset 0x0  of .debug_info in fission-with-type-unit -- dummy
     - TU at offset 0x0  of .debug_info in fission-with-type-unit
     - CU at offset 0xc  of .debug_info in fission-with-type-unit
     - CU at offset 0x25 of .debug_info in fission-with-type-unit -- dummy
    
    GDB now incorrectly believes there's a TU at offset 0 of .debug_info in
    the main file, which is wrong.  When trying to expand that TU with
    "maint expand-symtabs", we're not really reading the TU, so we hit the
    assert checking that the signature in the TU header matches what was
    given by the index.
    
    The .debug_names format has a way to list the TUs found in the .dwo
    files, called the "foreign TU list" (see section 6.1.1.2 "Structure of
    the Name Index" of DWARF 5).  That list only includes the signature of
    the type, and there is some capability to figure out which .dwo file
    contains that type unit.  The .gdb_index format does not have something
    like that.  We could try to retrofit such a feature in the .gdb_index
    format, but I think we prefer to put our efforts on the standard
    .debug_names format.
    
    To avoid producing a misleading index like shown above, I propose to
    make GDB refuse to produce an index if there exists a skeletonless type
    unit.  This patch implements that by looking at section of all
    signatured_types.  If the containing section ends with .dwo, then this
    is a skeletonless type unit.
    
    As a reminder: if a unit has a skeleton, the dwarf2_per_cu section will
    point at the skeleton section, in the main file.  If the unit does not
    have a skeleton, the dwarf2_per_cu section will point at the section in
    the .dwo file.  All .dwo section names end with ".dwo".
    
    Add a "endswith" utils function to help with that.
    
    With this patch, running the gdb.dwarf/fission-with-type-unit.exp leads
    to a compilation failure:
    
        gdb compile failed, Error while writing index for `/home/smarchi/build/binutils-gdb/gdb/testsuite/outputs/gdb.dwarf2/fission-with-type-unit/.tmp/fission-with-type-unit': Found skeletonless type units, unable to produce .gdb_index.  Consider using .debug_names instead.
    
    ... which makes the test "untested".
    
    Add a new test, gdb.dwarf2/gdb-index-skeletonless-tu.exp, to verify the new error path.
    
    Change-Id: I1e2e0204c9c2b48763aa99ce63521ae4a5262b22
    Reviewed-By: Eli Zaretskii <[email protected]>
    Approved-By: Tom Tromey <[email protected]>

Diff:
---
 gdb/doc/gdb.texinfo                                |   6 ++
 gdb/dwarf2/index-write.c                           |  22 +++++
 .../gdb.dwarf2/gdb-index-skeletonless-tu.c         |  23 +++++
 .../gdb.dwarf2/gdb-index-skeletonless-tu.exp       | 103 +++++++++++++++++++++
 gdbsupport/common-utils.h                          |  12 +++
 5 files changed, 166 insertions(+)

diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index ab0216ff477..68e760699bc 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -51205,6 +51205,12 @@ gdb-index} (@pxref{Index Files}).  The index section is
 DWARF-specific; some knowledge of DWARF is assumed in this
 description.
 
+Note that the @code{.gdb_index} format does not support describing
+skeletonless type units, that is, type units in @file{.dwo} files that
+don't have a corresponding skeleton in the main file.  @value{GDBN}
+will refuse to generate a @code{.gdb_index} index for such executables.
+Consider using the @code{.debug_names} format instead.
+
 The mapped index file format is designed to be directly
 @code{mmap}able on any architecture.  In most cases, a datum is
 represented using a little-endian 32-bit integer value, called an
diff --git a/gdb/dwarf2/index-write.c b/gdb/dwarf2/index-write.c
index 6f8e41ff96f..1fd6cd7f32b 100644
--- a/gdb/dwarf2/index-write.c
+++ b/gdb/dwarf2/index-write.c
@@ -633,6 +633,18 @@ write_address_map (const addrmap *addrmap, data_buf &addr_vec,
 		       addrmap_index_data.previous_cu_index);
 }
 
+/* Return true if TU is a foreign type unit, that is a type unit defined in a
+   .dwo file without a corresponding skeleton in the main file.  */
+
+static bool
+is_foreign_tu (const signatured_type *tu)
+{
+  /* If a type unit has a skeleton, then `tu->section ()` will be the section
+     of the skeleton, in the main file.  If it's foreign, it will point to the
+     section in the .dwo file.  */
+  return endswith (tu->section ()->get_name (), ".dwo");
+}
+
 /* DWARF-5 .debug_names builder.  */
 class debug_names
 {
@@ -1411,6 +1423,16 @@ write_gdbindex (dwarf2_per_bfd *per_bfd, cooked_index *table,
   cu_index_htab.reserve (per_bfd->all_units.size ());
 
   unit_lists units = get_unit_lists (*per_bfd);
+
+  /* .gdb_index doesn't have a way to describe skeletonless type units, the way
+     that DWARF 5's .debug_names does with "foreign type units".  If the
+     executable has such skeletonless type units, refuse to produce an index,
+     instead of producing a bogus one.  */
+  for (const signatured_type *tu : units.type)
+    if (is_foreign_tu (tu))
+      error (_("Found foreign (skeletonless) type unit, unable to produce "
+	       ".gdb_index.  Consider using .debug_names instead."));
+
   int counter = 0;
 
   /* Write comp units.  */
diff --git a/gdb/testsuite/gdb.dwarf2/gdb-index-skeletonless-tu.c b/gdb/testsuite/gdb.dwarf2/gdb-index-skeletonless-tu.c
new file mode 100644
index 00000000000..c86a4322de8
--- /dev/null
+++ b/gdb/testsuite/gdb.dwarf2/gdb-index-skeletonless-tu.c
@@ -0,0 +1,23 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2026 Free Software Foundation, Inc.
+
+   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 3 of the License, 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, see
+   <http://www.gnu.org/licenses/>.  */
+
+int
+main (int argc, char **argv)
+{
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.dwarf2/gdb-index-skeletonless-tu.exp b/gdb/testsuite/gdb.dwarf2/gdb-index-skeletonless-tu.exp
new file mode 100644
index 00000000000..31a45e224ca
--- /dev/null
+++ b/gdb/testsuite/gdb.dwarf2/gdb-index-skeletonless-tu.exp
@@ -0,0 +1,103 @@
+# Copyright 2026 Free Software Foundation, Inc.
+
+# 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+# Test that GDB refuses to produce a .gdb_index when skeletonless type units
+# are present.  A skeletonless type unit is a type unit in a .dwo file that
+# doesn't have a corresponding skeleton in the main file.  The .gdb_index
+# format cannot represent these, so GDB must refuse to produce one rather than
+# produce a bogus index.
+
+load_lib dwarf.exp
+
+# This test can only be run on targets which support DWARF-2 and use gas.
+require dwarf2_support
+
+# Can't produce an index with readnow.
+require !readnow
+
+standard_testfile .c -dw.S
+
+set asm_file [standard_output_file $srcfile2]
+
+Dwarf::assemble $asm_file {
+    # In the main file: a skeleton CU pointing to the .dwo file.
+    cu {
+	version 5
+	dwo_id 0xF00D
+    } {
+	compile_unit {
+	    DW_AT_dwo_name ${::gdb_test_file_name}-dw.dwo DW_FORM_strp
+	} {}
+    }
+
+    # In the .dwo file: a type unit (skeletonless, no corresponding skeleton
+    # in main file).
+    tu {
+	fission 1
+	version 5
+    } 0xCAFE "the_type" {
+	type_unit {} {
+	    the_type: base_type {
+		DW_AT_byte_size 4 DW_FORM_sdata
+		DW_AT_encoding  @DW_ATE_signed
+		DW_AT_name      int
+	    }
+	}
+    }
+
+    # In the .dwo file: the split compile unit.
+    cu {
+	fission 1
+	version 5
+	dwo_id 0xF00D
+    } {
+	compile_unit {} {
+	    DW_TAG_variable {
+		DW_AT_name global_var
+		DW_AT_type 0xCAFE DW_FORM_ref_sig8
+		DW_AT_location {
+		    DW_OP_const1u 12
+		    DW_OP_stack_value
+		} SPECIAL_expr
+	    }
+	}
+    }
+}
+
+set obj [standard_output_file "${testfile}-dw.o"]
+if {[build_executable_and_dwo_files "$testfile.exp" "${binfile}" {} \
+	 [list $asm_file {nodebug split-dwo} $obj] \
+	 [list $srcfile  {nodebug}]]} {
+    return
+}
+
+clean_restart ${testfile}
+
+# Sanity check, verify that the executable works correctly.
+gdb_test "print global_var" " = 12"
+
+# Verify that saving a .gdb_index index fails.
+set output_dir [standard_output_file ""]
+gdb_test "save gdb-index ${output_dir}" \
+    "Found foreign \\(skeletonless\\) type unit, unable to produce \\.gdb_index\\.  Consider using \\.debug_names instead\\." \
+    "save gdb-index fails"
+
+# Verify that saving a .debug_names index works.
+gdb_test_no_output "save gdb-index -dwarf-5 ${output_dir}" \
+    "save gdb-index -dwarf-5 succeeds"
+
+# Verify that the .debug_names file was created.
+set debug_names_file "${output_dir}/${testfile}.debug_names"
+gdb_assert {[file exists $debug_names_file]} ".debug_names file exists"
diff --git a/gdbsupport/common-utils.h b/gdbsupport/common-utils.h
index fb4b8ea28ce..de83a715ac4 100644
--- a/gdbsupport/common-utils.h
+++ b/gdbsupport/common-utils.h
@@ -115,6 +115,18 @@ startswith (const char *str, const std::string_view &prefix)
   return strncmp (str, prefix.data (), prefix.length ()) == 0;
 }
 
+/* Return true if the end of STR matches PATTERN, false otherwise.
+
+   This can be replaced with std::string_view::ends_with when we require
+   C++20.  */
+
+static inline bool
+endswith (std::string_view str, std::string_view pattern)
+{
+  return (str.length () >= pattern.length ()
+	  && str.substr (str.length () - pattern.length ()) == pattern);
+}
+
 /* Return true if the strings are equal.  */
 
 static inline bool
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.