* [PATCH 0/8] Handle foreign type units in .debug_names
@ 2026-03-16 23:19 simon.marchi
2026-03-16 23:19 ` [PATCH 1/8] gdb/dwarf: refuse to produce .gdb_index when skeletonless type units are present simon.marchi
` (8 more replies)
0 siblings, 9 replies; 20+ messages in thread
From: simon.marchi @ 2026-03-16 23:19 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
From: Simon Marchi <simon.marchi@polymtl.ca>
Foreign type units is a fancy name for type units in .dwo files.
As we stand, in the presence of foreign type units, we produce bogus
indexes (both .gdb_index and .debug_names). So I think the minimum
would be to refuse creating indexes if there are foreign type units.
However, this series adds support for writing them to .debug_name and
reading them back.
For .gdb_index, the series just makes GDB refuse to create an index in
that case.
Note that this series depends on the enumerate series here:
https://inbox.sourceware.org/gdb-patches/20260220202002.860008-1-simon.marchi@efficios.com/T/#m75d30311589e79b52dcbdd840f8843ffa207000f
Simon Marchi (8):
gdb: refuse to produce .gdb_index when skeletonless type units are
present
gdb/dwarf: move dwo_unit and dwo_file to read.h
gdb/dwarf: move dwarf2_cu::section to cu.c
gdb/dwarf: add dwo_file::find_tus
gdb/dwarf: generate foreign type units in .debug_names
gdb/dwarf: add debug output in read-debug-names.c
gdb/dwarf: add more context to complaints in
mapped_debug_names_reader::scan_one_entry
gdb/dwarf: read foreign type units
gdb/doc/gdb.texinfo | 6 +
gdb/dwarf2/cu.c | 11 +
gdb/dwarf2/index-write.c | 174 ++++++++-
gdb/dwarf2/read-debug-names.c | 355 +++++++++++++++---
gdb/dwarf2/read.c | 349 ++++++++---------
gdb/dwarf2/read.h | 208 +++++++++-
gdb/dwarf2/types.h | 2 +
...dwarf5-fission-debug-types-debug-names.exp | 29 ++
.../gdb.dwarf2/gdb-index-skeletonless-tu.c | 23 ++
.../gdb.dwarf2/gdb-index-skeletonless-tu.exp | 103 +++++
gdbsupport/common-utils.h | 12 +
11 files changed, 1032 insertions(+), 240 deletions(-)
create mode 100644 gdb/testsuite/boards/dwarf5-fission-debug-types-debug-names.exp
create mode 100644 gdb/testsuite/gdb.dwarf2/gdb-index-skeletonless-tu.c
create mode 100644 gdb/testsuite/gdb.dwarf2/gdb-index-skeletonless-tu.exp
base-commit: 9cc83ec0ce9b4b75e8cd2b0c46f23d4cbf4b2f2b
prerequisite-patch-id: 44023c5aa2f90e676c8b6f2479de04cf3ccb86fa
prerequisite-patch-id: acbee56e54b4f2ebd7a2b321eae58521699d66a3
prerequisite-patch-id: 1fe50e2f79d2cbbb73a5aff3b2f87506466443c2
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH 1/8] gdb/dwarf: refuse to produce .gdb_index when skeletonless type units are present
2026-03-16 23:19 [PATCH 0/8] Handle foreign type units in .debug_names simon.marchi
@ 2026-03-16 23:19 ` simon.marchi
2026-03-17 12:57 ` Eli Zaretskii
2026-03-16 23:19 ` [PATCH 2/8] gdb/dwarf: move dwo_unit and dwo_file to read.h simon.marchi
` (7 subsequent siblings)
8 siblings, 1 reply; 20+ messages in thread
From: simon.marchi @ 2026-03-16 23:19 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
From: Simon Marchi <simon.marchi@efficios.com>
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
---
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(+)
create mode 100644 gdb/testsuite/gdb.dwarf2/gdb-index-skeletonless-tu.c
create mode 100644 gdb/testsuite/gdb.dwarf2/gdb-index-skeletonless-tu.exp
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index ceb69669ea60..cacbdede50db 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -51095,6 +51095,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 3a70787355cc..0c8474b3e4ed 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
{
@@ -1375,6 +1387,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 000000000000..c86a4322de82
--- /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 000000000000..31a45e224cad
--- /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 fb4b8ea28ced..de83a715ac45 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
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH 2/8] gdb/dwarf: move dwo_unit and dwo_file to read.h
2026-03-16 23:19 [PATCH 0/8] Handle foreign type units in .debug_names simon.marchi
2026-03-16 23:19 ` [PATCH 1/8] gdb/dwarf: refuse to produce .gdb_index when skeletonless type units are present simon.marchi
@ 2026-03-16 23:19 ` simon.marchi
2026-03-16 23:19 ` [PATCH 3/8] gdb/dwarf: move dwarf2_cu::section to cu.c simon.marchi
` (6 subsequent siblings)
8 siblings, 0 replies; 20+ messages in thread
From: simon.marchi @ 2026-03-16 23:19 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
From: Simon Marchi <simon.marchi@polymtl.ca>
This is to allow index-write.c to see these types, in a later patch.
Change-Id: Ia32e0643f95561d3a1bfb67d501c8e20f5682f0e
---
gdb/dwarf2/read.c | 125 ----------------------------------------------
gdb/dwarf2/read.h | 125 +++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 124 insertions(+), 126 deletions(-)
diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index 8b87d58dd9c5..c13ea6c1622f 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -266,86 +266,6 @@ struct loclists_rnglists_header
unsigned int offset_entry_count;
};
-/* These sections are what may appear in a (real or virtual) DWO file. */
-
-struct dwo_sections
-{
- struct dwarf2_section_info abbrev;
- struct dwarf2_section_info line;
- struct dwarf2_section_info loc;
- struct dwarf2_section_info loclists;
- struct dwarf2_section_info macinfo;
- struct dwarf2_section_info macro;
- struct dwarf2_section_info rnglists;
- struct dwarf2_section_info str;
- struct dwarf2_section_info str_offsets;
- /* In the case of a virtual DWO file, these two are unused. */
- std::vector<dwarf2_section_info> infos;
- std::vector<dwarf2_section_info> types;
-};
-
-/* CUs/TUs in DWP/DWO files. */
-
-struct dwo_unit
-{
- /* Backlink to the containing struct dwo_file. */
- struct dwo_file *dwo_file = nullptr;
-
- /* The "id" that distinguishes this CU/TU.
- .debug_info calls this "dwo_id", .debug_types calls this "signature".
- Since signatures came first, we stick with it for consistency. */
- ULONGEST signature = 0;
-
- /* The section this CU/TU lives in, in the DWO file. */
- dwarf2_section_info *section = nullptr;
-
- /* This is set if SECTION is owned by this dwo_unit. */
- dwarf2_section_info_up section_holder;
-
- /* Same as dwarf2_per_cu::{sect_off,length} but in the DWO section. */
- sect_offset sect_off {};
- unsigned int length = 0;
-
- /* For types, offset in the type's DIE of the type defined by this TU. */
- cu_offset type_offset_in_tu;
-};
-
-using dwo_unit_up = std::unique_ptr<dwo_unit>;
-
-/* Hash function for dwo_unit objects, based on the signature. */
-
-struct dwo_unit_hash
-{
- using is_transparent = void;
-
- std::size_t operator() (ULONGEST signature) const noexcept
- { return signature; }
-
- std::size_t operator() (const dwo_unit_up &unit) const noexcept
- { return (*this) (unit->signature); }
-};
-
-/* Equal function for dwo_unit objects, based on the signature.
-
- The signature is assumed to be unique within the DWO file. So while object
- file CU dwo_id's always have the value zero, that's OK, assuming each object
- file DWO file has only one CU, and that's the rule for now. */
-
-struct dwo_unit_eq
-{
- using is_transparent = void;
-
- bool operator() (ULONGEST sig, const dwo_unit_up &unit) const noexcept
- { return sig == unit->signature; }
-
- bool operator() (const dwo_unit_up &a, const dwo_unit_up &b) const noexcept
- { return (*this) (a->signature, b); }
-};
-
-/* Set of dwo_unit object, using their signature as identity. */
-
-using dwo_unit_set = gdb::unordered_set<dwo_unit_up, dwo_unit_hash, dwo_unit_eq>;
-
/* include/dwarf2.h defines the DWP section codes.
It defines a max value but it doesn't define a min value, which we
use for error checking, so provide one. */
@@ -355,51 +275,6 @@ enum dwp_v2_section_ids
DW_SECT_MIN = 1
};
-/* Data for one DWO file.
-
- This includes virtual DWO files (a virtual DWO file is a DWO file as it
- appears in a DWP file). DWP files don't really have DWO files per se -
- comdat folding of types "loses" the DWO file they came from, and from
- a high level view DWP files appear to contain a mass of random types.
- However, to maintain consistency with the non-DWP case we pretend DWP
- files contain virtual DWO files, and we assign each TU with one virtual
- DWO file (generally based on the line and abbrev section offsets -
- a heuristic that seems to work in practice). */
-
-struct dwo_file
-{
- dwo_file () = default;
- DISABLE_COPY_AND_ASSIGN (dwo_file);
-
- /* The DW_AT_GNU_dwo_name or DW_AT_dwo_name attribute.
- For virtual DWO files the name is constructed from the section offsets
- of abbrev,line,loc,str_offsets so that we combine virtual DWO files
- from related CU+TUs. */
- std::string dwo_name;
-
- /* The DW_AT_comp_dir attribute. */
- const char *comp_dir = nullptr;
-
- /* The bfd, when the file is open. Otherwise this is NULL.
- This is unused(NULL) for virtual DWO files where we use dwp_file.dbfd. */
- gdb_bfd_ref_ptr dbfd;
-
- /* The sections that make up this DWO file.
- Remember that for virtual DWO files in DWP V2 or DWP V5, these are virtual
- sections (for lack of a better name). */
- struct dwo_sections sections {};
-
- /* The CUs in the file.
-
- Multiple CUs per DWO are supported as an extension to handle LLVM's Link
- Time Optimization output (where multiple source files may be compiled into
- a single object/dwo pair). */
- dwo_unit_set cus;
-
- /* Table of TUs in the file. */
- dwo_unit_set tus;
-};
-
/* See dwarf2/read.h. */
std::size_t
diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
index 86f97e7ccf4a..5a46786e4f3f 100644
--- a/gdb/dwarf2/read.h
+++ b/gdb/dwarf2/read.h
@@ -489,7 +489,130 @@ using signatured_type_set
= gdb::unordered_set<signatured_type *, signatured_type_hash,
signatured_type_eq>;
-struct dwo_file;
+/* CUs/TUs in DWP/DWO files. */
+
+struct dwo_unit
+{
+ /* Backlink to the containing struct dwo_file. */
+ struct dwo_file *dwo_file = nullptr;
+
+ /* The "id" that distinguishes this CU/TU.
+ .debug_info calls this "dwo_id", .debug_types calls this "signature".
+ Since signatures came first, we stick with it for consistency. */
+ ULONGEST signature = 0;
+
+ /* The section this CU/TU lives in, in the DWO file. */
+ dwarf2_section_info *section = nullptr;
+
+ /* This is set if SECTION is owned by this dwo_unit. */
+ dwarf2_section_info_up section_holder;
+
+ /* Same as dwarf2_per_cu::{sect_off,length} but in the DWO section. */
+ sect_offset sect_off {};
+ unsigned int length = 0;
+
+ /* For types, offset in the type's DIE of the type defined by this TU. */
+ cu_offset type_offset_in_tu;
+};
+
+using dwo_unit_up = std::unique_ptr<dwo_unit>;
+
+/* These sections are what may appear in a (real or virtual) DWO file. */
+
+struct dwo_sections
+{
+ struct dwarf2_section_info abbrev;
+ struct dwarf2_section_info line;
+ struct dwarf2_section_info loc;
+ struct dwarf2_section_info loclists;
+ struct dwarf2_section_info macinfo;
+ struct dwarf2_section_info macro;
+ struct dwarf2_section_info rnglists;
+ struct dwarf2_section_info str;
+ struct dwarf2_section_info str_offsets;
+ /* In the case of a virtual DWO file, these two are unused. */
+ std::vector<dwarf2_section_info> infos;
+ std::vector<dwarf2_section_info> types;
+};
+
+/* Hash function for dwo_unit objects, based on the signature. */
+
+struct dwo_unit_hash
+{
+ using is_transparent = void;
+
+ std::size_t operator() (ULONGEST signature) const noexcept
+ { return signature; }
+
+ std::size_t operator() (const dwo_unit_up &unit) const noexcept
+ { return (*this) (unit->signature); }
+};
+
+/* Equal function for dwo_unit objects, based on the signature.
+
+ The signature is assumed to be unique within the DWO file. So while object
+ file CU dwo_id's always have the value zero, that's OK, assuming each object
+ file DWO file has only one CU, and that's the rule for now. */
+
+struct dwo_unit_eq
+{
+ using is_transparent = void;
+
+ bool operator() (ULONGEST sig, const dwo_unit_up &unit) const noexcept
+ { return sig == unit->signature; }
+
+ bool operator() (const dwo_unit_up &a, const dwo_unit_up &b) const noexcept
+ { return (*this) (a->signature, b); }
+};
+
+/* Set of dwo_unit object, using their signature as identity. */
+
+using dwo_unit_set = gdb::unordered_set<dwo_unit_up, dwo_unit_hash, dwo_unit_eq>;
+
+/* Data for one DWO file.
+
+ This includes virtual DWO files (a virtual DWO file is a DWO file as it
+ appears in a DWP file). DWP files don't really have DWO files per se -
+ comdat folding of types "loses" the DWO file they came from, and from
+ a high level view DWP files appear to contain a mass of random types.
+ However, to maintain consistency with the non-DWP case we pretend DWP
+ files contain virtual DWO files, and we assign each TU with one virtual
+ DWO file (generally based on the line and abbrev section offsets -
+ a heuristic that seems to work in practice). */
+
+struct dwo_file
+{
+ dwo_file () = default;
+ DISABLE_COPY_AND_ASSIGN (dwo_file);
+
+ /* The DW_AT_GNU_dwo_name or DW_AT_dwo_name attribute.
+ For virtual DWO files the name is constructed from the section offsets
+ of abbrev,line,loc,str_offsets so that we combine virtual DWO files
+ from related CU+TUs. */
+ std::string dwo_name;
+
+ /* The DW_AT_comp_dir attribute. */
+ const char *comp_dir = nullptr;
+
+ /* The bfd, when the file is open. Otherwise this is NULL.
+ This is unused(NULL) for virtual DWO files where we use dwp_file.dbfd. */
+ gdb_bfd_ref_ptr dbfd;
+
+ /* The sections that make up this DWO file.
+ Remember that for virtual DWO files in DWP V2 or DWP V5, these are virtual
+ sections (for lack of a better name). */
+ struct dwo_sections sections {};
+
+ /* The CUs in the file.
+
+ Multiple CUs per DWO are supported as an extension to handle LLVM's Link
+ Time Optimization output (where multiple source files may be compiled into
+ a single object/dwo pair). */
+ dwo_unit_set cus;
+
+ /* Table of TUs in the file. */
+ dwo_unit_set tus;
+};
using dwo_file_up = std::unique_ptr<dwo_file>;
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH 3/8] gdb/dwarf: move dwarf2_cu::section to cu.c
2026-03-16 23:19 [PATCH 0/8] Handle foreign type units in .debug_names simon.marchi
2026-03-16 23:19 ` [PATCH 1/8] gdb/dwarf: refuse to produce .gdb_index when skeletonless type units are present simon.marchi
2026-03-16 23:19 ` [PATCH 2/8] gdb/dwarf: move dwo_unit and dwo_file to read.h simon.marchi
@ 2026-03-16 23:19 ` simon.marchi
2026-03-16 23:19 ` [PATCH 4/8] gdb/dwarf: add dwo_file::find_tus simon.marchi
` (5 subsequent siblings)
8 siblings, 0 replies; 20+ messages in thread
From: simon.marchi @ 2026-03-16 23:19 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
From: Simon Marchi <simon.marchi@efficios.com>
Following the previous patch that moves the dwo_unit structure from
dwarf2/read.c to dwarf2/read.h, dwarf2_cu::section has no reason to be
implemented in dwarf2/read.c anymore. Move it to dwarf2/cu.c.
Change-Id: I67e2bb42d878ac18e4bf3460d75f1394477a46ce
---
gdb/dwarf2/cu.c | 11 +++++++++++
gdb/dwarf2/read.c | 14 +-------------
2 files changed, 12 insertions(+), 13 deletions(-)
diff --git a/gdb/dwarf2/cu.c b/gdb/dwarf2/cu.c
index c3bfd15f14a1..3c89bd960d56 100644
--- a/gdb/dwarf2/cu.c
+++ b/gdb/dwarf2/cu.c
@@ -58,6 +58,17 @@ dwarf2_cu::dwarf2_cu (dwarf2_per_cu *per_cu, dwarf2_per_objfile *per_objfile)
/* See cu.h. */
+const dwarf2_section_info &
+dwarf2_cu::section () const
+{
+ if (this->dwo_unit != nullptr)
+ return *this->dwo_unit->section;
+ else
+ return *this->per_cu->section ();
+}
+
+/* See cu.h. */
+
struct type *
dwarf2_cu::addr_sized_int_type (bool unsigned_p) const
{
diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index c13ea6c1622f..3a0602b4ac08 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -5806,19 +5806,7 @@ read_file_scope (struct die_info *die, struct dwarf2_cu *cu)
}
}
-/* See cu.h.
-
- This function is defined in this file (instead of cu.c) because it needs
- to see the definition of struct dwo_unit. */
-
-const dwarf2_section_info &
-dwarf2_cu::section () const
-{
- if (this->dwo_unit != nullptr)
- return *this->dwo_unit->section;
- else
- return *this->per_cu->section ();
-}
+/* See cu.h. */
/* See cu.h.
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH 4/8] gdb/dwarf: add dwo_file::find_tus
2026-03-16 23:19 [PATCH 0/8] Handle foreign type units in .debug_names simon.marchi
` (2 preceding siblings ...)
2026-03-16 23:19 ` [PATCH 3/8] gdb/dwarf: move dwarf2_cu::section to cu.c simon.marchi
@ 2026-03-16 23:19 ` simon.marchi
2026-03-16 23:19 ` [PATCH 5/8] gdb/dwarf: generate foreign type units in .debug_names simon.marchi
` (4 subsequent siblings)
8 siblings, 0 replies; 20+ messages in thread
From: simon.marchi @ 2026-03-16 23:19 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
From: Simon Marchi <simon.marchi@efficios.com>
Add this little helper to make finding a TU a bit simpler. I always
find the STL way of doing things cryptic, so I think that hiding it in
small helper methods makes the code clearer.
A subsequent patch will add more uses of it
Change-Id: Ibfb20d0e44c65d2ff729f3e0980ec4435f223aef
---
gdb/dwarf2/read.c | 18 ++++++++++++++----
gdb/dwarf2/read.h | 5 +++++
2 files changed, 19 insertions(+), 4 deletions(-)
diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index 3a0602b4ac08..fd1c37ad8e89 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -266,6 +266,18 @@ struct loclists_rnglists_header
unsigned int offset_entry_count;
};
+/* See read.h. */
+
+dwo_unit *
+dwo_file::find_tu (ULONGEST signature) const
+{
+ auto it = this->tus.find (signature);
+ if (it == this->tus.end ())
+ return nullptr;
+
+ return it->get ();
+}
+
/* include/dwarf2.h defines the DWP section codes.
It defines a max value but it doesn't define a min value, which we
use for error checking, so provide one. */
@@ -2342,12 +2354,10 @@ lookup_dwo_signatured_type (struct dwarf2_cu *cu, ULONGEST sig)
/* Note: cu->dwo_unit is the dwo_unit that references this TU, not the
dwo_unit of the TU itself. */
dwo_file *dwo_file = cu->dwo_unit->dwo_file;
- auto it = dwo_file->tus.find (sig);
- if (it == dwo_file->tus.end ())
+ dwo_unit *dwo_entry = dwo_file->find_tu (sig);
+ if (dwo_entry == nullptr)
return nullptr;
- dwo_unit *dwo_entry = it->get ();
-
/* If the global table doesn't have an entry for this TU, add one. */
if (sig_type_it == per_bfd->signatured_types.end ())
{
diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
index 5a46786e4f3f..5c61e91870b4 100644
--- a/gdb/dwarf2/read.h
+++ b/gdb/dwarf2/read.h
@@ -585,6 +585,11 @@ struct dwo_file
dwo_file () = default;
DISABLE_COPY_AND_ASSIGN (dwo_file);
+ /* Look for a type unit with signature SIGNATURE in this dwo_file.
+
+ Return nullptr if not found. */
+ dwo_unit *find_tu (ULONGEST signature) const;
+
/* The DW_AT_GNU_dwo_name or DW_AT_dwo_name attribute.
For virtual DWO files the name is constructed from the section offsets
of abbrev,line,loc,str_offsets so that we combine virtual DWO files
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH 5/8] gdb/dwarf: generate foreign type units in .debug_names
2026-03-16 23:19 [PATCH 0/8] Handle foreign type units in .debug_names simon.marchi
` (3 preceding siblings ...)
2026-03-16 23:19 ` [PATCH 4/8] gdb/dwarf: add dwo_file::find_tus simon.marchi
@ 2026-03-16 23:19 ` simon.marchi
2026-03-16 23:19 ` [PATCH 6/8] gdb/dwarf: add debug output in read-debug-names.c simon.marchi
` (3 subsequent siblings)
8 siblings, 0 replies; 20+ messages in thread
From: simon.marchi @ 2026-03-16 23:19 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
From: Simon Marchi <simon.marchi@efficios.com>
The DWARF 5 .debug_names index makes the distinction between local type
units and foreign type units. Local type units are those present
directly in the main file. Foreign type units are those present in .dwo
files. GDB only knows how to produce local type units today, which
leads to invalid indexes whenever there are type units in .dwo files.
To observe this, run test gdb.dwarf2/fission-with-type-unit.exp with
board cc-with-debug-names. The test passes, but we see this in the log:
(gdb) file
/home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.dwarf2/fission-with-type-unit/fission-with-type-unit^M
Reading symbols from
/home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.dwarf2/fission-with-type-unit/fission-with-type-unit...^M
warning: Section .debug_names has incorrect entry in TU table,
ignoring .debug_names.^M
These are the units involved in this test. The first and last CUs are
dummy CUs added by the DWARF assembler, they are not important.
- 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 the content of the produced .debug_names:
Contents of the .debug_names section:
$ readelf --debug-dump=gdb_index testsuite/outputs/gdb.dwarf2/fission-with-type-unit/fission-with-type-unit
...
Version 5
Augmentation string: 47 44 42 33 00 00 00 00 ("GDB3")
CU table:
[ 0] 0
[ 1] 0xc
[ 2] 0x25
TU table:
[ 0] 0
Foreign TU table:
Used 0 of 0 buckets.
Symbol table:
[ 1] global_var: <0><1> DW_TAG_variable DW_IDX_compile_unit=(udata)=1 DW_IDX_die_offset=(ref_addr)=<0x1c> DW_IDX_GNU_language=(udata)=0 DW_IDX_GNU_internal=(flag_present)=1
[ 2] int:
<0x8><2> DW_TAG_base_type DW_IDX_compile_unit=(udata)=1 DW_IDX_die_offset=(ref_addr)=<0x15> DW_IDX_GNU_language=(udata)=0
<0xf><3> DW_TAG_base_type DW_IDX_type_unit=(udata)=0 DW_IDX_die_offset=(ref_addr)=<0x19> DW_IDX_GNU_language=(udata)=0
The TU table claims that there is a TU at offset 0 in the main file
(fission-with-type-unit). This is wrong: the TU is in
fission-with-type-unit-dw.dwo. It should be listed in the Foreign TU
table instead.
This patch therefore teaches GDB to use the foreign TU table for TUs in
.dwo files.
A note about foreign type units and skeletons: in the early history of
split DWARF and type units, 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. But if for some reason a type unit in a .dwo file had a
matching skeleton in the main file, then it would be ok for that TU to
be listed in the "TU table". The offset would be that of the skeleton.
While the list of CUs and local TUs contain the offset within the
.debug_info section where to find the unit, the foreign TU list only
contains the 8-byte signature of the types. With just that, a reader
wouldn't be able to easily locate a .dwo that contain the type with a
given signature.
To help with this, index entries for foreign type units may also include
a reference to a compilation unit that can be followed in order to find
a .dwo file containing the type. This patch implements it.
Implementation details
----------------------
The first change is the addition of the dwo_unit::per_cu field, which
allows going from the dwo_unit to the dwarf2_per_cu structure (which
describes the skeleton) that was used to lookup this dwo_unit. This
fields starts at nullptr, and it gets set in lookup_dwo_cutu whenever we
look up the dwo_unit for a given dwarf2_per_cu. This will come handy
later. I made this field an std::atomic, because I think it would be
possible to craft a weird test case that would make two indexer threads
try to set the field on the same dwo_unit. During normal operation, we
expect the field for each dwo_unit representing a CU to be written
exactly once.
In index-write.c, change the get_unit_lists function in
dwarf2/index-write.c to segregate local and foreign type units. Then,
update write_debug_names to emit the list of foreign TUs in the
.debug_names header. This consists of a list of type signatures.
In debug_names::build, for foreign type units, emit a
DW_IDX_compile_unit field. This is the reference to the CU that can be
used to locate the .dwo file containing that type unit. To obtain the
value for this field, look up a CU in the same dwo_file that has its
dwo_unit::per_cu field set (typically there will be exactly one CU, and
the field will be set).
With this patch, the index for the test case above looks like:
$ readelf --debug-dump=gdb_index testsuite/outputs/gdb.dwarf2/fission-with-type-unit/fission-with-type-unit
...
Version 5
Augmentation string: 47 44 42 33 00 00 00 00 ("GDB3")
CU table:
[ 0] 0
[ 1] 0xc
[ 2] 0x25
TU table:
Foreign TU table:
[ 0] 000000000000cafe
Used 0 of 0 buckets.
Symbol table:
[ 1] global_var: <0><1> DW_TAG_variable DW_IDX_compile_unit=(udata)=1 DW_IDX_die_offset=(ref_addr)=<0x1c> DW_IDX_GNU_language=(udata)=0 DW_IDX_GNU_internal=(flag_present)=1
[ 2] int:
<0x8><2> DW_TAG_base_type DW_IDX_compile_unit=(udata)=1 DW_IDX_die_offset=(ref_addr)=<0x15> DW_IDX_GNU_language=(udata)=0
<0xf><3> DW_TAG_base_type DW_IDX_type_unit=(udata)=0 DW_IDX_compile_unit=(udata)=1 DW_IDX_die_offset=(ref_addr)=<0x19> DW_IDX_GNU_language=(udata)=0 ...
We can see that the TU is correctly placed in the foreign TU list, and
that the index entry (the last line) points to the TU at index 0, but
also to the CU at index 1, which is indeed the CU that the reader can
follow to find the type unit.
With this patch, GDB still rejects the index:
(gdb) file /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.dwarf2/fission-with-type-unit/fission-with-type-unit
Reading symbols from /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.dwarf2/fission-with-type-unit/fission-with-type-unit...
warning: Section .debug_names in /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.dwarf2/fission-with-type-unit/fission-with-type-unit has unsupported 1 foreign TUs, ignoring .debug_names.
But at least, we don't produce a bogus index anymore, that's already an
improvement. A following patch in this series implements the reading
side
Change-Id: I311fd7b4ca57d9ff6d64ae08df805c6635961eed
---
gdb/dwarf2/index-write.c | 160 +++++++++++++++++++++++++++++++++------
gdb/dwarf2/read.c | 9 +++
gdb/dwarf2/read.h | 17 +++++
3 files changed, 162 insertions(+), 24 deletions(-)
diff --git a/gdb/dwarf2/index-write.c b/gdb/dwarf2/index-write.c
index 0c8474b3e4ed..5edada5d16c3 100644
--- a/gdb/dwarf2/index-write.c
+++ b/gdb/dwarf2/index-write.c
@@ -633,6 +633,10 @@ write_address_map (const addrmap *addrmap, data_buf &addr_vec,
addrmap_index_data.previous_cu_index);
}
+/* Is this symbol a compile unit, local type unit (type unit in the main file)
+ or foreign type unit (type unit in a .dwo file)? */
+enum class unit_kind { cu, local_tu, foreign_tu };
+
/* 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. */
@@ -645,6 +649,22 @@ is_foreign_tu (const signatured_type *tu)
return endswith (tu->section ()->get_name (), ".dwo");
}
+/* Determine the unit_kind for PER_CU. */
+
+static unit_kind
+classify_unit (const dwarf2_per_cu &per_cu)
+{
+ if (auto sig_type = per_cu.as_signatured_type (); sig_type != nullptr)
+ {
+ if (is_foreign_tu (sig_type))
+ return unit_kind::foreign_tu;
+ else
+ return unit_kind::local_tu;
+ }
+ else
+ return unit_kind::cu;
+}
+
/* DWARF-5 .debug_names builder. */
class debug_names
{
@@ -668,9 +688,6 @@ class debug_names
return dwarf5_is_dwarf64 ? 8 : 4;
}
- /* Is this symbol from DW_TAG_compile_unit or DW_TAG_type_unit? */
- enum class unit_kind { cu, tu };
-
/* Insert one symbol. */
void insert (const cooked_index_entry *entry)
{
@@ -723,9 +740,8 @@ class debug_names
for (const cooked_index_entry *entry : these_entries)
{
- unit_kind kind = (entry->per_cu->is_debug_types ()
- ? unit_kind::tu
- : unit_kind::cu);
+ unit_kind kind = classify_unit (*entry->per_cu);
+
/* Some Ada parentage is synthesized by the reader and so
must be ignored here. */
const cooked_index_entry *parent = entry->get_parent ();
@@ -752,6 +768,14 @@ class debug_names
: DW_IDX_type_unit);
m_abbrev_table.append_unsigned_leb128 (DW_FORM_udata);
+ /* For foreign TUs only: the index of a CU that can be used to
+ locate the .dwo file containing that TU. */
+ if (kind == unit_kind::foreign_tu)
+ {
+ m_abbrev_table.append_unsigned_leb128 (DW_IDX_compile_unit);
+ m_abbrev_table.append_unsigned_leb128 (DW_FORM_udata);
+ }
+
/* DIE offset. */
m_abbrev_table.append_unsigned_leb128 (DW_IDX_die_offset);
m_abbrev_table.append_unsigned_leb128 (DW_FORM_ref_addr);
@@ -809,6 +833,67 @@ class debug_names
gdb_assert (it != m_cu_index_htab.cend ());
m_entry_pool.append_unsigned_leb128 (it->second);
+ /* For foreign TUs only: the index of a CU that can be used to
+ locate the .dwo file containing that TU.
+
+ This code implements this snippet of the DWARF 5 standard, from
+ section 6.1.1.2, "Structure of the Name Index":
+
+ When an index entry refers to a foreign type unit, it may have
+ attributes for both CU and (foreign) TU. For such entries, the
+ CU attribute gives the consumer a reference to the CU that may
+ be used to locate a split DWARF object file that contains the
+ type unit
+
+ So, the idea here is to find a CU in the same .dwo file as the
+ type unit (there will typically be one) and write the offset to
+ its skeleton. */
+ if (kind == unit_kind::foreign_tu)
+ {
+ /* If we know about this TU that is only listed in some .dwo
+ file, it means that we looked up the .dwo file from a CU
+ skeleton at some point during indexing, and recorded it in
+ ENTRY->PER_CU. */
+ gdb_assert (entry->per_cu != nullptr);
+
+ signatured_type *sig_type
+ = entry->per_cu->as_signatured_type ();
+
+ /* We know it's a TU, because it has been classified as
+ foreign_tu. */
+ gdb_assert (sig_type != nullptr);
+
+ /* We know it has a dwo_unit, because it's foreign. */
+ gdb_assert (sig_type->dwo_unit != nullptr);
+
+ /* Find a CU in the same .dwo file as the TU. */
+ dwarf2_per_cu *per_cu = nullptr;
+ for (auto &dwo_cu : sig_type->dwo_unit->dwo_file->cus)
+ if (dwo_cu->per_cu != nullptr)
+ {
+ per_cu = dwo_cu->per_cu;
+ break;
+ }
+
+ /* It would be really weird to not find a CU that we looked up
+ in this .dwo file. Otherwise, how would we know about this
+ foreign TU? But it might be possible to craft a bad faith
+ .dwo file that does this by having a one TU with a skeleton
+ and one TU without a skeleton in the same .dwo file (i.e.
+ non-standard DWARF 5). So, just in case, we error out
+ gracefully. */
+ if (per_cu == nullptr)
+ error (_("Could not find a CU for foreign type unit in DWO "
+ "file %s"),
+ sig_type->dwo_unit->dwo_file->dwo_name.c_str ());
+
+ /* This is the CU that can be used to find the .dwo file
+ containing the type unit, write its offset. */
+ const auto cu_it = m_cu_index_htab.find (per_cu);
+ gdb_assert (cu_it != m_cu_index_htab.cend ());
+ m_entry_pool.append_unsigned_leb128 (cu_it->second);
+ }
+
/* DIE offset. */
m_entry_pool.append_uint (dwarf5_offset_size (),
m_dwarf5_byte_order,
@@ -1333,8 +1418,9 @@ struct unit_lists
/* Compilation units. */
std::vector<const dwarf2_per_cu *> comp;
- /* Type units. */
- std::vector<const signatured_type *> type;
+ /* Type units, local and foreign. */
+ std::vector<const signatured_type *> local_type;
+ std::vector<const signatured_type *> foreign_type;
};
/* Get sorted (by section offset) lists of comp units and type units. */
@@ -1345,16 +1431,28 @@ get_unit_lists (const dwarf2_per_bfd &per_bfd)
unit_lists lists;
for (const auto &unit : per_bfd.all_units)
- if (const signatured_type *sig_type = unit->as_signatured_type ();
- sig_type != nullptr)
- lists.type.emplace_back (sig_type);
- else
- lists.comp.emplace_back (unit.get ());
+ switch (classify_unit (*unit))
+ {
+ case unit_kind::cu:
+ lists.comp.emplace_back (unit.get ());
+ break;
+
+ case unit_kind::local_tu:
+ lists.local_type.emplace_back (unit->as_signatured_type ());
+ break;
+
+ case unit_kind::foreign_tu:
+ lists.foreign_type.emplace_back (unit->as_signatured_type ());
+ break;
+ }
auto by_sect_off = [] (const dwarf2_per_cu *lhs, const dwarf2_per_cu *rhs)
{ return lhs->sect_off () < rhs->sect_off (); };
- /* Sort both lists, even though it is technically not always required:
+ auto by_sig = [] (const signatured_type *lhs, const signatured_type *rhs)
+ { return lhs->signature < rhs->signature; };
+
+ /* Sort the lists, even though it is technically not always required:
- while .gdb_index requires the CU list to be sorted, DWARF 5 doesn't
say anything about the order of CUs in .debug_names.
@@ -1364,7 +1462,8 @@ get_unit_lists (const dwarf2_per_bfd &per_bfd)
However, it helps make sure that GDB produce a stable and predictable
output, which is nice. */
std::sort (lists.comp.begin (), lists.comp.end (), by_sect_off);
- std::sort (lists.type.begin (), lists.type.end (), by_sect_off);
+ std::sort (lists.local_type.begin (), lists.local_type.end (), by_sect_off);
+ std::sort (lists.foreign_type.begin (), lists.foreign_type.end (), by_sig);
return lists;
}
@@ -1392,10 +1491,9 @@ write_gdbindex (dwarf2_per_bfd *per_bfd, cooked_index *table,
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."));
+ if (!units.foreign_type.empty ())
+ error (_("Found foreign (skeletonless) type unit, unable to produce "
+ ".gdb_index. Consider using .debug_names instead."));
int counter = 0;
@@ -1421,7 +1519,7 @@ write_gdbindex (dwarf2_per_bfd *per_bfd, cooked_index *table,
/* Write type units. */
data_buf types_cu_list;
- for (const signatured_type *sig_type : units.type)
+ for (const signatured_type *sig_type : units.local_type)
{
const auto insertpair = cu_index_htab.emplace (sig_type, counter);
gdb_assert (insertpair.second);
@@ -1494,7 +1592,7 @@ write_debug_names (dwarf2_per_bfd *per_bfd, cooked_index *table,
data_buf type_unit_list;
- for (auto [i, per_cu] : gdb::enumerate (units.type))
+ for (auto [i, per_cu] : gdb::enumerate (units.local_type))
{
nametable.add_cu (per_cu, i);
type_unit_list.append_uint (nametable.dwarf5_offset_size (),
@@ -1502,9 +1600,21 @@ write_debug_names (dwarf2_per_bfd *per_bfd, cooked_index *table,
to_underlying (per_cu->sect_off ()));
}
+ data_buf foreign_type_unit_list;
+
+ for (auto [i, sig_type] : gdb::enumerate (units.foreign_type))
+ {
+ /* The numbering of foreign type units follows the numbering of local type
+ units. */
+ nametable.add_cu (sig_type, units.local_type.size () + i);
+ foreign_type_unit_list.append_uint (8, dwarf5_byte_order,
+ sig_type->signature);
+ }
+
/* Verify that all units are represented. */
gdb_assert (units.comp.size () == per_bfd->num_comp_units);
- gdb_assert (units.type.size () == per_bfd->num_type_units);
+ gdb_assert (units.local_type.size () + units.foreign_type.size ()
+ == per_bfd->num_type_units);
for (const cooked_index_entry *entry : table->all_entries ())
nametable.insert (entry);
@@ -1521,6 +1631,7 @@ write_debug_names (dwarf2_per_bfd *per_bfd, cooked_index *table,
expected_bytes += bytes_of_header;
expected_bytes += comp_unit_list.size ();
expected_bytes += type_unit_list.size ();
+ expected_bytes += foreign_type_unit_list.size ();
expected_bytes += nametable.bytes ();
data_buf header;
@@ -1547,11 +1658,11 @@ write_debug_names (dwarf2_per_bfd *per_bfd, cooked_index *table,
/* local_type_unit_count - The number of TUs in the local TU
list. */
- header.append_uint (4, dwarf5_byte_order, units.type.size ());
+ header.append_uint (4, dwarf5_byte_order, units.local_type.size ());
/* foreign_type_unit_count - The number of TUs in the foreign TU
list. */
- header.append_uint (4, dwarf5_byte_order, 0);
+ header.append_uint (4, dwarf5_byte_order, units.foreign_type.size ());
/* bucket_count - The number of hash buckets in the hash lookup
table. GDB does not use the hash table, so there's also no need
@@ -1577,6 +1688,7 @@ write_debug_names (dwarf2_per_bfd *per_bfd, cooked_index *table,
header.file_write (out_file);
comp_unit_list.file_write (out_file);
type_unit_list.file_write (out_file);
+ foreign_type_unit_list.file_write (out_file);
nametable.file_write (out_file, out_file_str);
assert_file_size (out_file, expected_bytes);
diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index fd1c37ad8e89..af7ef8d8f3db 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -7232,6 +7232,15 @@ cutu_reader::lookup_dwo_cutu (dwarf2_cu *cu, const char *dwo_name,
("DWO %s %s(%s) found: @%s", kind, dwo_name,
hex_string (signature),
host_address_to_string (dwo_unit_it->get ()));
+
+ /* Record the dwarf2_per_cu that was used to look up this
+ dwo_unit. There will typically be exactly one skeleton
+ pointing to each DWO CU. But if for some mysterious reason
+ there are multiple skeletons pointing to the same DWO CU, it's
+ fine, we just need to remember one. This will keep the last
+ one seen. */
+ (*dwo_unit_it)->per_cu = cu->per_cu;
+
return dwo_unit_it->get ();
}
}
diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
index 5c61e91870b4..1687bc52e432 100644
--- a/gdb/dwarf2/read.h
+++ b/gdb/dwarf2/read.h
@@ -259,6 +259,7 @@ struct dwarf2_per_cu
/* If this dwarf2_per_cu is a signatured_type, return "this" cast to
signatured_type. Otherwise, return nullptr. */
signatured_type *as_signatured_type ();
+ const signatured_type *as_signatured_type () const;
dwarf2_per_bfd *per_bfd () const
{ return m_per_bfd; }
@@ -455,6 +456,17 @@ dwarf2_per_cu::as_signatured_type ()
return nullptr;
}
+/* See dwarf2_per_cu declaration. */
+
+inline const signatured_type *
+dwarf2_per_cu::as_signatured_type () const
+{
+ if (m_is_debug_types)
+ return static_cast<const signatured_type *> (this);
+
+ return nullptr;
+}
+
/* Hash a signatured_type object based on its signature. */
struct signatured_type_hash
@@ -496,6 +508,11 @@ struct dwo_unit
/* Backlink to the containing struct dwo_file. */
struct dwo_file *dwo_file = nullptr;
+ /* For compile units only, the per-CU structure that represents the skeleton
+ that we used to reach this dwo_unit. It starts as nullptr, and gets set
+ in cutu_reader::lookup_dwo_cutu. */
+ std::atomic<dwarf2_per_cu *> per_cu = nullptr;
+
/* The "id" that distinguishes this CU/TU.
.debug_info calls this "dwo_id", .debug_types calls this "signature".
Since signatures came first, we stick with it for consistency. */
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH 6/8] gdb/dwarf: add debug output in read-debug-names.c
2026-03-16 23:19 [PATCH 0/8] Handle foreign type units in .debug_names simon.marchi
` (4 preceding siblings ...)
2026-03-16 23:19 ` [PATCH 5/8] gdb/dwarf: generate foreign type units in .debug_names simon.marchi
@ 2026-03-16 23:19 ` simon.marchi
2026-03-16 23:19 ` [PATCH 7/8] gdb/dwarf: add more context to complaints in mapped_debug_names_reader::scan_one_entry simon.marchi
` (2 subsequent siblings)
8 siblings, 0 replies; 20+ messages in thread
From: simon.marchi @ 2026-03-16 23:19 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
From: Simon Marchi <simon.marchi@polymtl.ca>
Add a bunch of debug prints in read-debug-names.c to dump what we are
reading. Some of if was helpful to me when debugging my changes to the
.debug_names reader and writer.
The debug prints are activated by "set debug dwarf-read" (I didn't feel
like we needed a separate knob for that). There are two levels of
verbosity. At level 1, we print everything that happens once, like the
header, counts, offsets of important parts of the index, etc. At
levels > 1, we print information about each abbrev, CU/TU and index
entry.
The output is made such that it is easy look at the output of readelf of
llvm-dwarfdump on the side and confirm that GDB is reading the index
right.
Here are some examples of the output.
Header and some abbrevs:
[dwarf-read] read_debug_names_from_section: start: reading .debug_names from /home/simark/build/babeltrace/src/lib/.libs/libbabeltrace2.so.0.0.0
[dwarf-read] read_debug_names_from_section: section size: 0x19d4e, initial length: 0x19d4a, dwarf64: 0, offset_size: 4
[dwarf-read] read_debug_names_from_section: version: 5
[dwarf-read] read_debug_names_from_section: cu_count: 56, tu_count: 0, foreign_tu_count: 0
[dwarf-read] read_debug_names_from_section: bucket_count: 0, name_count: 3418, abbrev_table_size: 374, augmentation_string_size: 8
[dwarf-read] read_debug_names_from_section: augmentation string: "GDB3\x00\x00\x00\x00"
[dwarf-read] read_debug_names_from_section: abbrev 1: tag DW_TAG_namespace, 4 attributes
[dwarf-read] read_debug_names_from_section: DW_IDX_compile_unit DW_FORM_udata
[dwarf-read] read_debug_names_from_section: DW_IDX_die_offset DW_FORM_ref_addr
[dwarf-read] read_debug_names_from_section: DW_IDX_GNU_language DW_FORM_udata
[dwarf-read] read_debug_names_from_section: DW_IDX_parent DW_FORM_data4
[dwarf-read] read_debug_names_from_section: abbrev 2: tag DW_TAG_enumerator, 4 attributes
[dwarf-read] read_debug_names_from_section: DW_IDX_compile_unit DW_FORM_udata
[dwarf-read] read_debug_names_from_section: DW_IDX_die_offset DW_FORM_ref_addr
[dwarf-read] read_debug_names_from_section: DW_IDX_GNU_language DW_FORM_udata
[dwarf-read] read_debug_names_from_section: DW_IDX_GNU_internal DW_FORM_flag_present
Excerpt of the CU list:
[dwarf-read] build_and_check_cu_list_from_debug_names: building CU list from .debug_names (56 CUs)
[dwarf-read] build_and_check_cu_list_from_debug_names: CU 0: offset 0x0
[dwarf-read] build_and_check_cu_list_from_debug_names: CU 1: offset 0x1bac
[dwarf-read] build_and_check_cu_list_from_debug_names: CU 2: offset 0x4262
[dwarf-read] build_and_check_cu_list_from_debug_names: CU 3: offset 0x4ff0
[dwarf-read] build_and_check_cu_list_from_debug_names: CU 4: offset 0x59e9
[dwarf-read] build_and_check_cu_list_from_debug_names: CU 5: offset 0x67af
[dwarf-read] build_and_check_cu_list_from_debug_names: CU 6: offset 0x8e1a
[dwarf-read] build_and_check_cu_list_from_debug_names: CU 7: offset 0xa5cc
[dwarf-read] build_and_check_cu_list_from_debug_names: CU 8: offset 0xcd39
Some entries:
[dwarf-read] scan_all_names: start: scanning 3418 names from .debug_names
[dwarf-read] scan_entries: scanning entries for name 1: "(anonymous namespace)" (entry pool offset 0x0)
[dwarf-read] scan_one_entry: entry pool offset 0x0: abbrev 1, tag DW_TAG_namespace
[dwarf-read] scan_one_entry: DW_IDX_compile_unit (DW_FORM_udata): 55
[dwarf-read] scan_one_entry: DW_IDX_die_offset (DW_FORM_ref_addr): 0x7d06b
[dwarf-read] scan_one_entry: DW_IDX_GNU_language (DW_FORM_udata): 33
[dwarf-read] scan_one_entry: DW_IDX_parent (DW_FORM_data4): 38201
[dwarf-read] scan_one_entry: -> die_offset 0x7d06b, per_cu offset 0x5af0e
[dwarf-read] scan_one_entry: entry pool offset 0xb: end of entries (abbrev 0)
[dwarf-read] scan_entries: scanning entries for name 2: "AUTO_SEEK_STREAM_STATE_PACKET_BEGAN" (entry pool offset 0xc)
[dwarf-read] scan_one_entry: entry pool offset 0xc: abbrev 2, tag DW_TAG_enumerator
[dwarf-read] scan_one_entry: DW_IDX_compile_unit (DW_FORM_udata): 17
[dwarf-read] scan_one_entry: DW_IDX_die_offset (DW_FORM_ref_addr): 0x1beea
[dwarf-read] scan_one_entry: DW_IDX_GNU_language (DW_FORM_udata): 29
[dwarf-read] scan_one_entry: DW_IDX_GNU_internal (DW_FORM_flag_present): 1
[dwarf-read] scan_one_entry: -> die_offset 0x1beea, per_cu offset 0x1a28e
[dwarf-read] scan_one_entry: entry pool offset 0x13: end of entries (abbrev 0)
Change-Id: I4a40bfb73fa7feccb5038814a7d1a1bcd4f6231b
---
gdb/dwarf2/read-debug-names.c | 120 +++++++++++++++++++++++++++++++++-
gdb/dwarf2/read.c | 24 +------
gdb/dwarf2/read.h | 24 +++++++
3 files changed, 143 insertions(+), 25 deletions(-)
diff --git a/gdb/dwarf2/read-debug-names.c b/gdb/dwarf2/read-debug-names.c
index 8e488377439b..6ac65d101b16 100644
--- a/gdb/dwarf2/read-debug-names.c
+++ b/gdb/dwarf2/read-debug-names.c
@@ -170,7 +170,12 @@ mapped_debug_names_reader::scan_one_entry (const char *name,
const ULONGEST abbrev = read_unsigned_leb128 (abfd, entry, &bytes_read);
entry += bytes_read;
if (abbrev == 0)
- return nullptr;
+ {
+ dwarf_read_debug_printf_v
+ (" entry pool offset 0x%tx: end of entries (abbrev 0)",
+ offset_in_entry_pool);
+ return nullptr;
+ }
const auto indexval_it = abbrev_map.find (abbrev);
if (indexval_it == abbrev_map.cend ())
@@ -182,6 +187,12 @@ mapped_debug_names_reader::scan_one_entry (const char *name,
}
const auto &indexval = indexval_it->second;
+
+ dwarf_read_debug_printf_v
+ (" entry pool offset 0x%tx: abbrev %s, tag %s",
+ offset_in_entry_pool, pulongest (abbrev),
+ dwarf_tag_name (indexval.dwarf_tag));
+
cooked_index_flag flags = 0;
sect_offset die_offset {};
enum language lang = language_unknown;
@@ -238,6 +249,14 @@ mapped_debug_names_reader::scan_one_entry (const char *name,
bfd_get_filename (abfd)));
return nullptr;
}
+
+ dwarf_read_debug_printf_v (" %s (%s): %s",
+ get_DW_IDX_name (attr.dw_idx),
+ dwarf_form_name (attr.form),
+ (attr.form == DW_FORM_ref_addr
+ ? hex_string (ull)
+ : pulongest (ull)));
+
switch (attr.dw_idx)
{
case DW_IDX_compile_unit:
@@ -302,6 +321,11 @@ mapped_debug_names_reader::scan_one_entry (const char *name,
/* Skip if we couldn't find a valid CU/TU index. */
if (per_cu != nullptr)
{
+ dwarf_read_debug_printf_v
+ (" -> die_offset %s, per_cu offset %s",
+ sect_offset_str (die_offset),
+ sect_offset_str (per_cu->sect_off ()));
+
*result
= indices[next_shard].add (die_offset, (dwarf_tag) indexval.dwarf_tag,
flags, lang, name, nullptr, per_cu);
@@ -312,6 +336,8 @@ mapped_debug_names_reader::scan_one_entry (const char *name,
entry_pool_offsets_to_entries.emplace (offset_in_entry_pool, *result);
}
+ else
+ dwarf_read_debug_printf_v (" -> no valid CU/TU, skipping");
return entry;
}
@@ -323,6 +349,12 @@ mapped_debug_names_reader::scan_entries (uint32_t index,
const char *name,
const gdb_byte *entry)
{
+ /* Print a 1-based index, because this is what readelf and llvm-dwarfdump
+ do. This makes it easier to compare output side-by-side. */
+ dwarf_read_debug_printf_v
+ ("scanning entries for name %u: \"%s\" (entry pool offset 0x%tx)",
+ index + 1, name, entry - entry_pool);
+
std::vector<cooked_index_entry *> these_entries;
while (true)
@@ -347,6 +379,9 @@ mapped_debug_names_reader::scan_entries (uint32_t index,
void
mapped_debug_names_reader::scan_all_names ()
{
+ DWARF_READ_SCOPED_DEBUG_START_END
+ ("scanning %u names from .debug_names", name_count);
+
all_entries.resize (name_count);
/* In the first pass, create all the entries. */
@@ -377,6 +412,9 @@ mapped_debug_names_reader::scan_all_names ()
Otherwise, the DW_IDX_parent value is an offset into the entry pool, which
is not ambiguous. */
+ dwarf_read_debug_printf ("resolving %zu parent pointers",
+ needs_parent.size ());
+
for (auto &[entry, parent_val] : needs_parent)
{
if (augmentation_is_gdb && gdb_augmentation_version == 2)
@@ -463,6 +501,9 @@ build_and_check_tu_list_from_debug_names (dwarf2_per_objfile *per_objfile,
mapped_debug_names_reader &map,
dwarf2_section_info *section)
{
+ dwarf_read_debug_printf ("building TU list from .debug_names (%u TUs)",
+ map.tu_count);
+
struct objfile *objfile = per_objfile->objfile;
dwarf2_per_bfd *per_bfd = per_objfile->per_bfd;
@@ -477,6 +518,9 @@ build_and_check_tu_list_from_debug_names (dwarf2_per_objfile *per_objfile,
map.offset_size,
map.dwarf5_byte_order));
+ dwarf_read_debug_printf_v (" TU %u: offset %s", i,
+ sect_offset_str (sect_off));
+
/* Find the matching dwarf2_per_cu. */
dwarf2_per_cu *per_cu = dwarf2_find_unit ({ section, sect_off },
per_bfd);
@@ -508,15 +552,24 @@ read_debug_names_from_section (dwarf2_per_objfile *per_objfile,
struct dwarf2_section_info *section,
mapped_debug_names_reader &map)
{
+ DWARF_READ_SCOPED_DEBUG_START_END
+ ("reading .debug_names from %s", filename);
+
struct objfile *objfile = per_objfile->objfile;
if (section->empty ())
- return false;
+ {
+ dwarf_read_debug_printf ("section is empty");
+ return false;
+ }
/* Older elfutils strip versions could keep the section in the main
executable while splitting it for the separate debug info file. */
if ((section->get_flags () & SEC_HAS_CONTENTS) == 0)
- return false;
+ {
+ dwarf_read_debug_printf ("section has no contents");
+ return false;
+ }
section->read (objfile);
@@ -534,6 +587,11 @@ read_debug_names_from_section (dwarf2_per_objfile *per_objfile,
map.dwarf5_is_dwarf64 = bytes_read != 4;
map.offset_size = map.dwarf5_is_dwarf64 ? 8 : 4;
+
+ dwarf_read_debug_printf ("section size: %s, initial length: %s, "
+ "dwarf64: %d, offset_size: %d",
+ hex_string (section->size), hex_string (length),
+ map.dwarf5_is_dwarf64, map.offset_size);
if (bytes_read + length != section->size)
{
/* There may be multiple per-CU indices. */
@@ -549,6 +607,9 @@ read_debug_names_from_section (dwarf2_per_objfile *per_objfile,
/* The version number. */
uint16_t version = read_2_bytes (abfd, addr);
addr += 2;
+
+ dwarf_read_debug_printf ("version: %d", version);
+
if (version != 5)
{
warning (_("Section .debug_names in %ps has unsupported version %d, "
@@ -585,6 +646,11 @@ read_debug_names_from_section (dwarf2_per_objfile *per_objfile,
list. */
uint32_t foreign_tu_count = read_4_bytes (abfd, addr);
addr += 4;
+
+ dwarf_read_debug_printf ("cu_count: %u, tu_count: %u, "
+ "foreign_tu_count: %u",
+ map.cu_count, map.tu_count, foreign_tu_count);
+
if (foreign_tu_count != 0)
{
warning (_("Section .debug_names in %ps has unsupported %lu foreign TUs, "
@@ -613,11 +679,34 @@ read_debug_names_from_section (dwarf2_per_objfile *per_objfile,
string. This value is rounded up to a multiple of 4. */
uint32_t augmentation_string_size = read_4_bytes (abfd, addr);
addr += 4;
+
+ dwarf_read_debug_printf ("bucket_count: %u, name_count: %u, "
+ "abbrev_table_size: %u, "
+ "augmentation_string_size: %u",
+ map.bucket_count, map.name_count,
+ abbrev_table_size, augmentation_string_size);
+
augmentation_string_size += (-augmentation_string_size) & 3;
const auto augmentation_string
= gdb::make_array_view (addr, augmentation_string_size);
+ if (dwarf_read_debug >= 1)
+ {
+ std::string aug_repr;
+ for (size_t i = 0; i < augmentation_string_size; i++)
+ {
+ gdb_byte b = addr[i];
+ if (c_isprint (b))
+ aug_repr += b;
+ else
+ aug_repr += string_printf ("\\x%02x", b);
+ }
+
+ dwarf_read_debug_printf ("augmentation string: \"%s\"",
+ aug_repr.c_str ());
+ }
+
if (augmentation_string == gdb::make_array_view (dwarf5_augmentation_1))
{
warning (_(".debug_names created by an old version of gdb; ignoring"));
@@ -704,7 +793,26 @@ read_debug_names_from_section (dwarf2_per_objfile *per_objfile,
break;
indexval.attr_vec.push_back (std::move (attr));
}
+
+ dwarf_read_debug_printf_v
+ (" abbrev %s: tag %s, %zu attributes",
+ pulongest (index_num), dwarf_tag_name (indexval.dwarf_tag),
+ indexval.attr_vec.size ());
+
+ for (const auto &attr : indexval.attr_vec)
+ dwarf_read_debug_printf_v
+ (" %s %s%s",
+ get_DW_IDX_name (attr.dw_idx),
+ dwarf_form_name (attr.form),
+ (attr.form == DW_FORM_implicit_const
+ ? string_printf (" (%s)",
+ plongest (attr.implicit_const)).c_str ()
+ : ""));
}
+
+ dwarf_read_debug_printf ("%zu abbreviations read",
+ map.abbrev_map.size ());
+
if (addr != abbrev_table_start + abbrev_table_size)
{
warning (_("Section .debug_names in %ps has abbreviation_table "
@@ -728,6 +836,9 @@ build_and_check_cu_list_from_debug_names (dwarf2_per_bfd *per_bfd,
mapped_debug_names_reader &map,
dwarf2_section_info §ion)
{
+ dwarf_read_debug_printf ("building CU list from .debug_names (%u CUs)",
+ map.cu_count);
+
int nr_cus = per_bfd->num_comp_units;
if (map.cu_count != nr_cus)
@@ -745,6 +856,9 @@ build_and_check_cu_list_from_debug_names (dwarf2_per_bfd *per_bfd,
map.offset_size,
map.dwarf5_byte_order));
+ dwarf_read_debug_printf_v (" CU %u: offset %s", i,
+ sect_offset_str (sect_off));
+
/* Find the matching dwarf2_per_cu. */
dwarf2_per_cu *per_cu = dwarf2_find_unit ({ §ion, sect_off }, per_bfd);
diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index af7ef8d8f3db..c58cc1731f47 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -99,28 +99,8 @@
#include "extract-store-integer.h"
#include "cli/cli-style.h"
-/* When == 1, print basic high level tracing messages.
- When > 1, be more verbose.
- This is in contrast to the low level DIE reading of dwarf_die_debug. */
-static unsigned int dwarf_read_debug = 0;
-
-/* Print a "dwarf-read" debug statement if dwarf_read_debug is >= 1. */
-
-#define dwarf_read_debug_printf(fmt, ...) \
- debug_prefixed_printf_cond (dwarf_read_debug >= 1, "dwarf-read", fmt, \
- ##__VA_ARGS__)
-
-/* Print a "dwarf-read" debug statement if dwarf_read_debug is >= 2. */
-
-#define dwarf_read_debug_printf_v(fmt, ...) \
- debug_prefixed_printf_cond (dwarf_read_debug >= 2, "dwarf-read", fmt, \
- ##__VA_ARGS__)
-
-/* Print "dwarf-read" start/end debug statements. */
-
-#define DWARF_READ_SCOPED_DEBUG_START_END(fmt, ...) \
- scoped_debug_start_end ([] { return dwarf_read_debug >= 1; }, "dwarf-read", \
- fmt, ##__VA_ARGS__)
+/* See read.h. */
+unsigned int dwarf_read_debug = 0;
/* When non-zero, dump DIEs after they are read in. */
static unsigned int dwarf_die_debug = 0;
diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
index 1687bc52e432..e4ed84b08ad9 100644
--- a/gdb/dwarf2/read.h
+++ b/gdb/dwarf2/read.h
@@ -1344,6 +1344,30 @@ type *dwarf2_fetch_die_type_sect_off (sect_offset sect_off,
dwarf2_per_objfile *per_objfile,
const char **var_name = nullptr);
+/* When == 1, print basic high level tracing messages.
+ When > 1, be more verbose.
+ This is in contrast to the low level DIE reading of dwarf_die_debug. */
+
+extern unsigned int dwarf_read_debug;
+
+/* Print a "dwarf-read" debug statement if dwarf_read_debug is >= 1. */
+
+#define dwarf_read_debug_printf(fmt, ...) \
+ debug_prefixed_printf_cond (dwarf_read_debug >= 1, "dwarf-read", fmt, \
+ ##__VA_ARGS__)
+
+/* Print a "dwarf-read" debug statement if dwarf_read_debug is >= 2. */
+
+#define dwarf_read_debug_printf_v(fmt, ...) \
+ debug_prefixed_printf_cond (dwarf_read_debug >= 2, "dwarf-read", fmt, \
+ ##__VA_ARGS__)
+
+/* Print "dwarf-read" start/end debug statements. */
+
+#define DWARF_READ_SCOPED_DEBUG_START_END(fmt, ...) \
+ scoped_debug_start_end ([] { return dwarf_read_debug >= 1; }, "dwarf-read", \
+ fmt, ##__VA_ARGS__)
+
/* When non-zero, dump line number entries as they are read in. */
extern unsigned int dwarf_line_debug;
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH 7/8] gdb/dwarf: add more context to complaints in mapped_debug_names_reader::scan_one_entry
2026-03-16 23:19 [PATCH 0/8] Handle foreign type units in .debug_names simon.marchi
` (5 preceding siblings ...)
2026-03-16 23:19 ` [PATCH 6/8] gdb/dwarf: add debug output in read-debug-names.c simon.marchi
@ 2026-03-16 23:19 ` simon.marchi
2026-03-16 23:19 ` [PATCH 8/8] gdb/dwarf: read foreign type units simon.marchi
2026-04-16 19:59 ` [PATCH v2 0/8] Handle foreign type units in .debug_names Simon Marchi
8 siblings, 0 replies; 20+ messages in thread
From: simon.marchi @ 2026-03-16 23:19 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
From: Simon Marchi <simon.marchi@polymtl.ca>
I realize that complaints are not often enabled and shown, but if we
emit them, I think they should include enough context to help a user who
wishes to dig in the problem to pinpoint where the problem happened
exactly. For this reason, change the complaints in
mapped_debug_names_reader::scan_one_entry to include precisions
regarding which index entry is problematic exactly.
I am not attached to the particular format. I thought that because this
is extra contextual information, I would put it in the square brackets
at the end (which only shows the module name currently). However, it
would be nice to converge towards a format that we could replicate
elsewhere in the DWARF reader, so feel free to chime in.
I factored out the formatting to a separate function, because I will be
adding more of these in a subsequent patch, and it's easier to have the
formatting centralized.
I have not touched the "Unsupported .debug_names form" warning. If that
comes up, we probably don't care about the specific index entry, we just
need to implement support for that form.
Change-Id: Id68a0ae406a3ca620408576b81f893f05c7e3df2
---
gdb/dwarf2/read-debug-names.c | 43 ++++++++++++++++++++++++++---------
1 file changed, 32 insertions(+), 11 deletions(-)
diff --git a/gdb/dwarf2/read-debug-names.c b/gdb/dwarf2/read-debug-names.c
index 6ac65d101b16..487e2ea87edb 100644
--- a/gdb/dwarf2/read-debug-names.c
+++ b/gdb/dwarf2/read-debug-names.c
@@ -155,6 +155,25 @@ struct mapped_debug_names_reader
std::vector<std::vector<cooked_index_entry *>> all_entries;
};
+/* Emit a complaint about a specific index entry. */
+
+static void ATTRIBUTE_PRINTF (4, 5)
+complain_about_index_entry (bfd *abfd, const char *name,
+ ptrdiff_t offset_in_entry_pool, const char *fmt,
+ ...)
+{
+ va_list ap;
+ va_start (ap, fmt);
+ std::string msg = string_vprintf (fmt, ap);
+ va_end (ap);
+
+ msg += string_printf (_(" [in module %s, index entry for name %s,"
+ " entry pool offset 0x%tx]"),
+ bfd_get_filename (abfd), name, offset_in_entry_pool);
+
+ complaint ("%s", msg.c_str ());
+}
+
/* Scan a single entry from the entries table. Set *RESULT and PARENT
(if needed) and return the updated pointer on success, or return
nullptr on error, or at the end of the table. */
@@ -180,9 +199,11 @@ mapped_debug_names_reader::scan_one_entry (const char *name,
const auto indexval_it = abbrev_map.find (abbrev);
if (indexval_it == abbrev_map.cend ())
{
- complaint (_("Wrong .debug_names undefined abbrev code %s "
- "[in module %s]"),
- pulongest (abbrev), bfd_get_filename (abfd));
+ complain_about_index_entry (abfd, name, offset_in_entry_pool,
+ _("Wrong .debug_names abbrev code %s"),
+ pulongest (abbrev));
+ /* We can't go past this entry because we don't know its size, stop
+ reading this entry chain. */
return nullptr;
}
@@ -264,10 +285,10 @@ mapped_debug_names_reader::scan_one_entry (const char *name,
/* Don't crash on bad data. */
if (ull >= this->comp_units.size ())
{
- complaint (_(".debug_names entry has bad CU index %s"
- " [in module %s]"),
- pulongest (ull),
- bfd_get_filename (abfd));
+ complain_about_index_entry
+ (abfd, name, offset_in_entry_pool,
+ _(".debug_names entry has bad CU index %s"),
+ pulongest (ull));
continue;
}
@@ -279,10 +300,10 @@ mapped_debug_names_reader::scan_one_entry (const char *name,
/* Don't crash on bad data. */
if (ull >= this->type_units.size ())
{
- complaint (_(".debug_names entry has bad TU index %s"
- " [in module %s]"),
- pulongest (ull),
- bfd_get_filename (abfd));
+ complain_about_index_entry
+ (abfd, name, offset_in_entry_pool,
+ _(".debug_names entry has bad TU index %s"),
+ pulongest (ull));
continue;
}
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH 8/8] gdb/dwarf: read foreign type units
2026-03-16 23:19 [PATCH 0/8] Handle foreign type units in .debug_names simon.marchi
` (6 preceding siblings ...)
2026-03-16 23:19 ` [PATCH 7/8] gdb/dwarf: add more context to complaints in mapped_debug_names_reader::scan_one_entry simon.marchi
@ 2026-03-16 23:19 ` simon.marchi
2026-04-16 19:59 ` [PATCH v2 0/8] Handle foreign type units in .debug_names Simon Marchi
8 siblings, 0 replies; 20+ messages in thread
From: simon.marchi @ 2026-03-16 23:19 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
From: Simon Marchi <simon.marchi@efficios.com>
In DWARF 5, foreign type units are type units present in .dwo files.
Type units in .dwo files don't have a matching skeleton in the main
file. When an .debug_names index is present, it can't include those
type units in the regular type unit list, since that list specifies
offsets in the main file. They are instead listed in the foreign TU
list, which is basically just a list of type signatures.
In order to help the debugger locate these units (i.e. find the .dwo
file containing them), individual index entries referencing foreign type
units may also include a reference to a compile unit that the debugger
can follow to find the appropriate .dwo file.
This patch implements reading the .debug_names foreign TU list and using
these "hint" CUs to locate foreign type units. I use the term "hint"
throughout the code, but I don't mind another name if someone has a
better idea.
The first part is to read the actual foreign TU list from the
.debug_names index and create signatured_type objects out of them. This
is done in the new function create_foreign_type_units_from_debug_names.
Append the newly created signatured_type to the
mapped_debug_names_reader::foreign_type_units vector, which will be used
later to resolve DW_IDX_type_unit indices. Populate the
dwarf2_per_bfd::signatured_types set, which contains signatured_types
indexed by signature. And finally, transfer ownership of the object to
the dwarf2_per_bfd::all_units vector.
Previously, all dwarf2_per_cu (including signatured_type) objects were
created with a non-nullptr section. With foreign type units, we don't
know the section at creation time. We also don't know the offset into
section nor the size of the unit. Therefore, add a
dwarf2_per_bfd::allocate_signatured_type overload that takes just the
signature. Remove the "section != nullptr" assert from the
dwarf2_per_cu constructor, but add it to the other allocate_* methods.
Since the new create_foreign_type_units_from_debug_names function adds
items to the dwarf2_per_bfd::all_units vector, the vector needs to be
sorted after create_foreign_type_units_from_debug_names runs. Remove
the finalize_all_units call from create_all_units, making the callers
responsible to call it.
The next step is to read the hint CU attributes when scanning the index
entries. Rework mapped_debug_names_reader::scan_one_entry to remember
which kind of unit references it saw for the entry (comp, type and/or
foreign type) and then figure out what this means. The logic is:
- Did the entry reference a foreign type unit? If so, it's a foreign
type unit. Does it also reference a hint CU? If not, drop the
entry, there's nothing useful we can do with it.
- Otherwise, did the entry reference a (non-foreign) type unit? Then
it's a regular type unit. If so, it shouldn't also have a
DW_IDX_compile_unit.
- Otherwise, did the entry reference a comp unit? If so, it's a comp
unit.
- Otherwise, we don't know what unit the entry references, it's an
error.
Since the .debug_name index attaches hint CU attributes to individual
index entries, my initial implementation added the hint CU information
to the cooked_index_entry structure. I am not sure why DWARF 5 chose to
do it this way, as opposed to attaching one hint per foreign TU. Does
this mean that two type units with the same signature could be different
and, for a specific index entry, it would be important to find one
specific instance of the type unit over the others? I have no idea.
However, I know that the current GDB DWARF reader is not able to load
multiple type units with the same signature but different content. Once
it loads one type unit with a given signature, all subsequent references
to that signature will use that loaded type unit. I therefore chose to
have the .debug_names reader record just one hint CU per foreign TU.
This avoids growing the cooked_index_entry structure for nothing, and
having to pass through this information through multiple layers.
The next step is to locate the .dwo file containing the foreign TUs when
we need them. This sometimes makes use of the hint CU, but not always.
I identified these 3 code paths:
1. When the type unit gets expanded directly, for instance if you use
"ptype" and there is a direct match into the type unit. This case
is handled in load_full_type_unit, calling a new function
fill_in_sig_entry_from_per_cu_hint. This one uses the hint recorded
by the .debug_names reader. When a cooked index entry exists and
refers to a foreign TU for which the section is not yet known, we
know that there exists a hint, otherwise we wouldn't have created
the entry in the first place.
2. The second one is when the type unit is referenced by some other
unit. This case is handled in follow_die_sig_1, calling another new
function fill_in_sig_entry_from_dwo_file. In this case, we know
which unit is referring to the TU, so we use that unit's dwo file to
fill in the details. As explained in the comment, this is sometimes
just an optimization, but sometimes also necessary, if the TU
does not have a hint, due to it not containing any indexed name.
3. Similarly, in dwarf2_base_index_functions::expand_all_symtabs, we
might have to handle foreign type units for which we don't have a
hint. I initially implemented something in two passes, to go dig in
the dwo_file structures to find those TUs, but ended up choosing to
just skip them, for the reasons explained in the comment there.
Setting a dwarf2_per_cu's section a posteriori breaks the assumed
ordering of the dwarf2_per_bfd::all_units vector. After setting the
section, re-sort the vector.
Add a target board to exercise this new code. This board builds with:
- type units (-fdebug-types-section)
- split DWARF (-gsplit-dwarf)
- .debug_names index (created by GDB)
I ran the whole testsuite with this board file and it's not perfect, but
the results are comparable to the dwarf5-fission-debug-types board, for
instance.
There is one known failure that I am unable to get to the bottom of. It
seems orthogonal to my change though, more like an indexer or symbol
reader issue. There are maybe more of this kind, but this is one
example:
FAIL: gdb.ada/tick_length_array_enum_idx.exp: ptype variable_table'length (GDB internal error)
/home/smarchi/src/binutils-gdb/gdb/dwarf2/read.c:1839: internal-error: search_one: Assertion `symtab != nullptr' failed.
The problem appears to be that a cooked index lookup for symbol
variable_table says that a given TU should contain a match. But then
trying to expand the TU makes dw2_instantiate_symtab yield a nullptr
compunit_symtab, I think because the symbol reader found nothing
interesting symbol-wise. And then the assert in search_one triggers.
The issue seems sensitive to some aspects of the environment (gnat
version?). I am able to reproduce the issue on Arch Linux (gnat 15)
with:
$ make check TESTS="gdb.ada/tick_length_array_enum_idx.exp" RUNTESTFLAGS="--target_board=dwarf5-fission-debug-types-debug-names"
But it doesn't reproduce on Debian 13 (gnat 14), Ubuntu 24.04 (gnat
13) or Fedora Rawhide (gnat 16).
Change-Id: I0d4ccc1cbbce3a337794341744d24091e8549d7f
---
gdb/dwarf2/read-debug-names.c | 206 ++++++++++++++----
gdb/dwarf2/read.c | 167 +++++++++++++-
gdb/dwarf2/read.h | 37 +++-
gdb/dwarf2/types.h | 2 +
...dwarf5-fission-debug-types-debug-names.exp | 29 +++
5 files changed, 389 insertions(+), 52 deletions(-)
create mode 100644 gdb/testsuite/boards/dwarf5-fission-debug-types-debug-names.exp
diff --git a/gdb/dwarf2/read-debug-names.c b/gdb/dwarf2/read-debug-names.c
index 487e2ea87edb..a062c1ad2562 100644
--- a/gdb/dwarf2/read-debug-names.c
+++ b/gdb/dwarf2/read-debug-names.c
@@ -88,9 +88,13 @@ struct mapped_debug_names_reader
uint8_t offset_size = 0;
uint32_t cu_count = 0;
- uint32_t tu_count = 0, bucket_count = 0, name_count = 0;
+ uint32_t tu_count = 0;
+ uint32_t foreign_tu_count = 0;
+ uint32_t bucket_count = 0;
+ uint32_t name_count = 0;
const gdb_byte *cu_table_reordered = nullptr;
const gdb_byte *tu_table_reordered = nullptr;
+ const gdb_byte *foreign_tu_table_reordered = nullptr;
const uint32_t *bucket_table_reordered = nullptr;
const uint32_t *hash_table_reordered = nullptr;
const gdb_byte *name_table_string_offs_reordered = nullptr;
@@ -122,7 +126,11 @@ struct mapped_debug_names_reader
/* List of local TUs in the same order as found in the index (DWARF 5 section
6.1.1.4.3). */
- std::vector<dwarf2_per_cu *> type_units;
+ std::vector<signatured_type *> type_units;
+
+ /* List of foreign TUs in the same order as found in the index (DWARF 5
+ section 6.1.1.4.4). */
+ std::vector<signatured_type *> foreign_type_units;
/* Even though the scanning of .debug_names and creation of the
cooked index entries is done serially, we create multiple shards
@@ -217,7 +225,10 @@ mapped_debug_names_reader::scan_one_entry (const char *name,
cooked_index_flag flags = 0;
sect_offset die_offset {};
enum language lang = language_unknown;
- dwarf2_per_cu *per_cu = nullptr;
+ dwarf2_per_cu *comp_unit = nullptr;
+ signatured_type *type_unit = nullptr;
+ signatured_type *foreign_type_unit = nullptr;
+
for (const auto &attr : indexval.attr_vec)
{
ULONGEST ull;
@@ -282,7 +293,6 @@ mapped_debug_names_reader::scan_one_entry (const char *name,
{
case DW_IDX_compile_unit:
{
- /* Don't crash on bad data. */
if (ull >= this->comp_units.size ())
{
complain_about_index_entry
@@ -292,13 +302,17 @@ mapped_debug_names_reader::scan_one_entry (const char *name,
continue;
}
- per_cu = this->comp_units[ull];
+ comp_unit = this->comp_units[ull];
break;
}
case DW_IDX_type_unit:
{
- /* Don't crash on bad data. */
- if (ull >= this->type_units.size ())
+ if (ull < this->type_units.size ())
+ type_unit = this->type_units[ull];
+ else if (auto foreign_idx = ull - this->type_units.size ();
+ foreign_idx < this->foreign_type_units.size ())
+ foreign_type_unit = this->foreign_type_units[foreign_idx];
+ else
{
complain_about_index_entry
(abfd, name, offset_in_entry_pool,
@@ -307,15 +321,10 @@ mapped_debug_names_reader::scan_one_entry (const char *name,
continue;
}
- per_cu = this->type_units[ull];
break;
}
case DW_IDX_die_offset:
die_offset = sect_offset (ull);
- /* In a per-CU index (as opposed to a per-module index), index
- entries without CU attribute implicitly refer to the single CU. */
- if (per_cu == nullptr)
- per_cu = this->comp_units[0];
break;
case DW_IDX_parent:
parent = ull;
@@ -339,27 +348,117 @@ mapped_debug_names_reader::scan_one_entry (const char *name,
}
}
- /* Skip if we couldn't find a valid CU/TU index. */
- if (per_cu != nullptr)
+ /* From DWARF 5 section 6.1.1.3 ("Per-CU versus Per-Module Indexes"):
+
+ In a per-CU index, the CU list may have only a single entry, and index
+ entries may omit the CU attribute. */
+ if (comp_unit == nullptr
+ && type_unit == nullptr
+ && foreign_type_unit == nullptr)
{
- dwarf_read_debug_printf_v
- (" -> die_offset %s, per_cu offset %s",
- sect_offset_str (die_offset),
- sect_offset_str (per_cu->sect_off ()));
+ if (this->comp_units.size () != 1)
+ {
+ complain_about_index_entry
+ (abfd, name, offset_in_entry_pool,
+ _(".debug_names entry is missing CU index, but index has %zu CUs "
+ "(expecting 1)"),
+ this->comp_units.size ());
+ return entry;
+ }
- *result
- = indices[next_shard].add (die_offset, (dwarf_tag) indexval.dwarf_tag,
- flags, lang, name, nullptr, per_cu);
+ comp_unit = this->comp_units[0];
+ }
+
+ /* Figure out which unit this entry refers to. */
+ dwarf2_per_cu *actual_unit;
+
+ if (foreign_type_unit != nullptr)
+ {
+ if (type_unit != nullptr)
+ {
+ complain_about_index_entry
+ (abfd, name, offset_in_entry_pool,
+ _(".debug_names entry refers to two TUs"));
+ return entry;
+ }
- ++next_shard;
- if (next_shard == indices.size ())
- next_shard = 0;
+ /* Implement this part of DWARF 5 section 6.1.1.2 ("Structure of the Name
+ Index"):
+
+ When an index entry refers to a foreign type unit, it may have
+ attributes for both CU and (foreign) TU. For such entries, the CU
+ attribute gives the consumer a reference to the CU that may be used
+ to locate a split DWARF object file that contains the type unit. */
+ if (comp_unit == nullptr)
+ {
+ /* If a foreign type unit does not say which compile unit to follow
+ to find it, it's pretty much useless. */
+ complain_about_index_entry
+ (abfd, name, offset_in_entry_pool,
+ _(".debug_names entry refers to foreign type unit but "
+ "no compile unit"));
+ return entry;
+ }
+
+ actual_unit = foreign_type_unit;
+
+ /* .debug_names attaches one "hint" CU per index entry, insinuating that
+ for some names / index entries, it is important which .dwo we choose to
+ locate the TU. Even if it's important, the DWARF reader is not
+ currently able to load multiple versions of the same TU. So just
+ record one hint CU for each foreign TU. */
+ if (foreign_type_unit->hint_per_cu == nullptr)
+ {
+ foreign_type_unit->hint_per_cu = comp_unit;
+ dwarf_read_debug_printf_v
+ (" hint CU for foreign type unit with signature %s: "
+ "unit offset %s",
+ hex_string (foreign_type_unit->signature),
+ sect_offset_str (comp_unit->sect_off ()));
+ }
+ }
+ else if (type_unit != nullptr)
+ {
+ if (comp_unit != nullptr)
+ {
+ complain_about_index_entry
+ (abfd, name, offset_in_entry_pool,
+ _(".debug_names entry refers to both a CU and a TU"));
+ return entry;
+ }
- entry_pool_offsets_to_entries.emplace (offset_in_entry_pool, *result);
+ actual_unit = type_unit;
}
+ else if (comp_unit != nullptr)
+ actual_unit = comp_unit;
else
- dwarf_read_debug_printf_v (" -> no valid CU/TU, skipping");
+ {
+ complain_about_index_entry
+ (abfd, name, offset_in_entry_pool,
+ _(".debug_names entry is missing a CU or TU reference"));
+ return entry;
+ }
+
+ gdb_assert (actual_unit != nullptr);
+
+ if (foreign_type_unit != nullptr)
+ dwarf_read_debug_printf_v (" -> signature %s, DIE offset: %s",
+ hex_string (foreign_type_unit->signature),
+ sect_offset_str (die_offset));
+ else
+ dwarf_read_debug_printf_v (" -> unit offset %s, DIE offset %s",
+ sect_offset_str (actual_unit->sect_off ()),
+ sect_offset_str (die_offset));
+
+ *result = indices[next_shard].add (die_offset,
+ (dwarf_tag) indexval.dwarf_tag, flags,
+ lang, name, nullptr, actual_unit);
+ ++next_shard;
+ if (next_shard == indices.size ())
+ next_shard = 0;
+
+ entry_pool_offsets_to_entries.emplace (offset_in_entry_pool, *result);
return entry;
}
@@ -553,7 +652,7 @@ build_and_check_tu_list_from_debug_names (dwarf2_per_objfile *per_objfile,
return false;
}
- map.type_units.emplace_back (per_cu);
+ map.type_units.emplace_back (per_cu->as_signatured_type ());
}
return true;
@@ -665,22 +764,12 @@ read_debug_names_from_section (dwarf2_per_objfile *per_objfile,
/* foreign_type_unit_count - The number of TUs in the foreign TU
list. */
- uint32_t foreign_tu_count = read_4_bytes (abfd, addr);
+ map.foreign_tu_count = read_4_bytes (abfd, addr);
addr += 4;
dwarf_read_debug_printf ("cu_count: %u, tu_count: %u, "
"foreign_tu_count: %u",
- map.cu_count, map.tu_count, foreign_tu_count);
-
- if (foreign_tu_count != 0)
- {
- warning (_("Section .debug_names in %ps has unsupported %lu foreign TUs, "
- "ignoring .debug_names."),
- styled_string (file_name_style.style (),
- filename),
- static_cast<unsigned long> (foreign_tu_count));
- return false;
- }
+ map.cu_count, map.tu_count, map.foreign_tu_count);
/* bucket_count - The number of hash buckets in the hash lookup
table. */
@@ -761,6 +850,10 @@ read_debug_names_from_section (dwarf2_per_objfile *per_objfile,
map.tu_table_reordered = addr;
addr += map.tu_count * map.offset_size;
+ /* List of foreign TUs */
+ map.foreign_tu_table_reordered = addr;
+ addr += map.foreign_tu_count * sizeof (std::uint64_t);
+
/* Hash Lookup Table */
map.bucket_table_reordered = reinterpret_cast<const uint32_t *> (addr);
addr += map.bucket_count * 4;
@@ -918,6 +1011,35 @@ build_and_check_cu_lists_from_debug_names (dwarf2_per_bfd *per_bfd,
return build_and_check_cu_list_from_debug_names (per_bfd, dwz_map, dwz->info);
}
+/* Create signatured_type objects for the foreign TU list in MAP. */
+
+static void
+create_foreign_type_units_from_debug_names (dwarf2_per_bfd *per_bfd,
+ mapped_debug_names_reader &map)
+{
+ for (uint32_t i = 0; i < map.foreign_tu_count; ++i)
+ {
+ /* The list of foreign TUs is a list of 64-bit (DW_FORM_ref_sig8) type
+ signatures representing type units placed in .dwo files. All we know
+ about them for now is the signature. */
+ const gdb_byte *ptr
+ = map.foreign_tu_table_reordered + i * sizeof (std::uint64_t);
+ std::uint64_t sig
+ = extract_unsigned_integer (ptr, sizeof (std::uint64_t),
+ map.dwarf5_byte_order);
+
+ dwarf_read_debug_printf_v (" Foreign TU %u (%u): signature %s", i,
+ i + map.tu_count, hex_string (sig));
+
+ signatured_type_up sig_type
+ = per_bfd->allocate_signatured_type (sig);
+
+ map.foreign_type_units.emplace_back (sig_type.get ());
+ per_bfd->signatured_types.emplace (sig_type.get ());
+ per_bfd->all_units.emplace_back (sig_type.release ());
+ }
+}
+
/* See read-debug-names.h. */
bool
@@ -975,6 +1097,12 @@ dwarf2_read_debug_names (dwarf2_per_objfile *per_objfile)
return false;
}
+ create_foreign_type_units_from_debug_names (per_objfile->per_bfd, map);
+
+ /* create_foreign_type_units_from_debug_names may add more entries to the
+ ALL_UNITS vector, so it must be called before finalize_all_units. */
+ finalize_all_units (per_objfile->per_bfd);
+
per_bfd->debug_aranges.read (per_objfile->objfile);
/* There is a single address map for the whole index (coming from
diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index c58cc1731f47..39909d103cc3 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -745,6 +745,13 @@ static struct die_info *follow_die_ref (struct die_info *,
const struct attribute *,
struct dwarf2_cu **);
+static void fill_in_sig_entry_from_dwo_entry (dwarf2_per_objfile *per_objfile,
+ signatured_type *sig_entry,
+ dwo_unit *dwo_entry);
+
+static void fill_in_sig_entry_from_per_cu_hint
+ (dwarf2_per_objfile &per_objfile, signatured_type &sig_type);
+
static struct die_info *follow_die_sig (struct die_info *,
const struct attribute *,
struct dwarf2_cu **);
@@ -1457,6 +1464,7 @@ dwarf2_per_bfd::allocate_per_cu (dwarf2_section_info *section,
sect_offset sect_off, unsigned int length,
bool is_dwz)
{
+ gdb_assert (section != nullptr);
dwarf2_per_cu_up result (new dwarf2_per_cu (this, section, sect_off,
length, is_dwz));
result->index = all_units.size ();
@@ -1473,6 +1481,7 @@ dwarf2_per_bfd::allocate_signatured_type (dwarf2_section_info *section,
bool is_dwz,
ULONGEST signature)
{
+ gdb_assert (section != nullptr);
auto result
= std::make_unique<signatured_type> (this, section, sect_off, length,
is_dwz, signature);
@@ -1481,6 +1490,20 @@ dwarf2_per_bfd::allocate_signatured_type (dwarf2_section_info *section,
return result;
}
+/* See read.h. */
+
+signatured_type_up
+dwarf2_per_bfd::allocate_signatured_type (ULONGEST signature)
+{
+ auto result
+ = std::make_unique<signatured_type> (this, nullptr,
+ invalid_sect_offset,
+ 0, false, signature);
+ result->index = all_units.size ();
+ this->num_type_units++;
+ return result;
+}
+
/* Subroutine of dw2_get_file_names_reader to simplify it.
Return the file name for the given file_entry.
CU_INFO describes the CU's DW_AT_name and DW_AT_comp_dir.
@@ -1764,6 +1787,20 @@ dwarf2_base_index_functions::expand_all_symtabs (struct objfile *objfile)
for (dwarf2_per_cu *per_cu : all_units_range (per_objfile->per_bfd))
{
+ /* If a .debug_names index contains a foreign TU but no index entry
+ references it, the TU won't have a hint CU. This is a problem, because
+ we won't be able to locate it. Skip them, for the following reasons:
+
+ - If they don't contain anything worthy of a named index entry, they
+ are unlikely to contain anything interesting, symbol-wise.
+ - They are likely to be referred to by some other unit (otherwise,
+ why does it exist?), so will get expanded anyway. */
+ if (signatured_type *sig_type = per_cu->as_signatured_type ();
+ (sig_type != nullptr
+ && sig_type->section () == nullptr
+ && sig_type->hint_per_cu == nullptr))
+ continue;
+
/* We don't want to directly expand a partial CU, because if we
read it with the wrong language, then assertion failures can
be triggered later on. See PR symtab/23010. So, tell
@@ -2266,8 +2303,7 @@ add_type_unit (dwarf2_per_bfd *per_bfd, dwarf2_section_info *section,
return emplace_ret.first;
}
-/* Subroutine of lookup_dwo_signatured_type and lookup_dwp_signatured_type.
- Fill in SIG_ENTRY with DWO_ENTRY. */
+/* Fill in the missing details in SIG_ENTRY from DWO_ENTRY. */
static void
fill_in_sig_entry_from_dwo_entry (dwarf2_per_objfile *per_objfile,
@@ -2723,6 +2759,10 @@ cutu_reader::cutu_reader (dwarf2_per_cu &this_cu,
{
struct objfile *objfile = per_objfile.objfile;
struct dwarf2_section_info *section = this_cu.section ();
+
+ /* Any foreign TU must have been located before getting here. */
+ gdb_assert (section != nullptr);
+
bfd *abfd = section->get_bfd_owner ();
const gdb_byte *begin_info_ptr;
struct dwarf2_section_info *abbrev_section;
@@ -3321,6 +3361,7 @@ cooked_index_worker_debug_info::do_reading ()
dwarf2_per_bfd *per_bfd = m_per_objfile->per_bfd;
create_all_units (m_per_objfile);
+ finalize_all_units (m_per_objfile->per_bfd);
process_type_units (m_per_objfile, &m_index_storage);
if (!per_bfd->debug_aranges.empty ())
@@ -3402,15 +3443,9 @@ read_comp_units_from_section (dwarf2_per_objfile *per_objfile,
/* See read.h. */
void
-finalize_all_units (dwarf2_per_bfd *per_bfd)
+dwarf2_per_bfd::sort_all_units ()
{
- /* Sanity check. */
- gdb_assert (per_bfd->all_units.size ()
- == per_bfd->num_comp_units + per_bfd->num_type_units);
-
- /* Ensure that the all_units vector is in the expected order for
- dwarf2_find_containing_unit to be able to perform a binary search. */
- std::sort (per_bfd->all_units.begin (), per_bfd->all_units.end (),
+ std::sort (this->all_units.begin (), this->all_units.end (),
[] (const dwarf2_per_cu_up &a, const dwarf2_per_cu_up &b)
{
return all_units_less_than (*a, { b->section (),
@@ -3420,6 +3455,18 @@ finalize_all_units (dwarf2_per_bfd *per_bfd)
/* See read.h. */
+void
+finalize_all_units (dwarf2_per_bfd *per_bfd)
+{
+ /* Sanity check. */
+ gdb_assert (per_bfd->all_units.size ()
+ == per_bfd->num_comp_units + per_bfd->num_type_units);
+
+ per_bfd->sort_all_units ();
+}
+
+/* See read.h. */
+
void
create_all_units (dwarf2_per_objfile *per_objfile)
{
@@ -3453,7 +3500,6 @@ create_all_units (dwarf2_per_objfile *per_objfile)
per_objfile->per_bfd->signatured_types = std::move (sig_types);
- finalize_all_units (per_objfile->per_bfd);
remove_all_units.disable ();
}
@@ -17113,6 +17159,77 @@ dwarf2_get_die_type (cu_offset die_offset, dwarf2_per_cu *per_cu,
return get_die_type_at_offset (die_offset_sect, per_cu, per_objfile);
}
+/* Fill in the missing details in SIG_TYPE from DWO_FILE.
+
+ Error out if there isn't a type unit with the appropriate signature in
+ DWO_FILE. */
+
+static void
+fill_in_sig_entry_from_dwo_file (dwarf2_per_objfile &per_objfile,
+ signatured_type &sig_type, dwo_file &dwo_file)
+{
+ gdb_assert (sig_type.section () == nullptr);
+
+ dwo_unit *dwo_tu = dwo_file.find_tu (sig_type.signature);
+ if (dwo_tu == nullptr)
+ error (_(DWARF_ERROR_PREFIX
+ "Unable to locate type unit with signature %s in DWO file %s "
+ "[in module %s]"),
+ hex_string (sig_type.signature), dwo_file.dwo_name.c_str (),
+ objfile_name (per_objfile.objfile));
+
+ fill_in_sig_entry_from_dwo_entry (&per_objfile, &sig_type, dwo_tu);
+ sig_type.set_section (dwo_tu->section);
+ sig_type.set_sect_off (dwo_tu->sect_off);
+ sig_type.set_length (dwo_tu->length);
+
+ /* Setting SIG_TYPE's section invalidates the ALL_UNITS vector order,
+ re-sort it. */
+ per_objfile.per_bfd->sort_all_units ();
+}
+
+/* Fill in the missing details in SIG_TYPE from its hint CU.
+
+ Error out if we're unable to locate the .dwo file using the hint CU. */
+
+static void
+fill_in_sig_entry_from_per_cu_hint (dwarf2_per_objfile &per_objfile,
+ signatured_type &sig_type)
+{
+ gdb_assert (sig_type.section () == nullptr);
+ gdb_assert (sig_type.hint_per_cu != nullptr);
+
+ dwarf2_per_cu *hint_per_cu = sig_type.hint_per_cu;
+ dwo_unit *dwo_unit;
+
+ /* If a dwarf2_cu already exists for HINT_PER_CU, no need to build another
+ one. */
+ if (dwarf2_cu *hint_cu = per_objfile.get_cu (hint_per_cu);
+ hint_cu != nullptr)
+ dwo_unit = hint_cu->dwo_unit;
+ else
+ {
+ /* Constructing this cutu_reader will look for the .dwo file, creating
+ the dwo_file if necessary. */
+ abbrev_table_cache abbrev_table_cache;
+ cutu_reader reader (*hint_per_cu, per_objfile, nullptr, true,
+ std::nullopt, abbrev_table_cache);
+
+ /* dwo_unit is owned by the per_bfd, which outlives the reader. */
+ dwo_unit = reader.cu ()->dwo_unit;
+ }
+
+ if (dwo_unit == nullptr)
+ error (_(DWARF_ERROR_PREFIX
+ "Hint compilation unit for foreign type unit with signature %s is "
+ "not in a DWO file [in module %s]"),
+ hex_string (sig_type.signature),
+ objfile_name (per_objfile.objfile));
+
+ fill_in_sig_entry_from_dwo_file (per_objfile, sig_type,
+ *dwo_unit->dwo_file);
+}
+
/* Follow type unit SIG_TYPE referenced by SRC_DIE.
On entry *REF_CU is the CU of SRC_DIE.
On exit *REF_CU is the CU of the result.
@@ -17128,6 +17245,28 @@ follow_die_sig_1 (struct die_info *src_die, struct signatured_type *sig_type,
we can get here for DW_AT_imported_declaration where we need
the DIE not the type. */
+ /* If SIG_TYPE's section is not set, it means it's a .debug_names foreign
+ type unit for which we don't know the containing file or section yet.
+ Use the referencing CU as the hint: we know there must exist a TU with
+ the correct signature in its .dwo file, and that .dwo is already open,
+ might as well use it.
+
+ But this is not only an optimization. If a .debug_names foreign unit
+ does not have any index entry referencing it, then we don't have any
+ "hint CU" for it. The only hint we have is the referencing CU. */
+ if (sig_type->section () == nullptr)
+ {
+ if ((*ref_cu)->dwo_unit == nullptr)
+ error (_(DWARF_ERROR_PREFIX
+ "Unit referencing foreign type unit with signature %s is "
+ "not from a DWO file [in module %s]"),
+ hex_string (sig_type->signature),
+ objfile_name (per_objfile->objfile));
+
+ fill_in_sig_entry_from_dwo_file (*per_objfile, *sig_type,
+ *(*ref_cu)->dwo_unit->dwo_file);
+ }
+
dwarf2_cu *sig_cu = ensure_loaded_type_unit (sig_type, per_objfile);
if (sig_cu == nullptr)
@@ -17308,6 +17447,12 @@ load_full_type_unit (signatured_type *sig_type,
gdb_assert (sig_type->is_debug_types ());
gdb_assert (per_objfile->get_cu (sig_type) == nullptr);
+ /* If the section is not set, this is a .debug_names foreign type unit for
+ which we don't know the containing file nor section yet. For these, we
+ must have a "hint" CU to follow to find the file and section. */
+ if (sig_type->section () == nullptr)
+ fill_in_sig_entry_from_per_cu_hint (*per_objfile, *sig_type);
+
abbrev_table_cache abbrev_table_cache;
cutu_reader reader (*sig_type, *per_objfile, nullptr, false,
std::nullopt, abbrev_table_cache);
diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
index e4ed84b08ad9..845f50e938d3 100644
--- a/gdb/dwarf2/read.h
+++ b/gdb/dwarf2/read.h
@@ -108,7 +108,6 @@ struct dwarf2_per_cu
m_per_bfd (per_bfd)
{
gdb_assert (per_bfd != nullptr);
- gdb_assert (section != nullptr);
}
private:
@@ -267,9 +266,24 @@ struct dwarf2_per_cu
dwarf2_section_info *section () const
{ return m_section; }
+ /* Set the section of this unit. */
+ void set_section (dwarf2_section_info *section)
+ {
+ gdb_assert (section != nullptr);
+ gdb_assert (m_section == nullptr);
+ m_section = section;
+ }
+
sect_offset sect_off () const
{ return m_sect_off; }
+ /* Set the section offset of this unit. */
+ void set_sect_off (sect_offset sect_off)
+ {
+ gdb_assert (m_sect_off == invalid_sect_offset);
+ m_sect_off = sect_off;
+ }
+
bool is_dwz () const
{ return m_is_dwz; }
@@ -441,6 +455,12 @@ struct signatured_type : public dwarf2_per_cu
/* Containing DWO unit.
This field is valid iff per_cu.reading_dwo_directly. */
struct dwo_unit *dwo_unit = nullptr;
+
+ /* When using a .debug_names index, the section is not initially known for
+ foreign type units (aka skeletonless type units). This is a reference to
+ a CU that can be used to locate the .dwo file and section containing the
+ type unit. */
+ dwarf2_per_cu *hint_per_cu = nullptr;
};
using signatured_type_up = std::unique_ptr<signatured_type>;
@@ -711,6 +731,10 @@ struct dwarf2_per_bfd
return this->all_units[index].get ();
}
+ /* Ensure that the all_units vector is in the expected order for
+ dwarf2_find_containing_unit to be able to perform a binary search. */
+ void sort_all_units ();
+
/* Return the separate '.dwz' debug file. If there is no
.gnu_debugaltlink or .debug_sup section in the file, then the
result depends on REQUIRE: if REQUIRE is true, error out; if
@@ -745,6 +769,12 @@ struct dwarf2_per_bfd
bool is_dwz,
ULONGEST signature);
+ /* A convenience function to allocate a signatured_type. The
+ returned object has its "index" field set properly.
+
+ This one is used when only the signature is known at creation time. */
+ signatured_type_up allocate_signatured_type (ULONGEST signature);
+
/* Map all the DWARF section data needed when scanning
.debug_info. */
void map_info_sections (struct objfile *objfile);
@@ -1468,7 +1498,10 @@ extern const char *read_indirect_string_at_offset
extern void finalize_all_units (dwarf2_per_bfd *per_bfd);
-/* Create a list of all compilation units in OBJFILE. */
+/* Create a list of all compilation units in OBJFILE.
+
+ After it is done creating all units, the caller is responsible for calling
+ finalize_all_units. */
extern void create_all_units (dwarf2_per_objfile *per_objfile);
diff --git a/gdb/dwarf2/types.h b/gdb/dwarf2/types.h
index 169b0fd08678..cb8ce33940ca 100644
--- a/gdb/dwarf2/types.h
+++ b/gdb/dwarf2/types.h
@@ -31,6 +31,8 @@ DEFINE_OFFSET_TYPE (cu_offset, unsigned int);
section. */
DEFINE_OFFSET_TYPE (sect_offset, uint64_t);
+constexpr auto invalid_sect_offset = static_cast<sect_offset> (-1);
+
static inline const char *
sect_offset_str (sect_offset offset)
{
diff --git a/gdb/testsuite/boards/dwarf5-fission-debug-types-debug-names.exp b/gdb/testsuite/boards/dwarf5-fission-debug-types-debug-names.exp
new file mode 100644
index 000000000000..be8bebed1a43
--- /dev/null
+++ b/gdb/testsuite/boards/dwarf5-fission-debug-types-debug-names.exp
@@ -0,0 +1,29 @@
+# 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/>.
+
+# A board that compiles with DWARF 5, split DWARF (fission) and type units,
+# and generates a .debug_names index using GDB.
+#
+# Example usage:
+# bash$ make check \
+# RUNTESTFLAGS='--target_board=dwarf5-fission-debug-types-debug-names'
+
+set CC_WITH_TWEAKS_FLAGS "-n"
+load_board_description "cc-with-tweaks"
+
+set_board_info debug_flags \
+ [join { "-gdwarf-5" \
+ "-gsplit-dwarf" \
+ "-fdebug-types-section" }]
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 1/8] gdb/dwarf: refuse to produce .gdb_index when skeletonless type units are present
2026-03-16 23:19 ` [PATCH 1/8] gdb/dwarf: refuse to produce .gdb_index when skeletonless type units are present simon.marchi
@ 2026-03-17 12:57 ` Eli Zaretskii
2026-04-16 19:43 ` Simon Marchi
0 siblings, 1 reply; 20+ messages in thread
From: Eli Zaretskii @ 2026-03-17 12:57 UTC (permalink / raw)
To: simon.marchi; +Cc: gdb-patches
> From: simon.marchi@polymtl.ca
> Cc: Simon Marchi <simon.marchi@efficios.com>
> Date: Mon, 16 Mar 2026 19:19:19 -0400
>
>
> Change-Id: I1e2e0204c9c2b48763aa99ce63521ae4a5262b22
> ---
> 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(+)
> create mode 100644 gdb/testsuite/gdb.dwarf2/gdb-index-skeletonless-tu.c
> create mode 100644 gdb/testsuite/gdb.dwarf2/gdb-index-skeletonless-tu.exp
Thanks, the documentation part is okay.
Reviewed-By: Eli Zaretskii <eliz@gnu.org>
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 1/8] gdb/dwarf: refuse to produce .gdb_index when skeletonless type units are present
2026-03-17 12:57 ` Eli Zaretskii
@ 2026-04-16 19:43 ` Simon Marchi
0 siblings, 0 replies; 20+ messages in thread
From: Simon Marchi @ 2026-04-16 19:43 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: gdb-patches
On 3/17/26 8:57 AM, Eli Zaretskii wrote:
>> From: simon.marchi@polymtl.ca
>> Cc: Simon Marchi <simon.marchi@efficios.com>
>> Date: Mon, 16 Mar 2026 19:19:19 -0400
>>
>>
>> Change-Id: I1e2e0204c9c2b48763aa99ce63521ae4a5262b22
>> ---
>> 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(+)
>> create mode 100644 gdb/testsuite/gdb.dwarf2/gdb-index-skeletonless-tu.c
>> create mode 100644 gdb/testsuite/gdb.dwarf2/gdb-index-skeletonless-tu.exp
>
> Thanks, the documentation part is okay.
>
> Reviewed-By: Eli Zaretskii <eliz@gnu.org>
Thanks, I applied your tag locally and will include it in subsequent
series versions.
Simon
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 0/8] Handle foreign type units in .debug_names
2026-03-16 23:19 [PATCH 0/8] Handle foreign type units in .debug_names simon.marchi
` (7 preceding siblings ...)
2026-03-16 23:19 ` [PATCH 8/8] gdb/dwarf: read foreign type units simon.marchi
@ 2026-04-16 19:59 ` Simon Marchi
2026-04-16 19:59 ` [PATCH v2 1/8] gdb: refuse to produce .gdb_index when skeletonless type units are present Simon Marchi
` (7 more replies)
8 siblings, 8 replies; 20+ messages in thread
From: Simon Marchi @ 2026-04-16 19:59 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
This is v2 of:
https://inbox.sourceware.org/20260316232042.368080-1-simon.marchi%40polymtl.ca
There was a non trivial rebase over the "visit defining CUs" patch.
Other than that, nothing changed.
The "enumerate" series was merged in the mean time.
Simon Marchi (8):
gdb: refuse to produce .gdb_index when skeletonless type units are
present
gdb/dwarf: move dwo_unit and dwo_file to read.h
gdb/dwarf: move dwarf2_cu::section to cu.c
gdb/dwarf: add dwo_file::find_tus
gdb/dwarf: generate foreign type units in .debug_names
gdb/dwarf: add debug output in read-debug-names.c
gdb/dwarf: add more context to complaints in
mapped_debug_names_reader::scan_one_entry
gdb/dwarf: read foreign type units
gdb/doc/gdb.texinfo | 6 +
gdb/dwarf2/cu.c | 11 +
gdb/dwarf2/index-write.c | 175 ++++++++-
gdb/dwarf2/read-debug-names.c | 357 +++++++++++++++---
gdb/dwarf2/read.c | 349 ++++++++---------
gdb/dwarf2/read.h | 208 +++++++++-
gdb/dwarf2/types.h | 2 +
...dwarf5-fission-debug-types-debug-names.exp | 29 ++
.../gdb.dwarf2/gdb-index-skeletonless-tu.c | 23 ++
.../gdb.dwarf2/gdb-index-skeletonless-tu.exp | 103 +++++
gdbsupport/common-utils.h | 12 +
11 files changed, 1034 insertions(+), 241 deletions(-)
create mode 100644 gdb/testsuite/boards/dwarf5-fission-debug-types-debug-names.exp
create mode 100644 gdb/testsuite/gdb.dwarf2/gdb-index-skeletonless-tu.c
create mode 100644 gdb/testsuite/gdb.dwarf2/gdb-index-skeletonless-tu.exp
base-commit: cc9d69394628641235da1483788c0d938c9b6661
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 1/8] gdb: refuse to produce .gdb_index when skeletonless type units are present
2026-04-16 19:59 ` [PATCH v2 0/8] Handle foreign type units in .debug_names Simon Marchi
@ 2026-04-16 19:59 ` Simon Marchi
2026-04-16 19:59 ` [PATCH v2 2/8] gdb/dwarf: move dwo_unit and dwo_file to read.h Simon Marchi
` (6 subsequent siblings)
7 siblings, 0 replies; 20+ messages in thread
From: Simon Marchi @ 2026-04-16 19:59 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi, Eli Zaretskii
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 <eliz@gnu.org>
---
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(+)
create mode 100644 gdb/testsuite/gdb.dwarf2/gdb-index-skeletonless-tu.c
create mode 100644 gdb/testsuite/gdb.dwarf2/gdb-index-skeletonless-tu.exp
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 1fc052bc1e65..d3d15de80262 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -51174,6 +51174,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 0f940920862f..c8375350b791 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 000000000000..c86a4322de82
--- /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 000000000000..31a45e224cad
--- /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 fb4b8ea28ced..de83a715ac45 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
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 2/8] gdb/dwarf: move dwo_unit and dwo_file to read.h
2026-04-16 19:59 ` [PATCH v2 0/8] Handle foreign type units in .debug_names Simon Marchi
2026-04-16 19:59 ` [PATCH v2 1/8] gdb: refuse to produce .gdb_index when skeletonless type units are present Simon Marchi
@ 2026-04-16 19:59 ` Simon Marchi
2026-04-16 19:59 ` [PATCH v2 3/8] gdb/dwarf: move dwarf2_cu::section to cu.c Simon Marchi
` (5 subsequent siblings)
7 siblings, 0 replies; 20+ messages in thread
From: Simon Marchi @ 2026-04-16 19:59 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
From: Simon Marchi <simon.marchi@polymtl.ca>
This is to allow index-write.c to see these types, in a later patch.
Change-Id: Ia32e0643f95561d3a1bfb67d501c8e20f5682f0e
---
gdb/dwarf2/read.c | 125 ----------------------------------------------
gdb/dwarf2/read.h | 125 +++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 124 insertions(+), 126 deletions(-)
diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index 4035bba6e45b..45fc8435ef62 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -266,86 +266,6 @@ struct loclists_rnglists_header
unsigned int offset_entry_count;
};
-/* These sections are what may appear in a (real or virtual) DWO file. */
-
-struct dwo_sections
-{
- struct dwarf2_section_info abbrev;
- struct dwarf2_section_info line;
- struct dwarf2_section_info loc;
- struct dwarf2_section_info loclists;
- struct dwarf2_section_info macinfo;
- struct dwarf2_section_info macro;
- struct dwarf2_section_info rnglists;
- struct dwarf2_section_info str;
- struct dwarf2_section_info str_offsets;
- /* In the case of a virtual DWO file, these two are unused. */
- std::vector<dwarf2_section_info> infos;
- std::vector<dwarf2_section_info> types;
-};
-
-/* CUs/TUs in DWP/DWO files. */
-
-struct dwo_unit
-{
- /* Backlink to the containing struct dwo_file. */
- struct dwo_file *dwo_file = nullptr;
-
- /* The "id" that distinguishes this CU/TU.
- .debug_info calls this "dwo_id", .debug_types calls this "signature".
- Since signatures came first, we stick with it for consistency. */
- ULONGEST signature = 0;
-
- /* The section this CU/TU lives in, in the DWO file. */
- dwarf2_section_info *section = nullptr;
-
- /* This is set if SECTION is owned by this dwo_unit. */
- dwarf2_section_info_up section_holder;
-
- /* Same as dwarf2_per_cu::{sect_off,length} but in the DWO section. */
- sect_offset sect_off {};
- unsigned int length = 0;
-
- /* For types, offset in the type's DIE of the type defined by this TU. */
- cu_offset type_offset_in_tu;
-};
-
-using dwo_unit_up = std::unique_ptr<dwo_unit>;
-
-/* Hash function for dwo_unit objects, based on the signature. */
-
-struct dwo_unit_hash
-{
- using is_transparent = void;
-
- std::size_t operator() (ULONGEST signature) const noexcept
- { return signature; }
-
- std::size_t operator() (const dwo_unit_up &unit) const noexcept
- { return (*this) (unit->signature); }
-};
-
-/* Equal function for dwo_unit objects, based on the signature.
-
- The signature is assumed to be unique within the DWO file. So while object
- file CU dwo_id's always have the value zero, that's OK, assuming each object
- file DWO file has only one CU, and that's the rule for now. */
-
-struct dwo_unit_eq
-{
- using is_transparent = void;
-
- bool operator() (ULONGEST sig, const dwo_unit_up &unit) const noexcept
- { return sig == unit->signature; }
-
- bool operator() (const dwo_unit_up &a, const dwo_unit_up &b) const noexcept
- { return (*this) (a->signature, b); }
-};
-
-/* Set of dwo_unit object, using their signature as identity. */
-
-using dwo_unit_set = gdb::unordered_set<dwo_unit_up, dwo_unit_hash, dwo_unit_eq>;
-
/* include/dwarf2.h defines the DWP section codes.
It defines a max value but it doesn't define a min value, which we
use for error checking, so provide one. */
@@ -355,51 +275,6 @@ enum dwp_v2_section_ids
DW_SECT_MIN = 1
};
-/* Data for one DWO file.
-
- This includes virtual DWO files (a virtual DWO file is a DWO file as it
- appears in a DWP file). DWP files don't really have DWO files per se -
- comdat folding of types "loses" the DWO file they came from, and from
- a high level view DWP files appear to contain a mass of random types.
- However, to maintain consistency with the non-DWP case we pretend DWP
- files contain virtual DWO files, and we assign each TU with one virtual
- DWO file (generally based on the line and abbrev section offsets -
- a heuristic that seems to work in practice). */
-
-struct dwo_file
-{
- dwo_file () = default;
- DISABLE_COPY_AND_ASSIGN (dwo_file);
-
- /* The DW_AT_GNU_dwo_name or DW_AT_dwo_name attribute.
- For virtual DWO files the name is constructed from the section offsets
- of abbrev,line,loc,str_offsets so that we combine virtual DWO files
- from related CU+TUs. */
- std::string dwo_name;
-
- /* The DW_AT_comp_dir attribute. */
- const char *comp_dir = nullptr;
-
- /* The bfd, when the file is open. Otherwise this is NULL.
- This is unused(NULL) for virtual DWO files where we use dwp_file.dbfd. */
- gdb_bfd_ref_ptr dbfd;
-
- /* The sections that make up this DWO file.
- Remember that for virtual DWO files in DWP V2 or DWP V5, these are virtual
- sections (for lack of a better name). */
- struct dwo_sections sections {};
-
- /* The CUs in the file.
-
- Multiple CUs per DWO are supported as an extension to handle LLVM's Link
- Time Optimization output (where multiple source files may be compiled into
- a single object/dwo pair). */
- dwo_unit_set cus;
-
- /* Table of TUs in the file. */
- dwo_unit_set tus;
-};
-
/* See dwarf2/read.h. */
std::size_t
diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
index 1150c3cfdcad..dfe1e671b9c8 100644
--- a/gdb/dwarf2/read.h
+++ b/gdb/dwarf2/read.h
@@ -526,7 +526,130 @@ using signatured_type_set
= gdb::unordered_set<signatured_type *, signatured_type_hash,
signatured_type_eq>;
-struct dwo_file;
+/* CUs/TUs in DWP/DWO files. */
+
+struct dwo_unit
+{
+ /* Backlink to the containing struct dwo_file. */
+ struct dwo_file *dwo_file = nullptr;
+
+ /* The "id" that distinguishes this CU/TU.
+ .debug_info calls this "dwo_id", .debug_types calls this "signature".
+ Since signatures came first, we stick with it for consistency. */
+ ULONGEST signature = 0;
+
+ /* The section this CU/TU lives in, in the DWO file. */
+ dwarf2_section_info *section = nullptr;
+
+ /* This is set if SECTION is owned by this dwo_unit. */
+ dwarf2_section_info_up section_holder;
+
+ /* Same as dwarf2_per_cu::{sect_off,length} but in the DWO section. */
+ sect_offset sect_off {};
+ unsigned int length = 0;
+
+ /* For types, offset in the type's DIE of the type defined by this TU. */
+ cu_offset type_offset_in_tu;
+};
+
+using dwo_unit_up = std::unique_ptr<dwo_unit>;
+
+/* These sections are what may appear in a (real or virtual) DWO file. */
+
+struct dwo_sections
+{
+ struct dwarf2_section_info abbrev;
+ struct dwarf2_section_info line;
+ struct dwarf2_section_info loc;
+ struct dwarf2_section_info loclists;
+ struct dwarf2_section_info macinfo;
+ struct dwarf2_section_info macro;
+ struct dwarf2_section_info rnglists;
+ struct dwarf2_section_info str;
+ struct dwarf2_section_info str_offsets;
+ /* In the case of a virtual DWO file, these two are unused. */
+ std::vector<dwarf2_section_info> infos;
+ std::vector<dwarf2_section_info> types;
+};
+
+/* Hash function for dwo_unit objects, based on the signature. */
+
+struct dwo_unit_hash
+{
+ using is_transparent = void;
+
+ std::size_t operator() (ULONGEST signature) const noexcept
+ { return signature; }
+
+ std::size_t operator() (const dwo_unit_up &unit) const noexcept
+ { return (*this) (unit->signature); }
+};
+
+/* Equal function for dwo_unit objects, based on the signature.
+
+ The signature is assumed to be unique within the DWO file. So while object
+ file CU dwo_id's always have the value zero, that's OK, assuming each object
+ file DWO file has only one CU, and that's the rule for now. */
+
+struct dwo_unit_eq
+{
+ using is_transparent = void;
+
+ bool operator() (ULONGEST sig, const dwo_unit_up &unit) const noexcept
+ { return sig == unit->signature; }
+
+ bool operator() (const dwo_unit_up &a, const dwo_unit_up &b) const noexcept
+ { return (*this) (a->signature, b); }
+};
+
+/* Set of dwo_unit object, using their signature as identity. */
+
+using dwo_unit_set = gdb::unordered_set<dwo_unit_up, dwo_unit_hash, dwo_unit_eq>;
+
+/* Data for one DWO file.
+
+ This includes virtual DWO files (a virtual DWO file is a DWO file as it
+ appears in a DWP file). DWP files don't really have DWO files per se -
+ comdat folding of types "loses" the DWO file they came from, and from
+ a high level view DWP files appear to contain a mass of random types.
+ However, to maintain consistency with the non-DWP case we pretend DWP
+ files contain virtual DWO files, and we assign each TU with one virtual
+ DWO file (generally based on the line and abbrev section offsets -
+ a heuristic that seems to work in practice). */
+
+struct dwo_file
+{
+ dwo_file () = default;
+ DISABLE_COPY_AND_ASSIGN (dwo_file);
+
+ /* The DW_AT_GNU_dwo_name or DW_AT_dwo_name attribute.
+ For virtual DWO files the name is constructed from the section offsets
+ of abbrev,line,loc,str_offsets so that we combine virtual DWO files
+ from related CU+TUs. */
+ std::string dwo_name;
+
+ /* The DW_AT_comp_dir attribute. */
+ const char *comp_dir = nullptr;
+
+ /* The bfd, when the file is open. Otherwise this is NULL.
+ This is unused(NULL) for virtual DWO files where we use dwp_file.dbfd. */
+ gdb_bfd_ref_ptr dbfd;
+
+ /* The sections that make up this DWO file.
+ Remember that for virtual DWO files in DWP V2 or DWP V5, these are virtual
+ sections (for lack of a better name). */
+ struct dwo_sections sections {};
+
+ /* The CUs in the file.
+
+ Multiple CUs per DWO are supported as an extension to handle LLVM's Link
+ Time Optimization output (where multiple source files may be compiled into
+ a single object/dwo pair). */
+ dwo_unit_set cus;
+
+ /* Table of TUs in the file. */
+ dwo_unit_set tus;
+};
using dwo_file_up = std::unique_ptr<dwo_file>;
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 3/8] gdb/dwarf: move dwarf2_cu::section to cu.c
2026-04-16 19:59 ` [PATCH v2 0/8] Handle foreign type units in .debug_names Simon Marchi
2026-04-16 19:59 ` [PATCH v2 1/8] gdb: refuse to produce .gdb_index when skeletonless type units are present Simon Marchi
2026-04-16 19:59 ` [PATCH v2 2/8] gdb/dwarf: move dwo_unit and dwo_file to read.h Simon Marchi
@ 2026-04-16 19:59 ` Simon Marchi
2026-04-16 19:59 ` [PATCH v2 4/8] gdb/dwarf: add dwo_file::find_tus Simon Marchi
` (4 subsequent siblings)
7 siblings, 0 replies; 20+ messages in thread
From: Simon Marchi @ 2026-04-16 19:59 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
Following the previous patch that moves the dwo_unit structure from
dwarf2/read.c to dwarf2/read.h, dwarf2_cu::section has no reason to be
implemented in dwarf2/read.c anymore. Move it to dwarf2/cu.c.
Change-Id: I67e2bb42d878ac18e4bf3460d75f1394477a46ce
---
gdb/dwarf2/cu.c | 11 +++++++++++
gdb/dwarf2/read.c | 14 +-------------
2 files changed, 12 insertions(+), 13 deletions(-)
diff --git a/gdb/dwarf2/cu.c b/gdb/dwarf2/cu.c
index c3bfd15f14a1..3c89bd960d56 100644
--- a/gdb/dwarf2/cu.c
+++ b/gdb/dwarf2/cu.c
@@ -58,6 +58,17 @@ dwarf2_cu::dwarf2_cu (dwarf2_per_cu *per_cu, dwarf2_per_objfile *per_objfile)
/* See cu.h. */
+const dwarf2_section_info &
+dwarf2_cu::section () const
+{
+ if (this->dwo_unit != nullptr)
+ return *this->dwo_unit->section;
+ else
+ return *this->per_cu->section ();
+}
+
+/* See cu.h. */
+
struct type *
dwarf2_cu::addr_sized_int_type (bool unsigned_p) const
{
diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index 45fc8435ef62..b627966a0c99 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -5807,19 +5807,7 @@ read_file_scope (struct die_info *die, struct dwarf2_cu *cu)
}
}
-/* See cu.h.
-
- This function is defined in this file (instead of cu.c) because it needs
- to see the definition of struct dwo_unit. */
-
-const dwarf2_section_info &
-dwarf2_cu::section () const
-{
- if (this->dwo_unit != nullptr)
- return *this->dwo_unit->section;
- else
- return *this->per_cu->section ();
-}
+/* See cu.h. */
/* See cu.h.
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 4/8] gdb/dwarf: add dwo_file::find_tus
2026-04-16 19:59 ` [PATCH v2 0/8] Handle foreign type units in .debug_names Simon Marchi
` (2 preceding siblings ...)
2026-04-16 19:59 ` [PATCH v2 3/8] gdb/dwarf: move dwarf2_cu::section to cu.c Simon Marchi
@ 2026-04-16 19:59 ` Simon Marchi
2026-04-16 19:59 ` [PATCH v2 5/8] gdb/dwarf: generate foreign type units in .debug_names Simon Marchi
` (3 subsequent siblings)
7 siblings, 0 replies; 20+ messages in thread
From: Simon Marchi @ 2026-04-16 19:59 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
Add this little helper to make finding a TU a bit simpler. I always
find the STL way of doing things cryptic, so I think that hiding it in
small helper methods makes the code clearer.
A subsequent patch will add more uses of it
Change-Id: Ibfb20d0e44c65d2ff729f3e0980ec4435f223aef
---
gdb/dwarf2/read.c | 18 ++++++++++++++----
gdb/dwarf2/read.h | 5 +++++
2 files changed, 19 insertions(+), 4 deletions(-)
diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index b627966a0c99..575b2383b20a 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -266,6 +266,18 @@ struct loclists_rnglists_header
unsigned int offset_entry_count;
};
+/* See read.h. */
+
+dwo_unit *
+dwo_file::find_tu (ULONGEST signature) const
+{
+ auto it = this->tus.find (signature);
+ if (it == this->tus.end ())
+ return nullptr;
+
+ return it->get ();
+}
+
/* include/dwarf2.h defines the DWP section codes.
It defines a max value but it doesn't define a min value, which we
use for error checking, so provide one. */
@@ -2352,12 +2364,10 @@ lookup_dwo_signatured_type (struct dwarf2_cu *cu, ULONGEST sig)
/* Note: cu->dwo_unit is the dwo_unit that references this TU, not the
dwo_unit of the TU itself. */
dwo_file *dwo_file = cu->dwo_unit->dwo_file;
- auto it = dwo_file->tus.find (sig);
- if (it == dwo_file->tus.end ())
+ dwo_unit *dwo_entry = dwo_file->find_tu (sig);
+ if (dwo_entry == nullptr)
return nullptr;
- dwo_unit *dwo_entry = it->get ();
-
/* If the global table doesn't have an entry for this TU, add one. */
if (sig_type_it == per_bfd->signatured_types.end ())
{
diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
index dfe1e671b9c8..17a158defcae 100644
--- a/gdb/dwarf2/read.h
+++ b/gdb/dwarf2/read.h
@@ -622,6 +622,11 @@ struct dwo_file
dwo_file () = default;
DISABLE_COPY_AND_ASSIGN (dwo_file);
+ /* Look for a type unit with signature SIGNATURE in this dwo_file.
+
+ Return nullptr if not found. */
+ dwo_unit *find_tu (ULONGEST signature) const;
+
/* The DW_AT_GNU_dwo_name or DW_AT_dwo_name attribute.
For virtual DWO files the name is constructed from the section offsets
of abbrev,line,loc,str_offsets so that we combine virtual DWO files
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 5/8] gdb/dwarf: generate foreign type units in .debug_names
2026-04-16 19:59 ` [PATCH v2 0/8] Handle foreign type units in .debug_names Simon Marchi
` (3 preceding siblings ...)
2026-04-16 19:59 ` [PATCH v2 4/8] gdb/dwarf: add dwo_file::find_tus Simon Marchi
@ 2026-04-16 19:59 ` Simon Marchi
2026-04-16 19:59 ` [PATCH v2 6/8] gdb/dwarf: add debug output in read-debug-names.c Simon Marchi
` (2 subsequent siblings)
7 siblings, 0 replies; 20+ messages in thread
From: Simon Marchi @ 2026-04-16 19:59 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
The DWARF 5 .debug_names index makes the distinction between local type
units and foreign type units. Local type units are those present
directly in the main file. Foreign type units are those present in .dwo
files. GDB only knows how to produce local type units today, which
leads to invalid indexes whenever there are type units in .dwo files.
To observe this, run test gdb.dwarf2/fission-with-type-unit.exp with
board cc-with-debug-names. The test passes, but we see this in the log:
(gdb) file
/home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.dwarf2/fission-with-type-unit/fission-with-type-unit^M
Reading symbols from
/home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.dwarf2/fission-with-type-unit/fission-with-type-unit...^M
warning: Section .debug_names has incorrect entry in TU table,
ignoring .debug_names.^M
These are the units involved in this test. The first and last CUs are
dummy CUs added by the DWARF assembler, they are not important.
- 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 the content of the produced .debug_names:
Contents of the .debug_names section:
$ readelf --debug-dump=gdb_index testsuite/outputs/gdb.dwarf2/fission-with-type-unit/fission-with-type-unit
...
Version 5
Augmentation string: 47 44 42 33 00 00 00 00 ("GDB3")
CU table:
[ 0] 0
[ 1] 0xc
[ 2] 0x25
TU table:
[ 0] 0
Foreign TU table:
Used 0 of 0 buckets.
Symbol table:
[ 1] global_var: <0><1> DW_TAG_variable DW_IDX_compile_unit=(udata)=1 DW_IDX_die_offset=(ref_addr)=<0x1c> DW_IDX_GNU_language=(udata)=0 DW_IDX_GNU_internal=(flag_present)=1
[ 2] int:
<0x8><2> DW_TAG_base_type DW_IDX_compile_unit=(udata)=1 DW_IDX_die_offset=(ref_addr)=<0x15> DW_IDX_GNU_language=(udata)=0
<0xf><3> DW_TAG_base_type DW_IDX_type_unit=(udata)=0 DW_IDX_die_offset=(ref_addr)=<0x19> DW_IDX_GNU_language=(udata)=0
The TU table claims that there is a TU at offset 0 in the main file
(fission-with-type-unit). This is wrong: the TU is in
fission-with-type-unit-dw.dwo. It should be listed in the Foreign TU
table instead.
This patch therefore teaches GDB to use the foreign TU table for TUs in
.dwo files.
A note about foreign type units and skeletons: in the early history of
split DWARF and type units, 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. But if for some reason a type unit in a .dwo file had a
matching skeleton in the main file, then it would be ok for that TU to
be listed in the "TU table". The offset would be that of the skeleton.
While the list of CUs and local TUs contain the offset within the
.debug_info section where to find the unit, the foreign TU list only
contains the 8-byte signature of the types. With just that, a reader
wouldn't be able to easily locate a .dwo that contain the type with a
given signature.
To help with this, index entries for foreign type units may also include
a reference to a compilation unit that can be followed in order to find
a .dwo file containing the type. This patch implements it.
Implementation details
----------------------
The first change is the addition of the dwo_unit::per_cu field, which
allows going from the dwo_unit to the dwarf2_per_cu structure (which
describes the skeleton) that was used to lookup this dwo_unit. This
fields starts at nullptr, and it gets set in lookup_dwo_cutu whenever we
look up the dwo_unit for a given dwarf2_per_cu. This will come handy
later. I made this field an std::atomic, because I think it would be
possible to craft a weird test case that would make two indexer threads
try to set the field on the same dwo_unit. During normal operation, we
expect the field for each dwo_unit representing a CU to be written
exactly once.
In index-write.c, change the get_unit_lists function in
dwarf2/index-write.c to segregate local and foreign type units. Then,
update write_debug_names to emit the list of foreign TUs in the
.debug_names header. This consists of a list of type signatures.
In debug_names::build, for foreign type units, emit a
DW_IDX_compile_unit field. This is the reference to the CU that can be
used to locate the .dwo file containing that type unit. To obtain the
value for this field, look up a CU in the same dwo_file that has its
dwo_unit::per_cu field set (typically there will be exactly one CU, and
the field will be set).
With this patch, the index for the test case above looks like:
$ readelf --debug-dump=gdb_index testsuite/outputs/gdb.dwarf2/fission-with-type-unit/fission-with-type-unit
...
Version 5
Augmentation string: 47 44 42 33 00 00 00 00 ("GDB3")
CU table:
[ 0] 0
[ 1] 0xc
[ 2] 0x25
TU table:
Foreign TU table:
[ 0] 000000000000cafe
Used 0 of 0 buckets.
Symbol table:
[ 1] global_var: <0><1> DW_TAG_variable DW_IDX_compile_unit=(udata)=1 DW_IDX_die_offset=(ref_addr)=<0x1c> DW_IDX_GNU_language=(udata)=0 DW_IDX_GNU_internal=(flag_present)=1
[ 2] int:
<0x8><2> DW_TAG_base_type DW_IDX_compile_unit=(udata)=1 DW_IDX_die_offset=(ref_addr)=<0x15> DW_IDX_GNU_language=(udata)=0
<0xf><3> DW_TAG_base_type DW_IDX_type_unit=(udata)=0 DW_IDX_compile_unit=(udata)=1 DW_IDX_die_offset=(ref_addr)=<0x19> DW_IDX_GNU_language=(udata)=0 ...
We can see that the TU is correctly placed in the foreign TU list, and
that the index entry (the last line) points to the TU at index 0, but
also to the CU at index 1, which is indeed the CU that the reader can
follow to find the type unit.
With this patch, GDB still rejects the index:
(gdb) file /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.dwarf2/fission-with-type-unit/fission-with-type-unit
Reading symbols from /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.dwarf2/fission-with-type-unit/fission-with-type-unit...
warning: Section .debug_names in /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.dwarf2/fission-with-type-unit/fission-with-type-unit has unsupported 1 foreign TUs, ignoring .debug_names.
But at least, we don't produce a bogus index anymore, that's already an
improvement. A following patch in this series implements the reading
side
Change-Id: I311fd7b4ca57d9ff6d64ae08df805c6635961eed
---
gdb/dwarf2/index-write.c | 161 +++++++++++++++++++++++++++++++++------
gdb/dwarf2/read.c | 9 +++
gdb/dwarf2/read.h | 17 +++++
3 files changed, 163 insertions(+), 24 deletions(-)
diff --git a/gdb/dwarf2/index-write.c b/gdb/dwarf2/index-write.c
index c8375350b791..d404f775ef77 100644
--- a/gdb/dwarf2/index-write.c
+++ b/gdb/dwarf2/index-write.c
@@ -633,6 +633,10 @@ write_address_map (const addrmap *addrmap, data_buf &addr_vec,
addrmap_index_data.previous_cu_index);
}
+/* Is this symbol a compile unit, local type unit (type unit in the main file)
+ or foreign type unit (type unit in a .dwo file)? */
+enum class unit_kind { cu, local_tu, foreign_tu };
+
/* 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. */
@@ -645,6 +649,22 @@ is_foreign_tu (const signatured_type *tu)
return endswith (tu->section ()->get_name (), ".dwo");
}
+/* Determine the unit_kind for PER_CU. */
+
+static unit_kind
+classify_unit (const dwarf2_per_cu &per_cu)
+{
+ if (auto sig_type = per_cu.as_signatured_type (); sig_type != nullptr)
+ {
+ if (is_foreign_tu (sig_type))
+ return unit_kind::foreign_tu;
+ else
+ return unit_kind::local_tu;
+ }
+ else
+ return unit_kind::cu;
+}
+
/* DWARF-5 .debug_names builder. */
class debug_names
{
@@ -668,9 +688,6 @@ class debug_names
return dwarf5_is_dwarf64 ? 8 : 4;
}
- /* Is this symbol from DW_TAG_compile_unit or DW_TAG_type_unit? */
- enum class unit_kind { cu, tu };
-
/* Insert one symbol. */
void insert (const cooked_index_entry *entry)
{
@@ -744,9 +761,8 @@ class debug_names
for (dwarf2_per_cu *one_cu : per_cus)
{
- unit_kind kind = (entry->per_cu->is_debug_types ()
- ? unit_kind::tu
- : unit_kind::cu);
+ unit_kind kind = classify_unit (*one_cu);
+
/* Some Ada parentage is synthesized by the reader and so
must be ignored here. */
const cooked_index_entry *parent = entry->get_parent ();
@@ -773,6 +789,15 @@ class debug_names
: DW_IDX_type_unit);
m_abbrev_table.append_unsigned_leb128 (DW_FORM_udata);
+ /* For foreign TUs only: the index of a CU that can be used
+ to locate the .dwo file containing that TU. */
+ if (kind == unit_kind::foreign_tu)
+ {
+ m_abbrev_table.append_unsigned_leb128
+ (DW_IDX_compile_unit);
+ m_abbrev_table.append_unsigned_leb128 (DW_FORM_udata);
+ }
+
/* DIE offset. */
m_abbrev_table.append_unsigned_leb128 (DW_IDX_die_offset);
m_abbrev_table.append_unsigned_leb128 (DW_FORM_ref_addr);
@@ -839,6 +864,67 @@ class debug_names
gdb_assert (it != m_cu_index_htab.cend ());
m_entry_pool.append_unsigned_leb128 (it->second);
+ /* For foreign TUs only: the index of a CU that can be used to
+ locate the .dwo file containing that TU.
+
+ This code implements this snippet of the DWARF 5 standard,
+ from section 6.1.1.2, "Structure of the Name Index":
+
+ When an index entry refers to a foreign type unit, it may
+ have attributes for both CU and (foreign) TU. For such
+ entries, the CU attribute gives the consumer a reference
+ to the CU that may be used to locate a split DWARF object
+ file that contains the type unit
+
+ So, the idea here is to find a CU in the same .dwo file as
+ the type unit (there will typically be one) and write the
+ offset to its skeleton. */
+ if (kind == unit_kind::foreign_tu)
+ {
+ /* If we know about this TU that is only listed in some .dwo
+ file, it means that we looked up the .dwo file from a CU
+ skeleton at some point during indexing, and recorded it
+ in ENTRY->PER_CU. */
+ gdb_assert (entry->per_cu != nullptr);
+
+ signatured_type *sig_type
+ = entry->per_cu->as_signatured_type ();
+
+ /* We know it's a TU, because it has been classified as
+ foreign_tu. */
+ gdb_assert (sig_type != nullptr);
+
+ /* We know it has a dwo_unit, because it's foreign. */
+ gdb_assert (sig_type->dwo_unit != nullptr);
+
+ /* Find a CU in the same .dwo file as the TU. */
+ dwarf2_per_cu *per_cu = nullptr;
+ for (auto &dwo_cu : sig_type->dwo_unit->dwo_file->cus)
+ if (dwo_cu->per_cu != nullptr)
+ {
+ per_cu = dwo_cu->per_cu;
+ break;
+ }
+
+ /* It would be really weird to not find a CU that we looked
+ up in this .dwo file. Otherwise, how would we know about
+ this foreign TU? But it might be possible to craft a bad
+ faith .dwo file that does this by having a one TU with a
+ skeleton and one TU without a skeleton in the same .dwo
+ file (i.e. non-standard DWARF 5). So, just in case, we
+ error out gracefully. */
+ if (per_cu == nullptr)
+ error (_("Could not find a CU for foreign type unit in "
+ "DWO file %s"),
+ sig_type->dwo_unit->dwo_file->dwo_name.c_str ());
+
+ /* This is the CU that can be used to find the .dwo file
+ containing the type unit, write its offset. */
+ const auto cu_it = m_cu_index_htab.find (per_cu);
+ gdb_assert (cu_it != m_cu_index_htab.cend ());
+ m_entry_pool.append_unsigned_leb128 (cu_it->second);
+ }
+
/* DIE offset. */
m_entry_pool.append_uint (dwarf5_offset_size (),
m_dwarf5_byte_order,
@@ -1369,8 +1455,9 @@ struct unit_lists
/* Compilation units. */
std::vector<const dwarf2_per_cu *> comp;
- /* Type units. */
- std::vector<const signatured_type *> type;
+ /* Type units, local and foreign. */
+ std::vector<const signatured_type *> local_type;
+ std::vector<const signatured_type *> foreign_type;
};
/* Get sorted (by section offset) lists of comp units and type units. */
@@ -1381,16 +1468,28 @@ get_unit_lists (const dwarf2_per_bfd &per_bfd)
unit_lists lists;
for (const auto &unit : per_bfd.all_units)
- if (const signatured_type *sig_type = unit->as_signatured_type ();
- sig_type != nullptr)
- lists.type.emplace_back (sig_type);
- else
- lists.comp.emplace_back (unit.get ());
+ switch (classify_unit (*unit))
+ {
+ case unit_kind::cu:
+ lists.comp.emplace_back (unit.get ());
+ break;
+
+ case unit_kind::local_tu:
+ lists.local_type.emplace_back (unit->as_signatured_type ());
+ break;
+
+ case unit_kind::foreign_tu:
+ lists.foreign_type.emplace_back (unit->as_signatured_type ());
+ break;
+ }
auto by_sect_off = [] (const dwarf2_per_cu *lhs, const dwarf2_per_cu *rhs)
{ return lhs->sect_off () < rhs->sect_off (); };
- /* Sort both lists, even though it is technically not always required:
+ auto by_sig = [] (const signatured_type *lhs, const signatured_type *rhs)
+ { return lhs->signature < rhs->signature; };
+
+ /* Sort the lists, even though it is technically not always required:
- while .gdb_index requires the CU list to be sorted, DWARF 5 doesn't
say anything about the order of CUs in .debug_names.
@@ -1400,7 +1499,8 @@ get_unit_lists (const dwarf2_per_bfd &per_bfd)
However, it helps make sure that GDB produce a stable and predictable
output, which is nice. */
std::sort (lists.comp.begin (), lists.comp.end (), by_sect_off);
- std::sort (lists.type.begin (), lists.type.end (), by_sect_off);
+ std::sort (lists.local_type.begin (), lists.local_type.end (), by_sect_off);
+ std::sort (lists.foreign_type.begin (), lists.foreign_type.end (), by_sig);
return lists;
}
@@ -1428,10 +1528,9 @@ write_gdbindex (dwarf2_per_bfd *per_bfd, cooked_index *table,
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."));
+ if (!units.foreign_type.empty ())
+ error (_("Found foreign (skeletonless) type unit, unable to produce "
+ ".gdb_index. Consider using .debug_names instead."));
int counter = 0;
@@ -1457,7 +1556,7 @@ write_gdbindex (dwarf2_per_bfd *per_bfd, cooked_index *table,
/* Write type units. */
data_buf types_cu_list;
- for (const signatured_type *sig_type : units.type)
+ for (const signatured_type *sig_type : units.local_type)
{
const auto insertpair = cu_index_htab.emplace (sig_type, counter);
gdb_assert (insertpair.second);
@@ -1530,7 +1629,7 @@ write_debug_names (dwarf2_per_bfd *per_bfd, cooked_index *table,
data_buf type_unit_list;
- for (auto [i, per_cu] : gdb::ranges::views::enumerate (units.type))
+ for (auto [i, per_cu] : gdb::ranges::views::enumerate (units.local_type))
{
nametable.add_cu (per_cu, i);
type_unit_list.append_uint (nametable.dwarf5_offset_size (),
@@ -1538,9 +1637,21 @@ write_debug_names (dwarf2_per_bfd *per_bfd, cooked_index *table,
to_underlying (per_cu->sect_off ()));
}
+ data_buf foreign_type_unit_list;
+
+ for (auto [i, sig_type] : gdb::ranges::views::enumerate (units.foreign_type))
+ {
+ /* The numbering of foreign type units follows the numbering of local type
+ units. */
+ nametable.add_cu (sig_type, units.local_type.size () + i);
+ foreign_type_unit_list.append_uint (8, dwarf5_byte_order,
+ sig_type->signature);
+ }
+
/* Verify that all units are represented. */
gdb_assert (units.comp.size () == per_bfd->num_comp_units);
- gdb_assert (units.type.size () == per_bfd->num_type_units);
+ gdb_assert (units.local_type.size () + units.foreign_type.size ()
+ == per_bfd->num_type_units);
for (const cooked_index_entry *entry : table->all_entries ())
nametable.insert (entry);
@@ -1557,6 +1668,7 @@ write_debug_names (dwarf2_per_bfd *per_bfd, cooked_index *table,
expected_bytes += bytes_of_header;
expected_bytes += comp_unit_list.size ();
expected_bytes += type_unit_list.size ();
+ expected_bytes += foreign_type_unit_list.size ();
expected_bytes += nametable.bytes ();
data_buf header;
@@ -1583,11 +1695,11 @@ write_debug_names (dwarf2_per_bfd *per_bfd, cooked_index *table,
/* local_type_unit_count - The number of TUs in the local TU
list. */
- header.append_uint (4, dwarf5_byte_order, units.type.size ());
+ header.append_uint (4, dwarf5_byte_order, units.local_type.size ());
/* foreign_type_unit_count - The number of TUs in the foreign TU
list. */
- header.append_uint (4, dwarf5_byte_order, 0);
+ header.append_uint (4, dwarf5_byte_order, units.foreign_type.size ());
/* bucket_count - The number of hash buckets in the hash lookup
table. GDB does not use the hash table, so there's also no need
@@ -1613,6 +1725,7 @@ write_debug_names (dwarf2_per_bfd *per_bfd, cooked_index *table,
header.file_write (out_file);
comp_unit_list.file_write (out_file);
type_unit_list.file_write (out_file);
+ foreign_type_unit_list.file_write (out_file);
nametable.file_write (out_file, out_file_str);
assert_file_size (out_file, expected_bytes);
diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index 575b2383b20a..400a0bb0599a 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -7233,6 +7233,15 @@ cutu_reader::lookup_dwo_cutu (dwarf2_cu *cu, const char *dwo_name,
("DWO %s %s(%s) found: @%s", kind, dwo_name,
hex_string (signature),
host_address_to_string (dwo_unit_it->get ()));
+
+ /* Record the dwarf2_per_cu that was used to look up this
+ dwo_unit. There will typically be exactly one skeleton
+ pointing to each DWO CU. But if for some mysterious reason
+ there are multiple skeletons pointing to the same DWO CU, it's
+ fine, we just need to remember one. This will keep the last
+ one seen. */
+ (*dwo_unit_it)->per_cu = cu->per_cu;
+
return dwo_unit_it->get ();
}
}
diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
index 17a158defcae..53129db9126f 100644
--- a/gdb/dwarf2/read.h
+++ b/gdb/dwarf2/read.h
@@ -275,6 +275,7 @@ struct dwarf2_per_cu
/* If this dwarf2_per_cu is a signatured_type, return "this" cast to
signatured_type. Otherwise, return nullptr. */
signatured_type *as_signatured_type ();
+ const signatured_type *as_signatured_type () const;
dwarf2_per_bfd *per_bfd () const
{ return m_per_bfd; }
@@ -492,6 +493,17 @@ dwarf2_per_cu::as_signatured_type ()
return nullptr;
}
+/* See dwarf2_per_cu declaration. */
+
+inline const signatured_type *
+dwarf2_per_cu::as_signatured_type () const
+{
+ if (m_is_debug_types)
+ return static_cast<const signatured_type *> (this);
+
+ return nullptr;
+}
+
/* Hash a signatured_type object based on its signature. */
struct signatured_type_hash
@@ -533,6 +545,11 @@ struct dwo_unit
/* Backlink to the containing struct dwo_file. */
struct dwo_file *dwo_file = nullptr;
+ /* For compile units only, the per-CU structure that represents the skeleton
+ that we used to reach this dwo_unit. It starts as nullptr, and gets set
+ in cutu_reader::lookup_dwo_cutu. */
+ std::atomic<dwarf2_per_cu *> per_cu = nullptr;
+
/* The "id" that distinguishes this CU/TU.
.debug_info calls this "dwo_id", .debug_types calls this "signature".
Since signatures came first, we stick with it for consistency. */
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 6/8] gdb/dwarf: add debug output in read-debug-names.c
2026-04-16 19:59 ` [PATCH v2 0/8] Handle foreign type units in .debug_names Simon Marchi
` (4 preceding siblings ...)
2026-04-16 19:59 ` [PATCH v2 5/8] gdb/dwarf: generate foreign type units in .debug_names Simon Marchi
@ 2026-04-16 19:59 ` Simon Marchi
2026-04-16 19:59 ` [PATCH v2 7/8] gdb/dwarf: add more context to complaints in mapped_debug_names_reader::scan_one_entry Simon Marchi
2026-04-16 19:59 ` [PATCH v2 8/8] gdb/dwarf: read foreign type units Simon Marchi
7 siblings, 0 replies; 20+ messages in thread
From: Simon Marchi @ 2026-04-16 19:59 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
From: Simon Marchi <simon.marchi@polymtl.ca>
Add a bunch of debug prints in read-debug-names.c to dump what we are
reading. Some of if was helpful to me when debugging my changes to the
.debug_names reader and writer.
The debug prints are activated by "set debug dwarf-read" (I didn't feel
like we needed a separate knob for that). There are two levels of
verbosity. At level 1, we print everything that happens once, like the
header, counts, offsets of important parts of the index, etc. At
levels > 1, we print information about each abbrev, CU/TU and index
entry.
The output is made such that it is easy look at the output of readelf of
llvm-dwarfdump on the side and confirm that GDB is reading the index
right.
Here are some examples of the output.
Header and some abbrevs:
[dwarf-read] read_debug_names_from_section: start: reading .debug_names from /home/simark/build/babeltrace/src/lib/.libs/libbabeltrace2.so.0.0.0
[dwarf-read] read_debug_names_from_section: section size: 0x19d4e, initial length: 0x19d4a, dwarf64: 0, offset_size: 4
[dwarf-read] read_debug_names_from_section: version: 5
[dwarf-read] read_debug_names_from_section: cu_count: 56, tu_count: 0, foreign_tu_count: 0
[dwarf-read] read_debug_names_from_section: bucket_count: 0, name_count: 3418, abbrev_table_size: 374, augmentation_string_size: 8
[dwarf-read] read_debug_names_from_section: augmentation string: "GDB3\x00\x00\x00\x00"
[dwarf-read] read_debug_names_from_section: abbrev 1: tag DW_TAG_namespace, 4 attributes
[dwarf-read] read_debug_names_from_section: DW_IDX_compile_unit DW_FORM_udata
[dwarf-read] read_debug_names_from_section: DW_IDX_die_offset DW_FORM_ref_addr
[dwarf-read] read_debug_names_from_section: DW_IDX_GNU_language DW_FORM_udata
[dwarf-read] read_debug_names_from_section: DW_IDX_parent DW_FORM_data4
[dwarf-read] read_debug_names_from_section: abbrev 2: tag DW_TAG_enumerator, 4 attributes
[dwarf-read] read_debug_names_from_section: DW_IDX_compile_unit DW_FORM_udata
[dwarf-read] read_debug_names_from_section: DW_IDX_die_offset DW_FORM_ref_addr
[dwarf-read] read_debug_names_from_section: DW_IDX_GNU_language DW_FORM_udata
[dwarf-read] read_debug_names_from_section: DW_IDX_GNU_internal DW_FORM_flag_present
Excerpt of the CU list:
[dwarf-read] build_and_check_cu_list_from_debug_names: building CU list from .debug_names (56 CUs)
[dwarf-read] build_and_check_cu_list_from_debug_names: CU 0: offset 0x0
[dwarf-read] build_and_check_cu_list_from_debug_names: CU 1: offset 0x1bac
[dwarf-read] build_and_check_cu_list_from_debug_names: CU 2: offset 0x4262
[dwarf-read] build_and_check_cu_list_from_debug_names: CU 3: offset 0x4ff0
[dwarf-read] build_and_check_cu_list_from_debug_names: CU 4: offset 0x59e9
[dwarf-read] build_and_check_cu_list_from_debug_names: CU 5: offset 0x67af
[dwarf-read] build_and_check_cu_list_from_debug_names: CU 6: offset 0x8e1a
[dwarf-read] build_and_check_cu_list_from_debug_names: CU 7: offset 0xa5cc
[dwarf-read] build_and_check_cu_list_from_debug_names: CU 8: offset 0xcd39
Some entries:
[dwarf-read] scan_all_names: start: scanning 3418 names from .debug_names
[dwarf-read] scan_entries: scanning entries for name 1: "(anonymous namespace)" (entry pool offset 0x0)
[dwarf-read] scan_one_entry: entry pool offset 0x0: abbrev 1, tag DW_TAG_namespace
[dwarf-read] scan_one_entry: DW_IDX_compile_unit (DW_FORM_udata): 55
[dwarf-read] scan_one_entry: DW_IDX_die_offset (DW_FORM_ref_addr): 0x7d06b
[dwarf-read] scan_one_entry: DW_IDX_GNU_language (DW_FORM_udata): 33
[dwarf-read] scan_one_entry: DW_IDX_parent (DW_FORM_data4): 38201
[dwarf-read] scan_one_entry: -> die_offset 0x7d06b, per_cu offset 0x5af0e
[dwarf-read] scan_one_entry: entry pool offset 0xb: end of entries (abbrev 0)
[dwarf-read] scan_entries: scanning entries for name 2: "AUTO_SEEK_STREAM_STATE_PACKET_BEGAN" (entry pool offset 0xc)
[dwarf-read] scan_one_entry: entry pool offset 0xc: abbrev 2, tag DW_TAG_enumerator
[dwarf-read] scan_one_entry: DW_IDX_compile_unit (DW_FORM_udata): 17
[dwarf-read] scan_one_entry: DW_IDX_die_offset (DW_FORM_ref_addr): 0x1beea
[dwarf-read] scan_one_entry: DW_IDX_GNU_language (DW_FORM_udata): 29
[dwarf-read] scan_one_entry: DW_IDX_GNU_internal (DW_FORM_flag_present): 1
[dwarf-read] scan_one_entry: -> die_offset 0x1beea, per_cu offset 0x1a28e
[dwarf-read] scan_one_entry: entry pool offset 0x13: end of entries (abbrev 0)
Change-Id: I4a40bfb73fa7feccb5038814a7d1a1bcd4f6231b
---
gdb/dwarf2/read-debug-names.c | 120 +++++++++++++++++++++++++++++++++-
gdb/dwarf2/read.c | 24 +------
gdb/dwarf2/read.h | 24 +++++++
3 files changed, 143 insertions(+), 25 deletions(-)
diff --git a/gdb/dwarf2/read-debug-names.c b/gdb/dwarf2/read-debug-names.c
index 8e488377439b..6ac65d101b16 100644
--- a/gdb/dwarf2/read-debug-names.c
+++ b/gdb/dwarf2/read-debug-names.c
@@ -170,7 +170,12 @@ mapped_debug_names_reader::scan_one_entry (const char *name,
const ULONGEST abbrev = read_unsigned_leb128 (abfd, entry, &bytes_read);
entry += bytes_read;
if (abbrev == 0)
- return nullptr;
+ {
+ dwarf_read_debug_printf_v
+ (" entry pool offset 0x%tx: end of entries (abbrev 0)",
+ offset_in_entry_pool);
+ return nullptr;
+ }
const auto indexval_it = abbrev_map.find (abbrev);
if (indexval_it == abbrev_map.cend ())
@@ -182,6 +187,12 @@ mapped_debug_names_reader::scan_one_entry (const char *name,
}
const auto &indexval = indexval_it->second;
+
+ dwarf_read_debug_printf_v
+ (" entry pool offset 0x%tx: abbrev %s, tag %s",
+ offset_in_entry_pool, pulongest (abbrev),
+ dwarf_tag_name (indexval.dwarf_tag));
+
cooked_index_flag flags = 0;
sect_offset die_offset {};
enum language lang = language_unknown;
@@ -238,6 +249,14 @@ mapped_debug_names_reader::scan_one_entry (const char *name,
bfd_get_filename (abfd)));
return nullptr;
}
+
+ dwarf_read_debug_printf_v (" %s (%s): %s",
+ get_DW_IDX_name (attr.dw_idx),
+ dwarf_form_name (attr.form),
+ (attr.form == DW_FORM_ref_addr
+ ? hex_string (ull)
+ : pulongest (ull)));
+
switch (attr.dw_idx)
{
case DW_IDX_compile_unit:
@@ -302,6 +321,11 @@ mapped_debug_names_reader::scan_one_entry (const char *name,
/* Skip if we couldn't find a valid CU/TU index. */
if (per_cu != nullptr)
{
+ dwarf_read_debug_printf_v
+ (" -> die_offset %s, per_cu offset %s",
+ sect_offset_str (die_offset),
+ sect_offset_str (per_cu->sect_off ()));
+
*result
= indices[next_shard].add (die_offset, (dwarf_tag) indexval.dwarf_tag,
flags, lang, name, nullptr, per_cu);
@@ -312,6 +336,8 @@ mapped_debug_names_reader::scan_one_entry (const char *name,
entry_pool_offsets_to_entries.emplace (offset_in_entry_pool, *result);
}
+ else
+ dwarf_read_debug_printf_v (" -> no valid CU/TU, skipping");
return entry;
}
@@ -323,6 +349,12 @@ mapped_debug_names_reader::scan_entries (uint32_t index,
const char *name,
const gdb_byte *entry)
{
+ /* Print a 1-based index, because this is what readelf and llvm-dwarfdump
+ do. This makes it easier to compare output side-by-side. */
+ dwarf_read_debug_printf_v
+ ("scanning entries for name %u: \"%s\" (entry pool offset 0x%tx)",
+ index + 1, name, entry - entry_pool);
+
std::vector<cooked_index_entry *> these_entries;
while (true)
@@ -347,6 +379,9 @@ mapped_debug_names_reader::scan_entries (uint32_t index,
void
mapped_debug_names_reader::scan_all_names ()
{
+ DWARF_READ_SCOPED_DEBUG_START_END
+ ("scanning %u names from .debug_names", name_count);
+
all_entries.resize (name_count);
/* In the first pass, create all the entries. */
@@ -377,6 +412,9 @@ mapped_debug_names_reader::scan_all_names ()
Otherwise, the DW_IDX_parent value is an offset into the entry pool, which
is not ambiguous. */
+ dwarf_read_debug_printf ("resolving %zu parent pointers",
+ needs_parent.size ());
+
for (auto &[entry, parent_val] : needs_parent)
{
if (augmentation_is_gdb && gdb_augmentation_version == 2)
@@ -463,6 +501,9 @@ build_and_check_tu_list_from_debug_names (dwarf2_per_objfile *per_objfile,
mapped_debug_names_reader &map,
dwarf2_section_info *section)
{
+ dwarf_read_debug_printf ("building TU list from .debug_names (%u TUs)",
+ map.tu_count);
+
struct objfile *objfile = per_objfile->objfile;
dwarf2_per_bfd *per_bfd = per_objfile->per_bfd;
@@ -477,6 +518,9 @@ build_and_check_tu_list_from_debug_names (dwarf2_per_objfile *per_objfile,
map.offset_size,
map.dwarf5_byte_order));
+ dwarf_read_debug_printf_v (" TU %u: offset %s", i,
+ sect_offset_str (sect_off));
+
/* Find the matching dwarf2_per_cu. */
dwarf2_per_cu *per_cu = dwarf2_find_unit ({ section, sect_off },
per_bfd);
@@ -508,15 +552,24 @@ read_debug_names_from_section (dwarf2_per_objfile *per_objfile,
struct dwarf2_section_info *section,
mapped_debug_names_reader &map)
{
+ DWARF_READ_SCOPED_DEBUG_START_END
+ ("reading .debug_names from %s", filename);
+
struct objfile *objfile = per_objfile->objfile;
if (section->empty ())
- return false;
+ {
+ dwarf_read_debug_printf ("section is empty");
+ return false;
+ }
/* Older elfutils strip versions could keep the section in the main
executable while splitting it for the separate debug info file. */
if ((section->get_flags () & SEC_HAS_CONTENTS) == 0)
- return false;
+ {
+ dwarf_read_debug_printf ("section has no contents");
+ return false;
+ }
section->read (objfile);
@@ -534,6 +587,11 @@ read_debug_names_from_section (dwarf2_per_objfile *per_objfile,
map.dwarf5_is_dwarf64 = bytes_read != 4;
map.offset_size = map.dwarf5_is_dwarf64 ? 8 : 4;
+
+ dwarf_read_debug_printf ("section size: %s, initial length: %s, "
+ "dwarf64: %d, offset_size: %d",
+ hex_string (section->size), hex_string (length),
+ map.dwarf5_is_dwarf64, map.offset_size);
if (bytes_read + length != section->size)
{
/* There may be multiple per-CU indices. */
@@ -549,6 +607,9 @@ read_debug_names_from_section (dwarf2_per_objfile *per_objfile,
/* The version number. */
uint16_t version = read_2_bytes (abfd, addr);
addr += 2;
+
+ dwarf_read_debug_printf ("version: %d", version);
+
if (version != 5)
{
warning (_("Section .debug_names in %ps has unsupported version %d, "
@@ -585,6 +646,11 @@ read_debug_names_from_section (dwarf2_per_objfile *per_objfile,
list. */
uint32_t foreign_tu_count = read_4_bytes (abfd, addr);
addr += 4;
+
+ dwarf_read_debug_printf ("cu_count: %u, tu_count: %u, "
+ "foreign_tu_count: %u",
+ map.cu_count, map.tu_count, foreign_tu_count);
+
if (foreign_tu_count != 0)
{
warning (_("Section .debug_names in %ps has unsupported %lu foreign TUs, "
@@ -613,11 +679,34 @@ read_debug_names_from_section (dwarf2_per_objfile *per_objfile,
string. This value is rounded up to a multiple of 4. */
uint32_t augmentation_string_size = read_4_bytes (abfd, addr);
addr += 4;
+
+ dwarf_read_debug_printf ("bucket_count: %u, name_count: %u, "
+ "abbrev_table_size: %u, "
+ "augmentation_string_size: %u",
+ map.bucket_count, map.name_count,
+ abbrev_table_size, augmentation_string_size);
+
augmentation_string_size += (-augmentation_string_size) & 3;
const auto augmentation_string
= gdb::make_array_view (addr, augmentation_string_size);
+ if (dwarf_read_debug >= 1)
+ {
+ std::string aug_repr;
+ for (size_t i = 0; i < augmentation_string_size; i++)
+ {
+ gdb_byte b = addr[i];
+ if (c_isprint (b))
+ aug_repr += b;
+ else
+ aug_repr += string_printf ("\\x%02x", b);
+ }
+
+ dwarf_read_debug_printf ("augmentation string: \"%s\"",
+ aug_repr.c_str ());
+ }
+
if (augmentation_string == gdb::make_array_view (dwarf5_augmentation_1))
{
warning (_(".debug_names created by an old version of gdb; ignoring"));
@@ -704,7 +793,26 @@ read_debug_names_from_section (dwarf2_per_objfile *per_objfile,
break;
indexval.attr_vec.push_back (std::move (attr));
}
+
+ dwarf_read_debug_printf_v
+ (" abbrev %s: tag %s, %zu attributes",
+ pulongest (index_num), dwarf_tag_name (indexval.dwarf_tag),
+ indexval.attr_vec.size ());
+
+ for (const auto &attr : indexval.attr_vec)
+ dwarf_read_debug_printf_v
+ (" %s %s%s",
+ get_DW_IDX_name (attr.dw_idx),
+ dwarf_form_name (attr.form),
+ (attr.form == DW_FORM_implicit_const
+ ? string_printf (" (%s)",
+ plongest (attr.implicit_const)).c_str ()
+ : ""));
}
+
+ dwarf_read_debug_printf ("%zu abbreviations read",
+ map.abbrev_map.size ());
+
if (addr != abbrev_table_start + abbrev_table_size)
{
warning (_("Section .debug_names in %ps has abbreviation_table "
@@ -728,6 +836,9 @@ build_and_check_cu_list_from_debug_names (dwarf2_per_bfd *per_bfd,
mapped_debug_names_reader &map,
dwarf2_section_info §ion)
{
+ dwarf_read_debug_printf ("building CU list from .debug_names (%u CUs)",
+ map.cu_count);
+
int nr_cus = per_bfd->num_comp_units;
if (map.cu_count != nr_cus)
@@ -745,6 +856,9 @@ build_and_check_cu_list_from_debug_names (dwarf2_per_bfd *per_bfd,
map.offset_size,
map.dwarf5_byte_order));
+ dwarf_read_debug_printf_v (" CU %u: offset %s", i,
+ sect_offset_str (sect_off));
+
/* Find the matching dwarf2_per_cu. */
dwarf2_per_cu *per_cu = dwarf2_find_unit ({ §ion, sect_off }, per_bfd);
diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index 400a0bb0599a..c335a8a97db0 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -99,28 +99,8 @@
#include "extract-store-integer.h"
#include "cli/cli-style.h"
-/* When == 1, print basic high level tracing messages.
- When > 1, be more verbose.
- This is in contrast to the low level DIE reading of dwarf_die_debug. */
-static unsigned int dwarf_read_debug = 0;
-
-/* Print a "dwarf-read" debug statement if dwarf_read_debug is >= 1. */
-
-#define dwarf_read_debug_printf(fmt, ...) \
- debug_prefixed_printf_cond (dwarf_read_debug >= 1, "dwarf-read", fmt, \
- ##__VA_ARGS__)
-
-/* Print a "dwarf-read" debug statement if dwarf_read_debug is >= 2. */
-
-#define dwarf_read_debug_printf_v(fmt, ...) \
- debug_prefixed_printf_cond (dwarf_read_debug >= 2, "dwarf-read", fmt, \
- ##__VA_ARGS__)
-
-/* Print "dwarf-read" start/end debug statements. */
-
-#define DWARF_READ_SCOPED_DEBUG_START_END(fmt, ...) \
- scoped_debug_start_end ([] { return dwarf_read_debug >= 1; }, "dwarf-read", \
- fmt, ##__VA_ARGS__)
+/* See read.h. */
+unsigned int dwarf_read_debug = 0;
/* When non-zero, dump DIEs after they are read in. */
static unsigned int dwarf_die_debug = 0;
diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
index 53129db9126f..e2533c8a91f2 100644
--- a/gdb/dwarf2/read.h
+++ b/gdb/dwarf2/read.h
@@ -1381,6 +1381,30 @@ type *dwarf2_fetch_die_type_sect_off (sect_offset sect_off,
dwarf2_per_objfile *per_objfile,
const char **var_name = nullptr);
+/* When == 1, print basic high level tracing messages.
+ When > 1, be more verbose.
+ This is in contrast to the low level DIE reading of dwarf_die_debug. */
+
+extern unsigned int dwarf_read_debug;
+
+/* Print a "dwarf-read" debug statement if dwarf_read_debug is >= 1. */
+
+#define dwarf_read_debug_printf(fmt, ...) \
+ debug_prefixed_printf_cond (dwarf_read_debug >= 1, "dwarf-read", fmt, \
+ ##__VA_ARGS__)
+
+/* Print a "dwarf-read" debug statement if dwarf_read_debug is >= 2. */
+
+#define dwarf_read_debug_printf_v(fmt, ...) \
+ debug_prefixed_printf_cond (dwarf_read_debug >= 2, "dwarf-read", fmt, \
+ ##__VA_ARGS__)
+
+/* Print "dwarf-read" start/end debug statements. */
+
+#define DWARF_READ_SCOPED_DEBUG_START_END(fmt, ...) \
+ scoped_debug_start_end ([] { return dwarf_read_debug >= 1; }, "dwarf-read", \
+ fmt, ##__VA_ARGS__)
+
/* When non-zero, dump line number entries as they are read in. */
extern unsigned int dwarf_line_debug;
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 7/8] gdb/dwarf: add more context to complaints in mapped_debug_names_reader::scan_one_entry
2026-04-16 19:59 ` [PATCH v2 0/8] Handle foreign type units in .debug_names Simon Marchi
` (5 preceding siblings ...)
2026-04-16 19:59 ` [PATCH v2 6/8] gdb/dwarf: add debug output in read-debug-names.c Simon Marchi
@ 2026-04-16 19:59 ` Simon Marchi
2026-04-16 19:59 ` [PATCH v2 8/8] gdb/dwarf: read foreign type units Simon Marchi
7 siblings, 0 replies; 20+ messages in thread
From: Simon Marchi @ 2026-04-16 19:59 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
From: Simon Marchi <simon.marchi@polymtl.ca>
I realize that complaints are not often enabled and shown, but if we
emit them, I think they should include enough context to help a user who
wishes to dig in the problem to pinpoint where the problem happened
exactly. For this reason, change the complaints in
mapped_debug_names_reader::scan_one_entry to include precisions
regarding which index entry is problematic exactly.
I am not attached to the particular format. I thought that because this
is extra contextual information, I would put it in the square brackets
at the end (which only shows the module name currently). However, it
would be nice to converge towards a format that we could replicate
elsewhere in the DWARF reader, so feel free to chime in.
I factored out the formatting to a separate function, because I will be
adding more of these in a subsequent patch, and it's easier to have the
formatting centralized.
I have not touched the "Unsupported .debug_names form" warning. If that
comes up, we probably don't care about the specific index entry, we just
need to implement support for that form.
Change-Id: Id68a0ae406a3ca620408576b81f893f05c7e3df2
---
gdb/dwarf2/read-debug-names.c | 43 ++++++++++++++++++++++++++---------
1 file changed, 32 insertions(+), 11 deletions(-)
diff --git a/gdb/dwarf2/read-debug-names.c b/gdb/dwarf2/read-debug-names.c
index 6ac65d101b16..487e2ea87edb 100644
--- a/gdb/dwarf2/read-debug-names.c
+++ b/gdb/dwarf2/read-debug-names.c
@@ -155,6 +155,25 @@ struct mapped_debug_names_reader
std::vector<std::vector<cooked_index_entry *>> all_entries;
};
+/* Emit a complaint about a specific index entry. */
+
+static void ATTRIBUTE_PRINTF (4, 5)
+complain_about_index_entry (bfd *abfd, const char *name,
+ ptrdiff_t offset_in_entry_pool, const char *fmt,
+ ...)
+{
+ va_list ap;
+ va_start (ap, fmt);
+ std::string msg = string_vprintf (fmt, ap);
+ va_end (ap);
+
+ msg += string_printf (_(" [in module %s, index entry for name %s,"
+ " entry pool offset 0x%tx]"),
+ bfd_get_filename (abfd), name, offset_in_entry_pool);
+
+ complaint ("%s", msg.c_str ());
+}
+
/* Scan a single entry from the entries table. Set *RESULT and PARENT
(if needed) and return the updated pointer on success, or return
nullptr on error, or at the end of the table. */
@@ -180,9 +199,11 @@ mapped_debug_names_reader::scan_one_entry (const char *name,
const auto indexval_it = abbrev_map.find (abbrev);
if (indexval_it == abbrev_map.cend ())
{
- complaint (_("Wrong .debug_names undefined abbrev code %s "
- "[in module %s]"),
- pulongest (abbrev), bfd_get_filename (abfd));
+ complain_about_index_entry (abfd, name, offset_in_entry_pool,
+ _("Wrong .debug_names abbrev code %s"),
+ pulongest (abbrev));
+ /* We can't go past this entry because we don't know its size, stop
+ reading this entry chain. */
return nullptr;
}
@@ -264,10 +285,10 @@ mapped_debug_names_reader::scan_one_entry (const char *name,
/* Don't crash on bad data. */
if (ull >= this->comp_units.size ())
{
- complaint (_(".debug_names entry has bad CU index %s"
- " [in module %s]"),
- pulongest (ull),
- bfd_get_filename (abfd));
+ complain_about_index_entry
+ (abfd, name, offset_in_entry_pool,
+ _(".debug_names entry has bad CU index %s"),
+ pulongest (ull));
continue;
}
@@ -279,10 +300,10 @@ mapped_debug_names_reader::scan_one_entry (const char *name,
/* Don't crash on bad data. */
if (ull >= this->type_units.size ())
{
- complaint (_(".debug_names entry has bad TU index %s"
- " [in module %s]"),
- pulongest (ull),
- bfd_get_filename (abfd));
+ complain_about_index_entry
+ (abfd, name, offset_in_entry_pool,
+ _(".debug_names entry has bad TU index %s"),
+ pulongest (ull));
continue;
}
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 8/8] gdb/dwarf: read foreign type units
2026-04-16 19:59 ` [PATCH v2 0/8] Handle foreign type units in .debug_names Simon Marchi
` (6 preceding siblings ...)
2026-04-16 19:59 ` [PATCH v2 7/8] gdb/dwarf: add more context to complaints in mapped_debug_names_reader::scan_one_entry Simon Marchi
@ 2026-04-16 19:59 ` Simon Marchi
7 siblings, 0 replies; 20+ messages in thread
From: Simon Marchi @ 2026-04-16 19:59 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
In DWARF 5, foreign type units are type units present in .dwo files.
Type units in .dwo files don't have a matching skeleton in the main
file. When an .debug_names index is present, it can't include those
type units in the regular type unit list, since that list specifies
offsets in the main file. They are instead listed in the foreign TU
list, which is basically just a list of type signatures.
In order to help the debugger locate these units (i.e. find the .dwo
file containing them), individual index entries referencing foreign type
units may also include a reference to a compile unit that the debugger
can follow to find the appropriate .dwo file.
This patch implements reading the .debug_names foreign TU list and using
these "hint" CUs to locate foreign type units. I use the term "hint"
throughout the code, but I don't mind another name if someone has a
better idea.
The first part is to read the actual foreign TU list from the
.debug_names index and create signatured_type objects out of them. This
is done in the new function create_foreign_type_units_from_debug_names.
Append the newly created signatured_type to the
mapped_debug_names_reader::foreign_type_units vector, which will be used
later to resolve DW_IDX_type_unit indices. Populate the
dwarf2_per_bfd::signatured_types set, which contains signatured_types
indexed by signature. And finally, transfer ownership of the object to
the dwarf2_per_bfd::all_units vector.
Previously, all dwarf2_per_cu (including signatured_type) objects were
created with a non-nullptr section. With foreign type units, we don't
know the section at creation time. We also don't know the offset into
section nor the size of the unit. Therefore, add a
dwarf2_per_bfd::allocate_signatured_type overload that takes just the
signature. Remove the "section != nullptr" assert from the
dwarf2_per_cu constructor, but add it to the other allocate_* methods.
Since the new create_foreign_type_units_from_debug_names function adds
items to the dwarf2_per_bfd::all_units vector, the vector needs to be
sorted after create_foreign_type_units_from_debug_names runs. Remove
the finalize_all_units call from create_all_units, making the callers
responsible to call it.
The next step is to read the hint CU attributes when scanning the index
entries. Rework mapped_debug_names_reader::scan_one_entry to remember
which kind of unit references it saw for the entry (comp, type and/or
foreign type) and then figure out what this means. The logic is:
- Did the entry reference a foreign type unit? If so, it's a foreign
type unit. Does it also reference a hint CU? If not, drop the
entry, there's nothing useful we can do with it.
- Otherwise, did the entry reference a (non-foreign) type unit? Then
it's a regular type unit. If so, it shouldn't also have a
DW_IDX_compile_unit.
- Otherwise, did the entry reference a comp unit? If so, it's a comp
unit.
- Otherwise, we don't know what unit the entry references, it's an
error.
Since the .debug_name index attaches hint CU attributes to individual
index entries, my initial implementation added the hint CU information
to the cooked_index_entry structure. I am not sure why DWARF 5 chose to
do it this way, as opposed to attaching one hint per foreign TU. Does
this mean that two type units with the same signature could be different
and, for a specific index entry, it would be important to find one
specific instance of the type unit over the others? I have no idea.
However, I know that the current GDB DWARF reader is not able to load
multiple type units with the same signature but different content. Once
it loads one type unit with a given signature, all subsequent references
to that signature will use that loaded type unit. I therefore chose to
have the .debug_names reader record just one hint CU per foreign TU.
This avoids growing the cooked_index_entry structure for nothing, and
having to pass through this information through multiple layers.
The next step is to locate the .dwo file containing the foreign TUs when
we need them. This sometimes makes use of the hint CU, but not always.
I identified these 3 code paths:
1. When the type unit gets expanded directly, for instance if you use
"ptype" and there is a direct match into the type unit. This case
is handled in load_full_type_unit, calling a new function
fill_in_sig_entry_from_per_cu_hint. This one uses the hint recorded
by the .debug_names reader. When a cooked index entry exists and
refers to a foreign TU for which the section is not yet known, we
know that there exists a hint, otherwise we wouldn't have created
the entry in the first place.
2. The second one is when the type unit is referenced by some other
unit. This case is handled in follow_die_sig_1, calling another new
function fill_in_sig_entry_from_dwo_file. In this case, we know
which unit is referring to the TU, so we use that unit's dwo file to
fill in the details. As explained in the comment, this is sometimes
just an optimization, but sometimes also necessary, if the TU
does not have a hint, due to it not containing any indexed name.
3. Similarly, in dwarf2_base_index_functions::expand_all_symtabs, we
might have to handle foreign type units for which we don't have a
hint. I initially implemented something in two passes, to go dig in
the dwo_file structures to find those TUs, but ended up choosing to
just skip them, for the reasons explained in the comment there.
Setting a dwarf2_per_cu's section a posteriori breaks the assumed
ordering of the dwarf2_per_bfd::all_units vector. After setting the
section, re-sort the vector.
Add a target board to exercise this new code. This board builds with:
- type units (-fdebug-types-section)
- split DWARF (-gsplit-dwarf)
- .debug_names index (created by GDB)
I ran the whole testsuite with this board file and it's not perfect, but
the results are comparable to the dwarf5-fission-debug-types board, for
instance.
There is one known failure that I am unable to get to the bottom of. It
seems orthogonal to my change though, more like an indexer or symbol
reader issue. There are maybe more of this kind, but this is one
example:
FAIL: gdb.ada/tick_length_array_enum_idx.exp: ptype variable_table'length (GDB internal error)
/home/smarchi/src/binutils-gdb/gdb/dwarf2/read.c:1839: internal-error: search_one: Assertion `symtab != nullptr' failed.
The problem appears to be that a cooked index lookup for symbol
variable_table says that a given TU should contain a match. But then
trying to expand the TU makes dw2_instantiate_symtab yield a nullptr
compunit_symtab, I think because the symbol reader found nothing
interesting symbol-wise. And then the assert in search_one triggers.
The issue seems sensitive to some aspects of the environment (gnat
version?). I am able to reproduce the issue on Arch Linux (gnat 15)
with:
$ make check TESTS="gdb.ada/tick_length_array_enum_idx.exp" RUNTESTFLAGS="--target_board=dwarf5-fission-debug-types-debug-names"
But it doesn't reproduce on Debian 13 (gnat 14), Ubuntu 24.04 (gnat
13) or Fedora Rawhide (gnat 16).
Change-Id: I0d4ccc1cbbce3a337794341744d24091e8549d7f
---
gdb/dwarf2/read-debug-names.c | 212 ++++++++++++++----
gdb/dwarf2/read.c | 167 +++++++++++++-
gdb/dwarf2/read.h | 37 ++-
gdb/dwarf2/types.h | 2 +
...dwarf5-fission-debug-types-debug-names.exp | 29 +++
5 files changed, 392 insertions(+), 55 deletions(-)
create mode 100644 gdb/testsuite/boards/dwarf5-fission-debug-types-debug-names.exp
diff --git a/gdb/dwarf2/read-debug-names.c b/gdb/dwarf2/read-debug-names.c
index 487e2ea87edb..a062c1ad2562 100644
--- a/gdb/dwarf2/read-debug-names.c
+++ b/gdb/dwarf2/read-debug-names.c
@@ -88,9 +88,13 @@ struct mapped_debug_names_reader
uint8_t offset_size = 0;
uint32_t cu_count = 0;
- uint32_t tu_count = 0, bucket_count = 0, name_count = 0;
+ uint32_t tu_count = 0;
+ uint32_t foreign_tu_count = 0;
+ uint32_t bucket_count = 0;
+ uint32_t name_count = 0;
const gdb_byte *cu_table_reordered = nullptr;
const gdb_byte *tu_table_reordered = nullptr;
+ const gdb_byte *foreign_tu_table_reordered = nullptr;
const uint32_t *bucket_table_reordered = nullptr;
const uint32_t *hash_table_reordered = nullptr;
const gdb_byte *name_table_string_offs_reordered = nullptr;
@@ -122,7 +126,11 @@ struct mapped_debug_names_reader
/* List of local TUs in the same order as found in the index (DWARF 5 section
6.1.1.4.3). */
- std::vector<dwarf2_per_cu *> type_units;
+ std::vector<signatured_type *> type_units;
+
+ /* List of foreign TUs in the same order as found in the index (DWARF 5
+ section 6.1.1.4.4). */
+ std::vector<signatured_type *> foreign_type_units;
/* Even though the scanning of .debug_names and creation of the
cooked index entries is done serially, we create multiple shards
@@ -217,7 +225,10 @@ mapped_debug_names_reader::scan_one_entry (const char *name,
cooked_index_flag flags = 0;
sect_offset die_offset {};
enum language lang = language_unknown;
- dwarf2_per_cu *per_cu = nullptr;
+ dwarf2_per_cu *comp_unit = nullptr;
+ signatured_type *type_unit = nullptr;
+ signatured_type *foreign_type_unit = nullptr;
+
for (const auto &attr : indexval.attr_vec)
{
ULONGEST ull;
@@ -282,7 +293,6 @@ mapped_debug_names_reader::scan_one_entry (const char *name,
{
case DW_IDX_compile_unit:
{
- /* Don't crash on bad data. */
if (ull >= this->comp_units.size ())
{
complain_about_index_entry
@@ -292,13 +302,17 @@ mapped_debug_names_reader::scan_one_entry (const char *name,
continue;
}
- per_cu = this->comp_units[ull];
+ comp_unit = this->comp_units[ull];
break;
}
case DW_IDX_type_unit:
{
- /* Don't crash on bad data. */
- if (ull >= this->type_units.size ())
+ if (ull < this->type_units.size ())
+ type_unit = this->type_units[ull];
+ else if (auto foreign_idx = ull - this->type_units.size ();
+ foreign_idx < this->foreign_type_units.size ())
+ foreign_type_unit = this->foreign_type_units[foreign_idx];
+ else
{
complain_about_index_entry
(abfd, name, offset_in_entry_pool,
@@ -307,15 +321,10 @@ mapped_debug_names_reader::scan_one_entry (const char *name,
continue;
}
- per_cu = this->type_units[ull];
break;
}
case DW_IDX_die_offset:
die_offset = sect_offset (ull);
- /* In a per-CU index (as opposed to a per-module index), index
- entries without CU attribute implicitly refer to the single CU. */
- if (per_cu == nullptr)
- per_cu = this->comp_units[0];
break;
case DW_IDX_parent:
parent = ull;
@@ -339,27 +348,117 @@ mapped_debug_names_reader::scan_one_entry (const char *name,
}
}
- /* Skip if we couldn't find a valid CU/TU index. */
- if (per_cu != nullptr)
+ /* From DWARF 5 section 6.1.1.3 ("Per-CU versus Per-Module Indexes"):
+
+ In a per-CU index, the CU list may have only a single entry, and index
+ entries may omit the CU attribute. */
+ if (comp_unit == nullptr
+ && type_unit == nullptr
+ && foreign_type_unit == nullptr)
{
- dwarf_read_debug_printf_v
- (" -> die_offset %s, per_cu offset %s",
- sect_offset_str (die_offset),
- sect_offset_str (per_cu->sect_off ()));
+ if (this->comp_units.size () != 1)
+ {
+ complain_about_index_entry
+ (abfd, name, offset_in_entry_pool,
+ _(".debug_names entry is missing CU index, but index has %zu CUs "
+ "(expecting 1)"),
+ this->comp_units.size ());
+ return entry;
+ }
- *result
- = indices[next_shard].add (die_offset, (dwarf_tag) indexval.dwarf_tag,
- flags, lang, name, nullptr, per_cu);
-
- ++next_shard;
- if (next_shard == indices.size ())
- next_shard = 0;
-
- entry_pool_offsets_to_entries.emplace (offset_in_entry_pool, *result);
+ comp_unit = this->comp_units[0];
}
- else
- dwarf_read_debug_printf_v (" -> no valid CU/TU, skipping");
+ /* Figure out which unit this entry refers to. */
+ dwarf2_per_cu *actual_unit;
+
+ if (foreign_type_unit != nullptr)
+ {
+ if (type_unit != nullptr)
+ {
+ complain_about_index_entry
+ (abfd, name, offset_in_entry_pool,
+ _(".debug_names entry refers to two TUs"));
+ return entry;
+ }
+
+ /* Implement this part of DWARF 5 section 6.1.1.2 ("Structure of the Name
+ Index"):
+
+ When an index entry refers to a foreign type unit, it may have
+ attributes for both CU and (foreign) TU. For such entries, the CU
+ attribute gives the consumer a reference to the CU that may be used
+ to locate a split DWARF object file that contains the type unit. */
+ if (comp_unit == nullptr)
+ {
+ /* If a foreign type unit does not say which compile unit to follow
+ to find it, it's pretty much useless. */
+ complain_about_index_entry
+ (abfd, name, offset_in_entry_pool,
+ _(".debug_names entry refers to foreign type unit but "
+ "no compile unit"));
+ return entry;
+ }
+
+ actual_unit = foreign_type_unit;
+
+ /* .debug_names attaches one "hint" CU per index entry, insinuating that
+ for some names / index entries, it is important which .dwo we choose to
+ locate the TU. Even if it's important, the DWARF reader is not
+ currently able to load multiple versions of the same TU. So just
+ record one hint CU for each foreign TU. */
+ if (foreign_type_unit->hint_per_cu == nullptr)
+ {
+ foreign_type_unit->hint_per_cu = comp_unit;
+ dwarf_read_debug_printf_v
+ (" hint CU for foreign type unit with signature %s: "
+ "unit offset %s",
+ hex_string (foreign_type_unit->signature),
+ sect_offset_str (comp_unit->sect_off ()));
+ }
+ }
+ else if (type_unit != nullptr)
+ {
+ if (comp_unit != nullptr)
+ {
+ complain_about_index_entry
+ (abfd, name, offset_in_entry_pool,
+ _(".debug_names entry refers to both a CU and a TU"));
+ return entry;
+ }
+
+ actual_unit = type_unit;
+ }
+ else if (comp_unit != nullptr)
+ actual_unit = comp_unit;
+ else
+ {
+ complain_about_index_entry
+ (abfd, name, offset_in_entry_pool,
+ _(".debug_names entry is missing a CU or TU reference"));
+ return entry;
+ }
+
+ gdb_assert (actual_unit != nullptr);
+
+ if (foreign_type_unit != nullptr)
+ dwarf_read_debug_printf_v (" -> signature %s, DIE offset: %s",
+ hex_string (foreign_type_unit->signature),
+ sect_offset_str (die_offset));
+ else
+ dwarf_read_debug_printf_v (" -> unit offset %s, DIE offset %s",
+ sect_offset_str (actual_unit->sect_off ()),
+ sect_offset_str (die_offset));
+
+ *result = indices[next_shard].add (die_offset,
+ (dwarf_tag) indexval.dwarf_tag, flags,
+ lang, name, nullptr, actual_unit);
+
+ ++next_shard;
+ if (next_shard == indices.size ())
+ next_shard = 0;
+
+ entry_pool_offsets_to_entries.emplace (offset_in_entry_pool, *result);
return entry;
}
@@ -553,7 +652,7 @@ build_and_check_tu_list_from_debug_names (dwarf2_per_objfile *per_objfile,
return false;
}
- map.type_units.emplace_back (per_cu);
+ map.type_units.emplace_back (per_cu->as_signatured_type ());
}
return true;
@@ -665,22 +764,12 @@ read_debug_names_from_section (dwarf2_per_objfile *per_objfile,
/* foreign_type_unit_count - The number of TUs in the foreign TU
list. */
- uint32_t foreign_tu_count = read_4_bytes (abfd, addr);
+ map.foreign_tu_count = read_4_bytes (abfd, addr);
addr += 4;
dwarf_read_debug_printf ("cu_count: %u, tu_count: %u, "
"foreign_tu_count: %u",
- map.cu_count, map.tu_count, foreign_tu_count);
-
- if (foreign_tu_count != 0)
- {
- warning (_("Section .debug_names in %ps has unsupported %lu foreign TUs, "
- "ignoring .debug_names."),
- styled_string (file_name_style.style (),
- filename),
- static_cast<unsigned long> (foreign_tu_count));
- return false;
- }
+ map.cu_count, map.tu_count, map.foreign_tu_count);
/* bucket_count - The number of hash buckets in the hash lookup
table. */
@@ -761,6 +850,10 @@ read_debug_names_from_section (dwarf2_per_objfile *per_objfile,
map.tu_table_reordered = addr;
addr += map.tu_count * map.offset_size;
+ /* List of foreign TUs */
+ map.foreign_tu_table_reordered = addr;
+ addr += map.foreign_tu_count * sizeof (std::uint64_t);
+
/* Hash Lookup Table */
map.bucket_table_reordered = reinterpret_cast<const uint32_t *> (addr);
addr += map.bucket_count * 4;
@@ -918,6 +1011,35 @@ build_and_check_cu_lists_from_debug_names (dwarf2_per_bfd *per_bfd,
return build_and_check_cu_list_from_debug_names (per_bfd, dwz_map, dwz->info);
}
+/* Create signatured_type objects for the foreign TU list in MAP. */
+
+static void
+create_foreign_type_units_from_debug_names (dwarf2_per_bfd *per_bfd,
+ mapped_debug_names_reader &map)
+{
+ for (uint32_t i = 0; i < map.foreign_tu_count; ++i)
+ {
+ /* The list of foreign TUs is a list of 64-bit (DW_FORM_ref_sig8) type
+ signatures representing type units placed in .dwo files. All we know
+ about them for now is the signature. */
+ const gdb_byte *ptr
+ = map.foreign_tu_table_reordered + i * sizeof (std::uint64_t);
+ std::uint64_t sig
+ = extract_unsigned_integer (ptr, sizeof (std::uint64_t),
+ map.dwarf5_byte_order);
+
+ dwarf_read_debug_printf_v (" Foreign TU %u (%u): signature %s", i,
+ i + map.tu_count, hex_string (sig));
+
+ signatured_type_up sig_type
+ = per_bfd->allocate_signatured_type (sig);
+
+ map.foreign_type_units.emplace_back (sig_type.get ());
+ per_bfd->signatured_types.emplace (sig_type.get ());
+ per_bfd->all_units.emplace_back (sig_type.release ());
+ }
+}
+
/* See read-debug-names.h. */
bool
@@ -975,6 +1097,12 @@ dwarf2_read_debug_names (dwarf2_per_objfile *per_objfile)
return false;
}
+ create_foreign_type_units_from_debug_names (per_objfile->per_bfd, map);
+
+ /* create_foreign_type_units_from_debug_names may add more entries to the
+ ALL_UNITS vector, so it must be called before finalize_all_units. */
+ finalize_all_units (per_objfile->per_bfd);
+
per_bfd->debug_aranges.read (per_objfile->objfile);
/* There is a single address map for the whole index (coming from
diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index c335a8a97db0..02eced926644 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -750,6 +750,13 @@ static struct die_info *follow_die_ref (struct die_info *,
const struct attribute *,
struct dwarf2_cu **);
+static void fill_in_sig_entry_from_dwo_entry (dwarf2_per_objfile *per_objfile,
+ signatured_type *sig_entry,
+ dwo_unit *dwo_entry);
+
+static void fill_in_sig_entry_from_per_cu_hint
+ (dwarf2_per_objfile &per_objfile, signatured_type &sig_type);
+
static struct die_info *follow_die_sig (struct die_info *,
const struct attribute *,
struct dwarf2_cu **);
@@ -1467,6 +1474,7 @@ dwarf2_per_bfd::allocate_per_cu (dwarf2_section_info *section,
sect_offset sect_off, unsigned int length,
bool is_dwz)
{
+ gdb_assert (section != nullptr);
dwarf2_per_cu_up result (new dwarf2_per_cu (this, section, sect_off,
length, is_dwz));
result->index = all_units.size ();
@@ -1483,6 +1491,7 @@ dwarf2_per_bfd::allocate_signatured_type (dwarf2_section_info *section,
bool is_dwz,
ULONGEST signature)
{
+ gdb_assert (section != nullptr);
auto result
= std::make_unique<signatured_type> (this, section, sect_off, length,
is_dwz, signature);
@@ -1491,6 +1500,20 @@ dwarf2_per_bfd::allocate_signatured_type (dwarf2_section_info *section,
return result;
}
+/* See read.h. */
+
+signatured_type_up
+dwarf2_per_bfd::allocate_signatured_type (ULONGEST signature)
+{
+ auto result
+ = std::make_unique<signatured_type> (this, nullptr,
+ invalid_sect_offset,
+ 0, false, signature);
+ result->index = all_units.size ();
+ this->num_type_units++;
+ return result;
+}
+
/* Subroutine of dw2_get_file_names_reader to simplify it.
Return the file name for the given file_entry.
CU_INFO describes the CU's DW_AT_name and DW_AT_comp_dir.
@@ -1774,6 +1797,20 @@ dwarf2_base_index_functions::expand_all_symtabs (struct objfile *objfile)
for (dwarf2_per_cu *per_cu : all_units_range (per_objfile->per_bfd))
{
+ /* If a .debug_names index contains a foreign TU but no index entry
+ references it, the TU won't have a hint CU. This is a problem, because
+ we won't be able to locate it. Skip them, for the following reasons:
+
+ - If they don't contain anything worthy of a named index entry, they
+ are unlikely to contain anything interesting, symbol-wise.
+ - They are likely to be referred to by some other unit (otherwise,
+ why does it exist?), so will get expanded anyway. */
+ if (signatured_type *sig_type = per_cu->as_signatured_type ();
+ (sig_type != nullptr
+ && sig_type->section () == nullptr
+ && sig_type->hint_per_cu == nullptr))
+ continue;
+
/* We don't want to directly expand a partial CU, because if we
read it with the wrong language, then assertion failures can
be triggered later on. See PR symtab/23010. So, tell
@@ -2276,8 +2313,7 @@ add_type_unit (dwarf2_per_bfd *per_bfd, dwarf2_section_info *section,
return emplace_ret.first;
}
-/* Subroutine of lookup_dwo_signatured_type and lookup_dwp_signatured_type.
- Fill in SIG_ENTRY with DWO_ENTRY. */
+/* Fill in the missing details in SIG_ENTRY from DWO_ENTRY. */
static void
fill_in_sig_entry_from_dwo_entry (dwarf2_per_objfile *per_objfile,
@@ -2733,6 +2769,10 @@ cutu_reader::cutu_reader (dwarf2_per_cu &this_cu,
{
struct objfile *objfile = per_objfile.objfile;
struct dwarf2_section_info *section = this_cu.section ();
+
+ /* Any foreign TU must have been located before getting here. */
+ gdb_assert (section != nullptr);
+
bfd *abfd = section->get_bfd_owner ();
const gdb_byte *begin_info_ptr;
struct dwarf2_section_info *abbrev_section;
@@ -3331,6 +3371,7 @@ cooked_index_worker_debug_info::do_reading ()
dwarf2_per_bfd *per_bfd = m_per_objfile->per_bfd;
create_all_units (m_per_objfile);
+ finalize_all_units (m_per_objfile->per_bfd);
process_type_units (m_per_objfile, &m_index_storage);
if (!per_bfd->debug_aranges.empty ())
@@ -3411,6 +3452,19 @@ read_comp_units_from_section (dwarf2_per_objfile *per_objfile,
/* See read.h. */
+void
+dwarf2_per_bfd::sort_all_units ()
+{
+ std::sort (this->all_units.begin (), this->all_units.end (),
+ [] (const dwarf2_per_cu_up &a, const dwarf2_per_cu_up &b)
+ {
+ return all_units_less_than (*a, { b->section (),
+ b->sect_off () });
+ });
+}
+
+/* See read.h. */
+
void
finalize_all_units (dwarf2_per_bfd *per_bfd)
{
@@ -3418,14 +3472,7 @@ finalize_all_units (dwarf2_per_bfd *per_bfd)
gdb_assert (per_bfd->all_units.size ()
== per_bfd->num_comp_units + per_bfd->num_type_units);
- /* Ensure that the all_units vector is in the expected order for
- dwarf2_find_containing_unit to be able to perform a binary search. */
- std::sort (per_bfd->all_units.begin (), per_bfd->all_units.end (),
- [] (const dwarf2_per_cu_up &a, const dwarf2_per_cu_up &b)
- {
- return all_units_less_than (*a, { b->section (),
- b->sect_off () });
- });
+ per_bfd->sort_all_units ();
}
/* See read.h. */
@@ -3463,7 +3510,6 @@ create_all_units (dwarf2_per_objfile *per_objfile)
per_objfile->per_bfd->signatured_types = std::move (sig_types);
- finalize_all_units (per_objfile->per_bfd);
remove_all_units.disable ();
}
@@ -17164,6 +17210,77 @@ dwarf2_get_die_type (cu_offset die_offset, dwarf2_per_cu *per_cu,
return get_die_type_at_offset (die_offset_sect, per_cu, per_objfile);
}
+/* Fill in the missing details in SIG_TYPE from DWO_FILE.
+
+ Error out if there isn't a type unit with the appropriate signature in
+ DWO_FILE. */
+
+static void
+fill_in_sig_entry_from_dwo_file (dwarf2_per_objfile &per_objfile,
+ signatured_type &sig_type, dwo_file &dwo_file)
+{
+ gdb_assert (sig_type.section () == nullptr);
+
+ dwo_unit *dwo_tu = dwo_file.find_tu (sig_type.signature);
+ if (dwo_tu == nullptr)
+ error (_(DWARF_ERROR_PREFIX
+ "Unable to locate type unit with signature %s in DWO file %s "
+ "[in module %s]"),
+ hex_string (sig_type.signature), dwo_file.dwo_name.c_str (),
+ objfile_name (per_objfile.objfile));
+
+ fill_in_sig_entry_from_dwo_entry (&per_objfile, &sig_type, dwo_tu);
+ sig_type.set_section (dwo_tu->section);
+ sig_type.set_sect_off (dwo_tu->sect_off);
+ sig_type.set_length (dwo_tu->length);
+
+ /* Setting SIG_TYPE's section invalidates the ALL_UNITS vector order,
+ re-sort it. */
+ per_objfile.per_bfd->sort_all_units ();
+}
+
+/* Fill in the missing details in SIG_TYPE from its hint CU.
+
+ Error out if we're unable to locate the .dwo file using the hint CU. */
+
+static void
+fill_in_sig_entry_from_per_cu_hint (dwarf2_per_objfile &per_objfile,
+ signatured_type &sig_type)
+{
+ gdb_assert (sig_type.section () == nullptr);
+ gdb_assert (sig_type.hint_per_cu != nullptr);
+
+ dwarf2_per_cu *hint_per_cu = sig_type.hint_per_cu;
+ dwo_unit *dwo_unit;
+
+ /* If a dwarf2_cu already exists for HINT_PER_CU, no need to build another
+ one. */
+ if (dwarf2_cu *hint_cu = per_objfile.get_cu (hint_per_cu);
+ hint_cu != nullptr)
+ dwo_unit = hint_cu->dwo_unit;
+ else
+ {
+ /* Constructing this cutu_reader will look for the .dwo file, creating
+ the dwo_file if necessary. */
+ abbrev_table_cache abbrev_table_cache;
+ cutu_reader reader (*hint_per_cu, per_objfile, nullptr, true,
+ std::nullopt, abbrev_table_cache);
+
+ /* dwo_unit is owned by the per_bfd, which outlives the reader. */
+ dwo_unit = reader.cu ()->dwo_unit;
+ }
+
+ if (dwo_unit == nullptr)
+ error (_(DWARF_ERROR_PREFIX
+ "Hint compilation unit for foreign type unit with signature %s is "
+ "not in a DWO file [in module %s]"),
+ hex_string (sig_type.signature),
+ objfile_name (per_objfile.objfile));
+
+ fill_in_sig_entry_from_dwo_file (per_objfile, sig_type,
+ *dwo_unit->dwo_file);
+}
+
/* Follow type unit SIG_TYPE referenced by SRC_DIE.
On entry *REF_CU is the CU of SRC_DIE.
On exit *REF_CU is the CU of the result.
@@ -17179,6 +17296,28 @@ follow_die_sig_1 (struct die_info *src_die, struct signatured_type *sig_type,
we can get here for DW_AT_imported_declaration where we need
the DIE not the type. */
+ /* If SIG_TYPE's section is not set, it means it's a .debug_names foreign
+ type unit for which we don't know the containing file or section yet.
+ Use the referencing CU as the hint: we know there must exist a TU with
+ the correct signature in its .dwo file, and that .dwo is already open,
+ might as well use it.
+
+ But this is not only an optimization. If a .debug_names foreign unit
+ does not have any index entry referencing it, then we don't have any
+ "hint CU" for it. The only hint we have is the referencing CU. */
+ if (sig_type->section () == nullptr)
+ {
+ if ((*ref_cu)->dwo_unit == nullptr)
+ error (_(DWARF_ERROR_PREFIX
+ "Unit referencing foreign type unit with signature %s is "
+ "not from a DWO file [in module %s]"),
+ hex_string (sig_type->signature),
+ objfile_name (per_objfile->objfile));
+
+ fill_in_sig_entry_from_dwo_file (*per_objfile, *sig_type,
+ *(*ref_cu)->dwo_unit->dwo_file);
+ }
+
dwarf2_cu *sig_cu = ensure_loaded_type_unit (sig_type, per_objfile);
if (sig_cu == nullptr)
@@ -17359,6 +17498,12 @@ load_full_type_unit (signatured_type *sig_type,
gdb_assert (sig_type->is_debug_types ());
gdb_assert (per_objfile->get_cu (sig_type) == nullptr);
+ /* If the section is not set, this is a .debug_names foreign type unit for
+ which we don't know the containing file nor section yet. For these, we
+ must have a "hint" CU to follow to find the file and section. */
+ if (sig_type->section () == nullptr)
+ fill_in_sig_entry_from_per_cu_hint (*per_objfile, *sig_type);
+
abbrev_table_cache abbrev_table_cache;
cutu_reader reader (*sig_type, *per_objfile, nullptr, false,
std::nullopt, abbrev_table_cache);
diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
index e2533c8a91f2..d47195f39b8e 100644
--- a/gdb/dwarf2/read.h
+++ b/gdb/dwarf2/read.h
@@ -108,7 +108,6 @@ struct dwarf2_per_cu
m_per_bfd (per_bfd)
{
gdb_assert (per_bfd != nullptr);
- gdb_assert (section != nullptr);
}
private:
@@ -283,9 +282,24 @@ struct dwarf2_per_cu
dwarf2_section_info *section () const
{ return m_section; }
+ /* Set the section of this unit. */
+ void set_section (dwarf2_section_info *section)
+ {
+ gdb_assert (section != nullptr);
+ gdb_assert (m_section == nullptr);
+ m_section = section;
+ }
+
sect_offset sect_off () const
{ return m_sect_off; }
+ /* Set the section offset of this unit. */
+ void set_sect_off (sect_offset sect_off)
+ {
+ gdb_assert (m_sect_off == invalid_sect_offset);
+ m_sect_off = sect_off;
+ }
+
bool is_dwz () const
{ return m_is_dwz; }
@@ -478,6 +492,12 @@ struct signatured_type : public dwarf2_per_cu
/* Containing DWO unit.
This field is valid iff per_cu.reading_dwo_directly. */
struct dwo_unit *dwo_unit = nullptr;
+
+ /* When using a .debug_names index, the section is not initially known for
+ foreign type units (aka skeletonless type units). This is a reference to
+ a CU that can be used to locate the .dwo file and section containing the
+ type unit. */
+ dwarf2_per_cu *hint_per_cu = nullptr;
};
using signatured_type_up = std::unique_ptr<signatured_type>;
@@ -748,6 +768,10 @@ struct dwarf2_per_bfd
return this->all_units[index].get ();
}
+ /* Ensure that the all_units vector is in the expected order for
+ dwarf2_find_containing_unit to be able to perform a binary search. */
+ void sort_all_units ();
+
/* Return the separate '.dwz' debug file. If there is no
.gnu_debugaltlink or .debug_sup section in the file, then the
result depends on REQUIRE: if REQUIRE is true, error out; if
@@ -782,6 +806,12 @@ struct dwarf2_per_bfd
bool is_dwz,
ULONGEST signature);
+ /* A convenience function to allocate a signatured_type. The
+ returned object has its "index" field set properly.
+
+ This one is used when only the signature is known at creation time. */
+ signatured_type_up allocate_signatured_type (ULONGEST signature);
+
/* Map all the DWARF section data needed when scanning
.debug_info. */
void map_info_sections (struct objfile *objfile);
@@ -1505,7 +1535,10 @@ extern const char *read_indirect_string_at_offset
extern void finalize_all_units (dwarf2_per_bfd *per_bfd);
-/* Create a list of all compilation units in OBJFILE. */
+/* Create a list of all compilation units in OBJFILE.
+
+ After it is done creating all units, the caller is responsible for calling
+ finalize_all_units. */
extern void create_all_units (dwarf2_per_objfile *per_objfile);
diff --git a/gdb/dwarf2/types.h b/gdb/dwarf2/types.h
index 169b0fd08678..cb8ce33940ca 100644
--- a/gdb/dwarf2/types.h
+++ b/gdb/dwarf2/types.h
@@ -31,6 +31,8 @@ DEFINE_OFFSET_TYPE (cu_offset, unsigned int);
section. */
DEFINE_OFFSET_TYPE (sect_offset, uint64_t);
+constexpr auto invalid_sect_offset = static_cast<sect_offset> (-1);
+
static inline const char *
sect_offset_str (sect_offset offset)
{
diff --git a/gdb/testsuite/boards/dwarf5-fission-debug-types-debug-names.exp b/gdb/testsuite/boards/dwarf5-fission-debug-types-debug-names.exp
new file mode 100644
index 000000000000..be8bebed1a43
--- /dev/null
+++ b/gdb/testsuite/boards/dwarf5-fission-debug-types-debug-names.exp
@@ -0,0 +1,29 @@
+# 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/>.
+
+# A board that compiles with DWARF 5, split DWARF (fission) and type units,
+# and generates a .debug_names index using GDB.
+#
+# Example usage:
+# bash$ make check \
+# RUNTESTFLAGS='--target_board=dwarf5-fission-debug-types-debug-names'
+
+set CC_WITH_TWEAKS_FLAGS "-n"
+load_board_description "cc-with-tweaks"
+
+set_board_info debug_flags \
+ [join { "-gdwarf-5" \
+ "-gsplit-dwarf" \
+ "-fdebug-types-section" }]
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
end of thread, other threads:[~2026-04-16 20:05 UTC | newest]
Thread overview: 20+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-03-16 23:19 [PATCH 0/8] Handle foreign type units in .debug_names simon.marchi
2026-03-16 23:19 ` [PATCH 1/8] gdb/dwarf: refuse to produce .gdb_index when skeletonless type units are present simon.marchi
2026-03-17 12:57 ` Eli Zaretskii
2026-04-16 19:43 ` Simon Marchi
2026-03-16 23:19 ` [PATCH 2/8] gdb/dwarf: move dwo_unit and dwo_file to read.h simon.marchi
2026-03-16 23:19 ` [PATCH 3/8] gdb/dwarf: move dwarf2_cu::section to cu.c simon.marchi
2026-03-16 23:19 ` [PATCH 4/8] gdb/dwarf: add dwo_file::find_tus simon.marchi
2026-03-16 23:19 ` [PATCH 5/8] gdb/dwarf: generate foreign type units in .debug_names simon.marchi
2026-03-16 23:19 ` [PATCH 6/8] gdb/dwarf: add debug output in read-debug-names.c simon.marchi
2026-03-16 23:19 ` [PATCH 7/8] gdb/dwarf: add more context to complaints in mapped_debug_names_reader::scan_one_entry simon.marchi
2026-03-16 23:19 ` [PATCH 8/8] gdb/dwarf: read foreign type units simon.marchi
2026-04-16 19:59 ` [PATCH v2 0/8] Handle foreign type units in .debug_names Simon Marchi
2026-04-16 19:59 ` [PATCH v2 1/8] gdb: refuse to produce .gdb_index when skeletonless type units are present Simon Marchi
2026-04-16 19:59 ` [PATCH v2 2/8] gdb/dwarf: move dwo_unit and dwo_file to read.h Simon Marchi
2026-04-16 19:59 ` [PATCH v2 3/8] gdb/dwarf: move dwarf2_cu::section to cu.c Simon Marchi
2026-04-16 19:59 ` [PATCH v2 4/8] gdb/dwarf: add dwo_file::find_tus Simon Marchi
2026-04-16 19:59 ` [PATCH v2 5/8] gdb/dwarf: generate foreign type units in .debug_names Simon Marchi
2026-04-16 19:59 ` [PATCH v2 6/8] gdb/dwarf: add debug output in read-debug-names.c Simon Marchi
2026-04-16 19:59 ` [PATCH v2 7/8] gdb/dwarf: add more context to complaints in mapped_debug_names_reader::scan_one_entry Simon Marchi
2026-04-16 19:59 ` [PATCH v2 8/8] gdb/dwarf: read foreign type units Simon Marchi
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox