* [PATCH] [GDB 18] gdb: resolve class name via DW_AT_signature in cooked index
@ 2026-08-19 10:03 Andrew Burgess
2026-08-21 17:07 ` Tom Tromey
2026-08-28 21:30 ` [PATCHv2] " Andrew Burgess
0 siblings, 2 replies; 36+ messages in thread
From: Andrew Burgess @ 2026-08-19 10:03 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
This patch fixes a regression that appeared since GDB 17, so if/when
approved I plan to merge this to both master and gdb-18-branch.
Thanks,
Andrew
---
This commit fixes PR gdb/33447, an issue where looking up qualified
member function names was not working for C++ binaries compiled with
Clang when using the -fdebug-types-section flag.
Before this commit we would see this behaviour:
(gdb) print base1::a_function
There is no field named a_function
When what we expect to see is:
(gdb) print base1::a_function
$1 = {void (const base1 * const)} 0x403060 <base1::a_function() const>
The problem is that the cooked index is unable to determine the name
of the parent class `base1` in this case, and so decides not to index
any of the member functions.
The problem was discovered by running gdb.cp/cpexprs-debug-types.exp
with Clang:
make check-gdb TESTS=gdb.cp/cpexprs-debug-types.exp \
RUNTESTFLAGS='CXX_FOR_TARGET=clang++ CC_FOR_TARGET=clang'
The cpexprs-debug-types.exp test forces use of the
'-fdebug-types-section' flag, which is not on by default. With this
flag, class definitions are placed in type units, and the compile unit
contains only a declaration stub for each class. Both clang++ and g++
emit these stubs, but they differ in one detail: GCC includes
DW_AT_name on the stub, while clang++ does not, the stub carries only
DW_AT_declaration and DW_AT_signature. The class name is only
available in the type unit, reachable by following the signature.
For example, Clang emits this in a CU:
<1><2e7d>: DW_TAG_class_type
DW_AT_declaration : 1
DW_AT_signature : 0x3abb...
<2><2eac>: DW_TAG_subprogram
DW_AT_name : a_function
DW_AT_declaration : 1
The definition for a_function is elsewhere in the same CU:
<1><3142>: DW_TAG_subprogram
DW_AT_specification: <0x2eac>
And in a TU elsewhere:
Compilation Unit @ offset 0xd2e:
... snip ...
Signature: 0x3abb...
<0><d46>: Abbrev Number: 1 (DW_TAG_type_unit)
... snip ...
<1><d51>: Abbrev Number: 30 (DW_TAG_class_type)
... snip ...
<d57> DW_AT_name : (indexed string: 0xaa): base1
To find the DW_AT_name the cooked index needs to look up the type
within the TU. Without the name the cooked indexer would skip the
children of `base1`.
This look up used to work; it works in GDB 17. PR gdb/33447
incorrectly identifies commit c879f4dc3e317cf6353a45a803ecf00d577a13d8
as the commit that introduced the regression. This is actually the
last working commit. The problem was introduced by the next commit in
the same series:
commit 86ac8c546235a67d6a6bb29476a3a9ac8f7a620a (HEAD)
Date: Thu Jan 2 15:28:18 2025 -0700
Convert lookup_symbol_in_objfile
Prior to this commit GDB's symbol lookup had two phases, a search
through already expanded symtabs, and a search via
lookup_symbol_via_quick_fns. After the above commit only
lookup_symbol_via_quick_fns remains.
The lookup_symbol_via_quick_fns lookup, which relies on the indexer,
was always broken, but the first phase, search via expanded symtabs,
could correctly find the type name via the signature.
The fix has three parts:
1. abbrev.c: Add DW_AT_signature to the set of attributes that mark a
DIE as "interesting" in has_specification_or_origin. Without this,
the unnamed class stub was classified as uninteresting at the
abbreviation level and scan_attributes was never called for it.
2. cooked-indexer.c (scan_attributes): Handle DW_AT_signature by
looking up the signatured_type via lookup_signatured_type and
constructing a section_and_offset origin pointing to the type DIE
in the type unit. Restructure the is_declaration / origin-
following control flow: change the "else if (origin)" to a
standalone "if" so that class declaration stubs marked with
IS_TYPE_DECLARATION can still follow their origin to retrieve the
class name from the type unit. Add origin.reset() in the other
declaration paths to preserve the original behaviour for non-class
declarations and Ada imports.
3. read.c/read.h: Make lookup_signatured_type externally visible so
it can be called from cooked-indexer.c.
A new DWARF assembler test gdb.dwarf2/sig-type-unnamed-class.exp
reproduces the problem case, there's a nameless declaration stub that
references its full type via DW_AT_signature. To match the Clang
output as much as possible the member function definition is separate
and makes use of DW_AT_specification.
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=33447
---
gdb/dwarf2/abbrev.c | 1 +
gdb/dwarf2/cooked-indexer.c | 27 ++++-
gdb/dwarf2/read.c | 6 +-
gdb/dwarf2/read.h | 7 ++
.../gdb.dwarf2/sig-type-unnamed-class.exp | 106 ++++++++++++++++++
5 files changed, 138 insertions(+), 9 deletions(-)
create mode 100644 gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp
diff --git a/gdb/dwarf2/abbrev.c b/gdb/dwarf2/abbrev.c
index 44d5c87a5f7..e99acc5e752 100644
--- a/gdb/dwarf2/abbrev.c
+++ b/gdb/dwarf2/abbrev.c
@@ -160,6 +160,7 @@ abbrev_table::read (struct dwarf2_section_info *section,
case DW_AT_specification:
case DW_AT_abstract_origin:
case DW_AT_extension:
+ case DW_AT_signature:
has_specification_or_origin = true;
break;
diff --git a/gdb/dwarf2/cooked-indexer.c b/gdb/dwarf2/cooked-indexer.c
index f55efc5e3a0..68222a95c49 100644
--- a/gdb/dwarf2/cooked-indexer.c
+++ b/gdb/dwarf2/cooked-indexer.c
@@ -234,6 +234,19 @@ cooked_indexer::scan_attributes (dwarf2_per_cu *scanning_per_cu,
attr.get_ref_die_offset () };
break;
+ case DW_AT_signature:
+ {
+ ULONGEST signature = attr.as_signature ();
+ signatured_type *sig_type = lookup_signatured_type (reader->cu (),
+ signature);
+ if (sig_type == nullptr)
+ complaint (_("cannot find DW_AT_signature type %s [in module %s]"),
+ hex_string (signature), bfd_get_filename (reader->abfd ()));
+ else
+ origin = { sig_type->section (), sig_type->type_offset_in_section };
+ }
+ break;
+
case DW_AT_external:
if (attr.as_boolean ())
*flags &= ~IS_STATIC;
@@ -325,13 +338,17 @@ cooked_indexer::scan_attributes (dwarf2_per_cu *scanning_per_cu,
{
*linkage_name = nullptr;
*name = nullptr;
+ origin.reset ();
}
+ else
+ origin.reset ();
}
- else if ((*name == nullptr
- || (*linkage_name == nullptr
- && tag_can_have_linkage_name (abbrev->tag))
- || (*parent_entry == nullptr && m_language != language_c))
- && origin.has_value ())
+
+ if ((*name == nullptr
+ || (*linkage_name == nullptr
+ && tag_can_have_linkage_name (abbrev->tag))
+ || (*parent_entry == nullptr && m_language != language_c))
+ && origin.has_value ())
{
cutu_reader *new_reader
= ensure_cu_exists (reader, *origin, false);
diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index ca475f53745..f69d34d51a2 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -2382,11 +2382,9 @@ lookup_dwp_signatured_type (struct dwarf2_cu *cu, ULONGEST sig)
return *sig_type_it;
}
-/* Lookup a signature based type for DW_FORM_ref_sig8.
- Returns NULL if signature SIG is not present in the table.
- It is up to the caller to complain about this. */
+/* See dwarf2/read.h. */
-static struct signatured_type *
+struct signatured_type *
lookup_signatured_type (struct dwarf2_cu *cu, ULONGEST sig)
{
dwarf2_per_objfile *per_objfile = cu->per_objfile;
diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
index 15dd2abf3a1..d3e2d9fb198 100644
--- a/gdb/dwarf2/read.h
+++ b/gdb/dwarf2/read.h
@@ -1489,4 +1489,11 @@ extern struct dwarf2_section_info *get_debug_line_section
extern bool is_ada_import_or_export (dwarf2_cu *cu, const char *name,
const char *linkagename);
+/* Lookup a signature based type for DW_FORM_ref_sig8. Returns NULL
+ if signature SIG is not present in the table of CU. It is up to
+ the caller to complain about this. */
+
+extern struct signatured_type *lookup_signatured_type (struct dwarf2_cu *cu,
+ ULONGEST sig);
+
#endif /* GDB_DWARF2_READ_H */
diff --git a/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp
new file mode 100644
index 00000000000..6f39e2c823d
--- /dev/null
+++ b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp
@@ -0,0 +1,106 @@
+# 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/>.
+
+# Check that the cooked index correctly resolves the parent of a member
+# function when the class declaration stub in the compile unit has
+# DW_AT_signature but no DW_AT_name.
+#
+# This replicates what Clang emits with -fdebug-types-section. The
+# compile unit contains an unnamed DW_TAG_class_type declaration with
+# only DW_AT_declaration, DW_AT_signature, and child member function
+# declarations.
+#
+# The class name must be resolved by following the signature to the
+# type unit. Without this, the member function definitions (which use
+# DW_AT_specification to point at the child declarations) end up with
+# no parent, and qualified lookup fails.
+
+load_lib dwarf.exp
+
+# This test can only be run on targets which support DWARF-2 and use gas.
+require dwarf2_support
+
+standard_testfile main-foo.c .S
+
+# Build the test program using VERSION for the CU/TU DWARF version.
+proc run_test { version } {
+ # Create the DWARF.
+ set asm_file [standard_output_file $::srcfile2]
+ Dwarf::assemble {
+ filename $asm_file
+ add_dummy_cus 0
+ } {
+ upvar version version
+
+ get_func_info foo
+ get_func_info main
+
+ declare_labels method_decl
+
+ tu { version $version } 0xdeadbeef01234567 the_type {
+ DW_TAG_type_unit {
+ DW_AT_language @DW_LANG_C_plus_plus
+ } {
+ the_type: DW_TAG_class_type {
+ DW_AT_name the_type
+ DW_AT_byte_size 1 sdata
+ }
+ }
+ }
+
+ cu { version $version } {
+ compile_unit {
+ DW_AT_language @DW_LANG_C_plus_plus
+ } {
+ DW_TAG_class_type {
+ DW_AT_declaration 1 flag
+ DW_AT_signature 0xdeadbeef01234567 ref_sig8
+ } {
+ method_decl: DW_TAG_subprogram {
+ DW_AT_name method
+ DW_AT_linkage_name _ZN8the_type6methodEv
+ DW_AT_declaration 1 flag
+ }
+ }
+
+ DW_TAG_subprogram {
+ DW_AT_specification %$method_decl
+ DW_AT_low_pc $foo_start DW_FORM_addr
+ DW_AT_high_pc $foo_end DW_FORM_addr
+ }
+
+ DW_TAG_subprogram {
+ DW_AT_name main
+ DW_AT_low_pc $main_start DW_FORM_addr
+ DW_AT_high_pc $main_end DW_FORM_addr
+ }
+ }
+ }
+ }
+
+ if { [prepare_for_testing "failed to prepare" ${::testfile}-${version} \
+ [list $asm_file $::srcfile] {nodebug}] } {
+ return
+ }
+
+ # Check that GDB was able to find the parent for "method", and as
+ # a result, can correctly find this field of "the_type".
+ gdb_test "print the_type::method" \
+ [string_to_regexp " <the_type::method()>"]
+}
+
+foreach_with_prefix version { 4 5 } {
+ run_test $version
+}
base-commit: afa6db16e6508d8ea269557085bc7c301e824382
--
2.25.4
^ permalink raw reply [flat|nested] 36+ messages in thread* Re: [PATCH] [GDB 18] gdb: resolve class name via DW_AT_signature in cooked index 2026-08-19 10:03 [PATCH] [GDB 18] gdb: resolve class name via DW_AT_signature in cooked index Andrew Burgess @ 2026-08-21 17:07 ` Tom Tromey 2026-08-28 21:30 ` [PATCHv2] " Andrew Burgess 1 sibling, 0 replies; 36+ messages in thread From: Tom Tromey @ 2026-08-21 17:07 UTC (permalink / raw) To: Andrew Burgess; +Cc: gdb-patches >>>>> "Andrew" == Andrew Burgess <aburgess@redhat.com> writes: Andrew> The problem is that the cooked index is unable to determine the name Andrew> of the parent class `base1` in this case, and so decides not to index Andrew> any of the member functions. Andrew> 2. cooked-indexer.c (scan_attributes): Handle DW_AT_signature by Andrew> looking up the signatured_type via lookup_signatured_type and Andrew> constructing a section_and_offset origin pointing to the type DIE Andrew> in the type unit. Restructure the is_declaration / origin- Andrew> following control flow: change the "else if (origin)" to a Andrew> standalone "if" so that class declaration stubs marked with Andrew> IS_TYPE_DECLARATION can still follow their origin to retrieve the Andrew> class name from the type unit. Add origin.reset() in the other Andrew> declaration paths to preserve the original behaviour for non-class Andrew> declarations and Ada imports. It's been a while since I was deep in the indexer, but looking at done_reading makes me wonder if this approach is safe: void cooked_index_worker_debug_info::done_reading () { /* This has to wait until we read the CUs, we need the list of DWOs. */ process_skeletonless_type_units (m_per_objfile, &m_index_storage); That is, I think skeletonless type units aren't processed until all other indexing is done. So if the signatured type appears in one of these, doesn't this mean the fix will fail? I don't remember how to set one of these up. Also my first thought when seeing this patch was that it might not be thread-safe. I'm still not completely sure. Perhaps it's fine because other type units seem to be (unfortunately) processed serially. A typical fix for these kinds of issues is to defer some of the work to the finalization step in the shard. I guess here the idea would be to store the signature and the relevant entry in some data structure, then patch up the parentage or whatever when finalizing. Tom ^ permalink raw reply [flat|nested] 36+ messages in thread
* [PATCHv2] gdb: resolve class name via DW_AT_signature in cooked index 2026-08-19 10:03 [PATCH] [GDB 18] gdb: resolve class name via DW_AT_signature in cooked index Andrew Burgess 2026-08-21 17:07 ` Tom Tromey @ 2026-08-28 21:30 ` Andrew Burgess 2026-09-01 13:31 ` [PATCHv3] " Andrew Burgess 1 sibling, 1 reply; 36+ messages in thread From: Andrew Burgess @ 2026-08-28 21:30 UTC (permalink / raw) To: gdb-patches; +Cc: Andrew Burgess In v2: - Complete rewrite to address the issues that Tom raised. - Rebase to HEAD and retest. - New tests added to cover the DWO case and an error case. Thanks, Andrew --- This commit fixes PR gdb/33447, an issue where looking up qualified member function names was not working for C++ binaries compiled with Clang when using the -fdebug-types-section flag. Before this commit we would see this behaviour: (gdb) print base1::a_function There is no field named a_function When what we expect to see is: (gdb) print base1::a_function $1 = {void (const base1 * const)} 0x403060 <base1::a_function() const> The problem is that the cooked index is unable to determine the name of the parent class `base1` in this case, and so decides not to index `base1` or any of its child DIEs, which includes its member functions. The problem was discovered by running gdb.cp/cpexprs-debug-types.exp with Clang: make check-gdb TESTS=gdb.cp/cpexprs-debug-types.exp \ RUNTESTFLAGS='CXX_FOR_TARGET=clang++ CC_FOR_TARGET=clang' The cpexprs-debug-types.exp test forces use of the '-fdebug-types-section' flag, which is not on by default. With this flag, class definitions are placed in type units, and the compile unit contains only a declaration stub for each class. Both clang++ and g++ emit these stubs, but they differ in one detail: GCC includes DW_AT_name on the stub, while clang++ does not, the stub carries only DW_AT_declaration and DW_AT_signature. The class name is only available in the type unit, reachable by following the signature. For example, Clang emits this in a CU: <1><2e7d>: DW_TAG_class_type DW_AT_declaration : 1 DW_AT_signature : 0x3abb... <2><2eac>: DW_TAG_subprogram DW_AT_name : a_function DW_AT_declaration : 1 The definition for a_function is elsewhere in the same CU: <1><3142>: DW_TAG_subprogram DW_AT_specification: <0x2eac> And in a TU elsewhere: Compilation Unit @ offset 0xd2e: ... snip ... Signature: 0x3abb... <0><d46>: Abbrev Number: 1 (DW_TAG_type_unit) ... snip ... <1><d51>: Abbrev Number: 30 (DW_TAG_class_type) ... snip ... <d57> DW_AT_name : (indexed string: 0xaa): base1 To find the DW_AT_name the cooked index needs to look up the type within the TU. Without the name the cooked indexer skips indexing `base1` as well as its children. This look up used to work; it works in GDB 17. PR gdb/33447 incorrectly identifies commit c879f4dc3e317cf6353a45a803ecf00d577a13d8 as the commit that introduced the regression. This is actually the last working commit. The problem was introduced by the next commit in the same series: commit 86ac8c546235a67d6a6bb29476a3a9ac8f7a620a Date: Thu Jan 2 15:28:18 2025 -0700 Convert lookup_symbol_in_objfile Prior to this commit GDB's symbol lookup had two phases, a search through already expanded symtabs, and a search via lookup_symbol_via_quick_fns. After the above commit only lookup_symbol_via_quick_fns remains. The lookup_symbol_via_quick_fns lookup, which relies on the indexer, was always broken, but the first phase, searching via expanded symtabs, could correctly find the type name via the signature. An initial attempt to solve this problem tried to fix this problem within cooked_indexer::scan_attributes, calling lookup_signatured_type and finding the name that way. However, there were three problems with this approach: 1. Possible thread safety issues; calling lookup_signatured_type for a DWO file ends up calling lookup_dwo_signatured_type, which can call add_type_unit and finalize_all_units, which modify state that is shared between parser threads. 2. The dwarf2_per_cu::type_offset_in_section for a TU is only set when the cutu_reader is constructed to parse that TU. Calling lookup_signatured_type doesn't fully parse the TU, it just finds the TU. The original code relied on type_offset_in_section being valid in order to then parse the TU and extract the name. This would break if the CU was processed before the TU. 3. For skeletonless TUs, these are not indexed until late in the indexing process, after all the CUs have been indexed. This means that if the TU referenced by the signature was skeletonless then the original approach would fail to find it. The new approach presented here is modeled more along the lines of the deferred parent handling that already exists within the indexer. The following changes have been made: 1. abbrev.c: Add DW_AT_signature to the set of attributes that mark a DIE as "interesting" in has_specification_or_origin. Without this, the unnamed class stub was classified as uninteresting at the abbreviation level and scan_attributes was never called for it. 2. cooked-indexer.c (cooked_indexer::scan_attributes): Capture the DW_AT_signature attribute. 3. cooked-indexer.c (cooked_indexer::index_dies): There are two different jobs done here: (a) If a DIE has no name, but does have a signature, then give the DIE a fake name (the empty string), and create an index entry for the DIE. Also keep a record that the cooked_index_entry for this DIE has a deferred name. (b) If we are indexing a TU, make a record of the signature, and the name of the primary type within the TU. This builds a signature to name map. 4. cooked-index-worker.c (cooked_index_worker::done_reading): Merge together all of the signature to name maps. 5. cooked-index-shard.c (cooked_index_shard::finalize): Look through all of the cooked_index_entry objects that were recorded in (3a), for each use the signature to name map to lookup the name, and update the cooked_index_entry. There are a bunch of header file changes to add the new maps, and accessor functions, the steps above describe the core mechanism. Added three new DWARF assembler tests. gdb.dwarf2/sig-type-unnamed-class.exp gdb.dwarf2/sig-type-unnamed-class-dwo.exp: These reproduce the problem case; there's a nameless declaration stub that references its full type via DW_AT_signature. To match the Clang output as much as possible the member function definition is separate and makes use of DW_AT_specification. The -dwo test places the DWARF into a DWO file and uses a skeletonless TU. gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp: This one tests some invalid DWARF, the type DIE references a signature that doesn't exist. In this case GDB just leaves the type DIE with an empty name string, which means symbols cannot be found. I think this is fine though, the DWARF is corrupted in this case. Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=33447 --- gdb/dwarf2/abbrev.c | 1 + gdb/dwarf2/cooked-index-entry.h | 5 + gdb/dwarf2/cooked-index-shard.c | 21 ++- gdb/dwarf2/cooked-index-shard.h | 19 ++- gdb/dwarf2/cooked-index-worker.c | 9 ++ gdb/dwarf2/cooked-index-worker.h | 38 +++++ gdb/dwarf2/cooked-index.c | 5 +- gdb/dwarf2/cooked-indexer.c | 44 +++++- gdb/dwarf2/cooked-indexer.h | 1 + .../sig-type-unnamed-class-bad-sig.exp | 133 +++++++++++++++++ .../gdb.dwarf2/sig-type-unnamed-class-dwo.exp | 139 ++++++++++++++++++ .../gdb.dwarf2/sig-type-unnamed-class.exp | 106 +++++++++++++ 12 files changed, 515 insertions(+), 6 deletions(-) create mode 100644 gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp create mode 100644 gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-dwo.exp create mode 100644 gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp diff --git a/gdb/dwarf2/abbrev.c b/gdb/dwarf2/abbrev.c index 44d5c87a5f7..e99acc5e752 100644 --- a/gdb/dwarf2/abbrev.c +++ b/gdb/dwarf2/abbrev.c @@ -160,6 +160,7 @@ abbrev_table::read (struct dwarf2_section_info *section, case DW_AT_specification: case DW_AT_abstract_origin: case DW_AT_extension: + case DW_AT_signature: has_specification_or_origin = true; break; diff --git a/gdb/dwarf2/cooked-index-entry.h b/gdb/dwarf2/cooked-index-entry.h index 60ea581cbbe..f0ff356a6d1 100644 --- a/gdb/dwarf2/cooked-index-entry.h +++ b/gdb/dwarf2/cooked-index-entry.h @@ -79,6 +79,11 @@ union cooked_index_entry_ref parent_map::addr_type deferred; }; +/* Type that maps DW_AT_signature values for a TU to the name of the + primary type within the TU. */ + +using signature_to_name_map = std::unordered_map<ULONGEST, const char *>; + /* Return a string representation of FLAGS. */ std::string to_string (cooked_index_flag flags); diff --git a/gdb/dwarf2/cooked-index-shard.c b/gdb/dwarf2/cooked-index-shard.c index 91dc9ab8945..ce13b0788f0 100644 --- a/gdb/dwarf2/cooked-index-shard.c +++ b/gdb/dwarf2/cooked-index-shard.c @@ -20,6 +20,7 @@ #include "dwarf2/cooked-index-shard.h" #include "dwarf2/tag.h" #include "dwarf2/index-common.h" +#include "dwarf2/read.h" #include "cp-support.h" #include "c-lang.h" #include "ada-lang.h" @@ -190,8 +191,26 @@ struct cooked_index_entry_name_ptr_eq /* See cooked-index-shard.h. */ void -cooked_index_shard::finalize (const parent_map_map *parent_maps) +cooked_index_shard::finalize (const parent_map_map *parent_maps, + const signature_to_name_map *sig_names) { + for (const std::pair<cooked_index_entry *, ULONGEST> &entry : m_deferred_names) + { + ULONGEST signature = entry.second; + const auto it = sig_names->find (signature); + if (it != sig_names->end ()) + { + /* The previous name was the empty string literal, so + overwriting it doesn't leak. The new name will point + into the DWARF data. */ + entry.first->name = it->second; + } + else + complaint (_("unable to find TU with signature 0x%s [in module %s]"), + phex (signature, sizeof (signature)), + entry.first->per_cu->per_bfd ()->filename ()); + } + gdb::unordered_set<const cooked_index_entry *, cooked_index_entry_name_ptr_hash, cooked_index_entry_name_ptr_eq> seen_names; diff --git a/gdb/dwarf2/cooked-index-shard.h b/gdb/dwarf2/cooked-index-shard.h index 84c37958c83..4f25fb594a1 100644 --- a/gdb/dwarf2/cooked-index-shard.h +++ b/gdb/dwarf2/cooked-index-shard.h @@ -79,6 +79,16 @@ class cooked_index_shard for completion, will be returned. */ range find (const std::string &name, bool completing) const; + /* Record that ENTRY didn't have a name attribute, but did have a + DW_AT_signature attribute, SIGNATURE. ENTRY will have been + assigned a fake, empty, name string. We will patch the name of + ENTRY during finalization once the TUs have been parsed, the + correct TU will be found using SIGNATURE. */ + void add_deferred_name (cooked_index_entry *entry, ULONGEST signature) + { + m_deferred_names.emplace_back (entry, signature); + } + private: /* Return the entry that is believed to represent the program's @@ -122,7 +132,8 @@ class cooked_index_shard the index has been fully populated. It enters all the entries into the internal table and fixes up all missing parent links. This may be invoked in a worker thread. */ - void finalize (const parent_map_map *parent_maps); + void finalize (const parent_map_map *parent_maps, + const signature_to_name_map *sig_names); /* Storage for the entries. */ auto_obstack m_storage; @@ -134,6 +145,12 @@ class cooked_index_shard addrmap_fixed *m_addrmap = nullptr; /* Storage for canonical names. */ gdb::string_set m_names; + + /* Entries without a name, but with a signature. These entries will + have been given a fake name, the empty string when they were + created, but we need to patch these up with a real name during + finalization. */ + std::vector<std::pair<cooked_index_entry *, ULONGEST>> m_deferred_names; }; using cooked_index_shard_up = std::unique_ptr<cooked_index_shard>; diff --git a/gdb/dwarf2/cooked-index-worker.c b/gdb/dwarf2/cooked-index-worker.c index 723e027172e..054b25ba147 100644 --- a/gdb/dwarf2/cooked-index-worker.c +++ b/gdb/dwarf2/cooked-index-worker.c @@ -249,6 +249,15 @@ cooked_index_worker::done_reading () m_all_parents_map.add_map (*one_result.get_parent_map ()); } + { + scoped_time_it time_it ("DWARF add signature name map", m_per_command_time); + + /* Combine all of the signature to name maps. */ + for (cooked_index_worker_result &one_result : m_results) + for (const auto &[sig, name] : one_result.get_sig_name_map ()) + m_all_sig_names_map.emplace (sig, name); + } + /* Update all the CU inclusion information. */ for (auto &item : m_results) item.invert_cu_inclusions (); diff --git a/gdb/dwarf2/cooked-index-worker.h b/gdb/dwarf2/cooked-index-worker.h index 1799c0b9e08..262b727b983 100644 --- a/gdb/dwarf2/cooked-index-worker.h +++ b/gdb/dwarf2/cooked-index-worker.h @@ -83,6 +83,25 @@ class cooked_index_worker_result return m_shard->add (name); } + /* Record that ENTRY was missing a name attribute, but did have a + DW_AT_signature attribute, SIGNATURE. When the TUs are + processed, which might not happen until after CU processing for + skeletonless TUs, we can find a name and supply it at that + point. */ + void add_deferred_name (cooked_index_entry *entry, ULONGEST signature) + { + m_shard->add_deferred_name (entry, signature); + } + + /* Called when processing TUs to record that the primary type within + a type unit with SIGNATURE, was called NAME. This information + will be used during finalization to fix-up the name of any + entries passed to add_deferred_name. */ + void add_signatured_type_name (ULONGEST signature, const char *name) + { + m_sig_name_map.emplace (signature, name); + } + /* Install the current addrmap into the shard being constructed, then transfer ownership of the index to the caller. */ cooked_index_shard_up release_shard () @@ -153,6 +172,12 @@ class cooked_index_worker_result discovered. */ void invert_cu_inclusions (); + /* The signature to name map for this worker. */ + const signature_to_name_map &get_sig_name_map () const + { + return m_sig_name_map; + } + private: /* The abbrev table cache used by this indexer. */ abbrev_table_cache m_abbrev_table_cache; @@ -188,6 +213,9 @@ class cooked_index_worker_result /* Parent map for each CU that is read. */ parent_map m_parent_map; + /* Signature to name map for the primary type in a TU. */ + signature_to_name_map m_sig_name_map; + /* A writeable addrmap being constructed by this scanner. */ addrmap_mutable m_addrmap; @@ -272,6 +300,12 @@ class cooked_index_worker return &m_all_parents_map; } + /* Return the map containing the complete signature to name information. */ + const signature_to_name_map &get_sig_name_map () const + { + return m_all_sig_names_map; + } + protected: /* Let cooked_index call the 'set' and 'write_to_cache' methods. */ @@ -319,6 +353,10 @@ class cooked_index_worker parent relationships. */ parent_map_map m_all_parents_map; + /* Map from signature to name of primary type within a TU. This is + the combined map, built after all the workers have finished. */ + signature_to_name_map m_all_sig_names_map; + /* Current state of this object. */ cooked_state m_state = cooked_state::INITIAL; /* Mutex and condition variable used to synchronize. */ diff --git a/gdb/dwarf2/cooked-index.c b/gdb/dwarf2/cooked-index.c index 167e39ffc89..d91f1267704 100644 --- a/gdb/dwarf2/cooked-index.c +++ b/gdb/dwarf2/cooked-index.c @@ -90,11 +90,12 @@ cooked_index::set_contents () { auto this_shard = shard.get (); const parent_map_map *parent_maps = m_state->get_parent_map_map (); - finalizers.add_task ([this, this_shard, parent_maps] () + const signature_to_name_map *sig_name_map = &m_state->get_sig_name_map (); + finalizers.add_task ([this, this_shard, parent_maps, sig_name_map] () { scoped_time_it time_it ("DWARF finalize worker", m_state->m_per_command_time); - this_shard->finalize (parent_maps); + this_shard->finalize (parent_maps, sig_name_map); }); } diff --git a/gdb/dwarf2/cooked-indexer.c b/gdb/dwarf2/cooked-indexer.c index 581c0eb0763..f94a0785f52 100644 --- a/gdb/dwarf2/cooked-indexer.c +++ b/gdb/dwarf2/cooked-indexer.c @@ -157,6 +157,7 @@ cooked_indexer::scan_attributes (dwarf2_per_cu *scanning_per_cu, parent_map::addr_type *maybe_defer, bool *is_enum_class, bool *is_inlined, + std::optional<ULONGEST> *signature, bool for_specification) { bool is_declaration = false; @@ -234,6 +235,16 @@ cooked_indexer::scan_attributes (dwarf2_per_cu *scanning_per_cu, attr.get_ref_die_offset () }; break; + case DW_AT_signature: + /* The DW_AT_signature could also be a direct reference to a + type DIE. We don't currently try to capture those + signatures as right now we're only capturing this in + order to handle the fact that some TUs are parsed after + the parallel CU parsing. */ + if (attr.form == DW_FORM_ref_sig8) + signature->emplace (attr.as_signature ()); + break; + case DW_AT_external: if (attr.as_boolean ()) *flags &= ~IS_STATIC; @@ -380,7 +391,7 @@ cooked_indexer::scan_attributes (dwarf2_per_cu *scanning_per_cu, scan_attributes (scanning_per_cu, new_reader, new_info_ptr, new_info_ptr, new_abbrev, name, linkage_name, flags, nullptr, parent_entry, maybe_defer, - is_enum_class, is_inlined, true); + is_enum_class, is_inlined, signature, true); } if (!for_specification) @@ -547,6 +558,7 @@ cooked_indexer::index_dies (cutu_reader *reader, const cooked_index_entry *this_parent_entry = parent_entry; bool is_enum_class = false; bool is_inlined = false; + std::optional<ULONGEST> signature; /* The scope of a DW_TAG_entry_point cooked_index_entry is the one of its surrounding subroutine. */ @@ -556,7 +568,7 @@ cooked_indexer::index_dies (cutu_reader *reader, = scan_attributes (reader->cu ()->per_cu, reader, info_ptr, info_ptr, abbrev, &name, &linkage_name, &flags, &sibling, &this_parent_entry, &defer, &is_enum_class, - &is_inlined, false); + &is_inlined, &signature, false); /* A DW_TAG_entry_point inherits its static/extern property from the enclosing subroutine. */ if (abbrev->tag == DW_TAG_entry_point) @@ -611,6 +623,17 @@ cooked_indexer::index_dies (cutu_reader *reader, cooked_index_entry *this_entry = nullptr; /* Always use the reader's CU for the entry CU. */ dwarf2_per_cu *cu_for_entry = reader->cu ()->per_cu; + + /* If scan_attributes failed to find a name then we might still + want to create an entry if we think we might later find a + name via a signature. If this is the case then we set NAME + to be the empty string to avoid having to handle NULL. */ + bool found_name = name != nullptr; + if (!found_name && signature.has_value ()) + name = ""; + + /* Check NAME here, not FOUND_NAME as NAME might have been + updated above. */ if (name != nullptr) { if (defer != 0) @@ -624,6 +647,11 @@ cooked_indexer::index_dies (cutu_reader *reader, = m_index_storage->add (this_die, abbrev->tag, flags, m_language, name, this_parent_entry, cu_for_entry); + + /* Record that this entry doesn't have a valid name and + needs patching. */ + if (signature.has_value () && !found_name) + m_index_storage->add_deferred_name (this_entry, signature.value ()); } else if (this_parent_entry != nullptr) { @@ -637,6 +665,18 @@ cooked_indexer::index_dies (cutu_reader *reader, m_die_range_map->add_entry (addr, addr, this_parent_entry); } + /* If THIS_DIE is the primary type within a TU, and has a valid + name, then add an entry mapping the signature to the name. */ + if (found_name + && cu_for_entry->is_debug_types () + && this_die == (reader->cu ()->header.sect_off + + to_underlying (reader->cu ()->header.type_offset_in_tu))) + { + const signatured_type *st = cu_for_entry->as_signatured_type (); + if (st != nullptr) + m_index_storage->add_signatured_type_name (st->signature, name); + } + if (linkage_name != nullptr) { /* We only want this to be "main" if it has a linkage name diff --git a/gdb/dwarf2/cooked-indexer.h b/gdb/dwarf2/cooked-indexer.h index 563742c90f6..393ef9fca5a 100644 --- a/gdb/dwarf2/cooked-indexer.h +++ b/gdb/dwarf2/cooked-indexer.h @@ -86,6 +86,7 @@ class cooked_indexer parent_map::addr_type *maybe_defer, bool *is_enum_class, bool *is_inlined, + std::optional<ULONGEST> *signature, bool for_specification); /* Handle DW_TAG_imported_unit, by scanning the DIE to find diff --git a/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp new file mode 100644 index 00000000000..5d3c26cfe41 --- /dev/null +++ b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp @@ -0,0 +1,133 @@ +# 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/>. + +# The test sets up a type DIE without a DW_AT_name, the type DIE links +# to a type within a TU via DW_AT_signature. Normally GDB would +# use the DW_AT_signature to find the name of the type DIE. +# +# However, in this test there is no type DIE with the specified +# signature! +# +# This test checks that GDB can emit a complaint when this situation is +# encountered. The test also checks that trying to find a method +# within the type using the qualified name will fail. + +load_lib dwarf.exp + +# This test can only be run on targets which support DWARF-2 and use gas. +require dwarf2_support + +standard_testfile main-foo.c .S + +# Build the test program using VERSION for the CU/TU DWARF version. +proc run_test { version } { + # Create the DWARF. + set asm_file [standard_output_file $::srcfile2] + Dwarf::assemble { + filename $asm_file + add_dummy_cus 0 + } { + upvar version version + + get_func_info foo + get_func_info main + + declare_labels method_decl + + tu { version $version } 0xdeadbeefdeadbeef the_type { + DW_TAG_type_unit { + DW_AT_language @DW_LANG_C_plus_plus + } { + the_type: DW_TAG_class_type { + DW_AT_name the_type + DW_AT_byte_size 1 sdata + } + } + } + + cu { version $version } { + compile_unit { + DW_AT_language @DW_LANG_C_plus_plus + } { + DW_TAG_class_type { + DW_AT_declaration 1 flag + DW_AT_signature 0xdeadbeef01234567 ref_sig8 + } { + method_decl: DW_TAG_subprogram { + DW_AT_name method + DW_AT_linkage_name _ZN8the_type6methodEv + DW_AT_declaration 1 flag + } + } + + DW_TAG_subprogram { + DW_AT_specification %$method_decl + DW_AT_low_pc $foo_start DW_FORM_addr + DW_AT_high_pc $foo_end DW_FORM_addr + } + + DW_TAG_subprogram { + DW_AT_name main + DW_AT_low_pc $main_start DW_FORM_addr + DW_AT_high_pc $main_end DW_FORM_addr + } + } + } + } + + set testfile_name ${::testfile}-${version} + set binfile_name [standard_output_file $testfile_name] + if { [build_executable "failed to build" $testfile_name \ + [list $asm_file $::srcfile] {nodebug}] } { + return + } + + clean_restart + + set host_binfile [gdb_remote_download host $binfile_name] + + # Load the executable and check we see the expected complaint from + # the DWARF indexer. + gdb_test_no_output "maint set dwarf synchronous on" + gdb_test_no_output "set complaints 100" + set saw_complaint false + gdb_test_multiple "file $host_binfile" "file command" -lbl { + -re "^\r\nDuring symbol reading: unable to find TU with signature 0xdeadbeef01234567 \\\[in module [string_to_regexp $host_binfile]\\\](?=\r\n)" { + set saw_complaint true + exp_continue + } + -re -wrap "" { + gdb_assert { $saw_complaint } $gdb_test_name + } + } + + if { ![readnow] } { + # Check that GDB is unable to find this via the index. If we + # load the full symbols then GDB is able to resolve this print + # via the linkage name. + # + # It is unfortunate that there is a difference in behaviour + # depending on whether GDB is using the index or not, but + # remember, the DWARF is corrupted at this point, so it's + # probably OK if things don't fully work, so long as GDB + # doesn't crash. + gdb_test "print the_type::method" \ + "^There is no field named method" + } +} + +foreach_with_prefix version { 4 5 } { + run_test $version +} diff --git a/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-dwo.exp b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-dwo.exp new file mode 100644 index 00000000000..0b89c811976 --- /dev/null +++ b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-dwo.exp @@ -0,0 +1,139 @@ +# 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/>. + +# Like sig-type-unnamed-class.exp, but with split DWARF. The type +# unit in the DWO file is skeletonless, which is the norm when using +# split DWARF. +# +# Check that the cooked index correctly resolves the parent of a member +# function when the class declaration stub in the DWO compile unit has +# DW_AT_signature but no DW_AT_name, and the referenced type unit is +# skeletonless. + +load_lib dwarf.exp + +# This test can only be run on targets which support DWARF-2 and use gas. +require dwarf2_support + +standard_testfile main-foo.c -dw.S + +# Build the test program using VERSION for the CU/TU DWARF version. + +proc run_test { version } { + set asm_file [standard_output_file $::srcfile2] + + # Setup some state based on DWARF version. + if { $version == 5 } { + set dwo_cu_opts [list fission 1 version 5 dwo_id 0xF00D] + set skel_cu_opts [list version 5 dwo_id 0xF00D] + set dwo_name_attr DW_AT_dwo_name + } else { + set dwo_cu_opts [list fission 1 version 4] + set skel_cu_opts [list version 4] + set dwo_name_attr DW_AT_GNU_dwo_name + } + + Dwarf::assemble $asm_file { + upvar version version + upvar dwo_cu_opts dwo_cu_opts + upvar skel_cu_opts skel_cu_opts + upvar dwo_name_attr dwo_name_attr + + declare_labels method_decl + + get_func_info foo + + # Type unit in the DWO file. This is skeletonless: there is no + # corresponding skeleton TU in the main file. + tu { + fission 1 + version $version + } 0xdeadbeef01234567 the_type { + type_unit { + DW_AT_language @DW_LANG_C_plus_plus + } { + the_type: DW_TAG_class_type { + DW_AT_name the_type + DW_AT_byte_size 1 DW_FORM_sdata + } + } + } + + set debug_addr_base [debug_addr_label] + + # Compile unit in the DWO file. Contains a class declaration + # stub with DW_AT_signature but no DW_AT_name, replicating what + # Clang emits with -fdebug-types-section. The class name must + # be resolved by following the signature to the type unit. + cu $dwo_cu_opts { + compile_unit { + DW_AT_language @DW_LANG_C_plus_plus + DW_AT_name ${::srcfile} + DW_AT_comp_dir . + if { $version == 4 } { + DW_AT_GNU_dwo_id 0xF00D DW_FORM_data8 + } + } { + DW_TAG_class_type { + DW_AT_declaration 1 DW_FORM_flag + DW_AT_signature 0xdeadbeef01234567 DW_FORM_ref_sig8 + } { + method_decl: DW_TAG_subprogram { + DW_AT_name method + DW_AT_linkage_name _ZN8the_type6methodEv + DW_AT_declaration 1 DW_FORM_flag + } + } + + DW_TAG_subprogram { + DW_AT_specification :$method_decl + DW_AT_low_pc $foo_start DW_FORM_GNU_addr_index + DW_AT_high_pc $foo_end DW_FORM_GNU_addr_index + } + } + } + + # Skeleton CU in the main file. + cu $skel_cu_opts { + compile_unit { + $dwo_name_attr ${::gdb_test_file_name}-dw.dwo DW_FORM_strp + DW_AT_comp_dir . + DW_AT_GNU_addr_base $debug_addr_base + if { $version == 4 } { + DW_AT_GNU_dwo_id 0xF00D DW_FORM_data8 + } + } {} + } + } + + set obj [standard_output_file "${::testfile}-dw.o"] + if {[build_executable_and_dwo_files "${::testfile}.exp" "${::binfile}" \ + {nodebug} \ + [list $asm_file {nodebug split-dwo} $obj] \ + [list $::srcfile {nodebug}]]} { + return + } + + clean_restart ${::testfile} + + # Check that GDB was able to find the parent for "method", and as + # a result, can correctly find this field of "the_type". + gdb_test "print the_type::method" \ + [string_to_regexp " <the_type::method()>"] +} + +foreach_with_prefix version { 4 5 } { + run_test $version +} diff --git a/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp new file mode 100644 index 00000000000..6f39e2c823d --- /dev/null +++ b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp @@ -0,0 +1,106 @@ +# 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/>. + +# Check that the cooked index correctly resolves the parent of a member +# function when the class declaration stub in the compile unit has +# DW_AT_signature but no DW_AT_name. +# +# This replicates what Clang emits with -fdebug-types-section. The +# compile unit contains an unnamed DW_TAG_class_type declaration with +# only DW_AT_declaration, DW_AT_signature, and child member function +# declarations. +# +# The class name must be resolved by following the signature to the +# type unit. Without this, the member function definitions (which use +# DW_AT_specification to point at the child declarations) end up with +# no parent, and qualified lookup fails. + +load_lib dwarf.exp + +# This test can only be run on targets which support DWARF-2 and use gas. +require dwarf2_support + +standard_testfile main-foo.c .S + +# Build the test program using VERSION for the CU/TU DWARF version. +proc run_test { version } { + # Create the DWARF. + set asm_file [standard_output_file $::srcfile2] + Dwarf::assemble { + filename $asm_file + add_dummy_cus 0 + } { + upvar version version + + get_func_info foo + get_func_info main + + declare_labels method_decl + + tu { version $version } 0xdeadbeef01234567 the_type { + DW_TAG_type_unit { + DW_AT_language @DW_LANG_C_plus_plus + } { + the_type: DW_TAG_class_type { + DW_AT_name the_type + DW_AT_byte_size 1 sdata + } + } + } + + cu { version $version } { + compile_unit { + DW_AT_language @DW_LANG_C_plus_plus + } { + DW_TAG_class_type { + DW_AT_declaration 1 flag + DW_AT_signature 0xdeadbeef01234567 ref_sig8 + } { + method_decl: DW_TAG_subprogram { + DW_AT_name method + DW_AT_linkage_name _ZN8the_type6methodEv + DW_AT_declaration 1 flag + } + } + + DW_TAG_subprogram { + DW_AT_specification %$method_decl + DW_AT_low_pc $foo_start DW_FORM_addr + DW_AT_high_pc $foo_end DW_FORM_addr + } + + DW_TAG_subprogram { + DW_AT_name main + DW_AT_low_pc $main_start DW_FORM_addr + DW_AT_high_pc $main_end DW_FORM_addr + } + } + } + } + + if { [prepare_for_testing "failed to prepare" ${::testfile}-${version} \ + [list $asm_file $::srcfile] {nodebug}] } { + return + } + + # Check that GDB was able to find the parent for "method", and as + # a result, can correctly find this field of "the_type". + gdb_test "print the_type::method" \ + [string_to_regexp " <the_type::method()>"] +} + +foreach_with_prefix version { 4 5 } { + run_test $version +} base-commit: 6e3ecea0e3ca191e81e82ee0194c49eea1ffb101 -- 2.25.4 ^ permalink raw reply [flat|nested] 36+ messages in thread
* [PATCHv3] gdb: resolve class name via DW_AT_signature in cooked index 2026-08-28 21:30 ` [PATCHv2] " Andrew Burgess @ 2026-09-01 13:31 ` Andrew Burgess 2026-09-10 16:11 ` Simon Marchi ` (2 more replies) 0 siblings, 3 replies; 36+ messages in thread From: Andrew Burgess @ 2026-09-01 13:31 UTC (permalink / raw) To: gdb-patches; +Cc: Andrew Burgess In v3: - I forgot to run check-all-boards on v2. There were some failures due to GDB's output not matching the patterns, I've fixed what I could in this iteration. - While looking at the different patterns I reworded one of the complaint messages in cooked-index-shard.c to match a warning that is emitted from elsewhere in the DWARF reader. There's no real functional change, it's just the wording that's updated. - There are still some check-all-boards failures, but I believe these are all wider issues when running DWARF assembler tests using the check-all-boards rule, so I'm ignoring them for now. In v2: - Complete rewrite to address the issues that Tom raised. - Rebase to HEAD and retest. - New tests added to cover the DWO case and an error case. Thanks, Andrew --- This commit fixes PR gdb/33447, an issue where looking up qualified member function names was not working for C++ binaries compiled with Clang when using the -fdebug-types-section flag. Before this commit we would see this behaviour: (gdb) print base1::a_function There is no field named a_function When what we expect to see is: (gdb) print base1::a_function $1 = {void (const base1 * const)} 0x403060 <base1::a_function() const> The problem is that the cooked index is unable to determine the name of the parent class `base1` in this case, and so decides not to index `base1` or any of its child DIEs, which includes its member functions. The problem was discovered by running gdb.cp/cpexprs-debug-types.exp with Clang: make check-gdb TESTS=gdb.cp/cpexprs-debug-types.exp \ RUNTESTFLAGS='CXX_FOR_TARGET=clang++ CC_FOR_TARGET=clang' The cpexprs-debug-types.exp test forces use of the '-fdebug-types-section' flag, which is not on by default. With this flag, class definitions are placed in type units, and the compile unit contains only a declaration stub for each class. Both clang++ and g++ emit these stubs, but they differ in one detail: GCC includes DW_AT_name on the stub, while clang++ does not, the stub carries only DW_AT_declaration and DW_AT_signature. The class name is only available in the type unit, reachable by following the signature. For example, Clang emits this in a CU: <1><2e7d>: DW_TAG_class_type DW_AT_declaration : 1 DW_AT_signature : 0x3abb... <2><2eac>: DW_TAG_subprogram DW_AT_name : a_function DW_AT_declaration : 1 The definition for a_function is elsewhere in the same CU: <1><3142>: DW_TAG_subprogram DW_AT_specification: <0x2eac> And in a TU elsewhere: Compilation Unit @ offset 0xd2e: ... snip ... Signature: 0x3abb... <0><d46>: Abbrev Number: 1 (DW_TAG_type_unit) ... snip ... <1><d51>: Abbrev Number: 30 (DW_TAG_class_type) ... snip ... <d57> DW_AT_name : (indexed string: 0xaa): base1 To find the DW_AT_name the cooked index needs to look up the type within the TU. Without the name the cooked indexer skips indexing `base1` as well as its children. This look up used to work; it works in GDB 17. PR gdb/33447 incorrectly identifies commit c879f4dc3e317cf6353a45a803ecf00d577a13d8 as the commit that introduced the regression. This is actually the last working commit. The problem was introduced by the next commit in the same series: commit 86ac8c546235a67d6a6bb29476a3a9ac8f7a620a Date: Thu Jan 2 15:28:18 2025 -0700 Convert lookup_symbol_in_objfile Prior to this commit GDB's symbol lookup had two phases, a search through already expanded symtabs, and a search via lookup_symbol_via_quick_fns. After the above commit only lookup_symbol_via_quick_fns remains. The lookup_symbol_via_quick_fns lookup, which relies on the indexer, was always broken, but the first phase, searching via expanded symtabs, could correctly find the type name via the signature. An initial attempt to solve this problem tried to fix this problem within cooked_indexer::scan_attributes, calling lookup_signatured_type and finding the name that way. However, there were three problems with this approach: 1. Possible thread safety issues; calling lookup_signatured_type for a DWO file ends up calling lookup_dwo_signatured_type, which can call add_type_unit and finalize_all_units, which modify state that is shared between parser threads. 2. The dwarf2_per_cu::type_offset_in_section for a TU is only set when the cutu_reader is constructed to parse that TU. Calling lookup_signatured_type doesn't fully parse the TU, it just finds the TU. The original code relied on type_offset_in_section being valid in order to then parse the TU and extract the name. This would break if the CU was processed before the TU. 3. For skeletonless TUs, these are not indexed until late in the indexing process, after all the CUs have been indexed. This means that if the TU referenced by the signature was skeletonless then the original approach would fail to find it. The new approach presented here is modeled more along the lines of the deferred parent handling that already exists within the indexer. The following changes have been made: 1. abbrev.c: Add DW_AT_signature to the set of attributes that mark a DIE as "interesting" in has_specification_or_origin. Without this, the unnamed class stub was classified as uninteresting at the abbreviation level and scan_attributes was never called for it. 2. cooked-indexer.c (cooked_indexer::scan_attributes): Capture the DW_AT_signature attribute. 3. cooked-indexer.c (cooked_indexer::index_dies): There are two different jobs done here: (a) If a DIE has no name, but does have a signature, then give the DIE a fake name (the empty string), and create an index entry for the DIE. Also keep a record that the cooked_index_entry for this DIE has a deferred name. (b) If we are indexing a TU, make a record of the signature, and the name of the primary type within the TU. This builds a signature to name map. 4. cooked-index-worker.c (cooked_index_worker::done_reading): Merge together all of the signature to name maps. 5. cooked-index-shard.c (cooked_index_shard::finalize): Look through all of the cooked_index_entry objects that were recorded in (3a), for each use the signature to name map to lookup the name, and update the cooked_index_entry. There are a bunch of header file changes to add the new maps, and accessor functions, the steps above describe the core mechanism. Added three new DWARF assembler tests. gdb.dwarf2/sig-type-unnamed-class.exp gdb.dwarf2/sig-type-unnamed-class-dwo.exp: These reproduce the problem case; there's a nameless declaration stub that references its full type via DW_AT_signature. To match the Clang output as much as possible the member function definition is separate and makes use of DW_AT_specification. The -dwo test places the DWARF into a DWO file and uses a skeletonless TU. gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp: This one tests some invalid DWARF, the type DIE references a signature that doesn't exist. In this case GDB just leaves the type DIE with an empty name string, which means symbols cannot be found. I think this is fine though, the DWARF is corrupted in this case. Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=33447 --- gdb/dwarf2/abbrev.c | 1 + gdb/dwarf2/cooked-index-entry.h | 5 + gdb/dwarf2/cooked-index-shard.c | 25 ++- gdb/dwarf2/cooked-index-shard.h | 19 ++- gdb/dwarf2/cooked-index-worker.c | 9 + gdb/dwarf2/cooked-index-worker.h | 38 +++++ gdb/dwarf2/cooked-index.c | 5 +- gdb/dwarf2/cooked-indexer.c | 44 ++++- gdb/dwarf2/cooked-indexer.h | 1 + .../sig-type-unnamed-class-bad-sig.exp | 160 ++++++++++++++++++ .../gdb.dwarf2/sig-type-unnamed-class-dwo.exp | 139 +++++++++++++++ .../gdb.dwarf2/sig-type-unnamed-class.exp | 106 ++++++++++++ 12 files changed, 546 insertions(+), 6 deletions(-) create mode 100644 gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp create mode 100644 gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-dwo.exp create mode 100644 gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp diff --git a/gdb/dwarf2/abbrev.c b/gdb/dwarf2/abbrev.c index 44d5c87a5f7..e99acc5e752 100644 --- a/gdb/dwarf2/abbrev.c +++ b/gdb/dwarf2/abbrev.c @@ -160,6 +160,7 @@ abbrev_table::read (struct dwarf2_section_info *section, case DW_AT_specification: case DW_AT_abstract_origin: case DW_AT_extension: + case DW_AT_signature: has_specification_or_origin = true; break; diff --git a/gdb/dwarf2/cooked-index-entry.h b/gdb/dwarf2/cooked-index-entry.h index 60ea581cbbe..f0ff356a6d1 100644 --- a/gdb/dwarf2/cooked-index-entry.h +++ b/gdb/dwarf2/cooked-index-entry.h @@ -79,6 +79,11 @@ union cooked_index_entry_ref parent_map::addr_type deferred; }; +/* Type that maps DW_AT_signature values for a TU to the name of the + primary type within the TU. */ + +using signature_to_name_map = std::unordered_map<ULONGEST, const char *>; + /* Return a string representation of FLAGS. */ std::string to_string (cooked_index_flag flags); diff --git a/gdb/dwarf2/cooked-index-shard.c b/gdb/dwarf2/cooked-index-shard.c index 91dc9ab8945..68ecbfa207c 100644 --- a/gdb/dwarf2/cooked-index-shard.c +++ b/gdb/dwarf2/cooked-index-shard.c @@ -20,6 +20,7 @@ #include "dwarf2/cooked-index-shard.h" #include "dwarf2/tag.h" #include "dwarf2/index-common.h" +#include "dwarf2/read.h" #include "cp-support.h" #include "c-lang.h" #include "ada-lang.h" @@ -190,8 +191,30 @@ struct cooked_index_entry_name_ptr_eq /* See cooked-index-shard.h. */ void -cooked_index_shard::finalize (const parent_map_map *parent_maps) +cooked_index_shard::finalize (const parent_map_map *parent_maps, + const signature_to_name_map *sig_names) { + for (const std::pair<cooked_index_entry *, ULONGEST> &entry_to_sig + : m_deferred_names) + { + ULONGEST signature = entry_to_sig.second; + cooked_index_entry *entry = entry_to_sig.first; + + const auto it = sig_names->find (signature); + if (it != sig_names->end ()) + { + /* The previous name was the empty string literal, so + overwriting it doesn't leak. The new name will point + into the DWARF data. */ + entry->name = it->second; + } + else + complaint (_("Cannot find signatured DIE %s referenced from DIE at %s" + " [in module %s]"), + hex_string (signature), sect_offset_str (entry->die_offset), + entry->per_cu->per_bfd ()->filename ()); + } + gdb::unordered_set<const cooked_index_entry *, cooked_index_entry_name_ptr_hash, cooked_index_entry_name_ptr_eq> seen_names; diff --git a/gdb/dwarf2/cooked-index-shard.h b/gdb/dwarf2/cooked-index-shard.h index 84c37958c83..4f25fb594a1 100644 --- a/gdb/dwarf2/cooked-index-shard.h +++ b/gdb/dwarf2/cooked-index-shard.h @@ -79,6 +79,16 @@ class cooked_index_shard for completion, will be returned. */ range find (const std::string &name, bool completing) const; + /* Record that ENTRY didn't have a name attribute, but did have a + DW_AT_signature attribute, SIGNATURE. ENTRY will have been + assigned a fake, empty, name string. We will patch the name of + ENTRY during finalization once the TUs have been parsed, the + correct TU will be found using SIGNATURE. */ + void add_deferred_name (cooked_index_entry *entry, ULONGEST signature) + { + m_deferred_names.emplace_back (entry, signature); + } + private: /* Return the entry that is believed to represent the program's @@ -122,7 +132,8 @@ class cooked_index_shard the index has been fully populated. It enters all the entries into the internal table and fixes up all missing parent links. This may be invoked in a worker thread. */ - void finalize (const parent_map_map *parent_maps); + void finalize (const parent_map_map *parent_maps, + const signature_to_name_map *sig_names); /* Storage for the entries. */ auto_obstack m_storage; @@ -134,6 +145,12 @@ class cooked_index_shard addrmap_fixed *m_addrmap = nullptr; /* Storage for canonical names. */ gdb::string_set m_names; + + /* Entries without a name, but with a signature. These entries will + have been given a fake name, the empty string when they were + created, but we need to patch these up with a real name during + finalization. */ + std::vector<std::pair<cooked_index_entry *, ULONGEST>> m_deferred_names; }; using cooked_index_shard_up = std::unique_ptr<cooked_index_shard>; diff --git a/gdb/dwarf2/cooked-index-worker.c b/gdb/dwarf2/cooked-index-worker.c index 723e027172e..054b25ba147 100644 --- a/gdb/dwarf2/cooked-index-worker.c +++ b/gdb/dwarf2/cooked-index-worker.c @@ -249,6 +249,15 @@ cooked_index_worker::done_reading () m_all_parents_map.add_map (*one_result.get_parent_map ()); } + { + scoped_time_it time_it ("DWARF add signature name map", m_per_command_time); + + /* Combine all of the signature to name maps. */ + for (cooked_index_worker_result &one_result : m_results) + for (const auto &[sig, name] : one_result.get_sig_name_map ()) + m_all_sig_names_map.emplace (sig, name); + } + /* Update all the CU inclusion information. */ for (auto &item : m_results) item.invert_cu_inclusions (); diff --git a/gdb/dwarf2/cooked-index-worker.h b/gdb/dwarf2/cooked-index-worker.h index 1799c0b9e08..262b727b983 100644 --- a/gdb/dwarf2/cooked-index-worker.h +++ b/gdb/dwarf2/cooked-index-worker.h @@ -83,6 +83,25 @@ class cooked_index_worker_result return m_shard->add (name); } + /* Record that ENTRY was missing a name attribute, but did have a + DW_AT_signature attribute, SIGNATURE. When the TUs are + processed, which might not happen until after CU processing for + skeletonless TUs, we can find a name and supply it at that + point. */ + void add_deferred_name (cooked_index_entry *entry, ULONGEST signature) + { + m_shard->add_deferred_name (entry, signature); + } + + /* Called when processing TUs to record that the primary type within + a type unit with SIGNATURE, was called NAME. This information + will be used during finalization to fix-up the name of any + entries passed to add_deferred_name. */ + void add_signatured_type_name (ULONGEST signature, const char *name) + { + m_sig_name_map.emplace (signature, name); + } + /* Install the current addrmap into the shard being constructed, then transfer ownership of the index to the caller. */ cooked_index_shard_up release_shard () @@ -153,6 +172,12 @@ class cooked_index_worker_result discovered. */ void invert_cu_inclusions (); + /* The signature to name map for this worker. */ + const signature_to_name_map &get_sig_name_map () const + { + return m_sig_name_map; + } + private: /* The abbrev table cache used by this indexer. */ abbrev_table_cache m_abbrev_table_cache; @@ -188,6 +213,9 @@ class cooked_index_worker_result /* Parent map for each CU that is read. */ parent_map m_parent_map; + /* Signature to name map for the primary type in a TU. */ + signature_to_name_map m_sig_name_map; + /* A writeable addrmap being constructed by this scanner. */ addrmap_mutable m_addrmap; @@ -272,6 +300,12 @@ class cooked_index_worker return &m_all_parents_map; } + /* Return the map containing the complete signature to name information. */ + const signature_to_name_map &get_sig_name_map () const + { + return m_all_sig_names_map; + } + protected: /* Let cooked_index call the 'set' and 'write_to_cache' methods. */ @@ -319,6 +353,10 @@ class cooked_index_worker parent relationships. */ parent_map_map m_all_parents_map; + /* Map from signature to name of primary type within a TU. This is + the combined map, built after all the workers have finished. */ + signature_to_name_map m_all_sig_names_map; + /* Current state of this object. */ cooked_state m_state = cooked_state::INITIAL; /* Mutex and condition variable used to synchronize. */ diff --git a/gdb/dwarf2/cooked-index.c b/gdb/dwarf2/cooked-index.c index 167e39ffc89..d91f1267704 100644 --- a/gdb/dwarf2/cooked-index.c +++ b/gdb/dwarf2/cooked-index.c @@ -90,11 +90,12 @@ cooked_index::set_contents () { auto this_shard = shard.get (); const parent_map_map *parent_maps = m_state->get_parent_map_map (); - finalizers.add_task ([this, this_shard, parent_maps] () + const signature_to_name_map *sig_name_map = &m_state->get_sig_name_map (); + finalizers.add_task ([this, this_shard, parent_maps, sig_name_map] () { scoped_time_it time_it ("DWARF finalize worker", m_state->m_per_command_time); - this_shard->finalize (parent_maps); + this_shard->finalize (parent_maps, sig_name_map); }); } diff --git a/gdb/dwarf2/cooked-indexer.c b/gdb/dwarf2/cooked-indexer.c index 581c0eb0763..f94a0785f52 100644 --- a/gdb/dwarf2/cooked-indexer.c +++ b/gdb/dwarf2/cooked-indexer.c @@ -157,6 +157,7 @@ cooked_indexer::scan_attributes (dwarf2_per_cu *scanning_per_cu, parent_map::addr_type *maybe_defer, bool *is_enum_class, bool *is_inlined, + std::optional<ULONGEST> *signature, bool for_specification) { bool is_declaration = false; @@ -234,6 +235,16 @@ cooked_indexer::scan_attributes (dwarf2_per_cu *scanning_per_cu, attr.get_ref_die_offset () }; break; + case DW_AT_signature: + /* The DW_AT_signature could also be a direct reference to a + type DIE. We don't currently try to capture those + signatures as right now we're only capturing this in + order to handle the fact that some TUs are parsed after + the parallel CU parsing. */ + if (attr.form == DW_FORM_ref_sig8) + signature->emplace (attr.as_signature ()); + break; + case DW_AT_external: if (attr.as_boolean ()) *flags &= ~IS_STATIC; @@ -380,7 +391,7 @@ cooked_indexer::scan_attributes (dwarf2_per_cu *scanning_per_cu, scan_attributes (scanning_per_cu, new_reader, new_info_ptr, new_info_ptr, new_abbrev, name, linkage_name, flags, nullptr, parent_entry, maybe_defer, - is_enum_class, is_inlined, true); + is_enum_class, is_inlined, signature, true); } if (!for_specification) @@ -547,6 +558,7 @@ cooked_indexer::index_dies (cutu_reader *reader, const cooked_index_entry *this_parent_entry = parent_entry; bool is_enum_class = false; bool is_inlined = false; + std::optional<ULONGEST> signature; /* The scope of a DW_TAG_entry_point cooked_index_entry is the one of its surrounding subroutine. */ @@ -556,7 +568,7 @@ cooked_indexer::index_dies (cutu_reader *reader, = scan_attributes (reader->cu ()->per_cu, reader, info_ptr, info_ptr, abbrev, &name, &linkage_name, &flags, &sibling, &this_parent_entry, &defer, &is_enum_class, - &is_inlined, false); + &is_inlined, &signature, false); /* A DW_TAG_entry_point inherits its static/extern property from the enclosing subroutine. */ if (abbrev->tag == DW_TAG_entry_point) @@ -611,6 +623,17 @@ cooked_indexer::index_dies (cutu_reader *reader, cooked_index_entry *this_entry = nullptr; /* Always use the reader's CU for the entry CU. */ dwarf2_per_cu *cu_for_entry = reader->cu ()->per_cu; + + /* If scan_attributes failed to find a name then we might still + want to create an entry if we think we might later find a + name via a signature. If this is the case then we set NAME + to be the empty string to avoid having to handle NULL. */ + bool found_name = name != nullptr; + if (!found_name && signature.has_value ()) + name = ""; + + /* Check NAME here, not FOUND_NAME as NAME might have been + updated above. */ if (name != nullptr) { if (defer != 0) @@ -624,6 +647,11 @@ cooked_indexer::index_dies (cutu_reader *reader, = m_index_storage->add (this_die, abbrev->tag, flags, m_language, name, this_parent_entry, cu_for_entry); + + /* Record that this entry doesn't have a valid name and + needs patching. */ + if (signature.has_value () && !found_name) + m_index_storage->add_deferred_name (this_entry, signature.value ()); } else if (this_parent_entry != nullptr) { @@ -637,6 +665,18 @@ cooked_indexer::index_dies (cutu_reader *reader, m_die_range_map->add_entry (addr, addr, this_parent_entry); } + /* If THIS_DIE is the primary type within a TU, and has a valid + name, then add an entry mapping the signature to the name. */ + if (found_name + && cu_for_entry->is_debug_types () + && this_die == (reader->cu ()->header.sect_off + + to_underlying (reader->cu ()->header.type_offset_in_tu))) + { + const signatured_type *st = cu_for_entry->as_signatured_type (); + if (st != nullptr) + m_index_storage->add_signatured_type_name (st->signature, name); + } + if (linkage_name != nullptr) { /* We only want this to be "main" if it has a linkage name diff --git a/gdb/dwarf2/cooked-indexer.h b/gdb/dwarf2/cooked-indexer.h index 563742c90f6..393ef9fca5a 100644 --- a/gdb/dwarf2/cooked-indexer.h +++ b/gdb/dwarf2/cooked-indexer.h @@ -86,6 +86,7 @@ class cooked_indexer parent_map::addr_type *maybe_defer, bool *is_enum_class, bool *is_inlined, + std::optional<ULONGEST> *signature, bool for_specification); /* Handle DW_TAG_imported_unit, by scanning the DIE to find diff --git a/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp new file mode 100644 index 00000000000..e52f1e3b21a --- /dev/null +++ b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp @@ -0,0 +1,160 @@ +# 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/>. + +# The test sets up a type DIE without a DW_AT_name, the type DIE links +# to a type within a TU via DW_AT_signature. Normally GDB would +# use the DW_AT_signature to find the name of the type DIE. +# +# However, in this test there is no type DIE with the specified +# signature! +# +# This test checks that GDB can emit a complaint when this situation is +# encountered. The test also checks that trying to find a method +# within the type using the qualified name will fail. + +load_lib dwarf.exp + +# This test can only be run on targets which support DWARF-2 and use gas. +require dwarf2_support + +standard_testfile main-foo.c .S + +# Build the test program using VERSION for the CU/TU DWARF version. +proc run_test { version } { + # Create the DWARF. + set asm_file [standard_output_file $::srcfile2] + Dwarf::assemble { + filename $asm_file + add_dummy_cus 0 + } { + upvar version version + + get_func_info foo + get_func_info main + + declare_labels method_decl + + tu { version $version } 0xdeadbeefdeadbeef the_type { + DW_TAG_type_unit { + DW_AT_language @DW_LANG_C_plus_plus + } { + the_type: DW_TAG_class_type { + DW_AT_name the_type + DW_AT_byte_size 1 sdata + } + } + } + + cu { version $version } { + compile_unit { + DW_AT_language @DW_LANG_C_plus_plus + } { + DW_TAG_class_type { + DW_AT_declaration 1 flag + DW_AT_signature 0xdeadbeef01234567 ref_sig8 + } { + method_decl: DW_TAG_subprogram { + DW_AT_name method + DW_AT_linkage_name _ZN8the_type6methodEv + DW_AT_declaration 1 flag + } + } + + DW_TAG_subprogram { + DW_AT_specification %$method_decl + DW_AT_low_pc $foo_start DW_FORM_addr + DW_AT_high_pc $foo_end DW_FORM_addr + } + + DW_TAG_subprogram { + DW_AT_name main + DW_AT_low_pc $main_start DW_FORM_addr + DW_AT_high_pc $main_end DW_FORM_addr + } + } + } + } + + set testfile_name ${::testfile}-${version} + set binfile_name [standard_output_file $testfile_name] + if { [prepare_for_testing "failed to prepare" $testfile_name \ + [list $asm_file $::srcfile] {nodebug}] } { + return + } + + set expect_complaint true + # If we have a .gdb_index already then the part of this test that + # checks for a warning when loading the executable is not going to + # work as the warning will have already been generated when the + # index was added. + if {[get_index_type $testfile_name] == "gdb"} { + set expect_complaint false + } + + # Restart without the executable so we can check for warnings when + # the executable is loaded. + clean_restart + + set host_binfile [gdb_remote_download host $binfile_name] + if { [is_remote host] } { + # For some remote host boards gdb_remote_download returns an + # absolute path, but for others it returns a relative path. + # The path reported in the complaint message is always + # absolute. Handle this with an optional prefix pattern. + set binfile_re "\[^\r\n\]*[string_to_regexp $host_binfile]" + } elseif {[section_get $binfile_name ".gnu_debuglink"] ne ""} { + set binfile_re "\[^\r\n\]+[string_to_regexp ${testfile_name}.debug]" + } else { + set binfile_re [string_to_regexp $binfile_name] + } + + # Load the executable and check we see the expected complaint from + # the DWARF indexer. + gdb_test_no_output "maint set dwarf synchronous on" + gdb_test_no_output "set complaints 100" + set saw_complaint false + + gdb_test_multiple "file $host_binfile" "file command" -lbl { + -re "\r\nDuring symbol reading: (?:DWARF Error: )?Cannot find signatured DIE 0xdeadbeef01234567 referenced from DIE at $::hex \\\[in module $binfile_re\\\](?=\r\n)" { + # The 'DWARF Error' part only occurs when using the + # 'readnow' board, this is the error when triggered from + # the full symbol reader rather than the indexer. + set saw_complaint true + exp_continue + } + + -re -wrap "" { + gdb_assert { $saw_complaint == $expect_complaint } $gdb_test_name + } + } + + if { ![readnow] } { + # Check that GDB is unable to find this via the index. If we + # load the full symbols then GDB is able to resolve this print + # via the linkage name. + # + # It is unfortunate that there is a difference in behaviour + # depending on whether GDB is using the index or not, but + # remember, the DWARF is corrupted at this point, so it's + # probably OK if things don't fully work, so long as GDB + # doesn't crash. + gdb_test "print the_type::method" \ + "^There is no field named method" + } +} + +foreach_with_prefix version { 4 5 } { + run_test $version +} diff --git a/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-dwo.exp b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-dwo.exp new file mode 100644 index 00000000000..0b89c811976 --- /dev/null +++ b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-dwo.exp @@ -0,0 +1,139 @@ +# 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/>. + +# Like sig-type-unnamed-class.exp, but with split DWARF. The type +# unit in the DWO file is skeletonless, which is the norm when using +# split DWARF. +# +# Check that the cooked index correctly resolves the parent of a member +# function when the class declaration stub in the DWO compile unit has +# DW_AT_signature but no DW_AT_name, and the referenced type unit is +# skeletonless. + +load_lib dwarf.exp + +# This test can only be run on targets which support DWARF-2 and use gas. +require dwarf2_support + +standard_testfile main-foo.c -dw.S + +# Build the test program using VERSION for the CU/TU DWARF version. + +proc run_test { version } { + set asm_file [standard_output_file $::srcfile2] + + # Setup some state based on DWARF version. + if { $version == 5 } { + set dwo_cu_opts [list fission 1 version 5 dwo_id 0xF00D] + set skel_cu_opts [list version 5 dwo_id 0xF00D] + set dwo_name_attr DW_AT_dwo_name + } else { + set dwo_cu_opts [list fission 1 version 4] + set skel_cu_opts [list version 4] + set dwo_name_attr DW_AT_GNU_dwo_name + } + + Dwarf::assemble $asm_file { + upvar version version + upvar dwo_cu_opts dwo_cu_opts + upvar skel_cu_opts skel_cu_opts + upvar dwo_name_attr dwo_name_attr + + declare_labels method_decl + + get_func_info foo + + # Type unit in the DWO file. This is skeletonless: there is no + # corresponding skeleton TU in the main file. + tu { + fission 1 + version $version + } 0xdeadbeef01234567 the_type { + type_unit { + DW_AT_language @DW_LANG_C_plus_plus + } { + the_type: DW_TAG_class_type { + DW_AT_name the_type + DW_AT_byte_size 1 DW_FORM_sdata + } + } + } + + set debug_addr_base [debug_addr_label] + + # Compile unit in the DWO file. Contains a class declaration + # stub with DW_AT_signature but no DW_AT_name, replicating what + # Clang emits with -fdebug-types-section. The class name must + # be resolved by following the signature to the type unit. + cu $dwo_cu_opts { + compile_unit { + DW_AT_language @DW_LANG_C_plus_plus + DW_AT_name ${::srcfile} + DW_AT_comp_dir . + if { $version == 4 } { + DW_AT_GNU_dwo_id 0xF00D DW_FORM_data8 + } + } { + DW_TAG_class_type { + DW_AT_declaration 1 DW_FORM_flag + DW_AT_signature 0xdeadbeef01234567 DW_FORM_ref_sig8 + } { + method_decl: DW_TAG_subprogram { + DW_AT_name method + DW_AT_linkage_name _ZN8the_type6methodEv + DW_AT_declaration 1 DW_FORM_flag + } + } + + DW_TAG_subprogram { + DW_AT_specification :$method_decl + DW_AT_low_pc $foo_start DW_FORM_GNU_addr_index + DW_AT_high_pc $foo_end DW_FORM_GNU_addr_index + } + } + } + + # Skeleton CU in the main file. + cu $skel_cu_opts { + compile_unit { + $dwo_name_attr ${::gdb_test_file_name}-dw.dwo DW_FORM_strp + DW_AT_comp_dir . + DW_AT_GNU_addr_base $debug_addr_base + if { $version == 4 } { + DW_AT_GNU_dwo_id 0xF00D DW_FORM_data8 + } + } {} + } + } + + set obj [standard_output_file "${::testfile}-dw.o"] + if {[build_executable_and_dwo_files "${::testfile}.exp" "${::binfile}" \ + {nodebug} \ + [list $asm_file {nodebug split-dwo} $obj] \ + [list $::srcfile {nodebug}]]} { + return + } + + clean_restart ${::testfile} + + # Check that GDB was able to find the parent for "method", and as + # a result, can correctly find this field of "the_type". + gdb_test "print the_type::method" \ + [string_to_regexp " <the_type::method()>"] +} + +foreach_with_prefix version { 4 5 } { + run_test $version +} diff --git a/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp new file mode 100644 index 00000000000..6f39e2c823d --- /dev/null +++ b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp @@ -0,0 +1,106 @@ +# 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/>. + +# Check that the cooked index correctly resolves the parent of a member +# function when the class declaration stub in the compile unit has +# DW_AT_signature but no DW_AT_name. +# +# This replicates what Clang emits with -fdebug-types-section. The +# compile unit contains an unnamed DW_TAG_class_type declaration with +# only DW_AT_declaration, DW_AT_signature, and child member function +# declarations. +# +# The class name must be resolved by following the signature to the +# type unit. Without this, the member function definitions (which use +# DW_AT_specification to point at the child declarations) end up with +# no parent, and qualified lookup fails. + +load_lib dwarf.exp + +# This test can only be run on targets which support DWARF-2 and use gas. +require dwarf2_support + +standard_testfile main-foo.c .S + +# Build the test program using VERSION for the CU/TU DWARF version. +proc run_test { version } { + # Create the DWARF. + set asm_file [standard_output_file $::srcfile2] + Dwarf::assemble { + filename $asm_file + add_dummy_cus 0 + } { + upvar version version + + get_func_info foo + get_func_info main + + declare_labels method_decl + + tu { version $version } 0xdeadbeef01234567 the_type { + DW_TAG_type_unit { + DW_AT_language @DW_LANG_C_plus_plus + } { + the_type: DW_TAG_class_type { + DW_AT_name the_type + DW_AT_byte_size 1 sdata + } + } + } + + cu { version $version } { + compile_unit { + DW_AT_language @DW_LANG_C_plus_plus + } { + DW_TAG_class_type { + DW_AT_declaration 1 flag + DW_AT_signature 0xdeadbeef01234567 ref_sig8 + } { + method_decl: DW_TAG_subprogram { + DW_AT_name method + DW_AT_linkage_name _ZN8the_type6methodEv + DW_AT_declaration 1 flag + } + } + + DW_TAG_subprogram { + DW_AT_specification %$method_decl + DW_AT_low_pc $foo_start DW_FORM_addr + DW_AT_high_pc $foo_end DW_FORM_addr + } + + DW_TAG_subprogram { + DW_AT_name main + DW_AT_low_pc $main_start DW_FORM_addr + DW_AT_high_pc $main_end DW_FORM_addr + } + } + } + } + + if { [prepare_for_testing "failed to prepare" ${::testfile}-${version} \ + [list $asm_file $::srcfile] {nodebug}] } { + return + } + + # Check that GDB was able to find the parent for "method", and as + # a result, can correctly find this field of "the_type". + gdb_test "print the_type::method" \ + [string_to_regexp " <the_type::method()>"] +} + +foreach_with_prefix version { 4 5 } { + run_test $version +} base-commit: 6e3ecea0e3ca191e81e82ee0194c49eea1ffb101 -- 2.25.4 ^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCHv3] gdb: resolve class name via DW_AT_signature in cooked index 2026-09-01 13:31 ` [PATCHv3] " Andrew Burgess @ 2026-09-10 16:11 ` Simon Marchi 2026-09-11 19:18 ` Tom Tromey 2026-09-10 16:18 ` Simon Marchi 2026-09-16 11:38 ` [PATCHv4] " Andrew Burgess 2 siblings, 1 reply; 36+ messages in thread From: Simon Marchi @ 2026-09-10 16:11 UTC (permalink / raw) To: Andrew Burgess, gdb-patches On 2026-09-01 09:31, Andrew Burgess wrote: > In v3: > > - I forgot to run check-all-boards on v2. There were some failures > due to GDB's output not matching the patterns, I've fixed what I > could in this iteration. > > - While looking at the different patterns I reworded one of the > complaint messages in cooked-index-shard.c to match a warning that > is emitted from elsewhere in the DWARF reader. There's no real > functional change, it's just the wording that's updated. > > - There are still some check-all-boards failures, but I believe > these are all wider issues when running DWARF assembler tests > using the check-all-boards rule, so I'm ignoring them for now. > > In v2: > > - Complete rewrite to address the issues that Tom raised. > > - Rebase to HEAD and retest. > > - New tests added to cover the DWO case and an error case. > > Thanks, > Andrew > > --- > > This commit fixes PR gdb/33447, an issue where looking up qualified > member function names was not working for C++ binaries compiled with > Clang when using the -fdebug-types-section flag. > > Before this commit we would see this behaviour: > > (gdb) print base1::a_function > There is no field named a_function > > When what we expect to see is: > > (gdb) print base1::a_function > $1 = {void (const base1 * const)} 0x403060 <base1::a_function() const> > > The problem is that the cooked index is unable to determine the name > of the parent class `base1` in this case, and so decides not to index > `base1` or any of its child DIEs, which includes its member functions. > > The problem was discovered by running gdb.cp/cpexprs-debug-types.exp > with Clang: > > make check-gdb TESTS=gdb.cp/cpexprs-debug-types.exp \ > RUNTESTFLAGS='CXX_FOR_TARGET=clang++ CC_FOR_TARGET=clang' > > The cpexprs-debug-types.exp test forces use of the > '-fdebug-types-section' flag, which is not on by default. With this > flag, class definitions are placed in type units, and the compile unit > contains only a declaration stub for each class. Both clang++ and g++ > emit these stubs, but they differ in one detail: GCC includes > DW_AT_name on the stub, while clang++ does not, the stub carries only > DW_AT_declaration and DW_AT_signature. The class name is only > available in the type unit, reachable by following the signature. > > For example, Clang emits this in a CU: > > <1><2e7d>: DW_TAG_class_type > DW_AT_declaration : 1 > DW_AT_signature : 0x3abb... > <2><2eac>: DW_TAG_subprogram > DW_AT_name : a_function > DW_AT_declaration : 1 > > The definition for a_function is elsewhere in the same CU: > > <1><3142>: DW_TAG_subprogram > DW_AT_specification: <0x2eac> > > And in a TU elsewhere: > > Compilation Unit @ offset 0xd2e: > ... snip ... > Signature: 0x3abb... > <0><d46>: Abbrev Number: 1 (DW_TAG_type_unit) > ... snip ... > <1><d51>: Abbrev Number: 30 (DW_TAG_class_type) > ... snip ... > <d57> DW_AT_name : (indexed string: 0xaa): base1 > > To find the DW_AT_name the cooked index needs to look up the type > within the TU. Without the name the cooked indexer skips indexing > `base1` as well as its children. > > This look up used to work; it works in GDB 17. PR gdb/33447 > incorrectly identifies commit c879f4dc3e317cf6353a45a803ecf00d577a13d8 > as the commit that introduced the regression. This is actually the > last working commit. The problem was introduced by the next commit in > the same series: > > commit 86ac8c546235a67d6a6bb29476a3a9ac8f7a620a > Date: Thu Jan 2 15:28:18 2025 -0700 > > Convert lookup_symbol_in_objfile > > Prior to this commit GDB's symbol lookup had two phases, a search > through already expanded symtabs, and a search via > lookup_symbol_via_quick_fns. After the above commit only > lookup_symbol_via_quick_fns remains. > > The lookup_symbol_via_quick_fns lookup, which relies on the indexer, > was always broken, but the first phase, searching via expanded > symtabs, could correctly find the type name via the signature. What this generally means is that it could work if the CU had been expanded previously, but not otherwise. > An initial attempt to solve this problem tried to fix this problem > within cooked_indexer::scan_attributes, calling lookup_signatured_type > and finding the name that way. However, there were three problems > with this approach: > > 1. Possible thread safety issues; calling lookup_signatured_type for > a DWO file ends up calling lookup_dwo_signatured_type, which can > call add_type_unit and finalize_all_units, which modify state > that is shared between parser threads. > > 2. The dwarf2_per_cu::type_offset_in_section for a TU is only set > when the cutu_reader is constructed to parse that TU. Calling > lookup_signatured_type doesn't fully parse the TU, it just finds > the TU. The original code relied on type_offset_in_section being > valid in order to then parse the TU and extract the name. This > would break if the CU was processed before the TU. > > 3. For skeletonless TUs, these are not indexed until late in the > indexing process, after all the CUs have been indexed. This > means that if the TU referenced by the signature was skeletonless > then the original approach would fail to find it. Ack, and reminder (for myself and others) that skeletonless TU is the norm when using type units + split DWARF. > The new approach presented here is modeled more along the lines of the > deferred parent handling that already exists within the indexer. The > following changes have been made: > > 1. abbrev.c: Add DW_AT_signature to the set of attributes that mark a > DIE as "interesting" in has_specification_or_origin. Without this, "in has_specification_or_origin" makes it sound like "has_specification_or_origin" is a function. > the unnamed class stub was classified as uninteresting at the > abbreviation level and scan_attributes was never called for it. > > 2. cooked-indexer.c (cooked_indexer::scan_attributes): Capture the > DW_AT_signature attribute. > > 3. cooked-indexer.c (cooked_indexer::index_dies): There are two > different jobs done here: > > (a) If a DIE has no name, but does have a signature, then give the > DIE a fake name (the empty string), and create an index entry > for the DIE. Also keep a record that the cooked_index_entry > for this DIE has a deferred name. Just wondering, if we end up not patching the entry for some reason, will an entry with an empty name cause problems / match things it's not supposed to match? Like will the child of that nameless entry be considered to be part of the top-level namespace or something like that? If it happens that we have an entry with an unresolved name, perhaps we should consider this entry invalid and just skip anything that would require it. > > (b) If we are indexing a TU, make a record of the signature, and > the name of the primary type within the TU. This builds a > signature to name map. > > 4. cooked-index-worker.c (cooked_index_worker::done_reading): Merge > together all of the signature to name maps. > > 5. cooked-index-shard.c (cooked_index_shard::finalize): Look through > all of the cooked_index_entry objects that were recorded in (3a), > for each use the signature to name map to lookup the name, and > update the cooked_index_entry. > > There are a bunch of header file changes to add the new maps, and > accessor functions, the steps above describe the core mechanism. > > Added three new DWARF assembler tests. > > gdb.dwarf2/sig-type-unnamed-class.exp > gdb.dwarf2/sig-type-unnamed-class-dwo.exp: > These reproduce the problem case; there's a nameless declaration > stub that references its full type via DW_AT_signature. To match > the Clang output as much as possible the member function definition > is separate and makes use of DW_AT_specification. The -dwo test > places the DWARF into a DWO file and uses a skeletonless TU. > > gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp: > This one tests some invalid DWARF, the type DIE references a > signature that doesn't exist. In this case GDB just leaves the > type DIE with an empty name string, which means symbols cannot be > found. I think this is fine though, the DWARF is corrupted in this > case. The approach LGTM. I had Claude review the patch while I was looking at it myself, here are some relevant comments from it (him? her? them?): - This crashes: $ make check TESTS="gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp" RUNTESTFLAGS=--target_board=cc-with-gdb-index I build with -D_GLIBCXX_DEBUG, so I see this: (gdb) file /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.dwarf2/sig-type-unnamed-class-bad-sig/sig-type-unnamed-class-bad-sig-4 Reading symbols from /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.dwarf2/sig-type-unnamed-class-bad-sig/sig-type-unnamed-class-bad-sig-4... (gdb) /usr/include/c++/16/string_view:291: constexpr const std::basic_string_view<_CharT, _Traits>::value_type& std::basic_string_view<_CharT, _Traits>::back() const [with _CharT = char; _Traits = std::char_traits<char>; const_reference = const char&]: Assertion 'this->_M_len > 0' failed. When we fail to find a signature, we complain and leave the empty string in place. From what I understand, when we try to write the .gdb_index, we try to generate the name of the child, which leads to "::method" (the name of our entry would have appeared before ::, but now it's the empty string). And something down the line doesn't like that. So yeah, I wonder if it wouldn't be better to leave the name as nullptr, that would force us to add some nullptr checks to realize that the entry doesn't have a valid name, and we would skip it. - the complaint runs on a thread pool worker, but no complaint_interceptor is installed in those threads, so complaint_internal writes straight to gdb_stderr from a worker thread, outside the collect-and-re-emit-on-the-main-thread machinery. Besides the raw thread-safety issue, the message can land at an arbitrary point in the main thread's output. Using the complaint interceptor would fix both. - The comment on cooked_index_entry::name says that it always points into mapped DWARF sections, which is not true anymore. The comment could talk about the "" case (or nullptr if we decided to go that route). - The complaint should maybe use DWARF_ERROR_PREFIX, like the identical complaints in read.c. - sig-type-unnamed-class-dwo.exp: the two foreach_with_prefix iterations write the same $binfile / -dw.o / .S names, unlike the other two tests which suffix with -${version}. Harmless for the run, but it makes post-mortem debugging of one version impossible. > diff --git a/gdb/dwarf2/cooked-index-entry.h b/gdb/dwarf2/cooked-index-entry.h > index 60ea581cbbe..f0ff356a6d1 100644 > --- a/gdb/dwarf2/cooked-index-entry.h > +++ b/gdb/dwarf2/cooked-index-entry.h > @@ -79,6 +79,11 @@ union cooked_index_entry_ref > parent_map::addr_type deferred; > }; > > +/* Type that maps DW_AT_signature values for a TU to the name of the > + primary type within the TU. */ > + > +using signature_to_name_map = std::unordered_map<ULONGEST, const char *>; We should be using gdb::unordered_map. > @@ -190,8 +191,30 @@ struct cooked_index_entry_name_ptr_eq > /* See cooked-index-shard.h. */ > > void > -cooked_index_shard::finalize (const parent_map_map *parent_maps) > +cooked_index_shard::finalize (const parent_map_map *parent_maps, > + const signature_to_name_map *sig_names) These two should be passed by reference, I suppose (maybe push an obvious patch for the existing one). > { > + for (const std::pair<cooked_index_entry *, ULONGEST> &entry_to_sig > + : m_deferred_names) That would be a good use of structured bindings: for (const auto &[entry, signature] : m_deferred_names) > + { > + ULONGEST signature = entry_to_sig.second; > + cooked_index_entry *entry = entry_to_sig.first; > + > + const auto it = sig_names->find (signature); > + if (it != sig_names->end ()) I would suggest: if (const auto it = sig_names->find (signature); it != sig_names->end ()) > @@ -134,6 +145,12 @@ class cooked_index_shard > addrmap_fixed *m_addrmap = nullptr; > /* Storage for canonical names. */ > gdb::string_set m_names; > + > + /* Entries without a name, but with a signature. These entries will > + have been given a fake name, the empty string when they were > + created, but we need to patch these up with a real name during > + finalization. */ > + std::vector<std::pair<cooked_index_entry *, ULONGEST>> m_deferred_names; I would mind if you introduced a little local struct instead of using std::pair: struct deferred_name { cooked_index_entry *entry; ULONGEST signature; }; > using cooked_index_shard_up = std::unique_ptr<cooked_index_shard>; > diff --git a/gdb/dwarf2/cooked-index-worker.c b/gdb/dwarf2/cooked-index-worker.c > index 723e027172e..054b25ba147 100644 > --- a/gdb/dwarf2/cooked-index-worker.c > +++ b/gdb/dwarf2/cooked-index-worker.c > @@ -249,6 +249,15 @@ cooked_index_worker::done_reading () > m_all_parents_map.add_map (*one_result.get_parent_map ()); > } > > + { > + scoped_time_it time_it ("DWARF add signature name map", m_per_command_time); > + > + /* Combine all of the signature to name maps. */ > + for (cooked_index_worker_result &one_result : m_results) > + for (const auto &[sig, name] : one_result.get_sig_name_map ()) > + m_all_sig_names_map.emplace (sig, name); I'm curious to see if that can be done more efficiently, but for now this is fine. I don't want to waste time prematurely optimizing when the immediate goal is to fix the bug. > @@ -637,6 +665,18 @@ cooked_indexer::index_dies (cutu_reader *reader, > m_die_range_map->add_entry (addr, addr, this_parent_entry); > } > > + /* If THIS_DIE is the primary type within a TU, and has a valid > + name, then add an entry mapping the signature to the name. */ > + if (found_name > + && cu_for_entry->is_debug_types () > + && this_die == (reader->cu ()->header.sect_off > + + to_underlying (reader->cu ()->header.type_offset_in_tu))) You can use signatured_type::type_offset_in_section instead of doing the addition (it should be the same value). This should work: if (const signatured_type *st = cu_for_entry->as_signatured_type (); found_name && st != nullptr && this_die == st->type_offset_in_section) > + { > + const signatured_type *st = cu_for_entry->as_signatured_type (); > + if (st != nullptr) > + m_index_storage->add_signatured_type_name (st->signature, name); Here, st can't be nullptr, as you checked is_debug_types above. You could assert instead (but it's made irrelevant by the proposal above). Simon ^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCHv3] gdb: resolve class name via DW_AT_signature in cooked index 2026-09-10 16:11 ` Simon Marchi @ 2026-09-11 19:18 ` Tom Tromey 2026-09-12 2:06 ` Simon Marchi 0 siblings, 1 reply; 36+ messages in thread From: Tom Tromey @ 2026-09-11 19:18 UTC (permalink / raw) To: Simon Marchi; +Cc: Andrew Burgess, gdb-patches >>>>> "Simon" == Simon Marchi <simark@simark.ca> writes: >> (a) If a DIE has no name, but does have a signature, then give the >> DIE a fake name (the empty string), and create an index entry >> for the DIE. Also keep a record that the cooked_index_entry >> for this DIE has a deferred name. Simon> Just wondering, if we end up not patching the entry for some reason, Simon> will an entry with an empty name cause problems / match things it's not Simon> supposed to match? Like will the child of that nameless entry be Simon> considered to be part of the top-level namespace or something like that? Simon> If it happens that we have an entry with an unresolved name, perhaps we Simon> should consider this entry invalid and just skip anything that would Simon> require it. Simon> So yeah, I wonder if it wouldn't be better to leave the name as Simon> nullptr, that would force us to add some nullptr checks to realize Simon> that the entry doesn't have a valid name, and we would skip it. I would much prefer a new cooked_index_flag_enum value over allowing NULL pointers. Simon> - the complaint runs on a thread pool worker, but no Simon> complaint_interceptor is installed in those threads, so Simon> complaint_internal writes straight to gdb_stderr from a worker Simon> thread, outside the collect-and-re-emit-on-the-main-thread machinery. Simon> Besides the raw thread-safety issue, the message can land at an Simon> arbitrary point in the main thread's output. Using the Simon> complaint interceptor would fix both. Complaints are worthless IMO. If this is user-actionable or interesting in any way, it's better to warn. If it isn't user-actionable, then it can just be ignored. Simon> - The comment on cooked_index_entry::name says that it always points Simon> into mapped DWARF sections, which is not true anymore. The comment Simon> could talk about the "" case (or nullptr if we decided to go that Simon> route). I think it's actually wrong already since cooked_index_shard::finalize can synthesize names. It's really the lifetime of the pointer that is important, not the storage location; and the important invariant is that there's never a case where the string is freed but the entry is live. Tom ^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCHv3] gdb: resolve class name via DW_AT_signature in cooked index 2026-09-11 19:18 ` Tom Tromey @ 2026-09-12 2:06 ` Simon Marchi 2026-09-14 13:23 ` Andrew Burgess 0 siblings, 1 reply; 36+ messages in thread From: Simon Marchi @ 2026-09-12 2:06 UTC (permalink / raw) To: Tom Tromey; +Cc: Andrew Burgess, gdb-patches On 2026-09-11 15:18, Tom Tromey wrote: >>>>>> "Simon" == Simon Marchi <simark@simark.ca> writes: > >>> (a) If a DIE has no name, but does have a signature, then give the >>> DIE a fake name (the empty string), and create an index entry >>> for the DIE. Also keep a record that the cooked_index_entry >>> for this DIE has a deferred name. > > Simon> Just wondering, if we end up not patching the entry for some reason, > Simon> will an entry with an empty name cause problems / match things it's not > Simon> supposed to match? Like will the child of that nameless entry be > Simon> considered to be part of the top-level namespace or something like that? > > Simon> If it happens that we have an entry with an unresolved name, perhaps we > Simon> should consider this entry invalid and just skip anything that would > Simon> require it. > > Simon> So yeah, I wonder if it wouldn't be better to leave the name as > Simon> nullptr, that would force us to add some nullptr checks to realize > Simon> that the entry doesn't have a valid name, and we would skip it. > > I would much prefer a new cooked_index_flag_enum value over allowing > NULL pointers. Why? Just wondering. > Simon> - the complaint runs on a thread pool worker, but no > Simon> complaint_interceptor is installed in those threads, so > Simon> complaint_internal writes straight to gdb_stderr from a worker > Simon> thread, outside the collect-and-re-emit-on-the-main-thread machinery. > Simon> Besides the raw thread-safety issue, the message can land at an > Simon> arbitrary point in the main thread's output. Using the > Simon> complaint interceptor would fix both. > > Complaints are worthless IMO. > > If this is user-actionable or interesting in any way, it's better to > warn. If it isn't user-actionable, then it can just be ignored. I don't recall, are we allowed to use debug_printf functions in non-main-threads? I'd like if that kind of anomaly left a trace somewhere, that you can look at without having to debug gdb itself. Of course the debug output will probably not look pretty if multiple threads spew some simultaneously, but you can always disable background threads just for this. > Simon> - The comment on cooked_index_entry::name says that it always points > Simon> into mapped DWARF sections, which is not true anymore. The comment > Simon> could talk about the "" case (or nullptr if we decided to go that > Simon> route). > > I think it's actually wrong already since cooked_index_shard::finalize > can synthesize names. It's really the lifetime of the pointer that is > important, not the storage location; and the important invariant is that > there's never a case where the string is freed but the entry is live. Makes sense yeah. Simon ^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCHv3] gdb: resolve class name via DW_AT_signature in cooked index 2026-09-12 2:06 ` Simon Marchi @ 2026-09-14 13:23 ` Andrew Burgess 2026-09-14 14:57 ` Simon Marchi 2026-09-14 15:36 ` Tom Tromey 0 siblings, 2 replies; 36+ messages in thread From: Andrew Burgess @ 2026-09-14 13:23 UTC (permalink / raw) To: Simon Marchi, Tom Tromey; +Cc: gdb-patches Simon Marchi <simark@simark.ca> writes: > On 2026-09-11 15:18, Tom Tromey wrote: >>>>>>> "Simon" == Simon Marchi <simark@simark.ca> writes: >> >>>> (a) If a DIE has no name, but does have a signature, then give the >>>> DIE a fake name (the empty string), and create an index entry >>>> for the DIE. Also keep a record that the cooked_index_entry >>>> for this DIE has a deferred name. >> >> Simon> Just wondering, if we end up not patching the entry for some reason, >> Simon> will an entry with an empty name cause problems / match things it's not >> Simon> supposed to match? Like will the child of that nameless entry be >> Simon> considered to be part of the top-level namespace or something like that? >> >> Simon> If it happens that we have an entry with an unresolved name, perhaps we >> Simon> should consider this entry invalid and just skip anything that would >> Simon> require it. >> >> Simon> So yeah, I wonder if it wouldn't be better to leave the name as >> Simon> nullptr, that would force us to add some nullptr checks to realize >> Simon> that the entry doesn't have a valid name, and we would skip it. >> >> I would much prefer a new cooked_index_flag_enum value over allowing >> NULL pointers. > > Why? Just wondering. Also, in this case, the point is that we end up creating the cooked_index_entry before we know the name, so what value should the name pointer hold? My V1 patch tried to find the name before the entry was created, but Tom correctly pointed out that this was not thread safe, and would fail to find the name in some cases. My V2 used the empty string in order to avoid NULL pointers, but empty name strings cannot usually (outside of this patch) be created, and as Simon pointed out, if these empty strings "escape" into the rest of GDB then problems arise. So V3 switched to NULL pointers as something that is fairly obviously an "unset" string. I haven't looked into it, but I'm sure I could add an enum flag, but this would still leave the question of what value to give NAME until it's actually filled in. > >> Simon> - the complaint runs on a thread pool worker, but no >> Simon> complaint_interceptor is installed in those threads, so >> Simon> complaint_internal writes straight to gdb_stderr from a worker >> Simon> thread, outside the collect-and-re-emit-on-the-main-thread machinery. >> Simon> Besides the raw thread-safety issue, the message can land at an >> Simon> arbitrary point in the main thread's output. Using the >> Simon> complaint interceptor would fix both. >> >> Complaints are worthless IMO. >> >> If this is user-actionable or interesting in any way, it's better to >> warn. If it isn't user-actionable, then it can just be ignored. > > I don't recall, are we allowed to use debug_printf functions in > non-main-threads? I'd like if that kind of anomaly left a trace > somewhere, that you can look at without having to debug gdb itself. Of > course the debug output will probably not look pretty if multiple > threads spew some simultaneously, but you can always disable background > threads just for this. I wouldn't want to turn these into debug prints. I understand Tom's point though, the complaints are off by default, and what are users actually going to do even if they are turned on? Usually the problem is one of the compiler's making, and the user is likely stuck. Still, it might be nice if we did a better job of altering the user in some way that we found issues with the DWARF, and some things might not work as they expect. I've long wondered if the problem is that the choice right now is "print all complaints" or "print no complaints", but that's not always that useful. Users likely care even less, or have even less power to change things, if the complaints are from some system library. What if, instead of printing the complaints (or not printing them) we instead stored the complaints somewhere, and associated the complaint with the objfile for which the debug information was loaded. Then, before printing the CLI prompt, we could inform the user: 123 debug information complaints seen. Use 'info complaints' for more details. (gdb) then 'info complaints' would like each objfile and the number of complaints seen, and 'info complaints <objfile name>' would actually list the complaints for the given objfile. Having the output come from a command would also allow us produce more verbose output, instead of the current 1 or 2 sentence style, we could fully explain what the issue is, and what this might mean. Of course the 'xxx debug information complaints seen' would actually be 'complaints seen since the last time GDB informed the user', so the user wouldn't be constantly spammed with that message. > >> Simon> - The comment on cooked_index_entry::name says that it always points >> Simon> into mapped DWARF sections, which is not true anymore. The comment >> Simon> could talk about the "" case (or nullptr if we decided to go that >> Simon> route). >> >> I think it's actually wrong already since cooked_index_shard::finalize >> can synthesize names. It's really the lifetime of the pointer that is >> important, not the storage location; and the important invariant is that >> there's never a case where the string is freed but the entry is live. > > Makes sense yeah. I need to update the comment, so I'll try to write something that covers this case too. Thanks, Andrew ^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCHv3] gdb: resolve class name via DW_AT_signature in cooked index 2026-09-14 13:23 ` Andrew Burgess @ 2026-09-14 14:57 ` Simon Marchi 2026-09-14 15:38 ` Tom Tromey 2026-09-15 10:25 ` Andrew Burgess 2026-09-14 15:36 ` Tom Tromey 1 sibling, 2 replies; 36+ messages in thread From: Simon Marchi @ 2026-09-14 14:57 UTC (permalink / raw) To: Andrew Burgess, Tom Tromey; +Cc: gdb-patches On 9/14/26 9:23 AM, Andrew Burgess wrote: >>> I would much prefer a new cooked_index_flag_enum value over allowing >>> NULL pointers. >> >> Why? Just wondering. > > Also, in this case, the point is that we end up creating the > cooked_index_entry before we know the name, so what value should the > name pointer hold? > > My V1 patch tried to find the name before the entry was created, but Tom > correctly pointed out that this was not thread safe, and would fail to > find the name in some cases. > > My V2 used the empty string in order to avoid NULL pointers, but empty > name strings cannot usually (outside of this patch) be created, and as > Simon pointed out, if these empty strings "escape" into the rest of GDB > then problems arise. > > So V3 switched to NULL pointers as something that is fairly obviously an > "unset" string. > > I haven't looked into it, but I'm sure I could add an enum flag, but > this would still leave the question of what value to give NAME until > it's actually filled in. That's why I was wondering, but really I am not opposed to a flag, I just wanted to know the rationale. We have 1 bit free in cooked_index_flag, so it wouldn't take up any more space. It's just that having a flag that says "this entry has no name and is therefore invalid" seems redundant with the name being nullptr. Instead of leaving them nullptr, another option would be delete those cooked_index_entries from the vectors, if we never plan to do anything with them. We would have to delete the name-less entries, and any child entry that refers to them, not sure how to do that efficiently though. Simon ^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCHv3] gdb: resolve class name via DW_AT_signature in cooked index 2026-09-14 14:57 ` Simon Marchi @ 2026-09-14 15:38 ` Tom Tromey 2026-09-15 10:25 ` Andrew Burgess 1 sibling, 0 replies; 36+ messages in thread From: Tom Tromey @ 2026-09-14 15:38 UTC (permalink / raw) To: Simon Marchi; +Cc: Andrew Burgess, Tom Tromey, gdb-patches Simon> That's why I was wondering, but really I am not opposed to a flag, I Simon> just wanted to know the rationale. We have 1 bit free in Simon> cooked_index_flag, so it wouldn't take up any more space. It looks that way because of the underlying type, but that is just a convenient choice: (gdb) ptype/o cooked_index_entry /* offset | size */ type = class cooked_index_entry : public allocate_on_obstack<cooked_index_entry> { public: /* 0 | 8 */ const char *name; /* 8 | 8 */ const char *canonical; /* 16 | 4 */ dwarf_tag tag; /* 20 | 1 */ cooked_index_flag flags; /* 21: 0 | 4 */ language lang : 5; /* XXX 3-bit hole */ /* XXX 2-byte hole */ We actually have 16 more bits available if we need them. Though maybe only 8 of those are easily usable. Tom ^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCHv3] gdb: resolve class name via DW_AT_signature in cooked index 2026-09-14 14:57 ` Simon Marchi 2026-09-14 15:38 ` Tom Tromey @ 2026-09-15 10:25 ` Andrew Burgess 2026-09-15 15:01 ` Tom Tromey 1 sibling, 1 reply; 36+ messages in thread From: Andrew Burgess @ 2026-09-15 10:25 UTC (permalink / raw) To: Simon Marchi, Tom Tromey; +Cc: gdb-patches Simon Marchi <simark@simark.ca> writes: > On 9/14/26 9:23 AM, Andrew Burgess wrote: >>>> I would much prefer a new cooked_index_flag_enum value over allowing >>>> NULL pointers. >>> >>> Why? Just wondering. >> >> Also, in this case, the point is that we end up creating the >> cooked_index_entry before we know the name, so what value should the >> name pointer hold? >> >> My V1 patch tried to find the name before the entry was created, but Tom >> correctly pointed out that this was not thread safe, and would fail to >> find the name in some cases. >> >> My V2 used the empty string in order to avoid NULL pointers, but empty >> name strings cannot usually (outside of this patch) be created, and as >> Simon pointed out, if these empty strings "escape" into the rest of GDB >> then problems arise. >> >> So V3 switched to NULL pointers as something that is fairly obviously an >> "unset" string. >> >> I haven't looked into it, but I'm sure I could add an enum flag, but >> this would still leave the question of what value to give NAME until >> it's actually filled in. > > That's why I was wondering, but really I am not opposed to a flag, I > just wanted to know the rationale. We have 1 bit free in > cooked_index_flag, so it wouldn't take up any more space. It's just > that having a flag that says "this entry has no name and is therefore > invalid" seems redundant with the name being nullptr. > > Instead of leaving them nullptr, another option would be delete those > cooked_index_entries from the vectors, if we never plan to do anything > with them. This is what I'm doing in v4. The entries all live on the obstack, so I can just remove them from the vector without concern. But .... > We would have to delete the name-less entries, and any child > entry that refers to them, not sure how to do that efficiently though. This is the problem I'm currently trying to solve. I also reached the conclusion that deleting the child entries would be too expensive, so my second plan was to just delete the parent pointer from child entries if the parent is nameless. This would leave the child entries in a weird state, e.g. 'the_type::method' would appear in the index as just 'method', but I think this would be fine. This isn't "normal" behaviour, and only triggers in the case where the parent's name cannot be found. The problem with this approach is that the parent might be from another shard, potentially resolved due to the IS_PARENT_DEFERRED flag from the parent map. The race is on the read of the parent's name field, the parent might appear nameless, but it might in fact be the case that the name hasn't been assigned yet. So the current idea I'm considering is leaving "nameless" entries around, but giving them a non-empty name, something like "__signature_0x..._not_found__". This name would then show up in the index, and a user could, in theory, say: (gdb) print __signature_0x..._not_found__::method which seems weird, but remember, this really is an edge case, for when a referenced signature isn't found. The other possibility is that, because this is an error case, we could have a serial action that cleans up the mess, deleting child entries with nameless parents. This would be done in cooked_index::set_contents, as part of this code: gdb::task_group finalizers ([this] () { // TODO: Fix up the state here. m_state->set (cooked_state::FINALIZED); m_state->write_to_cache (index_for_writing ()); m_state->set (cooked_state::CACHE_DONE); }); The fix up would be cheap if there was nothing to do, which would be the normal case, but in the error case, we'd search through and delete any child with a nameless parent, and their children, and their children, etc. Or maybe just clear the parent pointer at this point? I'm not really sure yet. Anyway, if you have any thoughts, I'd love to hear them. Thanks, Andrew ^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCHv3] gdb: resolve class name via DW_AT_signature in cooked index 2026-09-15 10:25 ` Andrew Burgess @ 2026-09-15 15:01 ` Tom Tromey 2026-09-15 15:55 ` Simon Marchi 2026-09-16 11:48 ` Andrew Burgess 0 siblings, 2 replies; 36+ messages in thread From: Tom Tromey @ 2026-09-15 15:01 UTC (permalink / raw) To: Andrew Burgess; +Cc: Simon Marchi, Tom Tromey, gdb-patches >>>>> "Andrew" == Andrew Burgess <aburgess@redhat.com> writes: >> We would have to delete the name-less entries, and any child >> entry that refers to them, not sure how to do that efficiently though. Andrew> This is the problem I'm currently trying to solve. Andrew> The problem with this approach is that the parent might be from another Andrew> shard, potentially resolved due to the IS_PARENT_DEFERRED flag from the Andrew> parent map. The race is on the read of the parent's name field, the Andrew> parent might appear nameless, but it might in fact be the case that the Andrew> name hasn't been assigned yet. Andrew> The other possibility is that, because this is an error case, we could Andrew> have a serial action that cleans up the mess, deleting child entries Andrew> with nameless parents. This would be done in Andrew> cooked_index::set_contents, as part of this code: Right now your patch is fixing up these entries in parallel, in each shard. But if this is uncommon enough, it could be done in cooked_index::set_contents instead, say when setting up the finalizer tasks: for (auto &shard : m_shards) { auto this_shard = shard.get (); const parent_map_map *parent_maps = m_state->get_parent_map_map (); ... signature->entry lookup here Then entries could be filtered out in cooked_index_shard::finalize if they have a "bad" parent somewhere in their "parent" chain. I'm not sure if this would work or not. TBH I find all this stuff in DWARF pretty maddening and also difficult to reason about. Like, even constructing the case you are talking about seems very tricky, seeing that it has to involve type signatures and somehow also cross-CU parent references. A different option might be to ignore such entries at lookup time. That is, let the child entries stay in the vector and just skip them in the relevant lookup loops. My intuition generally is that DWARF reading is slow and user-visible, as is CU expansion -- but the lookups themselves are not. This would probably just mean touching the index writers and cooked_index_functions::search. Perhaps the bad entries themselves (an entry with a signature that couldn't be found) could simply not appear in the shard vector, to avoid problems with their anonymity. In cooked_index_functions::search you could just stick a check here: if (!entry->matches (search_flags) || !entry->matches (domain)) continue; Like "entry->valid () || ..." I have no idea if this is helpful but didn't want to leave you hanging. I'm sorry you have to deal with this. Tom ^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCHv3] gdb: resolve class name via DW_AT_signature in cooked index 2026-09-15 15:01 ` Tom Tromey @ 2026-09-15 15:55 ` Simon Marchi 2026-09-15 17:21 ` Simon Marchi 2026-09-16 11:45 ` Andrew Burgess 2026-09-16 11:48 ` Andrew Burgess 1 sibling, 2 replies; 36+ messages in thread From: Simon Marchi @ 2026-09-15 15:55 UTC (permalink / raw) To: Tom Tromey, Andrew Burgess; +Cc: gdb-patches On 2026-09-15 11:01, Tom Tromey wrote: >>>>>> "Andrew" == Andrew Burgess <aburgess@redhat.com> writes: > >>> We would have to delete the name-less entries, and any child >>> entry that refers to them, not sure how to do that efficiently though. > > Andrew> This is the problem I'm currently trying to solve. > > Andrew> The problem with this approach is that the parent might be from another > Andrew> shard, potentially resolved due to the IS_PARENT_DEFERRED flag from the > Andrew> parent map. The race is on the read of the parent's name field, the > Andrew> parent might appear nameless, but it might in fact be the case that the > Andrew> name hasn't been assigned yet. > > Andrew> The other possibility is that, because this is an error case, we could > Andrew> have a serial action that cleans up the mess, deleting child entries > Andrew> with nameless parents. This would be done in > Andrew> cooked_index::set_contents, as part of this code: > > Right now your patch is fixing up these entries in parallel, in each > shard. But if this is uncommon enough, it could be done in > cooked_index::set_contents instead, say when setting up the finalizer > tasks: > > for (auto &shard : m_shards) > { > auto this_shard = shard.get (); > const parent_map_map *parent_maps = m_state->get_parent_map_map (); > ... signature->entry lookup here > > Then entries could be filtered out in cooked_index_shard::finalize if > they have a "bad" parent somewhere in their "parent" chain. Having a bad parent is rare, that would only happen when some (nameless) type has a DW_AT_signature and the matching type unit can't be found. That would be a buggy producer that "forgets" to add the necessary type units. But the basic case where the name fixup is needed isn't an error case, it's just what happens when using clang with -fdebug-types-section, since structures/classes are nameless: 0x0000099e: DW_TAG_structure_type DW_AT_declaration [DW_FORM_flag_present] (true) DW_AT_signature [DW_FORM_ref_sig8] (0xdf1329ababfff8ca) So I suppose you'll get one fixup to do per structure/class type. Doing the fixups in parallel is easy, since it's just looking up the signature -> maps, and the "finalize" step already exists, so why not. > > I'm not sure if this would work or not. TBH I find all this stuff in > DWARF pretty maddening and also difficult to reason about. Like, even > constructing the case you are talking about seems very tricky, seeing > that it has to involve type signatures and somehow also cross-CU parent > references. > > A different option might be to ignore such entries at lookup time. That > is, let the child entries stay in the vector and just skip them in the > relevant lookup loops. My intuition generally is that DWARF reading is > slow and user-visible, as is CU expansion -- but the lookups themselves > are not. > > This would probably just mean touching the index writers and > cooked_index_functions::search. Perhaps the bad entries themselves (an > entry with a signature that couldn't be found) could simply not appear > in the shard vector, to avoid problems with their anonymity. > > In cooked_index_functions::search you could just stick a check here: > > if (!entry->matches (search_flags) > || !entry->matches (domain)) > continue; > > Like "entry->valid () || ..." This was our earlier suggestion. Leave the name nullptr (or set a flag, whatever), marking them as invalid. When you need to compute the full name of an entry, you walk up the parent chain. If one of those parents is invalid, bail out. You can't correctly an entry's full name if the name of a parent is missing, as simple as that. And then you have to handle invalid entries in a few other locations (like cooked_index_functions::search and the index writers, as you said), but it shouldn't be too bad. That sounds simpler than trying to remove the entries. Simon ^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCHv3] gdb: resolve class name via DW_AT_signature in cooked index 2026-09-15 15:55 ` Simon Marchi @ 2026-09-15 17:21 ` Simon Marchi 2026-09-16 11:40 ` Andrew Burgess 2026-09-16 11:45 ` Andrew Burgess 1 sibling, 1 reply; 36+ messages in thread From: Simon Marchi @ 2026-09-15 17:21 UTC (permalink / raw) To: Tom Tromey, Andrew Burgess; +Cc: gdb-patches On 2026-09-15 11:55, Simon Marchi wrote: > Having a bad parent is rare, that would only happen when some > (nameless) type has a DW_AT_signature and the matching type unit can't > be found. That would be a buggy producer that "forgets" to add the > necessary type units. > > But the basic case where the name fixup is needed isn't an error case, > it's just what happens when using clang with -fdebug-types-section, > since structures/classes are nameless: > > 0x0000099e: DW_TAG_structure_type > DW_AT_declaration [DW_FORM_flag_present] (true) > DW_AT_signature [DW_FORM_ref_sig8] (0xdf1329ababfff8ca) > > So I suppose you'll get one fixup to do per structure/class type. > > Doing the fixups in parallel is easy, since it's just looking up the > signature -> maps, and the "finalize" step already exists, so why not. I tested this patch on a big program (Blender). I moved the deferred name computation to set_contents (done serially). It takes 0.014 seconds to do it for 820602 names. So yeah, I'm fine if you want to move it to set_contents (done serially) for simplicity. Simon ^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCHv3] gdb: resolve class name via DW_AT_signature in cooked index 2026-09-15 17:21 ` Simon Marchi @ 2026-09-16 11:40 ` Andrew Burgess 0 siblings, 0 replies; 36+ messages in thread From: Andrew Burgess @ 2026-09-16 11:40 UTC (permalink / raw) To: Simon Marchi, Tom Tromey; +Cc: gdb-patches Simon Marchi <simark@simark.ca> writes: > On 2026-09-15 11:55, Simon Marchi wrote: >> Having a bad parent is rare, that would only happen when some >> (nameless) type has a DW_AT_signature and the matching type unit can't >> be found. That would be a buggy producer that "forgets" to add the >> necessary type units. >> >> But the basic case where the name fixup is needed isn't an error case, >> it's just what happens when using clang with -fdebug-types-section, >> since structures/classes are nameless: >> >> 0x0000099e: DW_TAG_structure_type >> DW_AT_declaration [DW_FORM_flag_present] (true) >> DW_AT_signature [DW_FORM_ref_sig8] (0xdf1329ababfff8ca) >> >> So I suppose you'll get one fixup to do per structure/class type. >> >> Doing the fixups in parallel is easy, since it's just looking up the >> signature -> maps, and the "finalize" step already exists, so why not. > > I tested this patch on a big program (Blender). I moved the deferred > name computation to set_contents (done serially). It takes 0.014 > seconds to do it for 820602 names. So yeah, I'm fine if you want to > move it to set_contents (done serially) for simplicity. I'd already written v4 before I read this reply. In v4 I do have the cleanup performed in parallel, though this does add a little more complexity into cooked_index::set_contents. You'll have to let me know what you think, I can always go back to the approach you're proposing here if you don't like v4. Thanks, Andrew ^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCHv3] gdb: resolve class name via DW_AT_signature in cooked index 2026-09-15 15:55 ` Simon Marchi 2026-09-15 17:21 ` Simon Marchi @ 2026-09-16 11:45 ` Andrew Burgess 1 sibling, 0 replies; 36+ messages in thread From: Andrew Burgess @ 2026-09-16 11:45 UTC (permalink / raw) To: Simon Marchi, Tom Tromey; +Cc: gdb-patches Simon Marchi <simark@simark.ca> writes: > On 2026-09-15 11:01, Tom Tromey wrote: >>>>>>> "Andrew" == Andrew Burgess <aburgess@redhat.com> writes: >> >>>> We would have to delete the name-less entries, and any child >>>> entry that refers to them, not sure how to do that efficiently though. >> >> Andrew> This is the problem I'm currently trying to solve. >> >> Andrew> The problem with this approach is that the parent might be from another >> Andrew> shard, potentially resolved due to the IS_PARENT_DEFERRED flag from the >> Andrew> parent map. The race is on the read of the parent's name field, the >> Andrew> parent might appear nameless, but it might in fact be the case that the >> Andrew> name hasn't been assigned yet. >> >> Andrew> The other possibility is that, because this is an error case, we could >> Andrew> have a serial action that cleans up the mess, deleting child entries >> Andrew> with nameless parents. This would be done in >> Andrew> cooked_index::set_contents, as part of this code: >> >> Right now your patch is fixing up these entries in parallel, in each >> shard. But if this is uncommon enough, it could be done in >> cooked_index::set_contents instead, say when setting up the finalizer >> tasks: >> >> for (auto &shard : m_shards) >> { >> auto this_shard = shard.get (); >> const parent_map_map *parent_maps = m_state->get_parent_map_map (); >> ... signature->entry lookup here >> >> Then entries could be filtered out in cooked_index_shard::finalize if >> they have a "bad" parent somewhere in their "parent" chain. > > Having a bad parent is rare, that would only happen when some > (nameless) type has a DW_AT_signature and the matching type unit can't > be found. That would be a buggy producer that "forgets" to add the > necessary type units. > > But the basic case where the name fixup is needed isn't an error case, > it's just what happens when using clang with -fdebug-types-section, > since structures/classes are nameless: > > 0x0000099e: DW_TAG_structure_type > DW_AT_declaration [DW_FORM_flag_present] (true) > DW_AT_signature [DW_FORM_ref_sig8] (0xdf1329ababfff8ca) > > So I suppose you'll get one fixup to do per structure/class type. > > Doing the fixups in parallel is easy, since it's just looking up the > signature -> maps, and the "finalize" step already exists, so why not. > >> >> I'm not sure if this would work or not. TBH I find all this stuff in >> DWARF pretty maddening and also difficult to reason about. Like, even >> constructing the case you are talking about seems very tricky, seeing >> that it has to involve type signatures and somehow also cross-CU parent >> references. >> >> A different option might be to ignore such entries at lookup time. That >> is, let the child entries stay in the vector and just skip them in the >> relevant lookup loops. My intuition generally is that DWARF reading is >> slow and user-visible, as is CU expansion -- but the lookups themselves >> are not. > >> >> This would probably just mean touching the index writers and >> cooked_index_functions::search. Perhaps the bad entries themselves (an >> entry with a signature that couldn't be found) could simply not appear >> in the shard vector, to avoid problems with their anonymity. >> >> In cooked_index_functions::search you could just stick a check here: >> >> if (!entry->matches (search_flags) >> || !entry->matches (domain)) >> continue; >> >> Like "entry->valid () || ..." > > This was our earlier suggestion. Leave the name nullptr (or set a flag, > whatever), marking them as invalid. When you need to compute the full > name of an entry, you walk up the parent chain. If one of those parents > is invalid, bail out. You can't correctly an entry's full name if the > name of a parent is missing, as simple as that. And then you have to > handle invalid entries in a few other locations (like > cooked_index_functions::search and the index writers, as you said), but > it shouldn't be too bad. That sounds simpler than trying to remove the > entries. I've just posted v4. The approach taken there is to orphan entries that have an invalid parent during finalization. After that I just let things play out as they will. This means that something that should be 'the_type::method' will be added to the index as just 'method' IFF the DWARF for 'the_type' is broken such that the signature based lookup for 'the_type' fails. I think this is the same result as you've get by leaving the bad entries around (i.e. linked via the parent pointer) and just bailing out when trying to build the full name. At the end of the day, if the DWARF is bad then anything we come up with is not ideal. I guess the gold standard would be to remove the bad entry and all its (grand-)*children, but that's super expensive, and doesn't seem worth the hassle for something that should never happen. Anyway, you'll need to check out v4 and let me know what you think. Thanks, Andrew ^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCHv3] gdb: resolve class name via DW_AT_signature in cooked index 2026-09-15 15:01 ` Tom Tromey 2026-09-15 15:55 ` Simon Marchi @ 2026-09-16 11:48 ` Andrew Burgess 1 sibling, 0 replies; 36+ messages in thread From: Andrew Burgess @ 2026-09-16 11:48 UTC (permalink / raw) To: Tom Tromey; +Cc: Simon Marchi, Tom Tromey, gdb-patches Tom Tromey <tom@tromey.com> writes: >>>>>> "Andrew" == Andrew Burgess <aburgess@redhat.com> writes: > >>> We would have to delete the name-less entries, and any child >>> entry that refers to them, not sure how to do that efficiently though. > > Andrew> This is the problem I'm currently trying to solve. > > Andrew> The problem with this approach is that the parent might be from another > Andrew> shard, potentially resolved due to the IS_PARENT_DEFERRED flag from the > Andrew> parent map. The race is on the read of the parent's name field, the > Andrew> parent might appear nameless, but it might in fact be the case that the > Andrew> name hasn't been assigned yet. > > Andrew> The other possibility is that, because this is an error case, we could > Andrew> have a serial action that cleans up the mess, deleting child entries > Andrew> with nameless parents. This would be done in > Andrew> cooked_index::set_contents, as part of this code: > > Right now your patch is fixing up these entries in parallel, in each > shard. But if this is uncommon enough, it could be done in > cooked_index::set_contents instead, say when setting up the finalizer > tasks: > > for (auto &shard : m_shards) > { > auto this_shard = shard.get (); > const parent_map_map *parent_maps = m_state->get_parent_map_map (); > ... signature->entry lookup here > > Then entries could be filtered out in cooked_index_shard::finalize if > they have a "bad" parent somewhere in their "parent" chain. > > I'm not sure if this would work or not. TBH I find all this stuff in > DWARF pretty maddening and also difficult to reason about. Like, even > constructing the case you are talking about seems very tricky, seeing > that it has to involve type signatures and somehow also cross-CU parent > references. > > > A different option might be to ignore such entries at lookup time. That > is, let the child entries stay in the vector and just skip them in the > relevant lookup loops. My intuition generally is that DWARF reading is > slow and user-visible, as is CU expansion -- but the lookups themselves > are not. > > This would probably just mean touching the index writers and > cooked_index_functions::search. Perhaps the bad entries themselves (an > entry with a signature that couldn't be found) could simply not appear > in the shard vector, to avoid problems with their anonymity. > > In cooked_index_functions::search you could just stick a check here: > > if (!entry->matches (search_flags) > || !entry->matches (domain)) > continue; > > Like "entry->valid () || ..." > > > I have no idea if this is helpful but didn't want to leave you hanging. > I'm sorry you have to deal with this. Not a problem, and it's good to learn more about this part of GDB that I've previously not worked on much. I'd already written v4 before I read this email, so I've not taken all your suggestions on board, that's not because I don't value them, but I'd already written some code which I think is OK, even if it's not exactly what you're suggesting here. I'd value your thoughts on the v4 patch, and if you see problems then I'll take another pass through this email and do something more along these lines. Thanks, Andrew ^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCHv3] gdb: resolve class name via DW_AT_signature in cooked index 2026-09-14 13:23 ` Andrew Burgess 2026-09-14 14:57 ` Simon Marchi @ 2026-09-14 15:36 ` Tom Tromey 1 sibling, 0 replies; 36+ messages in thread From: Tom Tromey @ 2026-09-14 15:36 UTC (permalink / raw) To: Andrew Burgess; +Cc: Simon Marchi, Tom Tromey, gdb-patches >>> I would much prefer a new cooked_index_flag_enum value over allowing >>> NULL pointers. >> >> Why? Just wondering. Andrew> Also, in this case, the point is that we end up creating the Andrew> cooked_index_entry before we know the name, so what value should the Andrew> name pointer hold? Andrew> So V3 switched to NULL pointers as something that is fairly obviously an Andrew> "unset" string. I won't complain. I just think letting in NULL pointers often ends badly. Andrew> then 'info complaints' would like each objfile and the number of Andrew> complaints seen, and 'info complaints <objfile name>' would actually Andrew> list the complaints for the given objfile. This might be nicer, but consider that many of the existing complaints are just somebody's opinion about the compiler output, and not only are they not actionable, they will not ever be fixed. My contention is that no complaint has actually ever been fixed by virtue of being a complaint -- that is, maybe a compiler fix has been done, but it's been a separate action, not driven by gdb's output. Basically complaints are worthless. I won't object if you work on this, but IMNSHO putting any effort into this is a waste of time. Tom ^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCHv3] gdb: resolve class name via DW_AT_signature in cooked index 2026-09-01 13:31 ` [PATCHv3] " Andrew Burgess 2026-09-10 16:11 ` Simon Marchi @ 2026-09-10 16:18 ` Simon Marchi 2026-09-16 11:38 ` [PATCHv4] " Andrew Burgess 2 siblings, 0 replies; 36+ messages in thread From: Simon Marchi @ 2026-09-10 16:18 UTC (permalink / raw) To: Andrew Burgess, gdb-patches On 2026-09-01 09:31, Andrew Burgess wrote: > In v3: > > - I forgot to run check-all-boards on v2. There were some failures > due to GDB's output not matching the patterns, I've fixed what I > could in this iteration. > > - While looking at the different patterns I reworded one of the > complaint messages in cooked-index-shard.c to match a warning that > is emitted from elsewhere in the DWARF reader. There's no real > functional change, it's just the wording that's updated. > > - There are still some check-all-boards failures, but I believe > these are all wider issues when running DWARF assembler tests > using the check-all-boards rule, so I'm ignoring them for now. > > In v2: > > - Complete rewrite to address the issues that Tom raised. > > - Rebase to HEAD and retest. > > - New tests added to cover the DWO case and an error case. > > Thanks, > Andrew > > --- I just wanted to point out that your use of --- is the opposite of the convention, normally the "real" commit message goes above and the temporary message goes below. When applying this email as a whole (either with git-am or b4), it keeps the wrong part as the commit message. I would suggest either using the reversed order, or using a different marker if you prefer to keep the temporary message on top. Simon ^ permalink raw reply [flat|nested] 36+ messages in thread
* [PATCHv4] gdb: resolve class name via DW_AT_signature in cooked index 2026-09-01 13:31 ` [PATCHv3] " Andrew Burgess 2026-09-10 16:11 ` Simon Marchi 2026-09-10 16:18 ` Simon Marchi @ 2026-09-16 11:38 ` Andrew Burgess 2026-09-22 4:26 ` Simon Marchi ` (3 more replies) 2 siblings, 4 replies; 36+ messages in thread From: Andrew Burgess @ 2026-09-16 11:38 UTC (permalink / raw) To: gdb-patches; +Cc: Andrew Burgess, Simon Marchi, Tom Tromey In v4: - I believe I've addressed all the issues that Simon raised during his v3 review. Most of the issues were straight forward so I'll not mention them all here. If I've missed any then appologies. Here are the two issues that I think are worth additional explanation: - The crash Simon reported when using the cc-with-gdb-index board was causd by the empty name string I was previously using being emitted into the generated gdb_index. When that index is read back in the empty string causes a crash. What this actually means is that a baddly formed gdb_index can already trigger a GDB crash, but that's outside the scope of this patch. The fix I propose is, as was discussed elsewhere in the thread, to remove entries that end up with no name (due to bad DWARF). But this leads to another problem, because we end up needing to check a entry's parent's name, and the parent could be from a different shard, we need to ensure that all the names have been resolved before we look at the get_paent()->name. To handle this I propose that we split set_contents into two separate phases, a first resolve names phase, then a second finalize phase. With this done we should no longer encounter any entry with a NULL name after finalization, which means we no longer generate an invalid index, which means GDB will not trigger it's reading a corrupted index bug. - Simon did mention performance at the point where we merge togethe the signature to name map. I haveb't tried to improve this yet as that will just add more complexity, but we could potentially do something like the parent_map_map, where we have a data structure that holds a vector of maps. This would make merging the maps quick, moving the cost to the lookup instead, but this might be an improvement we want to make later on. In v3: - I forgot to run check-all-boards on v2. There were some failures due to GDB's output not matching the patterns, I've fixed what I could in this iteration. - While looking at the different patterns I reworded one of the complaint messages in cooked-index-shard.c to match a warning that is emitted from elsewhere in the DWARF reader. There's no real functional change, it's just the wording that's updated. - There are still some check-all-boards failures, but I believe these are all wider issues when running DWARF assembler tests using the check-all-boards rule, so I'm ignoring them for now. In v2: - Complete rewrite to address the issues that Tom raised. - Rebase to HEAD and retest. - New tests added to cover the DWO case and an error case. Thanks, Andrew ~~~~~ This commit fixes PR gdb/33447, an issue where looking up qualified member function names was not working for C++ binaries compiled with Clang when using the -fdebug-types-section flag. Before this commit we would see this behaviour: (gdb) print base1::a_function There is no field named a_function When what we expect to see is: (gdb) print base1::a_function $1 = {void (const base1 * const)} 0x403060 <base1::a_function() const> The problem is that the cooked index is unable to determine the name of the parent class `base1` in this case, and so decides not to index `base1` or any of its child DIEs, which includes its member functions. The problem was discovered by running gdb.cp/cpexprs-debug-types.exp with Clang: make check-gdb TESTS=gdb.cp/cpexprs-debug-types.exp \ RUNTESTFLAGS='CXX_FOR_TARGET=clang++ CC_FOR_TARGET=clang' The cpexprs-debug-types.exp test forces use of the '-fdebug-types-section' flag, which is not on by default. With this flag, class definitions are placed in type units, and the compile unit contains only a declaration stub for each class. Both clang++ and g++ emit these stubs, but they differ in one detail: GCC includes DW_AT_name on the stub, while clang++ does not, the stub carries only DW_AT_declaration and DW_AT_signature. The class name is only available in the type unit, reachable by following the signature. For example, Clang emits this in a CU: <1><2e7d>: DW_TAG_class_type DW_AT_declaration : 1 DW_AT_signature : 0x3abb... <2><2eac>: DW_TAG_subprogram DW_AT_name : a_function DW_AT_declaration : 1 The definition for a_function is elsewhere in the same CU: <1><3142>: DW_TAG_subprogram DW_AT_specification: <0x2eac> And in a TU elsewhere: Compilation Unit @ offset 0xd2e: ... snip ... Signature: 0x3abb... <0><d46>: Abbrev Number: 1 (DW_TAG_type_unit) ... snip ... <1><d51>: Abbrev Number: 30 (DW_TAG_class_type) ... snip ... <d57> DW_AT_name : (indexed string: 0xaa): base1 To find the DW_AT_name the cooked index needs to look up the type within the TU. Without the name the cooked indexer skips indexing `base1` as well as its children. This look up used to work; it works in GDB 17. PR gdb/33447 incorrectly identifies commit c879f4dc3e317cf6353a45a803ecf00d577a13d8 as the commit that introduced the regression. This is actually the last working commit. The problem was introduced by the next commit in the same series: commit 86ac8c546235a67d6a6bb29476a3a9ac8f7a620a Date: Thu Jan 2 15:28:18 2025 -0700 Convert lookup_symbol_in_objfile Prior to this commit GDB's symbol lookup had two phases, a search through already expanded symtabs, and a search via lookup_symbol_via_quick_fns. After the above commit only lookup_symbol_via_quick_fns remains. The lookup_symbol_via_quick_fns lookup, which relies on the indexer, was always broken, but the first phase, searching via expanded symtabs, could correctly find the type name via the signature. An initial attempt to solve this problem tried to fix this problem within cooked_indexer::scan_attributes, calling lookup_signatured_type and finding the name that way. However, there were three problems with this approach: 1. Possible thread safety issues; calling lookup_signatured_type for a DWO file ends up calling lookup_dwo_signatured_type, which can call add_type_unit and finalize_all_units, which modify state that is shared between parser threads. 2. The dwarf2_per_cu::type_offset_in_section for a TU is only set when the cutu_reader is constructed to parse that TU. Calling lookup_signatured_type doesn't fully parse the TU, it just finds the TU. The original code relied on type_offset_in_section being valid in order to then parse the TU and extract the name. This would break if the CU was processed before the TU. 3. For skeletonless TUs, these are not indexed until late in the indexing process, after all the CUs have been indexed. This means that if the TU referenced by the signature was skeletonless then the original approach would fail to find it. The new approach presented here is modeled more along the lines of the deferred parent handling that already exists within the indexer. The following changes have been made: 1. abbrev.c: Add DW_AT_signature to the set of attributes that mark a DIE as "interesting" in abbrev_table::read. Without this, the unnamed class stub was classified as uninteresting at the abbreviation level and scan_attributes was never called for it. 2. cooked-indexer.c (cooked_indexer::scan_attributes): Capture the DW_AT_signature attribute. 3. cooked-indexer.c (cooked_indexer::index_dies): There are two different jobs done here: (a) If a DIE has no name, but does have a signature, then allow the entry to be created with a NULL name. Record that the cooked_index_entry had a deferred name, and the signature with which the name can be found. (b) If we are indexing a TU, make a record of the signature, and the name of the primary type within the TU. This builds a signature to name map. 4. cooked-index-worker.c (cooked_index_worker::done_reading): Merge together all of the signature to name maps built in (3b). 5. cooked-index-shard.c (cooked_index_shard::resolve_deferred_names): A new function, look through all of the cooked_index_entry objects that were recorded in (3a), for each use the signature to name map created in (4) to lookup the name, and update the entry. If the name cannot be found then remove the cooked_index_entry from the entries list. The entry still lives on the obstack, but will no longer be found when iterating over all entries. 6. cooked-index-shard.c (cooked-index-shard::finalize): If the parent of an entry has no name, then discard the entry's parent. This prevents a nameless entry being found via the get_parent method. We don't want any entries with a NULL name to be found after this point as GDB currently assumes the names will not be NULL. 7. cooked-index.c (cooked_index::wait): After waiting, if we have completed finalization then emit any complaints that came from the finalization phase. 8. cooked-index.c (cooked_index::set_contents): This function gets a complete rewrite. We now need to perform finalization in two phases. Initially I wanted to place all the new work into the cooked_index_shard::finalize call, this would involve resolving deferred names, resolving deferred parents, and also detaching nameless parents from their children. The problem with this is that a parent could arrive from a different shard, and that shard, running on a separate thread, might not have resolved the name of the parent yet. As a result, GDB might decide to detach a parent as nameless, when if we waited a little longer the parent's name would be resolved correctly. To fix this race condition we need to ensure that all names are resolved before we make any decisions about whether or not to detach a parent for being nameless. And so, set_contents is now split into two phases. During the first phase we use a task_group to call resolve_deferred_names on every shard. This ensures every entry that can have a name will have a name. The done callback for this first phase starts the second phase. The second phase is just the old code from this function. We use a task_group to call finalize on every shard. The done callback for this second phase is unchanged, writing out the index and updating the index state. Thanks to this two phase approach we can now, within the finalize phase, detach nameless parents as we know that if they were going to get a name then they would have one. The first phase can generate a complaint so this function now makes use of complaint_interceptor to capture these. I also added a complaint_interceptor within the second phase even though it is not needed right now. This is pretty cheap, and means that if phase two does ever add a complaint in the future, then this would "just work". 9. cooked-index-entry.c (cooked_index_entry::write_scope): Add an assert that we are not processing an entry without a name. Entries like this should be removed during finalization. There are a bunch of header file changes to add the new data members, and accessor functions, which the steps above depend on. Added three new DWARF assembler tests. gdb.dwarf2/sig-type-unnamed-class.exp gdb.dwarf2/sig-type-unnamed-class-dwo.exp: These reproduce the problem case; there's a nameless declaration stub that references its full type via DW_AT_signature. To match the Clang output as much as possible the member function definition is separate and makes use of DW_AT_specification. The -dwo test places the DWARF into a DWO file and uses a skeletonless TU. gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp: This one tests some invalid DWARF, the type DIE references a signature that doesn't exist. In this case GDB just leaves the cooked_index_entry with a NULL name string, which means symbols cannot be found. I think this is fine though, the DWARF is corrupted in this case. Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=33447 --- gdb/dwarf2/abbrev.c | 1 + gdb/dwarf2/cooked-index-entry.c | 6 + gdb/dwarf2/cooked-index-entry.h | 25 ++- gdb/dwarf2/cooked-index-shard.c | 64 ++++++++ gdb/dwarf2/cooked-index-shard.h | 57 +++++++ gdb/dwarf2/cooked-index-worker.c | 9 + gdb/dwarf2/cooked-index-worker.h | 38 +++++ gdb/dwarf2/cooked-index.c | 99 ++++++++--- gdb/dwarf2/cooked-index.h | 6 + gdb/dwarf2/cooked-indexer.c | 35 +++- gdb/dwarf2/cooked-indexer.h | 1 + .../sig-type-unnamed-class-bad-sig.exp | 155 ++++++++++++++++++ .../gdb.dwarf2/sig-type-unnamed-class-dwo.exp | 143 ++++++++++++++++ .../gdb.dwarf2/sig-type-unnamed-class.exp | 106 ++++++++++++ 14 files changed, 716 insertions(+), 29 deletions(-) create mode 100644 gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp create mode 100644 gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-dwo.exp create mode 100644 gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp diff --git a/gdb/dwarf2/abbrev.c b/gdb/dwarf2/abbrev.c index 44d5c87a5f7..e99acc5e752 100644 --- a/gdb/dwarf2/abbrev.c +++ b/gdb/dwarf2/abbrev.c @@ -160,6 +160,7 @@ abbrev_table::read (struct dwarf2_section_info *section, case DW_AT_specification: case DW_AT_abstract_origin: case DW_AT_extension: + case DW_AT_signature: has_specification_or_origin = true; break; diff --git a/gdb/dwarf2/cooked-index-entry.c b/gdb/dwarf2/cooked-index-entry.c index 21e25e94615..4c3246144d8 100644 --- a/gdb/dwarf2/cooked-index-entry.c +++ b/gdb/dwarf2/cooked-index-entry.c @@ -233,6 +233,12 @@ cooked_index_entry::write_scope (struct obstack *storage, { if (get_parent () != nullptr) get_parent ()->write_scope (storage, sep, flags); + + /* Any entry without a name, or with an empty name, will have been + filtered out while the entries were being created, or during + finalization. */ + gdb_assert (name != nullptr && *name != '\0'); + /* When computing the Ada linkage name, the entry might not have been canonicalized yet, so use the 'name'. */ const char *local_name = ((flags & (FOR_MAIN | FOR_ADA_LINKAGE_NAME)) != 0 diff --git a/gdb/dwarf2/cooked-index-entry.h b/gdb/dwarf2/cooked-index-entry.h index 60ea581cbbe..b1056a33ffe 100644 --- a/gdb/dwarf2/cooked-index-entry.h +++ b/gdb/dwarf2/cooked-index-entry.h @@ -79,6 +79,11 @@ union cooked_index_entry_ref parent_map::addr_type deferred; }; +/* Type that maps DW_AT_signature values for a TU to the name of the + primary type within the TU. */ + +using signature_to_name_map = gdb::unordered_map<ULONGEST, const char *>; + /* Return a string representation of FLAGS. */ std::string to_string (cooked_index_flag flags); @@ -252,10 +257,22 @@ struct cooked_index_entry : public allocate_on_obstack<cooked_index_entry> defined in some CU that is included by many other CUs. */ iteration_status visit_defining_cus (per_cu_callback callback) const; - /* The name as it appears in DWARF. This always points into one of - the mapped DWARF sections. Note that this may be the name or the - linkage name -- two entries are created for DIEs which have both - attributes. */ + /* The entry's name. If not NULL then this must point to a string + which will outlive this entry. This usually means that NAME + points either into the mapped DWARF, or into storage within the + owning cooked_index_shard. + + This may be the name or the linkage name -- two entries are + created for DIEs which have both attributes. + + The NAME can be NULL. When a DIE lacks a name but has a + signature we create an entry with a NULL name and then try to + lookup the name via the signature during finalization. If the + name via signature lookup fails then the entry continues to exist + with a NULL name, however, the entry should be removed from the + entries vector, and should be removed as a parent, so entries + with a NULL name should not be discovered during normal + processing. */ const char *name; /* The canonical name. This may be equal to NAME. */ const char *canonical = nullptr; diff --git a/gdb/dwarf2/cooked-index-shard.c b/gdb/dwarf2/cooked-index-shard.c index 91dc9ab8945..e864c4c59d5 100644 --- a/gdb/dwarf2/cooked-index-shard.c +++ b/gdb/dwarf2/cooked-index-shard.c @@ -20,6 +20,8 @@ #include "dwarf2/cooked-index-shard.h" #include "dwarf2/tag.h" #include "dwarf2/index-common.h" +#include "dwarf2/read.h" +#include "dwarf2/error.h" #include "cp-support.h" #include "c-lang.h" #include "ada-lang.h" @@ -104,6 +106,7 @@ cooked_index_shard::add (sect_offset die_offset, enum dwarf_tag tag, && parent_entry.resolved == nullptr && m_main == nullptr && language_may_use_plain_main (lang) + && name != nullptr && streq (name, "main")) m_main = result; @@ -189,6 +192,52 @@ struct cooked_index_entry_name_ptr_eq /* See cooked-index-shard.h. */ +void +cooked_index_shard::resolve_deferred_names + (const signature_to_name_map &sig_names) +{ + bool need_to_cleanup_entries = false; + for (const auto &[entry, signature] : m_deferred_names) + { + if (const auto it = sig_names.find (signature); + it != sig_names.end ()) + { + /* Each entry should only occur once in M_DEFERRED_NAMES, + and the entry should only be added when it has no name. */ + gdb_assert (entry->name == nullptr); + + /* Patch the name. */ + entry->name = it->second; + } + else + { + need_to_cleanup_entries = true; + complaint (_(DWARF_ERROR_PREFIX + "Cannot find signatured DIE %s referenced from DIE " + "at %s [in module %s]"), + hex_string (signature), + sect_offset_str (entry->die_offset), + entry->per_cu->per_bfd ()->filename ()); + } + } + + /* If we failed to resolve the name of an entry via its signature + then remove the entry from the m_entries vector. This should be + rare, and should only happen when we have corrupted DWARF. The + entries still live on the obstack, so parent points are still + valid, but removing entries from the index means we don't try to + search them when looking for index hits. */ + if (need_to_cleanup_entries) + m_entries.erase (std::remove_if (m_entries.begin (), m_entries.end (), + [] (const cooked_index_entry *e) + { + return e->name == nullptr; + }), + m_entries.end ()); +} + +/* See cooked-index-shard.h. */ + void cooked_index_shard::finalize (const parent_map_map *parent_maps) { @@ -216,6 +265,11 @@ cooked_index_shard::finalize (const parent_map_map *parent_maps) for (cooked_index_entry *entry : m_entries) { + /* Entries without a name, or with an empty name, are filtered + out either as the entries are created, or during the call to + resolve_deferred_names. */ + gdb_assert (entry->name != nullptr && *entry->name != '\0'); + if ((entry->flags & IS_PARENT_DEFERRED) != 0) { const cooked_index_entry *new_parent @@ -223,6 +277,16 @@ cooked_index_shard::finalize (const parent_map_map *parent_maps) entry->resolve_parent (new_parent); } + /* Remove a parent reference if the parent has no name. This + leaves ENTRY as an orphan, but this only happens if the DWARF + is corrupted and we failed to find a name for the parent. We + can safely check the parent's name at this point because all + deferred names will have been resolved in all shards before + finalize is called on any shard. */ + if (const cooked_index_entry *parent = entry->get_parent (); + parent != nullptr && parent->name == nullptr) + entry->set_parent (nullptr); + /* Note that this code must be kept in sync with cooked_index::get_main -- if canonicalization is required here, then a check might be required there. */ diff --git a/gdb/dwarf2/cooked-index-shard.h b/gdb/dwarf2/cooked-index-shard.h index 84c37958c83..191182a132b 100644 --- a/gdb/dwarf2/cooked-index-shard.h +++ b/gdb/dwarf2/cooked-index-shard.h @@ -26,6 +26,7 @@ #include "addrmap.h" #include "gdbsupport/iterator-range.h" #include "gdbsupport/string-set.h" +#include "complaints.h" /* An index of interesting DIEs. This is "cooked", in contrast to a mapped .debug_names or .gdb_index, which are "raw". An entry in @@ -79,6 +80,16 @@ class cooked_index_shard for completion, will be returned. */ range find (const std::string &name, bool completing) const; + /* Record that ENTRY didn't have a name attribute, but did have a + DW_AT_signature attribute, SIGNATURE. ENTRY will have a NULL + name string pointer. We will patch the name of ENTRY during + finalization once the TUs have been parsed, the correct TU will + be found using SIGNATURE. */ + void add_deferred_name (cooked_index_entry *entry, ULONGEST signature) + { + m_deferred_names.push_back ({entry, signature}); + } + private: /* Return the entry that is believed to represent the program's @@ -124,6 +135,38 @@ class cooked_index_shard This may be invoked in a worker thread. */ void finalize (const parent_map_map *parent_maps); + /* Use SIG_NAMES to look up the name of any entry in + m_deferred_names. This should be called a single time, and must + be called before finalize is called. Every shard must have its + deferred names resolved before finalize can be called on any + shard as finalize can lookup cross-shard entries, and we need to + ensure that those entries have their name. */ + void resolve_deferred_names (const signature_to_name_map &sig_names); + + /* Called after each phase of the finalization process. Store + COMPLAINTS so they can be reported later on the main thread. */ + void merge_finalize_complaints (complaint_collection &&complaints) + { + if (m_finalize_complaints.empty ()) + m_finalize_complaints = std::move (complaints); + else + { + /* The current version of gdb::unordered_set doesn't support + the merge method that std::unordered_set supports. If we + update gdb::unordered_set then we could switch this to use + merge(). */ + m_finalize_complaints.insert (complaints.begin (), complaints.end ()); + } + } + + /* Return the set of complaints emitted during the finalization + process. We move these complaints out of the shard as these are + only emitted once, and don't need to be stored beyond that. */ + complaint_collection release_finalize_complaints () + { + return std::move (m_finalize_complaints); + } + /* Storage for the entries. */ auto_obstack m_storage; /* List of all entries. */ @@ -134,6 +177,20 @@ class cooked_index_shard addrmap_fixed *m_addrmap = nullptr; /* Storage for canonical names. */ gdb::string_set m_names; + + /* Entries without a name, but with a signature. These entries will + have a NULL name, but we need to patch these up with a real name + during finalization. */ + struct deferred_name + { + cooked_index_entry *entry; + ULONGEST signature; + }; + std::vector<deferred_name> m_deferred_names; + + /* Any complaints emitted during the call to finalize are stored + here until they can be emitted on the main thread. */ + complaint_collection m_finalize_complaints; }; using cooked_index_shard_up = std::unique_ptr<cooked_index_shard>; diff --git a/gdb/dwarf2/cooked-index-worker.c b/gdb/dwarf2/cooked-index-worker.c index 723e027172e..054b25ba147 100644 --- a/gdb/dwarf2/cooked-index-worker.c +++ b/gdb/dwarf2/cooked-index-worker.c @@ -249,6 +249,15 @@ cooked_index_worker::done_reading () m_all_parents_map.add_map (*one_result.get_parent_map ()); } + { + scoped_time_it time_it ("DWARF add signature name map", m_per_command_time); + + /* Combine all of the signature to name maps. */ + for (cooked_index_worker_result &one_result : m_results) + for (const auto &[sig, name] : one_result.get_sig_name_map ()) + m_all_sig_names_map.emplace (sig, name); + } + /* Update all the CU inclusion information. */ for (auto &item : m_results) item.invert_cu_inclusions (); diff --git a/gdb/dwarf2/cooked-index-worker.h b/gdb/dwarf2/cooked-index-worker.h index 1799c0b9e08..262b727b983 100644 --- a/gdb/dwarf2/cooked-index-worker.h +++ b/gdb/dwarf2/cooked-index-worker.h @@ -83,6 +83,25 @@ class cooked_index_worker_result return m_shard->add (name); } + /* Record that ENTRY was missing a name attribute, but did have a + DW_AT_signature attribute, SIGNATURE. When the TUs are + processed, which might not happen until after CU processing for + skeletonless TUs, we can find a name and supply it at that + point. */ + void add_deferred_name (cooked_index_entry *entry, ULONGEST signature) + { + m_shard->add_deferred_name (entry, signature); + } + + /* Called when processing TUs to record that the primary type within + a type unit with SIGNATURE, was called NAME. This information + will be used during finalization to fix-up the name of any + entries passed to add_deferred_name. */ + void add_signatured_type_name (ULONGEST signature, const char *name) + { + m_sig_name_map.emplace (signature, name); + } + /* Install the current addrmap into the shard being constructed, then transfer ownership of the index to the caller. */ cooked_index_shard_up release_shard () @@ -153,6 +172,12 @@ class cooked_index_worker_result discovered. */ void invert_cu_inclusions (); + /* The signature to name map for this worker. */ + const signature_to_name_map &get_sig_name_map () const + { + return m_sig_name_map; + } + private: /* The abbrev table cache used by this indexer. */ abbrev_table_cache m_abbrev_table_cache; @@ -188,6 +213,9 @@ class cooked_index_worker_result /* Parent map for each CU that is read. */ parent_map m_parent_map; + /* Signature to name map for the primary type in a TU. */ + signature_to_name_map m_sig_name_map; + /* A writeable addrmap being constructed by this scanner. */ addrmap_mutable m_addrmap; @@ -272,6 +300,12 @@ class cooked_index_worker return &m_all_parents_map; } + /* Return the map containing the complete signature to name information. */ + const signature_to_name_map &get_sig_name_map () const + { + return m_all_sig_names_map; + } + protected: /* Let cooked_index call the 'set' and 'write_to_cache' methods. */ @@ -319,6 +353,10 @@ class cooked_index_worker parent relationships. */ parent_map_map m_all_parents_map; + /* Map from signature to name of primary type within a TU. This is + the combined map, built after all the workers have finished. */ + signature_to_name_map m_all_sig_names_map; + /* Current state of this object. */ cooked_state m_state = cooked_state::INITIAL; /* Mutex and condition variable used to synchronize. */ diff --git a/gdb/dwarf2/cooked-index.c b/gdb/dwarf2/cooked-index.c index 167e39ffc89..799835bca61 100644 --- a/gdb/dwarf2/cooked-index.c +++ b/gdb/dwarf2/cooked-index.c @@ -58,7 +58,23 @@ cooked_index::wait (cooked_state desired_state, bool allow_quit) if (m_state == nullptr) return; - if (m_state->wait (desired_state, allow_quit)) + bool done = m_state->wait (desired_state, allow_quit); + + /* Emit any cached complaints if we have finalized and we are on the + main thread. Check for the requested state or the DONE flag + here, we might have only asked for MAIN_AVAILABLE, but if the + workers are quick then they might be done, in which case we + should emit the complaints now. */ + if (!m_finalize_complaints_emitted + && is_main_thread () + && (desired_state >= cooked_state::FINALIZED || done)) + { + m_finalize_complaints_emitted = true; + for (auto &shard : m_shards) + re_emit_complaints (shard->release_finalize_complaints ()); + } + + if (done) { /* Only the main thread can modify this. */ gdb_assert (is_main_thread ()); @@ -74,31 +90,70 @@ cooked_index::set_contents () m_state->set (cooked_state::MAIN_AVAILABLE); - /* This is run after finalization is done -- but not before. If - this task were submitted earlier, it would have to wait for - finalization. However, that would take a slot in the global - thread pool, and if enough such tasks were submitted at once, it - would cause a livelock. */ - gdb::task_group finalizers ([this] () - { - m_state->set (cooked_state::FINALIZED); - m_state->write_to_cache (index_for_writing ()); - m_state->set (cooked_state::CACHE_DONE); - }); - - for (auto &shard : m_shards) + /* Finalization is done in two phases, which we build up in reverse order. + During the second phase we call finalize on each shard then update the + state to FINALIZED then CACHE_DONE. */ + std::shared_ptr<gdb::task_group> phase2 + = std::make_shared<gdb::task_group> ([this] () { - auto this_shard = shard.get (); + /* This is run after finalization is done -- but not before. If this + task were submitted earlier, it would have to wait for finalization. + However, that would take a slot in the global thread pool, and if + enough such tasks were submitted at once, it would cause a + livelock. */ + m_state->set (cooked_state::FINALIZED); + m_state->write_to_cache (index_for_writing ()); + m_state->set (cooked_state::CACHE_DONE); + }); + + /* Arrange to call finalize on each shard. */ + for (cooked_index_shard_up &shard : m_shards) + { + cooked_index_shard *this_shard = shard.get (); const parent_map_map *parent_maps = m_state->get_parent_map_map (); - finalizers.add_task ([this, this_shard, parent_maps] () - { - scoped_time_it time_it ("DWARF finalize worker", - m_state->m_per_command_time); - this_shard->finalize (parent_maps); - }); + phase2->add_task ([this, this_shard, parent_maps] () + { + complaint_interceptor complaint_handler; + + scoped_time_it time_it ("DWARF finalize worker", + m_state->m_per_command_time); + + this_shard->finalize (parent_maps); + + this_shard->merge_finalize_complaints (complaint_handler.release ()); + }); } - finalizers.start (); + /* In the first phase we resolve any deferred cooked_index_entry names. + These names are needed in the second phase, but due to cross shard child + to parent references, trying to resolve deferred names in the same phase + as the names are used would lead to data races. */ + gdb::task_group phase1 ([phase2] () + { + /* This is run once after all the other phase1 tasks are done. */ + phase2->start (); + }); + + /* Arrange to call resolve_deferred_names on each shard. */ + for (cooked_index_shard_up &shard : m_shards) + { + cooked_index_shard *this_shard = shard.get (); + const signature_to_name_map *sig_name_map + = &m_state->get_sig_name_map (); + phase1.add_task ([this, this_shard, sig_name_map] () + { + complaint_interceptor complaint_handler; + + scoped_time_it time_it ("DWARF resolve deferred names worker", + m_state->m_per_command_time); + + this_shard->resolve_deferred_names (*sig_name_map); + + this_shard->merge_finalize_complaints (complaint_handler.release ()); + }); + } + + phase1.start (); } cooked_index::~cooked_index () diff --git a/gdb/dwarf2/cooked-index.h b/gdb/dwarf2/cooked-index.h index 2e177cb62dd..3a29f8434e1 100644 --- a/gdb/dwarf2/cooked-index.h +++ b/gdb/dwarf2/cooked-index.h @@ -179,6 +179,12 @@ class cooked_index : public dwarf_scanner_base that the state is CACHE_DONE -- it's important to note that only the main thread may change the value of this pointer. */ cooked_index_worker_up m_state; + + /* Any complaints raised during finalization are held within the + shards in M_SHARDS. Once the main thread has waited for + finalization to be complete then the cached complaints are + emitted, and this flag is set to true. */ + bool m_finalize_complaints_emitted = false; }; /* An implementation of quick_symbol_functions for the cooked DWARF diff --git a/gdb/dwarf2/cooked-indexer.c b/gdb/dwarf2/cooked-indexer.c index 581c0eb0763..59077b87920 100644 --- a/gdb/dwarf2/cooked-indexer.c +++ b/gdb/dwarf2/cooked-indexer.c @@ -157,6 +157,7 @@ cooked_indexer::scan_attributes (dwarf2_per_cu *scanning_per_cu, parent_map::addr_type *maybe_defer, bool *is_enum_class, bool *is_inlined, + std::optional<ULONGEST> *signature, bool for_specification) { bool is_declaration = false; @@ -234,6 +235,16 @@ cooked_indexer::scan_attributes (dwarf2_per_cu *scanning_per_cu, attr.get_ref_die_offset () }; break; + case DW_AT_signature: + /* The DW_AT_signature could also be a direct reference to a + type DIE. We don't currently try to capture those + signatures as right now we're only capturing this in + order to handle the fact that some TUs are parsed after + the parallel CU parsing. */ + if (attr.form == DW_FORM_ref_sig8) + signature->emplace (attr.as_signature ()); + break; + case DW_AT_external: if (attr.as_boolean ()) *flags &= ~IS_STATIC; @@ -380,7 +391,7 @@ cooked_indexer::scan_attributes (dwarf2_per_cu *scanning_per_cu, scan_attributes (scanning_per_cu, new_reader, new_info_ptr, new_info_ptr, new_abbrev, name, linkage_name, flags, nullptr, parent_entry, maybe_defer, - is_enum_class, is_inlined, true); + is_enum_class, is_inlined, signature, true); } if (!for_specification) @@ -547,6 +558,7 @@ cooked_indexer::index_dies (cutu_reader *reader, const cooked_index_entry *this_parent_entry = parent_entry; bool is_enum_class = false; bool is_inlined = false; + std::optional<ULONGEST> signature; /* The scope of a DW_TAG_entry_point cooked_index_entry is the one of its surrounding subroutine. */ @@ -556,7 +568,7 @@ cooked_indexer::index_dies (cutu_reader *reader, = scan_attributes (reader->cu ()->per_cu, reader, info_ptr, info_ptr, abbrev, &name, &linkage_name, &flags, &sibling, &this_parent_entry, &defer, &is_enum_class, - &is_inlined, false); + &is_inlined, &signature, false); /* A DW_TAG_entry_point inherits its static/extern property from the enclosing subroutine. */ if (abbrev->tag == DW_TAG_entry_point) @@ -611,7 +623,11 @@ cooked_indexer::index_dies (cutu_reader *reader, cooked_index_entry *this_entry = nullptr; /* Always use the reader's CU for the entry CU. */ dwarf2_per_cu *cu_for_entry = reader->cu ()->per_cu; - if (name != nullptr) + + /* If scan_attributes failed to find a name then we might still + want to create an entry if we think we might later find a + name via the signature. */ + if (name != nullptr || signature.has_value ()) { if (defer != 0) this_entry @@ -624,6 +640,11 @@ cooked_indexer::index_dies (cutu_reader *reader, = m_index_storage->add (this_die, abbrev->tag, flags, m_language, name, this_parent_entry, cu_for_entry); + + /* Record that this entry doesn't have a valid name and + needs patching. */ + if (name == nullptr) + m_index_storage->add_deferred_name (this_entry, signature.value ()); } else if (this_parent_entry != nullptr) { @@ -637,6 +658,14 @@ cooked_indexer::index_dies (cutu_reader *reader, m_die_range_map->add_entry (addr, addr, this_parent_entry); } + /* If THIS_DIE is the primary type within a TU, and has a valid + name, then add an entry mapping the signature to the name. */ + if (const signatured_type *st = cu_for_entry->as_signatured_type (); + name != nullptr + && st != nullptr + && this_die == st->type_offset_in_section) + m_index_storage->add_signatured_type_name (st->signature, name); + if (linkage_name != nullptr) { /* We only want this to be "main" if it has a linkage name diff --git a/gdb/dwarf2/cooked-indexer.h b/gdb/dwarf2/cooked-indexer.h index 563742c90f6..393ef9fca5a 100644 --- a/gdb/dwarf2/cooked-indexer.h +++ b/gdb/dwarf2/cooked-indexer.h @@ -86,6 +86,7 @@ class cooked_indexer parent_map::addr_type *maybe_defer, bool *is_enum_class, bool *is_inlined, + std::optional<ULONGEST> *signature, bool for_specification); /* Handle DW_TAG_imported_unit, by scanning the DIE to find diff --git a/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp new file mode 100644 index 00000000000..39a221a1b75 --- /dev/null +++ b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp @@ -0,0 +1,155 @@ +# 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/>. + +# The test sets up a type DIE without a DW_AT_name, the type DIE links +# to a type within a TU via DW_AT_signature. Normally GDB would +# use the DW_AT_signature to find the name of the type DIE. +# +# However, in this test there is no type DIE with the specified +# signature! +# +# This test checks that GDB can emit a complaint when this situation is +# encountered. The test also checks that trying to find a method +# within the type using the qualified name will fail. + +load_lib dwarf.exp + +# This test can only be run on targets which support DWARF-2 and use gas. +require dwarf2_support + +standard_testfile main-foo.c .S + +# Build the test program using VERSION for the CU/TU DWARF version. +proc run_test { version } { + # Create the DWARF. + set asm_file [standard_output_file $::srcfile2] + Dwarf::assemble { + filename $asm_file + add_dummy_cus 0 + } { + upvar version version + + get_func_info foo + get_func_info main + + declare_labels method_decl + + tu { version $version } 0xdeadbeefdeadbeef the_type { + DW_TAG_type_unit { + DW_AT_language @DW_LANG_C_plus_plus + } { + the_type: DW_TAG_class_type { + DW_AT_name the_type + DW_AT_byte_size 1 sdata + } + } + } + + cu { version $version } { + compile_unit { + DW_AT_language @DW_LANG_C_plus_plus + } { + DW_TAG_class_type { + DW_AT_declaration 1 flag + DW_AT_signature 0xdeadbeef01234567 ref_sig8 + } { + method_decl: DW_TAG_subprogram { + DW_AT_name method + DW_AT_linkage_name _ZN8the_type6methodEv + DW_AT_declaration 1 flag + } + } + + DW_TAG_subprogram { + DW_AT_specification %$method_decl + DW_AT_low_pc $foo_start DW_FORM_addr + DW_AT_high_pc $foo_end DW_FORM_addr + } + + DW_TAG_subprogram { + DW_AT_name main + DW_AT_low_pc $main_start DW_FORM_addr + DW_AT_high_pc $main_end DW_FORM_addr + } + } + } + } + + set testfile_name ${::testfile}-${version} + set binfile_name [standard_output_file $testfile_name] + if { [build_executable "failed to build" $testfile_name \ + [list $asm_file $::srcfile] {nodebug}] } { + return + } + + clean_restart $testfile_name + + set expect_complaint true + # If we have a .gdb_index already then the part of this test that + # checks for a warning when loading the executable is not going to + # work as the warning will have already been generated when the + # index was added. + if {[get_index_type $testfile_name] == "gdb"} { + set expect_complaint false + } + + clean_restart + + set host_binfile [gdb_remote_download host $binfile_name] + if { [is_remote host] } { + # For some remote host boards gdb_remote_download returns an + # absolute path, but for others it returns a relative path. + # The path reported in the complaint message is always + # absolute. Handle this with an optional prefix pattern. + set binfile_re "\[^\r\n\]*[string_to_regexp $host_binfile]" + } elseif {[section_get $binfile_name ".gnu_debuglink"] ne ""} { + set binfile_re "\[^\r\n\]+[string_to_regexp ${testfile_name}.debug]" + } else { + set binfile_re [string_to_regexp $binfile_name] + } + + # Load the executable and check we see the expected complaint from + # the DWARF indexer. + gdb_test_no_output "maint set dwarf synchronous on" + gdb_test_no_output "set complaints 100" + set saw_complaint false + gdb_test_multiple "file $host_binfile" "file command" -lbl { + -re "^\r\nDuring symbol reading: DWARF Error: Cannot find signatured DIE 0xdeadbeef01234567 referenced from DIE at $::hex \\\[in module $binfile_re\\\](?=\r\n)" { + set saw_complaint true + exp_continue + } + -re "^\r\n$::gdb_prompt $" { + gdb_assert { $saw_complaint == $expect_complaint } $gdb_test_name + } + } + + if { ![readnow] } { + # Check that GDB is unable to find this via the index. If we + # load the full symbols then GDB is able to resolve this print + # via the linkage name. + # + # It is unfortunate that there is a difference in behaviour + # depending on whether GDB is using the index or not, but + # remember, the DWARF is corrupted at this point, so it's + # probably OK if things don't fully work, so long as GDB + # doesn't crash. + gdb_test "print the_type::method" \ + "^There is no field named method" + } +} + +foreach_with_prefix version { 4 5 } { + run_test $version +} diff --git a/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-dwo.exp b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-dwo.exp new file mode 100644 index 00000000000..bc6420d7aa9 --- /dev/null +++ b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-dwo.exp @@ -0,0 +1,143 @@ +# 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/>. + +# Like sig-type-unnamed-class.exp, but with split DWARF. The type +# unit in the DWO file is skeletonless, which is the norm when using +# split DWARF. +# +# Check that the cooked index correctly resolves the parent of a member +# function when the class declaration stub in the DWO compile unit has +# DW_AT_signature but no DW_AT_name, and the referenced type unit is +# skeletonless. + +load_lib dwarf.exp + +# This test can only be run on targets which support DWARF-2 and use gas. +require dwarf2_support + +standard_testfile main-foo.c -dw.S + +# Build the test program using VERSION for the CU/TU DWARF version. + +proc run_test { version } { + set asm_file [standard_output_file $::srcfile2] + + # Setup some state based on DWARF version. + if { $version == 5 } { + set dwo_cu_opts [list fission 1 version 5 dwo_id 0xF00D] + set skel_cu_opts [list version 5 dwo_id 0xF00D] + set dwo_name_attr DW_AT_dwo_name + } else { + set dwo_cu_opts [list fission 1 version 4] + set skel_cu_opts [list version 4] + set dwo_name_attr DW_AT_GNU_dwo_name + } + + set testfile_name ${::testfile}-${version} + + Dwarf::assemble $asm_file { + upvar version version + upvar dwo_cu_opts dwo_cu_opts + upvar skel_cu_opts skel_cu_opts + upvar dwo_name_attr dwo_name_attr + upvar testfile_name testfile_name + + declare_labels method_decl + + get_func_info foo + + # Type unit in the DWO file. This is skeletonless: there is no + # corresponding skeleton TU in the main file. + tu { + fission 1 + version $version + } 0xdeadbeef01234567 the_type { + type_unit { + DW_AT_language @DW_LANG_C_plus_plus + } { + the_type: DW_TAG_class_type { + DW_AT_name the_type + DW_AT_byte_size 1 DW_FORM_sdata + } + } + } + + set debug_addr_base [debug_addr_label] + + # Compile unit in the DWO file. Contains a class declaration + # stub with DW_AT_signature but no DW_AT_name, replicating what + # Clang emits with -fdebug-types-section. The class name must + # be resolved by following the signature to the type unit. + cu $dwo_cu_opts { + compile_unit { + DW_AT_language @DW_LANG_C_plus_plus + DW_AT_name ${::srcfile} + DW_AT_comp_dir . + if { $version == 4 } { + DW_AT_GNU_dwo_id 0xF00D DW_FORM_data8 + } + } { + DW_TAG_class_type { + DW_AT_declaration 1 DW_FORM_flag + DW_AT_signature 0xdeadbeef01234567 DW_FORM_ref_sig8 + } { + method_decl: DW_TAG_subprogram { + DW_AT_name method + DW_AT_linkage_name _ZN8the_type6methodEv + DW_AT_declaration 1 DW_FORM_flag + } + } + + DW_TAG_subprogram { + DW_AT_specification :$method_decl + DW_AT_low_pc $foo_start DW_FORM_GNU_addr_index + DW_AT_high_pc $foo_end DW_FORM_GNU_addr_index + } + } + } + + # Skeleton CU in the main file. + cu $skel_cu_opts { + compile_unit { + $dwo_name_attr ${testfile_name}-dw.dwo DW_FORM_strp + DW_AT_comp_dir . + DW_AT_GNU_addr_base $debug_addr_base + if { $version == 4 } { + DW_AT_GNU_dwo_id 0xF00D DW_FORM_data8 + } + } {} + } + } + + set binfile_name [standard_output_file $testfile_name] + set obj [standard_output_file "${testfile_name}-dw.o"] + if {[build_executable_and_dwo_files "build exec and dwo" $binfile_name \ + {nodebug} \ + [list $asm_file {nodebug split-dwo} $obj] \ + [list $::srcfile {nodebug}]]} { + return + } + + clean_restart $testfile_name + + # Check that GDB was able to find the parent for "method", and as + # a result, can correctly find this field of "the_type". + gdb_test "print the_type::method" \ + [string_to_regexp " <the_type::method()>"] +} + +foreach_with_prefix version { 4 5 } { + run_test $version +} diff --git a/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp new file mode 100644 index 00000000000..6f39e2c823d --- /dev/null +++ b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp @@ -0,0 +1,106 @@ +# 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/>. + +# Check that the cooked index correctly resolves the parent of a member +# function when the class declaration stub in the compile unit has +# DW_AT_signature but no DW_AT_name. +# +# This replicates what Clang emits with -fdebug-types-section. The +# compile unit contains an unnamed DW_TAG_class_type declaration with +# only DW_AT_declaration, DW_AT_signature, and child member function +# declarations. +# +# The class name must be resolved by following the signature to the +# type unit. Without this, the member function definitions (which use +# DW_AT_specification to point at the child declarations) end up with +# no parent, and qualified lookup fails. + +load_lib dwarf.exp + +# This test can only be run on targets which support DWARF-2 and use gas. +require dwarf2_support + +standard_testfile main-foo.c .S + +# Build the test program using VERSION for the CU/TU DWARF version. +proc run_test { version } { + # Create the DWARF. + set asm_file [standard_output_file $::srcfile2] + Dwarf::assemble { + filename $asm_file + add_dummy_cus 0 + } { + upvar version version + + get_func_info foo + get_func_info main + + declare_labels method_decl + + tu { version $version } 0xdeadbeef01234567 the_type { + DW_TAG_type_unit { + DW_AT_language @DW_LANG_C_plus_plus + } { + the_type: DW_TAG_class_type { + DW_AT_name the_type + DW_AT_byte_size 1 sdata + } + } + } + + cu { version $version } { + compile_unit { + DW_AT_language @DW_LANG_C_plus_plus + } { + DW_TAG_class_type { + DW_AT_declaration 1 flag + DW_AT_signature 0xdeadbeef01234567 ref_sig8 + } { + method_decl: DW_TAG_subprogram { + DW_AT_name method + DW_AT_linkage_name _ZN8the_type6methodEv + DW_AT_declaration 1 flag + } + } + + DW_TAG_subprogram { + DW_AT_specification %$method_decl + DW_AT_low_pc $foo_start DW_FORM_addr + DW_AT_high_pc $foo_end DW_FORM_addr + } + + DW_TAG_subprogram { + DW_AT_name main + DW_AT_low_pc $main_start DW_FORM_addr + DW_AT_high_pc $main_end DW_FORM_addr + } + } + } + } + + if { [prepare_for_testing "failed to prepare" ${::testfile}-${version} \ + [list $asm_file $::srcfile] {nodebug}] } { + return + } + + # Check that GDB was able to find the parent for "method", and as + # a result, can correctly find this field of "the_type". + gdb_test "print the_type::method" \ + [string_to_regexp " <the_type::method()>"] +} + +foreach_with_prefix version { 4 5 } { + run_test $version +} base-commit: 6e3ecea0e3ca191e81e82ee0194c49eea1ffb101 -- 2.25.4 ^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCHv4] gdb: resolve class name via DW_AT_signature in cooked index 2026-09-16 11:38 ` [PATCHv4] " Andrew Burgess @ 2026-09-22 4:26 ` Simon Marchi 2026-09-23 13:47 ` Simon Marchi 2026-09-24 4:59 ` [PATCH 0/2] " Simon Marchi ` (2 subsequent siblings) 3 siblings, 1 reply; 36+ messages in thread From: Simon Marchi @ 2026-09-22 4:26 UTC (permalink / raw) To: Andrew Burgess, gdb-patches; +Cc: Tom Tromey On 2026-09-16 07:38, Andrew Burgess wrote: > gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp: > This one tests some invalid DWARF, the type DIE references a > signature that doesn't exist. In this case GDB just leaves the > cooked_index_entry with a NULL name string, which means symbols > cannot be found. I think this is fine though, the DWARF is > corrupted in this case. Not super important, but is the "GDB just leaves the cooked_index_entry with a NULL name string" stale? My understanding is that the entries with a NULL name are removed from the vector, and the parent links pointing to them are removed, so there is no way to reach them anymore. > @@ -189,6 +192,52 @@ struct cooked_index_entry_name_ptr_eq > > /* See cooked-index-shard.h. */ > > +void > +cooked_index_shard::resolve_deferred_names > + (const signature_to_name_map &sig_names) > +{ > + bool need_to_cleanup_entries = false; > + for (const auto &[entry, signature] : m_deferred_names) > + { > + if (const auto it = sig_names.find (signature); > + it != sig_names.end ()) > + { > + /* Each entry should only occur once in M_DEFERRED_NAMES, > + and the entry should only be added when it has no name. */ > + gdb_assert (entry->name == nullptr); You can move this assert above the if (run it before looking up the signature). > + > + /* Patch the name. */ > + entry->name = it->second; > + } > + else > + { > + need_to_cleanup_entries = true; > + complaint (_(DWARF_ERROR_PREFIX > + "Cannot find signatured DIE %s referenced from DIE " > + "at %s [in module %s]"), > + hex_string (signature), > + sect_offset_str (entry->die_offset), > + entry->per_cu->per_bfd ()->filename ()); > + } > + } > + > + /* If we failed to resolve the name of an entry via its signature > + then remove the entry from the m_entries vector. This should be > + rare, and should only happen when we have corrupted DWARF. The > + entries still live on the obstack, so parent points are still points -> pointers? > @@ -223,6 +277,16 @@ cooked_index_shard::finalize (const parent_map_map *parent_maps) > entry->resolve_parent (new_parent); > } > > + /* Remove a parent reference if the parent has no name. This > + leaves ENTRY as an orphan, but this only happens if the DWARF > + is corrupted and we failed to find a name for the parent. We > + can safely check the parent's name at this point because all > + deferred names will have been resolved in all shards before > + finalize is called on any shard. */ > + if (const cooked_index_entry *parent = entry->get_parent (); > + parent != nullptr && parent->name == nullptr) > + entry->set_parent (nullptr); Unlikely, but Claude pointed out that we break the links to nameless parents here, but in the same loop below there is the possibility that we call full_name on the current entry: if (entry->get_parent () != nullptr) { const char *fullname = entry->full_name (&m_storage, FOR_ADA_LINKAGE_NAME); So imagine that in the vector we have in this order: - Entry - Parent entry - Grandparent entry, which is nameless The parent of "Entry" has a name, so we did not remove that link. We did not process "Parent" yet, so it still points to its parent (Grandparent). If we call full_name on "Entry", it will go up the parent chain and will hit the assert for the grandparent. It might be safer to split it into a separate step: 1. resolve deferred names 2. prune entries without name / links to parents without name 3. finalize If you want to optimize for the common case, step 1 can record if there were any problematic entry. If not, you can skip directly to step 3. That can be done by using a task_group for step 2, but only posting the tasks if necessary. If the task_group is started with no tasks, it will immediately invoke the "done" callback, which in this case would kick off step 3. I have a similar suggestion below for step 1, to avoid posting tasks unnecessarily if we know there are no deferred names to compute in a given shard. Claude also pointed out a possible race condition between cooked_index::get_main_name(), which can be called as soon as we reached the MAIN_AVAILABLE state, and the "resolve deferred names" step. get_main_name consults cooked_index_entry::name, while "resolve deferred names" sets some cooked_index_entry::name from nullptr to the actual name. It might not be an actual problem, because the cooked_index_entry for "main" is not going to be a type, so it's not part of those with a deferred name. > + > /* Note that this code must be kept in sync with > cooked_index::get_main -- if canonicalization is required > here, then a check might be required there. */ > diff --git a/gdb/dwarf2/cooked-index-shard.h b/gdb/dwarf2/cooked-index-shard.h > index 84c37958c83..191182a132b 100644 > --- a/gdb/dwarf2/cooked-index-shard.h > +++ b/gdb/dwarf2/cooked-index-shard.h > @@ -26,6 +26,7 @@ > #include "addrmap.h" > #include "gdbsupport/iterator-range.h" > #include "gdbsupport/string-set.h" > +#include "complaints.h" > > /* An index of interesting DIEs. This is "cooked", in contrast to a > mapped .debug_names or .gdb_index, which are "raw". An entry in > @@ -79,6 +80,16 @@ class cooked_index_shard > for completion, will be returned. */ > range find (const std::string &name, bool completing) const; > > + /* Record that ENTRY didn't have a name attribute, but did have a > + DW_AT_signature attribute, SIGNATURE. ENTRY will have a NULL > + name string pointer. We will patch the name of ENTRY during > + finalization once the TUs have been parsed, the correct TU will > + be found using SIGNATURE. */ > + void add_deferred_name (cooked_index_entry *entry, ULONGEST signature) > + { > + m_deferred_names.push_back ({entry, signature}); It makes no meaningful difference, but I'd write: m_deferred_names.emplace_back (entry, signature); > diff --git a/gdb/dwarf2/cooked-index.c b/gdb/dwarf2/cooked-index.c > index 167e39ffc89..799835bca61 100644 > --- a/gdb/dwarf2/cooked-index.c > +++ b/gdb/dwarf2/cooked-index.c > @@ -58,7 +58,23 @@ cooked_index::wait (cooked_state desired_state, bool allow_quit) > if (m_state == nullptr) > return; > > - if (m_state->wait (desired_state, allow_quit)) > + bool done = m_state->wait (desired_state, allow_quit); > + > + /* Emit any cached complaints if we have finalized and we are on the > + main thread. Check for the requested state or the DONE flag > + here, we might have only asked for MAIN_AVAILABLE, but if the > + workers are quick then they might be done, in which case we > + should emit the complaints now. */ > + if (!m_finalize_complaints_emitted > + && is_main_thread () > + && (desired_state >= cooked_state::FINALIZED || done)) > + { > + m_finalize_complaints_emitted = true; > + for (auto &shard : m_shards) Can be: for (const auto &shard : m_shards) > @@ -74,31 +90,70 @@ cooked_index::set_contents () > > m_state->set (cooked_state::MAIN_AVAILABLE); > > - /* This is run after finalization is done -- but not before. If > - this task were submitted earlier, it would have to wait for > - finalization. However, that would take a slot in the global > - thread pool, and if enough such tasks were submitted at once, it > - would cause a livelock. */ > - gdb::task_group finalizers ([this] () > - { > - m_state->set (cooked_state::FINALIZED); > - m_state->write_to_cache (index_for_writing ()); > - m_state->set (cooked_state::CACHE_DONE); > - }); > - > - for (auto &shard : m_shards) > + /* Finalization is done in two phases, which we build up in reverse order. > + During the second phase we call finalize on each shard then update the > + state to FINALIZED then CACHE_DONE. */ > + std::shared_ptr<gdb::task_group> phase2 > + = std::make_shared<gdb::task_group> ([this] () > { > - auto this_shard = shard.get (); > + /* This is run after finalization is done -- but not before. If this > + task were submitted earlier, it would have to wait for finalization. > + However, that would take a slot in the global thread pool, and if > + enough such tasks were submitted at once, it would cause a > + livelock. */ > + m_state->set (cooked_state::FINALIZED); > + m_state->write_to_cache (index_for_writing ()); > + m_state->set (cooked_state::CACHE_DONE); > + }); > + > + /* Arrange to call finalize on each shard. */ > + for (cooked_index_shard_up &shard : m_shards) > + { > + cooked_index_shard *this_shard = shard.get (); > const parent_map_map *parent_maps = m_state->get_parent_map_map (); > - finalizers.add_task ([this, this_shard, parent_maps] () > - { > - scoped_time_it time_it ("DWARF finalize worker", > - m_state->m_per_command_time); > - this_shard->finalize (parent_maps); > - }); > + phase2->add_task ([this, this_shard, parent_maps] () > + { > + complaint_interceptor complaint_handler; > + > + scoped_time_it time_it ("DWARF finalize worker", > + m_state->m_per_command_time); > + > + this_shard->finalize (parent_maps); > + > + this_shard->merge_finalize_complaints (complaint_handler.release ()); > + }); > } > > - finalizers.start (); > + /* In the first phase we resolve any deferred cooked_index_entry names. > + These names are needed in the second phase, but due to cross shard child > + to parent references, trying to resolve deferred names in the same phase > + as the names are used would lead to data races. */ > + gdb::task_group phase1 ([phase2] () > + { > + /* This is run once after all the other phase1 tasks are done. */ > + phase2->start (); > + }); > + > + /* Arrange to call resolve_deferred_names on each shard. */ > + for (cooked_index_shard_up &shard : m_shards) > + { > + cooked_index_shard *this_shard = shard.get (); > + const signature_to_name_map *sig_name_map > + = &m_state->get_sig_name_map (); > + phase1.add_task ([this, this_shard, sig_name_map] () > + { > + complaint_interceptor complaint_handler; > + > + scoped_time_it time_it ("DWARF resolve deferred names worker", > + m_state->m_per_command_time); > + > + this_shard->resolve_deferred_names (*sig_name_map); > + > + this_shard->merge_finalize_complaints (complaint_handler.release ()); > + }); > + } > + > + phase1.start (); I would maybe have a suggestion to make this easier to follow. Instead of cramming it all in a single function, give each phase its own start_* helper method, which kicks off that parallel task. In the "done" callback of each phase, call the start_* method for the next task. You could have as many such chained tasks as you want, and it would remain clear (IMO). Also, if my understanding of task_group is correct, you could avoid posting a deferred name task when you know in advance that there is no deferred name to compute in that shard. In the common case where there are no deferred name to compute at all, the task_group will have no tasks, and it will end as soon as you call start on it. So we'll pay almost nothing for that step if it's not necessary. So, I imagine something like this: void cooked_index::set_contents () { gdb_assert (m_shards.empty ()); m_shards = m_state->release_shards (); m_state->set (cooked_state::MAIN_AVAILABLE); this->start_resolve_deferred_names (); } void cooked_index::start_resolve_deferred_names () { gdb::task_group task_group ([this] () { /* This is run once after all the other phase1 tasks are done. */ this->start_finalize (); }); /* Arrange to call resolve_deferred_names on each shard. */ for (const cooked_index_shard_up &shard : m_shards) { if (shard->m_deferred_names.empty ()) continue; task_group.add_task ([this, this_shard = shard.get (), sig_name_map = &m_state->get_sig_name_map ()] () { complaint_interceptor complaint_handler; scoped_time_it time_it ("DWARF resolve deferred names worker", m_state->m_per_command_time); this_shard->resolve_deferred_names (*sig_name_map); this_shard->merge_finalize_complaints (complaint_handler.release ()); }); } task_group.start (); } void cooked_index::start_finalize () { gdb::task_group task_group ([this] () { /* This is run after finalization is done -- but not before. If this task were submitted earlier, it would have to wait for finalization. However, that would take a slot in the global thread pool, and if enough such tasks were submitted at once, it would cause a livelock. */ m_state->set (cooked_state::FINALIZED); m_state->write_to_cache (index_for_writing ()); m_state->set (cooked_state::CACHE_DONE); }); /* Arrange to call finalize on each shard. */ for (const cooked_index_shard_up &shard : m_shards) { task_group.add_task ([this, this_shard = shard.get (), parent_maps = m_state->get_parent_map_map ()] () { complaint_interceptor complaint_handler; scoped_time_it time_it ("DWARF finalize worker", m_state->m_per_command_time); this_shard->finalize (parent_maps); this_shard->merge_finalize_complaints (complaint_handler.release ()); }); } task_group.start (); } We could even consider moving out the index_for_writing / CACHE_DONE to a separate helper method (which just runs serially), because it's conceptually a separate step than the "finalize" step. Simon ^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCHv4] gdb: resolve class name via DW_AT_signature in cooked index 2026-09-22 4:26 ` Simon Marchi @ 2026-09-23 13:47 ` Simon Marchi 0 siblings, 0 replies; 36+ messages in thread From: Simon Marchi @ 2026-09-23 13:47 UTC (permalink / raw) To: Andrew Burgess, gdb-patches; +Cc: Tom Tromey On 9/22/26 12:26 AM, Simon Marchi wrote: > > > On 2026-09-16 07:38, Andrew Burgess wrote: >> gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp: >> This one tests some invalid DWARF, the type DIE references a >> signature that doesn't exist. In this case GDB just leaves the >> cooked_index_entry with a NULL name string, which means symbols >> cannot be found. I think this is fine though, the DWARF is >> corrupted in this case. > > Not super important, but is the "GDB just leaves the cooked_index_entry > with a NULL name string" stale? My understanding is that the entries > with a NULL name are removed from the vector, and the parent links > pointing to them are removed, so there is no way to reach them anymore. > >> @@ -189,6 +192,52 @@ struct cooked_index_entry_name_ptr_eq >> >> /* See cooked-index-shard.h. */ >> >> +void >> +cooked_index_shard::resolve_deferred_names >> + (const signature_to_name_map &sig_names) >> +{ >> + bool need_to_cleanup_entries = false; >> + for (const auto &[entry, signature] : m_deferred_names) >> + { >> + if (const auto it = sig_names.find (signature); >> + it != sig_names.end ()) >> + { >> + /* Each entry should only occur once in M_DEFERRED_NAMES, >> + and the entry should only be added when it has no name. */ >> + gdb_assert (entry->name == nullptr); > > You can move this assert above the if (run it before looking up the > signature). > >> + >> + /* Patch the name. */ >> + entry->name = it->second; >> + } >> + else >> + { >> + need_to_cleanup_entries = true; >> + complaint (_(DWARF_ERROR_PREFIX >> + "Cannot find signatured DIE %s referenced from DIE " >> + "at %s [in module %s]"), >> + hex_string (signature), >> + sect_offset_str (entry->die_offset), >> + entry->per_cu->per_bfd ()->filename ()); >> + } >> + } >> + >> + /* If we failed to resolve the name of an entry via its signature >> + then remove the entry from the m_entries vector. This should be >> + rare, and should only happen when we have corrupted DWARF. The >> + entries still live on the obstack, so parent points are still > > points -> pointers? > >> @@ -223,6 +277,16 @@ cooked_index_shard::finalize (const parent_map_map *parent_maps) >> entry->resolve_parent (new_parent); >> } >> >> + /* Remove a parent reference if the parent has no name. This >> + leaves ENTRY as an orphan, but this only happens if the DWARF >> + is corrupted and we failed to find a name for the parent. We >> + can safely check the parent's name at this point because all >> + deferred names will have been resolved in all shards before >> + finalize is called on any shard. */ >> + if (const cooked_index_entry *parent = entry->get_parent (); >> + parent != nullptr && parent->name == nullptr) >> + entry->set_parent (nullptr); > > Unlikely, but Claude pointed out that we break the links to nameless > parents here, but in the same loop below there is the possibility that > we call full_name on the current entry: > > if (entry->get_parent () != nullptr) > { > const char *fullname > = entry->full_name (&m_storage, FOR_ADA_LINKAGE_NAME); > > So imagine that in the vector we have in this order: > > - Entry > - Parent entry > - Grandparent entry, which is nameless > > The parent of "Entry" has a name, so we did not remove that link. We > did not process "Parent" yet, so it still points to its parent > (Grandparent). If we call full_name on "Entry", it will go up the > parent chain and will hit the assert for the grandparent. > > It might be safer to split it into a separate step: > > 1. resolve deferred names > 2. prune entries without name / links to parents without name > 3. finalize > > If you want to optimize for the common case, step 1 can record if there > were any problematic entry. If not, you can skip directly to step 3. > That can be done by using a task_group for step 2, but only posting the > tasks if necessary. If the task_group is started with no tasks, it will > immediately invoke the "done" callback, which in this case would kick > off step 3. I have a similar suggestion below for step 1, to avoid > posting tasks unnecessarily if we know there are no deferred names to > compute in a given shard. > > Claude also pointed out a possible race condition between > cooked_index::get_main_name(), which can be called as soon as we reached > the MAIN_AVAILABLE state, and the "resolve deferred names" step. > get_main_name consults cooked_index_entry::name, while "resolve deferred > names" sets some cooked_index_entry::name from nullptr to the actual > name. It might not be an actual problem, because the cooked_index_entry > for "main" is not going to be a type, so it's not part of those with a > deferred name. > >> + >> /* Note that this code must be kept in sync with >> cooked_index::get_main -- if canonicalization is required >> here, then a check might be required there. */ >> diff --git a/gdb/dwarf2/cooked-index-shard.h b/gdb/dwarf2/cooked-index-shard.h >> index 84c37958c83..191182a132b 100644 >> --- a/gdb/dwarf2/cooked-index-shard.h >> +++ b/gdb/dwarf2/cooked-index-shard.h >> @@ -26,6 +26,7 @@ >> #include "addrmap.h" >> #include "gdbsupport/iterator-range.h" >> #include "gdbsupport/string-set.h" >> +#include "complaints.h" >> >> /* An index of interesting DIEs. This is "cooked", in contrast to a >> mapped .debug_names or .gdb_index, which are "raw". An entry in >> @@ -79,6 +80,16 @@ class cooked_index_shard >> for completion, will be returned. */ >> range find (const std::string &name, bool completing) const; >> >> + /* Record that ENTRY didn't have a name attribute, but did have a >> + DW_AT_signature attribute, SIGNATURE. ENTRY will have a NULL >> + name string pointer. We will patch the name of ENTRY during >> + finalization once the TUs have been parsed, the correct TU will >> + be found using SIGNATURE. */ >> + void add_deferred_name (cooked_index_entry *entry, ULONGEST signature) >> + { >> + m_deferred_names.push_back ({entry, signature}); > > It makes no meaningful difference, but I'd write: > > m_deferred_names.emplace_back (entry, signature); > >> diff --git a/gdb/dwarf2/cooked-index.c b/gdb/dwarf2/cooked-index.c >> index 167e39ffc89..799835bca61 100644 >> --- a/gdb/dwarf2/cooked-index.c >> +++ b/gdb/dwarf2/cooked-index.c >> @@ -58,7 +58,23 @@ cooked_index::wait (cooked_state desired_state, bool allow_quit) >> if (m_state == nullptr) >> return; >> >> - if (m_state->wait (desired_state, allow_quit)) >> + bool done = m_state->wait (desired_state, allow_quit); >> + >> + /* Emit any cached complaints if we have finalized and we are on the >> + main thread. Check for the requested state or the DONE flag >> + here, we might have only asked for MAIN_AVAILABLE, but if the >> + workers are quick then they might be done, in which case we >> + should emit the complaints now. */ >> + if (!m_finalize_complaints_emitted >> + && is_main_thread () >> + && (desired_state >= cooked_state::FINALIZED || done)) >> + { >> + m_finalize_complaints_emitted = true; >> + for (auto &shard : m_shards) > > Can be: > > for (const auto &shard : m_shards) > > >> @@ -74,31 +90,70 @@ cooked_index::set_contents () >> >> m_state->set (cooked_state::MAIN_AVAILABLE); >> >> - /* This is run after finalization is done -- but not before. If >> - this task were submitted earlier, it would have to wait for >> - finalization. However, that would take a slot in the global >> - thread pool, and if enough such tasks were submitted at once, it >> - would cause a livelock. */ >> - gdb::task_group finalizers ([this] () >> - { >> - m_state->set (cooked_state::FINALIZED); >> - m_state->write_to_cache (index_for_writing ()); >> - m_state->set (cooked_state::CACHE_DONE); >> - }); >> - >> - for (auto &shard : m_shards) >> + /* Finalization is done in two phases, which we build up in reverse order. >> + During the second phase we call finalize on each shard then update the >> + state to FINALIZED then CACHE_DONE. */ >> + std::shared_ptr<gdb::task_group> phase2 >> + = std::make_shared<gdb::task_group> ([this] () >> { >> - auto this_shard = shard.get (); >> + /* This is run after finalization is done -- but not before. If this >> + task were submitted earlier, it would have to wait for finalization. >> + However, that would take a slot in the global thread pool, and if >> + enough such tasks were submitted at once, it would cause a >> + livelock. */ >> + m_state->set (cooked_state::FINALIZED); >> + m_state->write_to_cache (index_for_writing ()); >> + m_state->set (cooked_state::CACHE_DONE); >> + }); >> + >> + /* Arrange to call finalize on each shard. */ >> + for (cooked_index_shard_up &shard : m_shards) >> + { >> + cooked_index_shard *this_shard = shard.get (); >> const parent_map_map *parent_maps = m_state->get_parent_map_map (); >> - finalizers.add_task ([this, this_shard, parent_maps] () >> - { >> - scoped_time_it time_it ("DWARF finalize worker", >> - m_state->m_per_command_time); >> - this_shard->finalize (parent_maps); >> - }); >> + phase2->add_task ([this, this_shard, parent_maps] () >> + { >> + complaint_interceptor complaint_handler; >> + >> + scoped_time_it time_it ("DWARF finalize worker", >> + m_state->m_per_command_time); >> + >> + this_shard->finalize (parent_maps); >> + >> + this_shard->merge_finalize_complaints (complaint_handler.release ()); >> + }); >> } >> >> - finalizers.start (); >> + /* In the first phase we resolve any deferred cooked_index_entry names. >> + These names are needed in the second phase, but due to cross shard child >> + to parent references, trying to resolve deferred names in the same phase >> + as the names are used would lead to data races. */ >> + gdb::task_group phase1 ([phase2] () >> + { >> + /* This is run once after all the other phase1 tasks are done. */ >> + phase2->start (); >> + }); >> + >> + /* Arrange to call resolve_deferred_names on each shard. */ >> + for (cooked_index_shard_up &shard : m_shards) >> + { >> + cooked_index_shard *this_shard = shard.get (); >> + const signature_to_name_map *sig_name_map >> + = &m_state->get_sig_name_map (); >> + phase1.add_task ([this, this_shard, sig_name_map] () >> + { >> + complaint_interceptor complaint_handler; >> + >> + scoped_time_it time_it ("DWARF resolve deferred names worker", >> + m_state->m_per_command_time); >> + >> + this_shard->resolve_deferred_names (*sig_name_map); >> + >> + this_shard->merge_finalize_complaints (complaint_handler.release ()); >> + }); >> + } >> + >> + phase1.start (); > > I would maybe have a suggestion to make this easier to follow. Instead > of cramming it all in a single function, give each phase its own start_* > helper method, which kicks off that parallel task. In the "done" > callback of each phase, call the start_* method for the next task. You > could have as many such chained tasks as you want, and it would remain > clear (IMO). > > Also, if my understanding of task_group is correct, you could avoid > posting a deferred name task when you know in advance that there is no > deferred name to compute in that shard. In the common case where there > are no deferred name to compute at all, the task_group will have no > tasks, and it will end as soon as you call start on it. So we'll pay > almost nothing for that step if it's not necessary. > > So, I imagine something like this: > > void > cooked_index::set_contents () > { > gdb_assert (m_shards.empty ()); > m_shards = m_state->release_shards (); > > m_state->set (cooked_state::MAIN_AVAILABLE); > > this->start_resolve_deferred_names (); > } > > void > cooked_index::start_resolve_deferred_names () > { > gdb::task_group task_group ([this] () > { > /* This is run once after all the other phase1 tasks are done. */ > this->start_finalize (); > }); > > /* Arrange to call resolve_deferred_names on each shard. */ > for (const cooked_index_shard_up &shard : m_shards) > { > if (shard->m_deferred_names.empty ()) > continue; > > task_group.add_task ([this, this_shard = shard.get (), > sig_name_map = &m_state->get_sig_name_map ()] () > { > complaint_interceptor complaint_handler; > > scoped_time_it time_it ("DWARF resolve deferred names worker", > m_state->m_per_command_time); > > this_shard->resolve_deferred_names (*sig_name_map); > > this_shard->merge_finalize_complaints (complaint_handler.release ()); > }); > } > > task_group.start (); > } > > void > cooked_index::start_finalize () > { > gdb::task_group task_group ([this] () > { > /* This is run after finalization is done -- but not before. If this > task were submitted earlier, it would have to wait for finalization. > However, that would take a slot in the global thread pool, and if > enough such tasks were submitted at once, it would cause a > livelock. */ > m_state->set (cooked_state::FINALIZED); > m_state->write_to_cache (index_for_writing ()); > m_state->set (cooked_state::CACHE_DONE); > }); > > /* Arrange to call finalize on each shard. */ > for (const cooked_index_shard_up &shard : m_shards) > { > task_group.add_task ([this, this_shard = shard.get (), > parent_maps = m_state->get_parent_map_map ()] () > { > complaint_interceptor complaint_handler; > > scoped_time_it time_it ("DWARF finalize worker", > m_state->m_per_command_time); > > this_shard->finalize (parent_maps); > > this_shard->merge_finalize_complaints (complaint_handler.release ()); > }); > } > > task_group.start (); > } > > We could even consider moving out the index_for_writing / CACHE_DONE to > a separate helper method (which just runs serially), because it's > conceptually a separate step than the "finalize" step. > > Simon I don't know if you are working on this right now, but in order to help have this merged before the planned release on Friday, I'll prepare an updated version of your patch where I address my own comments. You'll be able to decide what you keep from it. ^ permalink raw reply [flat|nested] 36+ messages in thread
* [PATCH 0/2] gdb: resolve class name via DW_AT_signature in cooked index 2026-09-16 11:38 ` [PATCHv4] " Andrew Burgess 2026-09-22 4:26 ` Simon Marchi @ 2026-09-24 4:59 ` Simon Marchi 2026-09-24 5:05 ` Simon Marchi 2026-09-24 15:09 ` [PATCH v6 0/3] gdb/dwarf: " Simon Marchi 2026-09-24 4:59 ` [PATCH 1/2] gdb: split cooked index finalization into separate steps Simon Marchi 2026-09-24 4:59 ` [PATCH 2/2] gdb: resolve class name via DW_AT_signature in cooked index Simon Marchi 3 siblings, 2 replies; 36+ messages in thread From: Simon Marchi @ 2026-09-24 4:59 UTC (permalink / raw) To: gdb-patches; +Cc: Simon Marchi This is v5 of Andrew's fix for PR gdb/33447, where qualified member function lookup fails for C++ binaries built by Clang with -fdebug-types-section. v4 was here: https://inbox.sourceware.org/gdb-patches/e78a75720042dba3d3b052883d2d84d374ffc157.1789555833.git.aburgess@redhat.com/ I wrote this version addressing some comments I had on v4, with the hope that it can be ready to merge before the release planned on Friday. The main changes are: - Patch 1 is new preparatory patch that does a bit of refactoring. It splits cooked_index_shard::finalize into separate steps, making it easier for patch 2 to add more steps. - The deferred names mechanism now uses a flag on the cooked_index_entries (like the deferred parents)rather than a vector. This is probably what Tom meant in earlier reviews, but I did not understand at the time. When I finally understood how the deferred parent mechanism worked, it seemed logical to implement the deferred names mechanism the same way, at least for consistency. Scanning over all index entries is relatively fast anyway, and this step only runs if actually needed. Andrew Burgess (1): gdb: resolve class name via DW_AT_signature in cooked index Simon Marchi (1): gdb: split cooked index finalization into separate steps gdb/dwarf2/abbrev.c | 1 + gdb/dwarf2/cooked-index-entry.c | 15 +- gdb/dwarf2/cooked-index-entry.h | 75 +++++++- gdb/dwarf2/cooked-index-shard.c | 132 ++++++++++--- gdb/dwarf2/cooked-index-shard.h | 69 ++++++- gdb/dwarf2/cooked-index-worker.c | 9 + gdb/dwarf2/cooked-index-worker.h | 30 ++- gdb/dwarf2/cooked-index.c | 179 +++++++++++++++--- gdb/dwarf2/cooked-index.h | 64 ++++++- gdb/dwarf2/cooked-indexer.c | 60 ++++-- gdb/dwarf2/cooked-indexer.h | 1 + gdb/dwarf2/index-write.c | 2 +- .../ada-forward-spec-deferred-grandparent.exp | 81 ++++++++ .../sig-type-unnamed-class-bad-sig.exp | 155 +++++++++++++++ .../gdb.dwarf2/sig-type-unnamed-class-dwo.exp | 143 ++++++++++++++ .../gdb.dwarf2/sig-type-unnamed-class.exp | 106 +++++++++++ 16 files changed, 1030 insertions(+), 92 deletions(-) create mode 100644 gdb/testsuite/gdb.dwarf2/ada-forward-spec-deferred-grandparent.exp create mode 100644 gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp create mode 100644 gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-dwo.exp create mode 100644 gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp base-commit: af53f4d1644ef024c8c91a3dcc733958becfeac0 -- 2.55.0 ^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH 0/2] gdb: resolve class name via DW_AT_signature in cooked index 2026-09-24 4:59 ` [PATCH 0/2] " Simon Marchi @ 2026-09-24 5:05 ` Simon Marchi 2026-09-24 15:09 ` [PATCH v6 0/3] gdb/dwarf: " Simon Marchi 1 sibling, 0 replies; 36+ messages in thread From: Simon Marchi @ 2026-09-24 5:05 UTC (permalink / raw) To: gdb-patches On 9/24/26 12:59 AM, Simon Marchi wrote:> This is v5 of Andrew's fix for PR gdb/33447, where qualified member function > lookup fails for C++ binaries built by Clang with -fdebug-types-section. v4 > was here: > > https://inbox.sourceware.org/gdb-patches/e78a75720042dba3d3b052883d2d84d374ffc157.1789555833.git.aburgess@redhat.com/ > > I wrote this version addressing some comments I had on v4, with the hope that > it can be ready to merge before the release planned on Friday. > > The main changes are: > > - Patch 1 is new preparatory patch that does a bit of refactoring. It splits > cooked_index_shard::finalize into separate steps, making it easier for patch > 2 to add more steps. > > - The deferred names mechanism now uses a flag on the cooked_index_entries > (like the deferred parents)rather than a vector. This is probably what Tom > meant in earlier reviews, but I did not understand at the time. When I > finally understood how the deferred parent mechanism worked, it seemed > logical to implement the deferred names mechanism the same way, at least for > consistency. Scanning over all index entries is relatively fast anyway, and > this step only runs if actually needed. > > Andrew Burgess (1): > gdb: resolve class name via DW_AT_signature in cooked index > > Simon Marchi (1): > gdb: split cooked index finalization into separate steps Arf, it's too late here, I forgot the v5 tag :(. Simon ^ permalink raw reply [flat|nested] 36+ messages in thread
* [PATCH v6 0/3] gdb/dwarf: resolve class name via DW_AT_signature in cooked index 2026-09-24 4:59 ` [PATCH 0/2] " Simon Marchi 2026-09-24 5:05 ` Simon Marchi @ 2026-09-24 15:09 ` Simon Marchi 2026-09-24 15:09 ` [PATCH v6 1/3] gdb/dwarf: split cooked index finalization into separate steps Simon Marchi ` (2 more replies) 1 sibling, 3 replies; 36+ messages in thread From: Simon Marchi @ 2026-09-24 15:09 UTC (permalink / raw) To: gdb-patches; +Cc: Simon Marchi This is the series as I intend to push it later today. I tacked an additional patch on top to add a parent_is_deferred helper method, mirroring name_is_deferred added by patch 2. It is borderline obvious, but I would still appreciate a quick check to make sure I did not mess something up. Andrew Burgess (1): gdb/dwarf: resolve class name via DW_AT_signature in cooked index Simon Marchi (2): gdb/dwarf: split cooked index finalization into separate steps gdb/dwarf: add cooked_index_entry::parent_is_deferred gdb/dwarf2/abbrev.c | 1 + gdb/dwarf2/cooked-index-entry.c | 16 +- gdb/dwarf2/cooked-index-entry.h | 87 +++++++-- gdb/dwarf2/cooked-index-shard.c | 139 ++++++++++--- gdb/dwarf2/cooked-index-shard.h | 69 ++++++- gdb/dwarf2/cooked-index-worker.c | 9 + gdb/dwarf2/cooked-index-worker.h | 30 ++- gdb/dwarf2/cooked-index.c | 183 +++++++++++++++--- gdb/dwarf2/cooked-index.h | 64 +++++- gdb/dwarf2/cooked-indexer.c | 60 ++++-- gdb/dwarf2/cooked-indexer.h | 1 + gdb/dwarf2/index-write.c | 2 +- .../ada-forward-spec-deferred-grandparent.exp | 81 ++++++++ .../sig-type-unnamed-class-bad-sig.exp | 155 +++++++++++++++ .../gdb.dwarf2/sig-type-unnamed-class-dwo.exp | 143 ++++++++++++++ .../gdb.dwarf2/sig-type-unnamed-class.exp | 106 ++++++++++ 16 files changed, 1048 insertions(+), 98 deletions(-) create mode 100644 gdb/testsuite/gdb.dwarf2/ada-forward-spec-deferred-grandparent.exp create mode 100644 gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp create mode 100644 gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-dwo.exp create mode 100644 gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp base-commit: c52597d61e1fbd7c0f7d84c75ca52d6c7446f745 -- 2.55.0 ^ permalink raw reply [flat|nested] 36+ messages in thread
* [PATCH v6 1/3] gdb/dwarf: split cooked index finalization into separate steps 2026-09-24 15:09 ` [PATCH v6 0/3] gdb/dwarf: " Simon Marchi @ 2026-09-24 15:09 ` Simon Marchi 2026-09-24 15:09 ` [PATCH v6 2/3] gdb/dwarf: resolve class name via DW_AT_signature in cooked index Simon Marchi 2026-09-24 15:09 ` [PATCH v6 3/3] gdb/dwarf: add cooked_index_entry::parent_is_deferred Simon Marchi 2 siblings, 0 replies; 36+ messages in thread From: Simon Marchi @ 2026-09-24 15:09 UTC (permalink / raw) To: gdb-patches; +Cc: Simon Marchi, Andrew Burgess This patch refactors the existing cooked index "finalize" step into multiple steps. This is a preparatory patch for the following patch, which will add some more steps. I believe that using this pattern will help keep the code readable and easy to understand, despite the increasing amount of work done. The following patch introduces a step that needs to run after the deferred parents have been resolved, but before the name canonicalization happens, so factor out the deferred parents code into a step of its own. The code that remains in cooked_index_shard::finalize is all about name canonicalization (if we consider the Ada stuff to be a special case of that), so rename that step to cooked_index_shard::canonicalize_names(). For clarity, move the "write to cache" code out of the "done" callback to make it clear that it is logically a separate step. So as of this patch, we have the steps: - resolve deferred parents - canonicalize names - write to cache The term "finalization" now refers to all these steps. Or, depending on your definition, it could refer to all but "write to cache", since the index reaches the FINALIZED state (i.e. becomes fully usable) just before that one. Here are the implementation details: - The first step is called by cooked_index::set_contents(), then each step is responsible for calling the next one (thus forming a chain), until we are done. - Steps that involve per-shard parallel processing use gdb::task_group, the "done" callback of the task_group starts the next task. A special case of that is: if some steps are able to detect ahead of time that it's unnecessary to run the step for a given shard, they can omit adding a task for that shard. An even more special case of that is: if no tasks are added to a task group, the "done" callback for that task group is invoked immediately when the group is started. This allows easily and cheaply skipping entire steps when they are not needed. This is used in this patch for the new "resolve deferred parents" step. Each shard records whether it has at least one deferred parent during the initial scan. If a shard has none (which is a common case), then cooked_index_shard::resolve_deferred_parents would iterate the index entries for nothing. The step therefore omits adding a task for that shard in that case. If no shards have any deferred parents that require resolving, we go directly to the next step with very little cost. During review, Claude pointed out that my change actually fixes a potential problem. The current code resolves the deferred parent of an entry in the same loop where it potentially calls "full_name" for that entry (to create the special Ada entries). "full_name" walks up the parent chain, and thus requires all grand-parents to be resolved. Depending on the order of DIEs in the file, it might not be true. Having the "resolve deferred parents" step run completely before the name canonicalization step avoids this. I built a gdb.dwarf2 test case around the DWARF structure it proposed to hit the bug. It is perhaps not something we expect from real-world producers, but it's also not completely unthinkable. It hits this assert when the patch is not applied: gdb/dwarf2/cooked-index-entry.h:217: internal-error: get_parent: Assertion `(flags & IS_PARENT_DEFERRED) == 0' failed. ... and passes with it. Change-Id: I1cf5e181ecd2d7399a1e885fe686e7e76a5419cb Approved-By: Andrew Burgess <aburgess@redhat.com> --- gdb/dwarf2/cooked-index-entry.c | 2 +- gdb/dwarf2/cooked-index-shard.c | 30 +++++-- gdb/dwarf2/cooked-index-shard.h | 22 +++-- gdb/dwarf2/cooked-index.c | 80 +++++++++++++----- gdb/dwarf2/cooked-index.h | 36 ++++++--- .../ada-forward-spec-deferred-grandparent.exp | 81 +++++++++++++++++++ 6 files changed, 210 insertions(+), 41 deletions(-) create mode 100644 gdb/testsuite/gdb.dwarf2/ada-forward-spec-deferred-grandparent.exp diff --git a/gdb/dwarf2/cooked-index-entry.c b/gdb/dwarf2/cooked-index-entry.c index 21e25e946158..8c324f77904b 100644 --- a/gdb/dwarf2/cooked-index-entry.c +++ b/gdb/dwarf2/cooked-index-entry.c @@ -194,7 +194,7 @@ cooked_index_entry::full_name (struct obstack *storage, of writing), then we need to compute the linkage name here. However for traditional GNAT, the linkage name will be in 'name'. Detect this by looking for "__"; see also - cooked_index_shard::finalize. */ + cooked_index_shard::canonicalize_names. */ if ((name_flags & FOR_ADA_LINKAGE_NAME) != 0) { if (strstr (name, "__") != nullptr) diff --git a/gdb/dwarf2/cooked-index-shard.c b/gdb/dwarf2/cooked-index-shard.c index 91dc9ab89455..51b2eacd46d8 100644 --- a/gdb/dwarf2/cooked-index-shard.c +++ b/gdb/dwarf2/cooked-index-shard.c @@ -82,6 +82,9 @@ cooked_index_shard::add (sect_offset die_offset, enum dwarf_tag tag, parent_entry, per_cu); m_entries.push_back (result); + if ((flags & IS_PARENT_DEFERRED) != 0) + m_have_deferred_parents = true; + /* An explicitly-tagged main program should always override the implicit "main" discovery. */ if ((flags & IS_MAIN) != 0) @@ -190,7 +193,24 @@ struct cooked_index_entry_name_ptr_eq /* See cooked-index-shard.h. */ void -cooked_index_shard::finalize (const parent_map_map *parent_maps) +cooked_index_shard::resolve_deferred_parents + (const parent_map_map *parent_maps) +{ + gdb_assert (m_have_deferred_parents); + + for (cooked_index_entry *entry : m_entries) + if ((entry->flags & IS_PARENT_DEFERRED) != 0) + { + const cooked_index_entry *new_parent + = parent_maps->find (entry->get_deferred_parent ()); + entry->resolve_parent (new_parent); + } +} + +/* See cooked-index-shard.h. */ + +void +cooked_index_shard::canonicalize_names () { gdb::unordered_set<const cooked_index_entry *, cooked_index_entry_name_ptr_hash, @@ -216,12 +236,8 @@ cooked_index_shard::finalize (const parent_map_map *parent_maps) for (cooked_index_entry *entry : m_entries) { - if ((entry->flags & IS_PARENT_DEFERRED) != 0) - { - const cooked_index_entry *new_parent - = parent_maps->find (entry->get_deferred_parent ()); - entry->resolve_parent (new_parent); - } + /* Deferred parents should not reach this point. */ + gdb_assert ((entry->flags & IS_PARENT_DEFERRED) == 0); /* Note that this code must be kept in sync with cooked_index::get_main -- if canonicalization is required diff --git a/gdb/dwarf2/cooked-index-shard.h b/gdb/dwarf2/cooked-index-shard.h index 84c37958c833..9b9ffa7a76e3 100644 --- a/gdb/dwarf2/cooked-index-shard.h +++ b/gdb/dwarf2/cooked-index-shard.h @@ -118,22 +118,34 @@ class cooked_index_shard (cooked_index_entry *entry, htab_t gnat_entries, std::vector<cooked_index_entry *> &new_entries); - /* Finalize the index. This should be called a single time, when - the index has been fully populated. It enters all the entries - into the internal table and fixes up all missing parent links. - This may be invoked in a worker thread. */ - void finalize (const parent_map_map *parent_maps); + /* Use PARENT_MAPS to resolve the deferred parent links of entries in this + shard. */ + void resolve_deferred_parents (const parent_map_map *parent_maps); + + /* Compute the canonical name for the entries in this shard. + + Due to how Ada name lookups work, this function may also create new index + entries with full names. */ + void canonicalize_names (); /* Storage for the entries. */ auto_obstack m_storage; + /* List of all entries. */ std::vector<cooked_index_entry *> m_entries; + /* If we found an entry with 'is_main' set, store it here. */ cooked_index_entry *m_main = nullptr; + /* The addrmap. This maps address ranges to dwarf2_per_cu objects. */ addrmap_fixed *m_addrmap = nullptr; + /* Storage for canonical names. */ gdb::string_set m_names; + + /* True if at least one entry in this shard has a parent link that requires + deferred resolution. */ + bool m_have_deferred_parents = false; }; using cooked_index_shard_up = std::unique_ptr<cooked_index_shard>; diff --git a/gdb/dwarf2/cooked-index.c b/gdb/dwarf2/cooked-index.c index 167e39ffc89a..bf1ea5223671 100644 --- a/gdb/dwarf2/cooked-index.c +++ b/gdb/dwarf2/cooked-index.c @@ -74,31 +74,73 @@ cooked_index::set_contents () m_state->set (cooked_state::MAIN_AVAILABLE); - /* This is run after finalization is done -- but not before. If - this task were submitted earlier, it would have to wait for - finalization. However, that would take a slot in the global - thread pool, and if enough such tasks were submitted at once, it - would cause a livelock. */ - gdb::task_group finalizers ([this] () - { - m_state->set (cooked_state::FINALIZED); - m_state->write_to_cache (index_for_writing ()); - m_state->set (cooked_state::CACHE_DONE); - }); + /* Start the first step of index finalization. */ + this->start_resolve_deferred_parents (); +} - for (auto &shard : m_shards) +/* See cooked-index.h. */ + +void +cooked_index::start_resolve_deferred_parents () +{ + gdb::task_group group ([this] () { - auto this_shard = shard.get (); - const parent_map_map *parent_maps = m_state->get_parent_map_map (); - finalizers.add_task ([this, this_shard, parent_maps] () + this->start_canonicalize_names (); + }); + + /* Arrange to call resolve_deferred_parents on each shard that has at least + one deferred parent link. */ + for (const cooked_index_shard_up &shard : m_shards) + { + if (!shard->m_have_deferred_parents) + continue; + + group.add_task ([this, this_shard = shard.get ()] () { - scoped_time_it time_it ("DWARF finalize worker", + scoped_time_it time_it ("DWARF resolve deferred parents worker", m_state->m_per_command_time); - this_shard->finalize (parent_maps); + + this_shard->resolve_deferred_parents (m_state->get_parent_map_map ()); }); } - finalizers.start (); + group.start (); +} + +/* See cooked-index.h. */ + +void +cooked_index::start_canonicalize_names () +{ + gdb::task_group group ([this] () + { + /* The index is considered finalized (fully usable) at this point. */ + m_state->set (cooked_state::FINALIZED); + this->write_to_cache (); + }); + + /* Arrange to call canonicalize_names on each shard. */ + for (const cooked_index_shard_up &shard : m_shards) + { + group.add_task ([this, this_shard = shard.get ()] () + { + scoped_time_it time_it ("DWARF canonicalize names worker", + m_state->m_per_command_time); + + this_shard->canonicalize_names (); + }); + } + + group.start (); +} + +/* See cooked-index.h. */ + +void +cooked_index::write_to_cache () +{ + m_state->write_to_cache (index_for_writing ()); + m_state->set (cooked_state::CACHE_DONE); } cooked_index::~cooked_index () @@ -190,7 +232,7 @@ cooked_index::get_main () const if ((entry->flags & IS_MAIN) != 0) { /* This should be kept in sync with - cooked_index_shard::finalize. Note that there, C + cooked_index_shard::canonicalize_names. Note that there, C requires canonicalization -- but that is only for types, 'main' doesn't count. Similarly, C++ requires canonicalization, but again "main" is an diff --git a/gdb/dwarf2/cooked-index.h b/gdb/dwarf2/cooked-index.h index 2e177cb62dd2..758e399706ba 100644 --- a/gdb/dwarf2/cooked-index.h +++ b/gdb/dwarf2/cooked-index.h @@ -70,14 +70,17 @@ . | . if main thread calls... v . compute_main_name cooked_index::set_contents - . | / | \ - . v / | \ - . wait (MAIN_AVAILABLE) finalization - . | \ | / - . v \ | / - . done state = FINALIZED - . | - . v + . | | + . v v + . wait (MAIN_AVAILABLE) resolve deferred parents + . | | + . v v + . done canonicalize names + . | + . v + . state = FINALIZED + . | + . v . maybe write to index cache . state = CACHE_DONE . ~cooked_index_worker @@ -91,7 +94,10 @@ . | . v . use the index -*/ + + The steps between set_contents and FINALIZED can be thought of as the + "index finalization", where we fix up a number of things that couldn't + be done during the parallel scan. */ class cooked_index : public dwarf_scanner_base { @@ -170,6 +176,18 @@ class cooked_index : public dwarf_scanner_base { wait (cooked_state::CACHE_DONE); } private: + /* Start the "resolve deferred parents" step of index finalization. */ + void start_resolve_deferred_parents (); + + /* Start the "canonicalize names" step of index finalization. + + This step must run after "resolve deferred parents", because it depends on + the parents being set. */ + void start_canonicalize_names (); + + /* Execute the "write to cache" step at the end of index + finalization. */ + void write_to_cache (); /* The vector of cooked_index objects. This is stored because the entries are stored on the obstacks in those objects. */ diff --git a/gdb/testsuite/gdb.dwarf2/ada-forward-spec-deferred-grandparent.exp b/gdb/testsuite/gdb.dwarf2/ada-forward-spec-deferred-grandparent.exp new file mode 100644 index 000000000000..24a55e5fbae3 --- /dev/null +++ b/gdb/testsuite/gdb.dwarf2/ada-forward-spec-deferred-grandparent.exp @@ -0,0 +1,81 @@ +# 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/>. + +# This test reproduces a case that caused a crash in the DWARF indexer, where +# we were trying to compute the full name of an index entry whose parent's +# parent link was not yet resolved. + +load_lib dwarf.exp + +# This test can only be run on targets which support DWARF-2 and use gas. +require dwarf2_support + +standard_testfile main.c -debug.S + +set asm_file [standard_output_file $srcfile2] +Dwarf::assemble $asm_file { + declare_labels v_decl pkg_decl myint + + cu {} { + DW_TAG_compile_unit { + DW_AT_language @DW_LANG_Ada95 + } { + # The definition of variable "v". Its DW_AT_specification + # points forward, so its parent link is deferred. + # + # The crash would happen when the index attempted to create a + # "full name" entry for this variable. + DW_TAG_variable { + DW_AT_specification %$v_decl + DW_AT_location { + DW_OP_const1u 23 + DW_OP_stack_value + } SPECIAL_expr + } + + # The definition of package "pkg". Its DW_AT_specification + # points forward, so its parent link is also deferred. + DW_TAG_module { + DW_AT_specification %$pkg_decl + } { + v_decl: DW_TAG_variable { + DW_AT_name v + DW_AT_type :$myint + DW_AT_declaration 1 DW_FORM_flag_present + } + } + + pkg_decl: DW_TAG_module { + DW_AT_name pkg + DW_AT_declaration 1 DW_FORM_flag_present + } {} + + myint: DW_TAG_base_type { + DW_AT_byte_size 4 DW_FORM_sdata + DW_AT_encoding @DW_ATE_signed + DW_AT_name myint + } + } + } +} + +if {[build_executable "failed to build executable" ${testfile} \ + [list $srcfile $asm_file] {nodebug}]} { + return +} + +clean_restart $testfile + +gdb_test "with language ada -- print pkg.v" " = 23" -- 2.55.0 ^ permalink raw reply [flat|nested] 36+ messages in thread
* [PATCH v6 2/3] gdb/dwarf: resolve class name via DW_AT_signature in cooked index 2026-09-24 15:09 ` [PATCH v6 0/3] gdb/dwarf: " Simon Marchi 2026-09-24 15:09 ` [PATCH v6 1/3] gdb/dwarf: split cooked index finalization into separate steps Simon Marchi @ 2026-09-24 15:09 ` Simon Marchi 2026-09-24 15:09 ` [PATCH v6 3/3] gdb/dwarf: add cooked_index_entry::parent_is_deferred Simon Marchi 2 siblings, 0 replies; 36+ messages in thread From: Simon Marchi @ 2026-09-24 15:09 UTC (permalink / raw) To: gdb-patches; +Cc: Andrew Burgess, Simon Marchi From: Andrew Burgess <aburgess@redhat.com> This commit fixes PR gdb/33447, an issue where looking up qualified member function names was not working for C++ binaries compiled with Clang when using the -fdebug-types-section flag. Before this commit we would see this behaviour: (gdb) print base1::a_function There is no field named a_function When what we expect to see is: (gdb) print base1::a_function $1 = {void (const base1 * const)} 0x403060 <base1::a_function() const> The problem is that the cooked index is unable to determine the name of the parent class `base1` in this case, and so decides not to index `base1` or any of its child DIEs, which includes its member functions. The problem was discovered by running gdb.cp/cpexprs-debug-types.exp with Clang: make check-gdb TESTS=gdb.cp/cpexprs-debug-types.exp \ RUNTESTFLAGS='CXX_FOR_TARGET=clang++ CC_FOR_TARGET=clang' The cpexprs-debug-types.exp test forces use of the '-fdebug-types-section' flag, which is not on by default. With this flag, class definitions are placed in type units, and the compile unit contains only a declaration stub for each class. Both clang++ and g++ emit these stubs, but they differ in one detail: GCC includes DW_AT_name on the stub, while clang++ does not, the stub carries only DW_AT_declaration and DW_AT_signature. The class name is only available in the type unit, reachable by following the signature. For example, Clang emits this in a CU: <1><2e7d>: DW_TAG_class_type DW_AT_declaration : 1 DW_AT_signature : 0x3abb... <2><2eac>: DW_TAG_subprogram DW_AT_name : a_function DW_AT_declaration : 1 The definition for a_function is elsewhere in the same CU: <1><3142>: DW_TAG_subprogram DW_AT_specification: <0x2eac> And in a TU elsewhere: Compilation Unit @ offset 0xd2e: ... snip ... Signature: 0x3abb... <0><d46>: Abbrev Number: 1 (DW_TAG_type_unit) ... snip ... <1><d51>: Abbrev Number: 30 (DW_TAG_class_type) ... snip ... <d57> DW_AT_name : (indexed string: 0xaa): base1 To find the DW_AT_name the cooked index needs to look up the type within the TU. Without the name the cooked indexer skips indexing `base1` as well as its children. This look up used to work; it works in GDB 17. PR gdb/33447 incorrectly identifies commit c879f4dc3e317cf6353a45a803ecf00d577a13d8 as the commit that introduced the regression. This is actually the last working commit. The problem was introduced by the next commit in the same series: commit 86ac8c546235a67d6a6bb29476a3a9ac8f7a620a Date: Thu Jan 2 15:28:18 2025 -0700 Convert lookup_symbol_in_objfile Prior to this commit GDB's symbol lookup had two phases, a search through already expanded symtabs, and a search via lookup_symbol_via_quick_fns. After the above commit only lookup_symbol_via_quick_fns remains. The lookup_symbol_via_quick_fns lookup, which relies on the indexer, was always broken, but the first phase, searching via expanded symtabs, could correctly find the type name via the signature. An initial attempt to solve this problem tried to fix this problem within cooked_indexer::scan_attributes, calling lookup_signatured_type and finding the name that way. However, there were three problems with this approach: 1. Possible thread safety issues; calling lookup_signatured_type for a DWO file ends up calling lookup_dwo_signatured_type, which can call add_type_unit and finalize_all_units, which modify state that is shared between parser threads. 2. The dwarf2_per_cu::type_offset_in_section for a TU is only set when the cutu_reader is constructed to parse that TU. Calling lookup_signatured_type doesn't fully parse the TU, it just finds the TU. The original code relied on type_offset_in_section being valid in order to then parse the TU and extract the name. This would break if the CU was processed before the TU. 3. For skeletonless TUs, these are not indexed until late in the indexing process, after all the CUs have been indexed. This means that if the TU referenced by the signature was skeletonless then the original approach would fail to find it. The new approach presented here is modeled more along the lines of the deferred parent handling that already exists within the indexer. The following changes have been made: 1. abbrev.c: Add DW_AT_signature to the set of attributes that mark a DIE as "interesting" in abbrev_table::read. Without this, the unnamed class stub was classified as uninteresting at the abbreviation level and scan_attributes was never called for it. 2. cooked-indexer.c (cooked_indexer::scan_attributes): Capture the DW_AT_signature attribute. 3. cooked-index-entry.h: Allow a cooked_index_entry to hold a deferred name, in the same way it can already hold a deferred parent. The name member becomes a union, cooked_index_entry_name_ref, holding either the resolved name or the signature of the DIE that carries the real name. The new IS_NAME_DEFERRED flag says which member is active, mirroring IS_PARENT_DEFERRED and cooked_index_entry_ref. cooked_index_entry also gets other methods mirroring the deferred parent mechanism: name(), name_is_deferred(), get_deferred_name() and resolve_name(). name() asserts that the name is no longer deferred, so any attempt to read the name too early is caught. All readers of the old public 'name' member are updated to call name(). 4. cooked-indexer.c (cooked_indexer::index_dies): There are two different jobs done here: (a) If a DIE has no name, but does have a signature, then create the entry anyway, storing the signature in the name union and setting IS_NAME_DEFERRED. (b) If we are indexing a TU, make a record of the signature, and the name of the primary type within the TU. This builds a signature to name map. 5. cooked-index-worker.c (cooked_index_worker::done_reading): Merge together all of the signature to name maps built in (4b). 6. cooked-index-shard.c (cooked_index_shard::resolve_deferred_names): A new function, look through all of the cooked_index_entry objects in the shard for those with IS_NAME_DEFERRED, and use the signature to name map created in (5) to look up the name and update the entry. resolve_deferred_names returns a bool, true if any deferred name couldn't be resolved. 7. cooked-index-shard.c (cooked_index_shard::prune_nameless_entries): A new function, look through all of the cooked_index_entry objects in the shard, if the entry's parent still has a deferred name, break the parent relationship. If the entry itself still has a deferred name, remove it from the index. This prevents a nameless entry from being found via the get_parent method. After this point every entry in the index has a resolved name, so name() can be called on any of them. 8. cooked-index.c (cooked_index::wait): After waiting, if we have completed finalization then emit any complaints that came from the finalization phase. 9. cooked-index.c: Add the "resolve deferred names" and "prune nameless entries" steps to the index finalization steps, implemented with the functions mentioned in points 6 and 7. The "resolve deferred names" step is only applied to shards that have m_have_deferred_names set. The "prune nameless entries" step is only run if cooked_index::m_have_nameless_entries is set. 10. cooked-index-entry.c (cooked_index_entry::write_scope): Add an assert that the entry's name is resolved. Entries that don't satisfy this should have been removed during finalization. Added three new DWARF assembler tests. gdb.dwarf2/sig-type-unnamed-class.exp gdb.dwarf2/sig-type-unnamed-class-dwo.exp: These reproduce the problem case; there's a nameless declaration stub that references its full type via DW_AT_signature. To match the Clang output as much as possible the member function definition is separate and makes use of DW_AT_specification. The -dwo test places the DWARF into a DWO file and uses a skeletonless TU. gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp: This one tests some invalid DWARF, the type DIE references a signature that doesn't exist. In this case GDB discards the cooked_index_entries whose name could not be resolved, and orphans their children. I think this is fine though, the DWARF is corrupted in this case. Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=33447 Co-Authored-By: Simon Marchi <simon.marchi@efficios.com> Change-Id: I1e0ba20e58af169abbf99e97c14c788164f0d1b2 --- gdb/dwarf2/abbrev.c | 1 + gdb/dwarf2/cooked-index-entry.c | 14 +- gdb/dwarf2/cooked-index-entry.h | 75 ++++++++- gdb/dwarf2/cooked-index-shard.c | 105 ++++++++++-- gdb/dwarf2/cooked-index-shard.h | 47 +++++- gdb/dwarf2/cooked-index-worker.c | 9 + gdb/dwarf2/cooked-index-worker.h | 30 +++- gdb/dwarf2/cooked-index.c | 105 +++++++++++- gdb/dwarf2/cooked-index.h | 34 +++- gdb/dwarf2/cooked-indexer.c | 60 +++++-- gdb/dwarf2/cooked-indexer.h | 1 + gdb/dwarf2/index-write.c | 2 +- .../sig-type-unnamed-class-bad-sig.exp | 155 ++++++++++++++++++ .../gdb.dwarf2/sig-type-unnamed-class-dwo.exp | 143 ++++++++++++++++ .../gdb.dwarf2/sig-type-unnamed-class.exp | 106 ++++++++++++ 15 files changed, 832 insertions(+), 55 deletions(-) create mode 100644 gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp create mode 100644 gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-dwo.exp create mode 100644 gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp diff --git a/gdb/dwarf2/abbrev.c b/gdb/dwarf2/abbrev.c index 44d5c87a5f79..e99acc5e752d 100644 --- a/gdb/dwarf2/abbrev.c +++ b/gdb/dwarf2/abbrev.c @@ -160,6 +160,7 @@ abbrev_table::read (struct dwarf2_section_info *section, case DW_AT_specification: case DW_AT_abstract_origin: case DW_AT_extension: + case DW_AT_signature: has_specification_or_origin = true; break; diff --git a/gdb/dwarf2/cooked-index-entry.c b/gdb/dwarf2/cooked-index-entry.c index 8c324f77904b..44b065157b80 100644 --- a/gdb/dwarf2/cooked-index-entry.c +++ b/gdb/dwarf2/cooked-index-entry.c @@ -35,6 +35,7 @@ to_string (cooked_index_flag flags) MAP_ENUM_FLAG (IS_PARENT_DEFERRED), MAP_ENUM_FLAG (IS_SYNTHESIZED), MAP_ENUM_FLAG (IS_INLINED), + MAP_ENUM_FLAG (IS_NAME_DEFERRED), }; return flags.to_string (mapping); @@ -175,7 +176,7 @@ cooked_index_entry::full_name (struct obstack *storage, cooked_index_full_name_flag name_flags, const char *default_sep) const { - const char *local_name = ((name_flags & FOR_MAIN) != 0) ? name : canonical; + const char *local_name = ((name_flags & FOR_MAIN) != 0) ? name () : canonical; if ((flags & IS_LINKAGE) != 0 || get_parent () == nullptr) return local_name; @@ -197,8 +198,8 @@ cooked_index_entry::full_name (struct obstack *storage, cooked_index_shard::canonicalize_names. */ if ((name_flags & FOR_ADA_LINKAGE_NAME) != 0) { - if (strstr (name, "__") != nullptr) - return name; + if (strstr (name (), "__") != nullptr) + return name (); sep = "__"; break; } @@ -233,10 +234,15 @@ cooked_index_entry::write_scope (struct obstack *storage, { if (get_parent () != nullptr) get_parent ()->write_scope (storage, sep, flags); + + /* Any entry with an unresolved name will have been filtered out by + cooked_index_shard::prune_nameless_entries. */ + gdb_assert (!name_is_deferred ()); + /* When computing the Ada linkage name, the entry might not have been canonicalized yet, so use the 'name'. */ const char *local_name = ((flags & (FOR_MAIN | FOR_ADA_LINKAGE_NAME)) != 0 - ? name + ? name () : canonical); obstack_grow (storage, local_name, strlen (local_name)); obstack_grow (storage, sep, strlen (sep)); diff --git a/gdb/dwarf2/cooked-index-entry.h b/gdb/dwarf2/cooked-index-entry.h index 60ea581cbbe5..2637b8255258 100644 --- a/gdb/dwarf2/cooked-index-entry.h +++ b/gdb/dwarf2/cooked-index-entry.h @@ -47,6 +47,8 @@ enum cooked_index_flag_enum : unsigned char /* True if this is a function that has DW_AT_inline set in a way that indicates it was inlined. */ IS_INLINED = 64, + /* True if m_name.deferred has a value rather than m_name.resolved. */ + IS_NAME_DEFERRED = 128, }; DEF_ENUM_FLAGS_TYPE (enum cooked_index_flag_enum, cooked_index_flag); @@ -79,6 +81,31 @@ union cooked_index_entry_ref parent_map::addr_type deferred; }; +/* Type representing either a resolved or deferred cooked_index_entry + name. A deferred name is held as the DW_AT_signature of the DIE + that carries the real name. */ + +union cooked_index_entry_name_ref +{ + cooked_index_entry_name_ref (ULONGEST deferred_) + { + deferred = deferred_; + } + + cooked_index_entry_name_ref (const char *resolved_) + { + resolved = resolved_; + } + + const char *resolved; + ULONGEST deferred; +}; + +/* Type that maps DW_AT_signature values for a TU to the name of the + primary type within the TU. */ + +using signature_to_name_map = gdb::unordered_map<ULONGEST, const char *>; + /* Return a string representation of FLAGS. */ std::string to_string (cooked_index_flag flags); @@ -94,15 +121,16 @@ struct cooked_index_entry : public allocate_on_obstack<cooked_index_entry> { cooked_index_entry (sect_offset die_offset_, enum dwarf_tag tag_, cooked_index_flag flags_, - enum language lang_, const char *name_, + enum language lang_, + cooked_index_entry_name_ref name_, cooked_index_entry_ref parent_entry_, dwarf2_per_cu *per_cu_) - : name (name_), - tag (tag_), + : tag (tag_), flags (flags_), lang (lang_), die_offset (die_offset_), per_cu (per_cu_), + m_name (name_), m_parent_entry (parent_entry_) { } @@ -225,6 +253,36 @@ struct cooked_index_entry : public allocate_on_obstack<cooked_index_entry> return m_parent_entry.deferred; } + /* Return the entry's name. This may be the name or the linkage + name -- two entries are created for DIEs which have both + attributes. It points to a string that outlives this entry, + which in practice means the mapped DWARF or the storage of one of + the cooked_index_shard objects. */ + const char *name () const + { + gdb_assert (!name_is_deferred ()); + return m_name.resolved; + } + + /* Return true if this entry's name still has to be resolved. */ + bool name_is_deferred () const + { return (flags & IS_NAME_DEFERRED) != 0; } + + /* Return the signature of the DIE holding this entry's name. */ + ULONGEST get_deferred_name () const + { + gdb_assert (name_is_deferred ()); + return m_name.deferred; + } + + /* Resolve deferred name to NAME. */ + void resolve_name (const char *name) + { + gdb_assert (name_is_deferred ()); + flags = flags & ~IS_NAME_DEFERRED; + m_name.resolved = name; + } + /* Force the language to be set to the CU's language. This may only be called when the language is unknown, which can only happen with .gdb_index. This method is const because it is called from @@ -252,12 +310,7 @@ struct cooked_index_entry : public allocate_on_obstack<cooked_index_entry> defined in some CU that is included by many other CUs. */ iteration_status visit_defining_cus (per_cu_callback callback) const; - /* The name as it appears in DWARF. This always points into one of - the mapped DWARF sections. Note that this may be the name or the - linkage name -- two entries are created for DIEs which have both - attributes. */ - const char *name; - /* The canonical name. This may be equal to NAME. */ + /* The canonical name. This may be equal to the name. */ const char *canonical = nullptr; /* The DWARF tag. */ enum dwarf_tag tag; @@ -284,6 +337,10 @@ struct cooked_index_entry : public allocate_on_obstack<cooked_index_entry> void write_scope (struct obstack *storage, const char *sep, cooked_index_full_name_flag flags) const; + /* The entry's name, or the signature by which it can be found. See + IS_NAME_DEFERRED and the accessors above. */ + cooked_index_entry_name_ref m_name; + /* The parent entry. This is NULL for top-level entries. Otherwise, it points to the parent entry, such as a namespace or class. */ diff --git a/gdb/dwarf2/cooked-index-shard.c b/gdb/dwarf2/cooked-index-shard.c index 51b2eacd46d8..af8c3e9527ac 100644 --- a/gdb/dwarf2/cooked-index-shard.c +++ b/gdb/dwarf2/cooked-index-shard.c @@ -20,6 +20,8 @@ #include "dwarf2/cooked-index-shard.h" #include "dwarf2/tag.h" #include "dwarf2/index-common.h" +#include "dwarf2/read.h" +#include "dwarf2/error.h" #include "cp-support.h" #include "c-lang.h" #include "ada-lang.h" @@ -48,7 +50,7 @@ cooked_index_shard::create (sect_offset die_offset, enum dwarf_tag tag, cooked_index_flag flags, enum language lang, - const char *name, + cooked_index_entry_name_ref name, cooked_index_entry_ref parent_entry, dwarf2_per_cu *per_cu) { @@ -75,7 +77,8 @@ cooked_index_shard::create (sect_offset die_offset, cooked_index_entry * cooked_index_shard::add (sect_offset die_offset, enum dwarf_tag tag, cooked_index_flag flags, enum language lang, - const char *name, cooked_index_entry_ref parent_entry, + cooked_index_entry_name_ref name, + cooked_index_entry_ref parent_entry, dwarf2_per_cu *per_cu) { cooked_index_entry *result = create (die_offset, tag, flags, lang, name, @@ -85,6 +88,9 @@ cooked_index_shard::add (sect_offset die_offset, enum dwarf_tag tag, if ((flags & IS_PARENT_DEFERRED) != 0) m_have_deferred_parents = true; + if (result->name_is_deferred ()) + m_have_deferred_names = true; + /* An explicitly-tagged main program should always override the implicit "main" discovery. */ if ((flags & IS_MAIN) != 0) @@ -107,7 +113,8 @@ cooked_index_shard::add (sect_offset die_offset, enum dwarf_tag tag, && parent_entry.resolved == nullptr && m_main == nullptr && language_may_use_plain_main (lang) - && streq (name, "main")) + && !result->name_is_deferred () + && streq (result->name (), "main")) m_main = result; return result; @@ -125,10 +132,10 @@ cooked_index_shard::handle_gnat_encoded_entry characters are left as-is. This is done to make name matching a bit simpler; and for wide characters, it means the choice of Ada source charset does not affect the indexer directly. */ - std::string canonical = ada_decode (entry->name, false, false); + std::string canonical = ada_decode (entry->name (), false, false); if (canonical.empty ()) { - entry->canonical = entry->name; + entry->canonical = entry->name (); return; } std::vector<std::string_view> names = split_name (canonical.c_str (), @@ -151,7 +158,7 @@ cooked_index_shard::handle_gnat_encoded_entry last = create (entry->die_offset, DW_TAG_module, IS_SYNTHESIZED, language_ada, new_name, parent, entry->per_cu); - last->canonical = last->name; + last->canonical = last->name (); new_entries.push_back (last); *slot = last; } @@ -175,7 +182,7 @@ struct cooked_index_entry_name_ptr_hash std::uint64_t operator () (const cooked_index_entry *entry) const noexcept { - return ankerl::unordered_dense::hash<const char *> () (entry->name); + return ankerl::unordered_dense::hash<const char *> () (entry->name ()); } }; @@ -186,12 +193,44 @@ struct cooked_index_entry_name_ptr_eq bool operator () (const cooked_index_entry *a, const cooked_index_entry *b) const noexcept { - return a->name == b->name; + return a->name () == b->name (); } }; /* See cooked-index-shard.h. */ +bool +cooked_index_shard::resolve_deferred_names + (const signature_to_name_map &sig_names) +{ + bool have_nameless_entries = false; + for (cooked_index_entry *entry : m_entries) + { + if (!entry->name_is_deferred ()) + continue; + + const ULONGEST signature = entry->get_deferred_name (); + + if (const auto it = sig_names.find (signature); + it != sig_names.end ()) + entry->resolve_name (it->second); + else + { + have_nameless_entries = true; + complaint (_(DWARF_ERROR_PREFIX + "Cannot find signatured DIE %s referenced from DIE " + "at %s [in module %s]"), + hex_string (signature), + sect_offset_str (entry->die_offset), + entry->per_cu->per_bfd ()->filename ()); + } + } + + return have_nameless_entries; +} + +/* See cooked-index-shard.h. */ + void cooked_index_shard::resolve_deferred_parents (const parent_map_map *parent_maps) @@ -209,6 +248,38 @@ cooked_index_shard::resolve_deferred_parents /* See cooked-index-shard.h. */ +void +cooked_index_shard::prune_nameless_entries () +{ + for (cooked_index_entry *entry : m_entries) + { + /* Remove a parent reference if the parent has no name. This + leaves ENTRY as an orphan, but this only happens if the DWARF + is corrupted and we failed to find a name for the parent. We + can safely check the parent's name at this point because all + deferred names and parent links will have been resolved in + all shards before this is called on any shard. */ + if (const cooked_index_entry *parent = entry->get_parent (); + parent != nullptr && parent->name_is_deferred ()) + entry->set_parent (nullptr); + } + + /* If we failed to resolve the name of an entry via its signature + then remove the entry from the m_entries vector. This should be + rare, and should only happen when we have corrupted DWARF. The + entries still live on the obstack, so parent pointers are still + valid, but removing entries from the index means we don't try to + search them when looking for index hits. */ + m_entries.erase (std::remove_if (m_entries.begin (), m_entries.end (), + [] (const cooked_index_entry *e) + { + return e->name_is_deferred (); + }), + m_entries.end ()); +} + +/* See cooked-index-shard.h. */ + void cooked_index_shard::canonicalize_names () { @@ -239,21 +310,25 @@ cooked_index_shard::canonicalize_names () /* Deferred parents should not reach this point. */ gdb_assert ((entry->flags & IS_PARENT_DEFERRED) == 0); + /* Entries without a name are filtered out during the call to + prune_nameless_entries. */ + gdb_assert (!entry->name_is_deferred ()); + /* Note that this code must be kept in sync with cooked_index::get_main -- if canonicalization is required here, then a check might be required there. */ gdb_assert (entry->canonical == nullptr); if ((entry->flags & IS_LINKAGE) != 0) - entry->canonical = entry->name; + entry->canonical = entry->name (); else if (entry->lang == language_ada) { /* Newer versions of GNAT emit DW_TAG_module and use a hierarchical structure. In this case, we don't need to do any extra work. This can be detected by looking for a GNAT-encoded name. */ - if (strstr (entry->name, "__") == nullptr) + if (strstr (entry->name (), "__") == nullptr) { - entry->canonical = entry->name; + entry->canonical = entry->name (); /* If the entry does not have a parent, then there's nothing extra to do here -- the entry itself is @@ -300,10 +375,10 @@ cooked_index_shard::canonicalize_names () name. */ gdb::unique_xmalloc_ptr<char> canon_name = (entry->lang == language_cplus - ? cp_canonicalize_string (entry->name) - : c_canonicalize_name (entry->name)); + ? cp_canonicalize_string (entry->name ()) + : c_canonicalize_name (entry->name ())); if (canon_name == nullptr) - entry->canonical = entry->name; + entry->canonical = entry->name (); else entry->canonical = m_names.insert (std::move (canon_name)); } @@ -315,7 +390,7 @@ cooked_index_shard::canonicalize_names () } } else - entry->canonical = entry->name; + entry->canonical = entry->name (); } /* Make sure any new Ada entries end up in the results. This isn't diff --git a/gdb/dwarf2/cooked-index-shard.h b/gdb/dwarf2/cooked-index-shard.h index 9b9ffa7a76e3..ca4e415ed326 100644 --- a/gdb/dwarf2/cooked-index-shard.h +++ b/gdb/dwarf2/cooked-index-shard.h @@ -26,6 +26,7 @@ #include "addrmap.h" #include "gdbsupport/iterator-range.h" #include "gdbsupport/string-set.h" +#include "complaints.h" /* An index of interesting DIEs. This is "cooked", in contrast to a mapped .debug_names or .gdb_index, which are "raw". An entry in @@ -44,7 +45,7 @@ class cooked_index_shard Entries are owned by this object. The new item is returned. */ cooked_index_entry *add (sect_offset die_offset, enum dwarf_tag tag, cooked_index_flag flags, enum language lang, - const char *name, + cooked_index_entry_name_ref name, cooked_index_entry_ref parent_entry, dwarf2_per_cu *per_cu); @@ -105,7 +106,7 @@ class cooked_index_shard enum dwarf_tag tag, cooked_index_flag flags, enum language lang, - const char *name, + cooked_index_entry_name_ref name, cooked_index_entry_ref parent_entry, dwarf2_per_cu *per_cu); @@ -118,16 +119,50 @@ class cooked_index_shard (cooked_index_entry *entry, htab_t gnat_entries, std::vector<cooked_index_entry *> &new_entries); + /* Use SIG_NAMES to resolve the deferred names of entries in this shard. + + Return true if any deferred name could not be resolved. */ + bool resolve_deferred_names (const signature_to_name_map &sig_names); + /* Use PARENT_MAPS to resolve the deferred parent links of entries in this shard. */ void resolve_deferred_parents (const parent_map_map *parent_maps); + /* Remove index entries that have no name (for which we failed to + resolve the name in resolve_deferred_names). Break any parent link + pointing to an entry with no name. */ + void prune_nameless_entries (); + /* Compute the canonical name for the entries in this shard. Due to how Ada name lookups work, this function may also create new index entries with full names. */ void canonicalize_names (); + /* Called after each step of the finalization process. Store + COMPLAINTS so they can be reported later on the main thread. */ + void merge_finalize_complaints (complaint_collection &&complaints) + { + if (m_finalize_complaints.empty ()) + m_finalize_complaints = std::move (complaints); + else + { + /* The current version of gdb::unordered_set doesn't support + the merge method that std::unordered_set supports. If we + update gdb::unordered_set then we could switch this to use + merge(). */ + m_finalize_complaints.insert (complaints.begin (), complaints.end ()); + } + } + + /* Return the set of complaints emitted during the finalization + process. We move these complaints out of the shard as these are + only emitted once, and don't need to be stored beyond that. */ + complaint_collection release_finalize_complaints () + { + return std::move (m_finalize_complaints); + } + /* Storage for the entries. */ auto_obstack m_storage; @@ -143,9 +178,17 @@ class cooked_index_shard /* Storage for canonical names. */ gdb::string_set m_names; + /* True if at least one entry in this shard has a name that requires + deferred resolution. */ + bool m_have_deferred_names = false; + /* True if at least one entry in this shard has a parent link that requires deferred resolution. */ bool m_have_deferred_parents = false; + + /* Any complaints emitted while finalizing the index are stored + here until they can be emitted on the main thread. */ + complaint_collection m_finalize_complaints; }; using cooked_index_shard_up = std::unique_ptr<cooked_index_shard>; diff --git a/gdb/dwarf2/cooked-index-worker.c b/gdb/dwarf2/cooked-index-worker.c index 723e027172e8..054b25ba1479 100644 --- a/gdb/dwarf2/cooked-index-worker.c +++ b/gdb/dwarf2/cooked-index-worker.c @@ -249,6 +249,15 @@ cooked_index_worker::done_reading () m_all_parents_map.add_map (*one_result.get_parent_map ()); } + { + scoped_time_it time_it ("DWARF add signature name map", m_per_command_time); + + /* Combine all of the signature to name maps. */ + for (cooked_index_worker_result &one_result : m_results) + for (const auto &[sig, name] : one_result.get_sig_name_map ()) + m_all_sig_names_map.emplace (sig, name); + } + /* Update all the CU inclusion information. */ for (auto &item : m_results) item.invert_cu_inclusions (); diff --git a/gdb/dwarf2/cooked-index-worker.h b/gdb/dwarf2/cooked-index-worker.h index 1799c0b9e08b..d62ed01faac1 100644 --- a/gdb/dwarf2/cooked-index-worker.h +++ b/gdb/dwarf2/cooked-index-worker.h @@ -68,7 +68,7 @@ class cooked_index_worker_result cooked-index.h. The new entry is returned. */ cooked_index_entry *add (sect_offset die_offset, enum dwarf_tag tag, cooked_index_flag flags, enum language lang, - const char *name, + cooked_index_entry_name_ref name, cooked_index_entry_ref parent_entry, dwarf2_per_cu *per_cu) { @@ -83,6 +83,15 @@ class cooked_index_worker_result return m_shard->add (name); } + /* Called when processing TUs to record that the primary type within + a type unit with SIGNATURE, was called NAME. This information + will be used during finalization to fix-up the name of any + entries carrying the IS_NAME_DEFERRED flag. */ + void add_signatured_type_name (ULONGEST signature, const char *name) + { + m_sig_name_map.emplace (signature, name); + } + /* Install the current addrmap into the shard being constructed, then transfer ownership of the index to the caller. */ cooked_index_shard_up release_shard () @@ -153,6 +162,12 @@ class cooked_index_worker_result discovered. */ void invert_cu_inclusions (); + /* The signature to name map for this worker. */ + const signature_to_name_map &get_sig_name_map () const + { + return m_sig_name_map; + } + private: /* The abbrev table cache used by this indexer. */ abbrev_table_cache m_abbrev_table_cache; @@ -188,6 +203,9 @@ class cooked_index_worker_result /* Parent map for each CU that is read. */ parent_map m_parent_map; + /* Signature to name map for the primary type in a TU. */ + signature_to_name_map m_sig_name_map; + /* A writeable addrmap being constructed by this scanner. */ addrmap_mutable m_addrmap; @@ -272,6 +290,12 @@ class cooked_index_worker return &m_all_parents_map; } + /* Return the map containing the complete signature to name information. */ + const signature_to_name_map &get_sig_name_map () const + { + return m_all_sig_names_map; + } + protected: /* Let cooked_index call the 'set' and 'write_to_cache' methods. */ @@ -319,6 +343,10 @@ class cooked_index_worker parent relationships. */ parent_map_map m_all_parents_map; + /* Map from signature to name of primary type within a TU. This is + the combined map, built after all the workers have finished. */ + signature_to_name_map m_all_sig_names_map; + /* Current state of this object. */ cooked_state m_state = cooked_state::INITIAL; /* Mutex and condition variable used to synchronize. */ diff --git a/gdb/dwarf2/cooked-index.c b/gdb/dwarf2/cooked-index.c index bf1ea5223671..96996ebc4603 100644 --- a/gdb/dwarf2/cooked-index.c +++ b/gdb/dwarf2/cooked-index.c @@ -58,7 +58,23 @@ cooked_index::wait (cooked_state desired_state, bool allow_quit) if (m_state == nullptr) return; - if (m_state->wait (desired_state, allow_quit)) + bool done = m_state->wait (desired_state, allow_quit); + + /* Emit any cached complaints if we have finalized and we are on the + main thread. Check for the requested state or the DONE flag + here, we might have only asked for MAIN_AVAILABLE, but if the + workers are quick then they might be done, in which case we + should emit the complaints now. */ + if (!m_finalize_complaints_emitted + && is_main_thread () + && (desired_state >= cooked_state::FINALIZED || done)) + { + m_finalize_complaints_emitted = true; + for (const auto &shard : m_shards) + re_emit_complaints (shard->release_finalize_complaints ()); + } + + if (done) { /* Only the main thread can modify this. */ gdb_assert (is_main_thread ()); @@ -75,7 +91,40 @@ cooked_index::set_contents () m_state->set (cooked_state::MAIN_AVAILABLE); /* Start the first step of index finalization. */ - this->start_resolve_deferred_parents (); + this->start_resolve_deferred_names (); +} + +/* See cooked-index.h. */ + +void +cooked_index::start_resolve_deferred_names () +{ + gdb::task_group group ([this] () + { + this->start_resolve_deferred_parents (); + }); + + /* Arrange to call resolve_deferred_names on each shard that has + deferred names. */ + for (const cooked_index_shard_up &shard : m_shards) + { + if (!shard->m_have_deferred_names) + continue; + + group.add_task ([this, this_shard = shard.get ()] () + { + complaint_interceptor complaint_handler; + scoped_time_it time_it ("DWARF resolve deferred names worker", + m_state->m_per_command_time); + + if (this_shard->resolve_deferred_names (m_state->get_sig_name_map ())) + m_have_nameless_entries.store (true); + + this_shard->merge_finalize_complaints (complaint_handler.release ()); + }); + } + + group.start (); } /* See cooked-index.h. */ @@ -85,7 +134,7 @@ cooked_index::start_resolve_deferred_parents () { gdb::task_group group ([this] () { - this->start_canonicalize_names (); + this->start_prune_nameless_entries (); }); /* Arrange to call resolve_deferred_parents on each shard that has at least @@ -97,10 +146,12 @@ cooked_index::start_resolve_deferred_parents () group.add_task ([this, this_shard = shard.get ()] () { + complaint_interceptor complaint_handler; scoped_time_it time_it ("DWARF resolve deferred parents worker", m_state->m_per_command_time); this_shard->resolve_deferred_parents (m_state->get_parent_map_map ()); + this_shard->merge_finalize_complaints (complaint_handler.release ()); }); } @@ -109,6 +160,44 @@ cooked_index::start_resolve_deferred_parents () /* See cooked-index.h. */ +void +cooked_index::start_prune_nameless_entries () +{ + gdb::task_group group ([this] () + { + this->start_canonicalize_names (); + }); + + /* Remove index entries whose name we could not resolve. + + If there is any nameless entry, this step needs to run on all the shards, + because there could be children of a nameless entry in other shards, + whose parent links we want to break. + + This step normally only runs in case there is something wrong with the + DWARF info. */ + if (m_have_nameless_entries.load ()) + for (const cooked_index_shard_up &shard : m_shards) + { + group.add_task ([this, this_shard = shard.get ()] () + { + complaint_interceptor complaint_handler; + + scoped_time_it time_it ("DWARF prune nameless entries worker", + m_state->m_per_command_time); + + this_shard->prune_nameless_entries (); + + this_shard->merge_finalize_complaints + (complaint_handler.release ()); + }); + } + + group.start (); +} + +/* See cooked-index.h. */ + void cooked_index::start_canonicalize_names () { @@ -124,10 +213,12 @@ cooked_index::start_canonicalize_names () { group.add_task ([this, this_shard = shard.get ()] () { + complaint_interceptor complaint_handler; scoped_time_it time_it ("DWARF canonicalize names worker", m_state->m_per_command_time); this_shard->canonicalize_names (); + this_shard->merge_finalize_complaints (complaint_handler.release ()); }); } @@ -239,7 +330,7 @@ cooked_index::get_main () const exception. */ if ((entry->lang != language_ada && entry->lang != language_cplus) - || streq (entry->name, "main")) + || streq (entry->name (), "main")) { /* There won't be one better than this. */ return entry; @@ -289,7 +380,7 @@ cooked_index::dump (gdbarch *arch) gdb_printf (" [%zu] ((cooked_index_entry *) %p)\n", i++, entry); gdb_printf (" name: %ps\n", styled_string (style, - entry->name)); + entry->name ())); gdb_printf (" canonical: %ps\n", styled_string (style, entry->canonical)); gdb_printf (" qualified: %ps\n", @@ -305,7 +396,7 @@ cooked_index::dump (gdbarch *arch) entry->get_deferred_parent ()); else if (entry->get_parent () != nullptr) gdb_printf (" parent: ((cooked_index_entry *) %p) [%s]\n", - entry->get_parent (), entry->get_parent ()->name); + entry->get_parent (), entry->get_parent ()->name ()); else gdb_printf (" parent: ((cooked_index_entry *) 0)\n"); @@ -315,7 +406,7 @@ cooked_index::dump (gdbarch *arch) const cooked_index_entry *main_entry = this->get_main (); if (main_entry != nullptr) gdb_printf (" main: ((cooked_index_entry *) %p) [%s]\n", main_entry, - main_entry->name); + main_entry->name ()); else gdb_printf (" main: ((cooked_index_entry *) 0)\n"); diff --git a/gdb/dwarf2/cooked-index.h b/gdb/dwarf2/cooked-index.h index 758e399706ba..83387b547474 100644 --- a/gdb/dwarf2/cooked-index.h +++ b/gdb/dwarf2/cooked-index.h @@ -31,6 +31,8 @@ #include "dwarf2/cooked-index-shard.h" #include "dwarf2/cooked-index-worker.h" +#include <atomic> + /* The main index of DIEs. The index is created by multiple threads. The overall process is @@ -72,10 +74,16 @@ . compute_main_name cooked_index::set_contents . | | . v v - . wait (MAIN_AVAILABLE) resolve deferred parents + . wait (MAIN_AVAILABLE) resolve deferred names . | | . v v - . done canonicalize names + . done resolve deferred parents + . | + . v + . prune nameless entries + . | + . v + . canonicalize names . | . v . state = FINALIZED @@ -176,13 +184,23 @@ class cooked_index : public dwarf_scanner_base { wait (cooked_state::CACHE_DONE); } private: + /* Start the "resolve deferred names" step of index finalization. */ + void start_resolve_deferred_names (); + /* Start the "resolve deferred parents" step of index finalization. */ void start_resolve_deferred_parents (); + /* Start the "prune nameless entries" step of index finalization. + + This step must run after "resolve deferred names" and "resolve deferred + parents", because it relies on names and parents being set. */ + void start_prune_nameless_entries (); + /* Start the "canonicalize names" step of index finalization. This step must run after "resolve deferred parents", because it depends on - the parents being set. */ + the parents being set, and after "prune nameless entries", because it + requires all entries to have a name. */ void start_canonicalize_names (); /* Execute the "write to cache" step at the end of index @@ -197,6 +215,16 @@ class cooked_index : public dwarf_scanner_base that the state is CACHE_DONE -- it's important to note that only the main thread may change the value of this pointer. */ cooked_index_worker_up m_state; + + /* Any complaints raised during finalization are held within the + shards in M_SHARDS. Once the main thread has waited for + finalization to be complete then the cached complaints are + emitted, and this flag is set to true. */ + bool m_finalize_complaints_emitted = false; + + /* True if some deferred name could not be resolved during the "resolve + deferred names" step. */ + std::atomic<bool> m_have_nameless_entries = false; }; /* An implementation of quick_symbol_functions for the cooked DWARF diff --git a/gdb/dwarf2/cooked-indexer.c b/gdb/dwarf2/cooked-indexer.c index 581c0eb07635..23029bc4f13c 100644 --- a/gdb/dwarf2/cooked-indexer.c +++ b/gdb/dwarf2/cooked-indexer.c @@ -157,6 +157,7 @@ cooked_indexer::scan_attributes (dwarf2_per_cu *scanning_per_cu, parent_map::addr_type *maybe_defer, bool *is_enum_class, bool *is_inlined, + std::optional<ULONGEST> *signature, bool for_specification) { bool is_declaration = false; @@ -234,6 +235,16 @@ cooked_indexer::scan_attributes (dwarf2_per_cu *scanning_per_cu, attr.get_ref_die_offset () }; break; + case DW_AT_signature: + /* The DW_AT_signature could also be a direct reference to a + type DIE. We don't currently try to capture those + signatures as right now we're only capturing this in + order to handle the fact that some TUs are parsed after + the parallel CU parsing. */ + if (attr.form == DW_FORM_ref_sig8) + signature->emplace (attr.as_signature ()); + break; + case DW_AT_external: if (attr.as_boolean ()) *flags &= ~IS_STATIC; @@ -380,7 +391,7 @@ cooked_indexer::scan_attributes (dwarf2_per_cu *scanning_per_cu, scan_attributes (scanning_per_cu, new_reader, new_info_ptr, new_info_ptr, new_abbrev, name, linkage_name, flags, nullptr, parent_entry, maybe_defer, - is_enum_class, is_inlined, true); + is_enum_class, is_inlined, signature, true); } if (!for_specification) @@ -547,6 +558,7 @@ cooked_indexer::index_dies (cutu_reader *reader, const cooked_index_entry *this_parent_entry = parent_entry; bool is_enum_class = false; bool is_inlined = false; + std::optional<ULONGEST> signature; /* The scope of a DW_TAG_entry_point cooked_index_entry is the one of its surrounding subroutine. */ @@ -556,7 +568,7 @@ cooked_indexer::index_dies (cutu_reader *reader, = scan_attributes (reader->cu ()->per_cu, reader, info_ptr, info_ptr, abbrev, &name, &linkage_name, &flags, &sibling, &this_parent_entry, &defer, &is_enum_class, - &is_inlined, false); + &is_inlined, &signature, false); /* A DW_TAG_entry_point inherits its static/extern property from the enclosing subroutine. */ if (abbrev->tag == DW_TAG_entry_point) @@ -611,19 +623,33 @@ cooked_indexer::index_dies (cutu_reader *reader, cooked_index_entry *this_entry = nullptr; /* Always use the reader's CU for the entry CU. */ dwarf2_per_cu *cu_for_entry = reader->cu ()->per_cu; - if (name != nullptr) + + /* If scan_attributes failed to find a name then we might still + want to create an entry if we think we might later find a + name via the signature. In that case the entry holds the + signature until the name is resolved. */ + if (name != nullptr || signature.has_value ()) { + cooked_index_flag entry_flags = flags; + cooked_index_entry_name_ref name_ref = name; + cooked_index_entry_ref parent_ref = this_parent_entry; + + if (name == nullptr) + { + name_ref = signature.value (); + entry_flags |= IS_NAME_DEFERRED; + } + if (defer != 0) - this_entry - = m_index_storage->add (this_die, abbrev->tag, - flags | IS_PARENT_DEFERRED, - m_language, name, - defer, cu_for_entry); - else - this_entry - = m_index_storage->add (this_die, abbrev->tag, flags, - m_language, name, - this_parent_entry, cu_for_entry); + { + parent_ref = defer; + entry_flags |= IS_PARENT_DEFERRED; + } + + this_entry + = m_index_storage->add (this_die, abbrev->tag, entry_flags, + m_language, name_ref, parent_ref, + cu_for_entry); } else if (this_parent_entry != nullptr) { @@ -637,6 +663,14 @@ cooked_indexer::index_dies (cutu_reader *reader, m_die_range_map->add_entry (addr, addr, this_parent_entry); } + /* If THIS_DIE is the primary type within a TU, and has a valid + name, then add an entry mapping the signature to the name. */ + if (const signatured_type *st = cu_for_entry->as_signatured_type (); + name != nullptr + && st != nullptr + && this_die == st->type_offset_in_section) + m_index_storage->add_signatured_type_name (st->signature, name); + if (linkage_name != nullptr) { /* We only want this to be "main" if it has a linkage name diff --git a/gdb/dwarf2/cooked-indexer.h b/gdb/dwarf2/cooked-indexer.h index 563742c90f6a..393ef9fca5ac 100644 --- a/gdb/dwarf2/cooked-indexer.h +++ b/gdb/dwarf2/cooked-indexer.h @@ -86,6 +86,7 @@ class cooked_indexer parent_map::addr_type *maybe_defer, bool *is_enum_class, bool *is_inlined, + std::optional<ULONGEST> *signature, bool for_specification); /* Handle DW_TAG_imported_unit, by scanning the DIE to find diff --git a/gdb/dwarf2/index-write.c b/gdb/dwarf2/index-write.c index 2b92b2cae472..4712f08515c7 100644 --- a/gdb/dwarf2/index-write.c +++ b/gdb/dwarf2/index-write.c @@ -697,7 +697,7 @@ class debug_names if ((entry->flags & IS_SYNTHESIZED) != 0) return; - m_name_to_value_set[entry->name].emplace_back (entry); + m_name_to_value_set[entry->name ()].emplace_back (entry); } /* Build all the tables. All symbols must be already inserted. diff --git a/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp new file mode 100644 index 000000000000..39a221a1b755 --- /dev/null +++ b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp @@ -0,0 +1,155 @@ +# 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/>. + +# The test sets up a type DIE without a DW_AT_name, the type DIE links +# to a type within a TU via DW_AT_signature. Normally GDB would +# use the DW_AT_signature to find the name of the type DIE. +# +# However, in this test there is no type DIE with the specified +# signature! +# +# This test checks that GDB can emit a complaint when this situation is +# encountered. The test also checks that trying to find a method +# within the type using the qualified name will fail. + +load_lib dwarf.exp + +# This test can only be run on targets which support DWARF-2 and use gas. +require dwarf2_support + +standard_testfile main-foo.c .S + +# Build the test program using VERSION for the CU/TU DWARF version. +proc run_test { version } { + # Create the DWARF. + set asm_file [standard_output_file $::srcfile2] + Dwarf::assemble { + filename $asm_file + add_dummy_cus 0 + } { + upvar version version + + get_func_info foo + get_func_info main + + declare_labels method_decl + + tu { version $version } 0xdeadbeefdeadbeef the_type { + DW_TAG_type_unit { + DW_AT_language @DW_LANG_C_plus_plus + } { + the_type: DW_TAG_class_type { + DW_AT_name the_type + DW_AT_byte_size 1 sdata + } + } + } + + cu { version $version } { + compile_unit { + DW_AT_language @DW_LANG_C_plus_plus + } { + DW_TAG_class_type { + DW_AT_declaration 1 flag + DW_AT_signature 0xdeadbeef01234567 ref_sig8 + } { + method_decl: DW_TAG_subprogram { + DW_AT_name method + DW_AT_linkage_name _ZN8the_type6methodEv + DW_AT_declaration 1 flag + } + } + + DW_TAG_subprogram { + DW_AT_specification %$method_decl + DW_AT_low_pc $foo_start DW_FORM_addr + DW_AT_high_pc $foo_end DW_FORM_addr + } + + DW_TAG_subprogram { + DW_AT_name main + DW_AT_low_pc $main_start DW_FORM_addr + DW_AT_high_pc $main_end DW_FORM_addr + } + } + } + } + + set testfile_name ${::testfile}-${version} + set binfile_name [standard_output_file $testfile_name] + if { [build_executable "failed to build" $testfile_name \ + [list $asm_file $::srcfile] {nodebug}] } { + return + } + + clean_restart $testfile_name + + set expect_complaint true + # If we have a .gdb_index already then the part of this test that + # checks for a warning when loading the executable is not going to + # work as the warning will have already been generated when the + # index was added. + if {[get_index_type $testfile_name] == "gdb"} { + set expect_complaint false + } + + clean_restart + + set host_binfile [gdb_remote_download host $binfile_name] + if { [is_remote host] } { + # For some remote host boards gdb_remote_download returns an + # absolute path, but for others it returns a relative path. + # The path reported in the complaint message is always + # absolute. Handle this with an optional prefix pattern. + set binfile_re "\[^\r\n\]*[string_to_regexp $host_binfile]" + } elseif {[section_get $binfile_name ".gnu_debuglink"] ne ""} { + set binfile_re "\[^\r\n\]+[string_to_regexp ${testfile_name}.debug]" + } else { + set binfile_re [string_to_regexp $binfile_name] + } + + # Load the executable and check we see the expected complaint from + # the DWARF indexer. + gdb_test_no_output "maint set dwarf synchronous on" + gdb_test_no_output "set complaints 100" + set saw_complaint false + gdb_test_multiple "file $host_binfile" "file command" -lbl { + -re "^\r\nDuring symbol reading: DWARF Error: Cannot find signatured DIE 0xdeadbeef01234567 referenced from DIE at $::hex \\\[in module $binfile_re\\\](?=\r\n)" { + set saw_complaint true + exp_continue + } + -re "^\r\n$::gdb_prompt $" { + gdb_assert { $saw_complaint == $expect_complaint } $gdb_test_name + } + } + + if { ![readnow] } { + # Check that GDB is unable to find this via the index. If we + # load the full symbols then GDB is able to resolve this print + # via the linkage name. + # + # It is unfortunate that there is a difference in behaviour + # depending on whether GDB is using the index or not, but + # remember, the DWARF is corrupted at this point, so it's + # probably OK if things don't fully work, so long as GDB + # doesn't crash. + gdb_test "print the_type::method" \ + "^There is no field named method" + } +} + +foreach_with_prefix version { 4 5 } { + run_test $version +} diff --git a/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-dwo.exp b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-dwo.exp new file mode 100644 index 000000000000..bc6420d7aa9d --- /dev/null +++ b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-dwo.exp @@ -0,0 +1,143 @@ +# 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/>. + +# Like sig-type-unnamed-class.exp, but with split DWARF. The type +# unit in the DWO file is skeletonless, which is the norm when using +# split DWARF. +# +# Check that the cooked index correctly resolves the parent of a member +# function when the class declaration stub in the DWO compile unit has +# DW_AT_signature but no DW_AT_name, and the referenced type unit is +# skeletonless. + +load_lib dwarf.exp + +# This test can only be run on targets which support DWARF-2 and use gas. +require dwarf2_support + +standard_testfile main-foo.c -dw.S + +# Build the test program using VERSION for the CU/TU DWARF version. + +proc run_test { version } { + set asm_file [standard_output_file $::srcfile2] + + # Setup some state based on DWARF version. + if { $version == 5 } { + set dwo_cu_opts [list fission 1 version 5 dwo_id 0xF00D] + set skel_cu_opts [list version 5 dwo_id 0xF00D] + set dwo_name_attr DW_AT_dwo_name + } else { + set dwo_cu_opts [list fission 1 version 4] + set skel_cu_opts [list version 4] + set dwo_name_attr DW_AT_GNU_dwo_name + } + + set testfile_name ${::testfile}-${version} + + Dwarf::assemble $asm_file { + upvar version version + upvar dwo_cu_opts dwo_cu_opts + upvar skel_cu_opts skel_cu_opts + upvar dwo_name_attr dwo_name_attr + upvar testfile_name testfile_name + + declare_labels method_decl + + get_func_info foo + + # Type unit in the DWO file. This is skeletonless: there is no + # corresponding skeleton TU in the main file. + tu { + fission 1 + version $version + } 0xdeadbeef01234567 the_type { + type_unit { + DW_AT_language @DW_LANG_C_plus_plus + } { + the_type: DW_TAG_class_type { + DW_AT_name the_type + DW_AT_byte_size 1 DW_FORM_sdata + } + } + } + + set debug_addr_base [debug_addr_label] + + # Compile unit in the DWO file. Contains a class declaration + # stub with DW_AT_signature but no DW_AT_name, replicating what + # Clang emits with -fdebug-types-section. The class name must + # be resolved by following the signature to the type unit. + cu $dwo_cu_opts { + compile_unit { + DW_AT_language @DW_LANG_C_plus_plus + DW_AT_name ${::srcfile} + DW_AT_comp_dir . + if { $version == 4 } { + DW_AT_GNU_dwo_id 0xF00D DW_FORM_data8 + } + } { + DW_TAG_class_type { + DW_AT_declaration 1 DW_FORM_flag + DW_AT_signature 0xdeadbeef01234567 DW_FORM_ref_sig8 + } { + method_decl: DW_TAG_subprogram { + DW_AT_name method + DW_AT_linkage_name _ZN8the_type6methodEv + DW_AT_declaration 1 DW_FORM_flag + } + } + + DW_TAG_subprogram { + DW_AT_specification :$method_decl + DW_AT_low_pc $foo_start DW_FORM_GNU_addr_index + DW_AT_high_pc $foo_end DW_FORM_GNU_addr_index + } + } + } + + # Skeleton CU in the main file. + cu $skel_cu_opts { + compile_unit { + $dwo_name_attr ${testfile_name}-dw.dwo DW_FORM_strp + DW_AT_comp_dir . + DW_AT_GNU_addr_base $debug_addr_base + if { $version == 4 } { + DW_AT_GNU_dwo_id 0xF00D DW_FORM_data8 + } + } {} + } + } + + set binfile_name [standard_output_file $testfile_name] + set obj [standard_output_file "${testfile_name}-dw.o"] + if {[build_executable_and_dwo_files "build exec and dwo" $binfile_name \ + {nodebug} \ + [list $asm_file {nodebug split-dwo} $obj] \ + [list $::srcfile {nodebug}]]} { + return + } + + clean_restart $testfile_name + + # Check that GDB was able to find the parent for "method", and as + # a result, can correctly find this field of "the_type". + gdb_test "print the_type::method" \ + [string_to_regexp " <the_type::method()>"] +} + +foreach_with_prefix version { 4 5 } { + run_test $version +} diff --git a/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp new file mode 100644 index 000000000000..6f39e2c823d5 --- /dev/null +++ b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp @@ -0,0 +1,106 @@ +# 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/>. + +# Check that the cooked index correctly resolves the parent of a member +# function when the class declaration stub in the compile unit has +# DW_AT_signature but no DW_AT_name. +# +# This replicates what Clang emits with -fdebug-types-section. The +# compile unit contains an unnamed DW_TAG_class_type declaration with +# only DW_AT_declaration, DW_AT_signature, and child member function +# declarations. +# +# The class name must be resolved by following the signature to the +# type unit. Without this, the member function definitions (which use +# DW_AT_specification to point at the child declarations) end up with +# no parent, and qualified lookup fails. + +load_lib dwarf.exp + +# This test can only be run on targets which support DWARF-2 and use gas. +require dwarf2_support + +standard_testfile main-foo.c .S + +# Build the test program using VERSION for the CU/TU DWARF version. +proc run_test { version } { + # Create the DWARF. + set asm_file [standard_output_file $::srcfile2] + Dwarf::assemble { + filename $asm_file + add_dummy_cus 0 + } { + upvar version version + + get_func_info foo + get_func_info main + + declare_labels method_decl + + tu { version $version } 0xdeadbeef01234567 the_type { + DW_TAG_type_unit { + DW_AT_language @DW_LANG_C_plus_plus + } { + the_type: DW_TAG_class_type { + DW_AT_name the_type + DW_AT_byte_size 1 sdata + } + } + } + + cu { version $version } { + compile_unit { + DW_AT_language @DW_LANG_C_plus_plus + } { + DW_TAG_class_type { + DW_AT_declaration 1 flag + DW_AT_signature 0xdeadbeef01234567 ref_sig8 + } { + method_decl: DW_TAG_subprogram { + DW_AT_name method + DW_AT_linkage_name _ZN8the_type6methodEv + DW_AT_declaration 1 flag + } + } + + DW_TAG_subprogram { + DW_AT_specification %$method_decl + DW_AT_low_pc $foo_start DW_FORM_addr + DW_AT_high_pc $foo_end DW_FORM_addr + } + + DW_TAG_subprogram { + DW_AT_name main + DW_AT_low_pc $main_start DW_FORM_addr + DW_AT_high_pc $main_end DW_FORM_addr + } + } + } + } + + if { [prepare_for_testing "failed to prepare" ${::testfile}-${version} \ + [list $asm_file $::srcfile] {nodebug}] } { + return + } + + # Check that GDB was able to find the parent for "method", and as + # a result, can correctly find this field of "the_type". + gdb_test "print the_type::method" \ + [string_to_regexp " <the_type::method()>"] +} + +foreach_with_prefix version { 4 5 } { + run_test $version +} -- 2.55.0 ^ permalink raw reply [flat|nested] 36+ messages in thread
* [PATCH v6 3/3] gdb/dwarf: add cooked_index_entry::parent_is_deferred 2026-09-24 15:09 ` [PATCH v6 0/3] gdb/dwarf: " Simon Marchi 2026-09-24 15:09 ` [PATCH v6 1/3] gdb/dwarf: split cooked index finalization into separate steps Simon Marchi 2026-09-24 15:09 ` [PATCH v6 2/3] gdb/dwarf: resolve class name via DW_AT_signature in cooked index Simon Marchi @ 2026-09-24 15:09 ` Simon Marchi 2026-09-24 20:53 ` Andrew Burgess 2 siblings, 1 reply; 36+ messages in thread From: Simon Marchi @ 2026-09-24 15:09 UTC (permalink / raw) To: gdb-patches; +Cc: Simon Marchi Add a parent_is_deferred method to cooked_index_entry, mirroring name_is_deferred, and use it instead of checkingthe IS_PARENT_DEFERRED flag directly. Change-Id: Id684b30ec8aa81ac554e5c3edb8f7197c7238469 --- gdb/dwarf2/cooked-index-entry.h | 12 ++++++++---- gdb/dwarf2/cooked-index-shard.c | 10 +++++----- gdb/dwarf2/cooked-index.c | 2 +- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/gdb/dwarf2/cooked-index-entry.h b/gdb/dwarf2/cooked-index-entry.h index 2637b8255258..462ec0640a9b 100644 --- a/gdb/dwarf2/cooked-index-entry.h +++ b/gdb/dwarf2/cooked-index-entry.h @@ -224,17 +224,21 @@ struct cooked_index_entry : public allocate_on_obstack<cooked_index_entry> return compare (canonical, other.canonical, SORT) < 0; } + /* Return true if this entry's parent still has to be resolved. */ + bool parent_is_deferred () const + { return (flags & IS_PARENT_DEFERRED) != 0; } + /* Set parent entry to PARENT. */ void set_parent (const cooked_index_entry *parent) { - gdb_assert ((flags & IS_PARENT_DEFERRED) == 0); + gdb_assert (!parent_is_deferred ()); m_parent_entry.resolved = parent; } /* Resolve deferred parent entry to PARENT. */ void resolve_parent (const cooked_index_entry *parent) { - gdb_assert ((flags & IS_PARENT_DEFERRED) != 0); + gdb_assert (parent_is_deferred ()); flags = flags & ~IS_PARENT_DEFERRED; m_parent_entry.resolved = parent; } @@ -242,14 +246,14 @@ struct cooked_index_entry : public allocate_on_obstack<cooked_index_entry> /* Return parent entry. */ const cooked_index_entry *get_parent () const { - gdb_assert ((flags & IS_PARENT_DEFERRED) == 0); + gdb_assert (!parent_is_deferred ()); return m_parent_entry.resolved; } /* Return deferred parent entry. */ parent_map::addr_type get_deferred_parent () const { - gdb_assert ((flags & IS_PARENT_DEFERRED) != 0); + gdb_assert (parent_is_deferred ()); return m_parent_entry.deferred; } diff --git a/gdb/dwarf2/cooked-index-shard.c b/gdb/dwarf2/cooked-index-shard.c index af8c3e9527ac..206fe4483a40 100644 --- a/gdb/dwarf2/cooked-index-shard.c +++ b/gdb/dwarf2/cooked-index-shard.c @@ -85,7 +85,7 @@ cooked_index_shard::add (sect_offset die_offset, enum dwarf_tag tag, parent_entry, per_cu); m_entries.push_back (result); - if ((flags & IS_PARENT_DEFERRED) != 0) + if (result->parent_is_deferred ()) m_have_deferred_parents = true; if (result->name_is_deferred ()) @@ -109,8 +109,8 @@ cooked_index_shard::add (sect_offset die_offset, enum dwarf_tag tag, by language_may_use_plain_main as well), but it's handy as a spot to document. */ else if (lang != language_unknown - && (flags & IS_PARENT_DEFERRED) == 0 - && parent_entry.resolved == nullptr + && !result->parent_is_deferred () + && result->get_parent () == nullptr && m_main == nullptr && language_may_use_plain_main (lang) && !result->name_is_deferred () @@ -238,7 +238,7 @@ cooked_index_shard::resolve_deferred_parents gdb_assert (m_have_deferred_parents); for (cooked_index_entry *entry : m_entries) - if ((entry->flags & IS_PARENT_DEFERRED) != 0) + if (entry->parent_is_deferred ()) { const cooked_index_entry *new_parent = parent_maps->find (entry->get_deferred_parent ()); @@ -308,7 +308,7 @@ cooked_index_shard::canonicalize_names () for (cooked_index_entry *entry : m_entries) { /* Deferred parents should not reach this point. */ - gdb_assert ((entry->flags & IS_PARENT_DEFERRED) == 0); + gdb_assert (!entry->parent_is_deferred ()); /* Entries without a name are filtered out during the call to prune_nameless_entries. */ diff --git a/gdb/dwarf2/cooked-index.c b/gdb/dwarf2/cooked-index.c index 96996ebc4603..89a93eff6998 100644 --- a/gdb/dwarf2/cooked-index.c +++ b/gdb/dwarf2/cooked-index.c @@ -391,7 +391,7 @@ cooked_index::dump (gdbarch *arch) gdb_printf (" DIE offset: %s\n", sect_offset_str (entry->die_offset)); gdb_printf (" CU index: %u\n", entry->per_cu->index); - if ((entry->flags & IS_PARENT_DEFERRED) != 0) + if (entry->parent_is_deferred ()) gdb_printf (" parent: deferred (%" PRIx64 ")\n", entry->get_deferred_parent ()); else if (entry->get_parent () != nullptr) -- 2.55.0 ^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v6 3/3] gdb/dwarf: add cooked_index_entry::parent_is_deferred 2026-09-24 15:09 ` [PATCH v6 3/3] gdb/dwarf: add cooked_index_entry::parent_is_deferred Simon Marchi @ 2026-09-24 20:53 ` Andrew Burgess 2026-09-25 2:34 ` Simon Marchi 0 siblings, 1 reply; 36+ messages in thread From: Andrew Burgess @ 2026-09-24 20:53 UTC (permalink / raw) To: Simon Marchi, gdb-patches; +Cc: Simon Marchi This looks good to me. But because I have to find something to earn my pay... Simon Marchi <simon.marchi@efficios.com> writes: > Add a parent_is_deferred method to cooked_index_entry, mirroring > name_is_deferred, and use it instead of checkingthe IS_PARENT_DEFERRED ... missing space in 'checkingthe'. But otherwise, LGTM: Approved-By: Andrew Burgess <aburgess@redhat.com> thanks, Andrew > flag directly. > > Change-Id: Id684b30ec8aa81ac554e5c3edb8f7197c7238469 > --- > gdb/dwarf2/cooked-index-entry.h | 12 ++++++++---- > gdb/dwarf2/cooked-index-shard.c | 10 +++++----- > gdb/dwarf2/cooked-index.c | 2 +- > 3 files changed, 14 insertions(+), 10 deletions(-) > > diff --git a/gdb/dwarf2/cooked-index-entry.h b/gdb/dwarf2/cooked-index-entry.h > index 2637b8255258..462ec0640a9b 100644 > --- a/gdb/dwarf2/cooked-index-entry.h > +++ b/gdb/dwarf2/cooked-index-entry.h > @@ -224,17 +224,21 @@ struct cooked_index_entry : public allocate_on_obstack<cooked_index_entry> > return compare (canonical, other.canonical, SORT) < 0; > } > > + /* Return true if this entry's parent still has to be resolved. */ > + bool parent_is_deferred () const > + { return (flags & IS_PARENT_DEFERRED) != 0; } > + > /* Set parent entry to PARENT. */ > void set_parent (const cooked_index_entry *parent) > { > - gdb_assert ((flags & IS_PARENT_DEFERRED) == 0); > + gdb_assert (!parent_is_deferred ()); > m_parent_entry.resolved = parent; > } > > /* Resolve deferred parent entry to PARENT. */ > void resolve_parent (const cooked_index_entry *parent) > { > - gdb_assert ((flags & IS_PARENT_DEFERRED) != 0); > + gdb_assert (parent_is_deferred ()); > flags = flags & ~IS_PARENT_DEFERRED; > m_parent_entry.resolved = parent; > } > @@ -242,14 +246,14 @@ struct cooked_index_entry : public allocate_on_obstack<cooked_index_entry> > /* Return parent entry. */ > const cooked_index_entry *get_parent () const > { > - gdb_assert ((flags & IS_PARENT_DEFERRED) == 0); > + gdb_assert (!parent_is_deferred ()); > return m_parent_entry.resolved; > } > > /* Return deferred parent entry. */ > parent_map::addr_type get_deferred_parent () const > { > - gdb_assert ((flags & IS_PARENT_DEFERRED) != 0); > + gdb_assert (parent_is_deferred ()); > return m_parent_entry.deferred; > } > > diff --git a/gdb/dwarf2/cooked-index-shard.c b/gdb/dwarf2/cooked-index-shard.c > index af8c3e9527ac..206fe4483a40 100644 > --- a/gdb/dwarf2/cooked-index-shard.c > +++ b/gdb/dwarf2/cooked-index-shard.c > @@ -85,7 +85,7 @@ cooked_index_shard::add (sect_offset die_offset, enum dwarf_tag tag, > parent_entry, per_cu); > m_entries.push_back (result); > > - if ((flags & IS_PARENT_DEFERRED) != 0) > + if (result->parent_is_deferred ()) > m_have_deferred_parents = true; > > if (result->name_is_deferred ()) > @@ -109,8 +109,8 @@ cooked_index_shard::add (sect_offset die_offset, enum dwarf_tag tag, > by language_may_use_plain_main as well), but it's handy as a spot > to document. */ > else if (lang != language_unknown > - && (flags & IS_PARENT_DEFERRED) == 0 > - && parent_entry.resolved == nullptr > + && !result->parent_is_deferred () > + && result->get_parent () == nullptr > && m_main == nullptr > && language_may_use_plain_main (lang) > && !result->name_is_deferred () > @@ -238,7 +238,7 @@ cooked_index_shard::resolve_deferred_parents > gdb_assert (m_have_deferred_parents); > > for (cooked_index_entry *entry : m_entries) > - if ((entry->flags & IS_PARENT_DEFERRED) != 0) > + if (entry->parent_is_deferred ()) > { > const cooked_index_entry *new_parent > = parent_maps->find (entry->get_deferred_parent ()); > @@ -308,7 +308,7 @@ cooked_index_shard::canonicalize_names () > for (cooked_index_entry *entry : m_entries) > { > /* Deferred parents should not reach this point. */ > - gdb_assert ((entry->flags & IS_PARENT_DEFERRED) == 0); > + gdb_assert (!entry->parent_is_deferred ()); > > /* Entries without a name are filtered out during the call to > prune_nameless_entries. */ > diff --git a/gdb/dwarf2/cooked-index.c b/gdb/dwarf2/cooked-index.c > index 96996ebc4603..89a93eff6998 100644 > --- a/gdb/dwarf2/cooked-index.c > +++ b/gdb/dwarf2/cooked-index.c > @@ -391,7 +391,7 @@ cooked_index::dump (gdbarch *arch) > gdb_printf (" DIE offset: %s\n", sect_offset_str (entry->die_offset)); > gdb_printf (" CU index: %u\n", entry->per_cu->index); > > - if ((entry->flags & IS_PARENT_DEFERRED) != 0) > + if (entry->parent_is_deferred ()) > gdb_printf (" parent: deferred (%" PRIx64 ")\n", > entry->get_deferred_parent ()); > else if (entry->get_parent () != nullptr) > -- > 2.55.0 ^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v6 3/3] gdb/dwarf: add cooked_index_entry::parent_is_deferred 2026-09-24 20:53 ` Andrew Burgess @ 2026-09-25 2:34 ` Simon Marchi 0 siblings, 0 replies; 36+ messages in thread From: Simon Marchi @ 2026-09-25 2:34 UTC (permalink / raw) To: Andrew Burgess, gdb-patches On 9/24/26 4:53 PM, Andrew Burgess wrote: > > This looks good to me. But because I have to find something to earn my > pay... > > Simon Marchi <simon.marchi@efficios.com> writes: > >> Add a parent_is_deferred method to cooked_index_entry, mirroring >> name_is_deferred, and use it instead of checkingthe IS_PARENT_DEFERRED > > ... missing space in 'checkingthe'. Fixed. > But otherwise, LGTM: > > Approved-By: Andrew Burgess <aburgess@redhat.com> Thanks, will push to master and gdb-18-branch. Simon ^ permalink raw reply [flat|nested] 36+ messages in thread
* [PATCH 1/2] gdb: split cooked index finalization into separate steps 2026-09-16 11:38 ` [PATCHv4] " Andrew Burgess 2026-09-22 4:26 ` Simon Marchi 2026-09-24 4:59 ` [PATCH 0/2] " Simon Marchi @ 2026-09-24 4:59 ` Simon Marchi 2026-09-24 10:16 ` Andrew Burgess 2026-09-24 4:59 ` [PATCH 2/2] gdb: resolve class name via DW_AT_signature in cooked index Simon Marchi 3 siblings, 1 reply; 36+ messages in thread From: Simon Marchi @ 2026-09-24 4:59 UTC (permalink / raw) To: gdb-patches; +Cc: Simon Marchi This patch refactors the existing cooked index "finalize" step into multiple steps. This is a preparatory patch for the following patch, which will add some more steps. I believe that using this pattern will help keep the code readable and easy to understand, despite the increasing amount of work done. The following patch introduces a step that needs to run after the deferred parents have been resolved, but before the name canonicalization happens, so factor out the deferred parents code into a step of its own. The code that remains in cooked_index_shard::finalize is all about name canonicalization (if we consider the Ada stuff to be a special case of that), so rename that step to cooked_index_shard::canonicalize_names(). For clarity, move the "write to cache" code out of the "done" callback to make it clear that it is logically a separate step. So as of this patch, we have the steps: - resolve deferred parents - canonicalize names - write to cache The term "finalization" now refers to all these steps. Or, depending on your definition, it could refer to all but "write to cache", since the index reaches the FINALIZED state (i.e. becomes fully usable) just before that one. Here are the implementation details: - The first step is called by cooked_index::set_contents(), then each step is responsible for calling the next one (thus forming a chain), until we are done. - Steps that involve per-shard parallel processing use gdb::task_group, the "done" callback of the task_group starts the next task. A special case of that is: if some steps are able to detect ahead of time that it's unnecessary to run the step for a given shard, they can omit adding a task for that shard. An even more special case of that is: if no tasks are added to a task group, the "done" callback for that task group is invoked immediately when the group is started. This allows easily and cheaply skipping entire steps when they are not needed. This is used in this patch for the new "resolve deferred parents" step. Each shard records whether it has at least one deferred parent during the initial scan. If a shard has none (which is a common case), then cooked_index_shard::resolve_deferred_parents would iterate the index entries for nothing. The step therefore omits adding a task for that shard in that case. If no shards have any deferred parents that require resolving, we go directly to the next step with very little cost. During review, Claude pointed out that my change actually fixes a potential problem. The current code resolves the deferred parent of an entry in the same loop where it potentially calls "full_name" for that entry (to create the special Ada entries). "full_name" walks up the parent chain, and thus requires all grand-parents to be resolved. Depending on the order of DIEs in the file, it might not be true. Having the "resolve deferred parents" step run completely before the name canonicalization step avoids this. I built a gdb.dwarf2 test case around the DWARF structure it proposed to hit the bug. It is perhaps not something we expect from real-world producers, but it's also not completely unthinkable. It hits this assert when the patch is not applied: gdb/dwarf2/cooked-index-entry.h:217: internal-error: get_parent: Assertion `(flags & IS_PARENT_DEFERRED) == 0' failed. ... and passes with it. Change-Id: I1cf5e181ecd2d7399a1e885fe686e7e76a5419cb --- gdb/dwarf2/cooked-index-entry.c | 2 +- gdb/dwarf2/cooked-index-shard.c | 29 +++++-- gdb/dwarf2/cooked-index-shard.h | 22 +++-- gdb/dwarf2/cooked-index.c | 82 ++++++++++++++----- gdb/dwarf2/cooked-index.h | 36 ++++++-- .../ada-forward-spec-deferred-grandparent.exp | 81 ++++++++++++++++++ 6 files changed, 209 insertions(+), 43 deletions(-) create mode 100644 gdb/testsuite/gdb.dwarf2/ada-forward-spec-deferred-grandparent.exp diff --git a/gdb/dwarf2/cooked-index-entry.c b/gdb/dwarf2/cooked-index-entry.c index 21e25e946158..8c324f77904b 100644 --- a/gdb/dwarf2/cooked-index-entry.c +++ b/gdb/dwarf2/cooked-index-entry.c @@ -194,7 +194,7 @@ cooked_index_entry::full_name (struct obstack *storage, of writing), then we need to compute the linkage name here. However for traditional GNAT, the linkage name will be in 'name'. Detect this by looking for "__"; see also - cooked_index_shard::finalize. */ + cooked_index_shard::canonicalize_names. */ if ((name_flags & FOR_ADA_LINKAGE_NAME) != 0) { if (strstr (name, "__") != nullptr) diff --git a/gdb/dwarf2/cooked-index-shard.c b/gdb/dwarf2/cooked-index-shard.c index 91dc9ab89455..a69b29faa62f 100644 --- a/gdb/dwarf2/cooked-index-shard.c +++ b/gdb/dwarf2/cooked-index-shard.c @@ -82,6 +82,9 @@ cooked_index_shard::add (sect_offset die_offset, enum dwarf_tag tag, parent_entry, per_cu); m_entries.push_back (result); + if ((flags & IS_PARENT_DEFERRED) != 0) + m_have_deferred_parents = true; + /* An explicitly-tagged main program should always override the implicit "main" discovery. */ if ((flags & IS_MAIN) != 0) @@ -190,7 +193,24 @@ struct cooked_index_entry_name_ptr_eq /* See cooked-index-shard.h. */ void -cooked_index_shard::finalize (const parent_map_map *parent_maps) +cooked_index_shard::resolve_deferred_parents + (const parent_map_map *parent_maps) +{ + gdb_assert (m_have_deferred_parents); + + for (cooked_index_entry *entry : m_entries) + if ((entry->flags & IS_PARENT_DEFERRED) != 0) + { + const cooked_index_entry *new_parent + = parent_maps->find (entry->get_deferred_parent ()); + entry->resolve_parent (new_parent); + } +} + +/* See cooked-index-shard.h. */ + +void +cooked_index_shard::canonicalize_names () { gdb::unordered_set<const cooked_index_entry *, cooked_index_entry_name_ptr_hash, @@ -216,13 +236,6 @@ cooked_index_shard::finalize (const parent_map_map *parent_maps) for (cooked_index_entry *entry : m_entries) { - if ((entry->flags & IS_PARENT_DEFERRED) != 0) - { - const cooked_index_entry *new_parent - = parent_maps->find (entry->get_deferred_parent ()); - entry->resolve_parent (new_parent); - } - /* Note that this code must be kept in sync with cooked_index::get_main -- if canonicalization is required here, then a check might be required there. */ diff --git a/gdb/dwarf2/cooked-index-shard.h b/gdb/dwarf2/cooked-index-shard.h index 84c37958c833..9b9ffa7a76e3 100644 --- a/gdb/dwarf2/cooked-index-shard.h +++ b/gdb/dwarf2/cooked-index-shard.h @@ -118,22 +118,34 @@ class cooked_index_shard (cooked_index_entry *entry, htab_t gnat_entries, std::vector<cooked_index_entry *> &new_entries); - /* Finalize the index. This should be called a single time, when - the index has been fully populated. It enters all the entries - into the internal table and fixes up all missing parent links. - This may be invoked in a worker thread. */ - void finalize (const parent_map_map *parent_maps); + /* Use PARENT_MAPS to resolve the deferred parent links of entries in this + shard. */ + void resolve_deferred_parents (const parent_map_map *parent_maps); + + /* Compute the canonical name for the entries in this shard. + + Due to how Ada name lookups work, this function may also create new index + entries with full names. */ + void canonicalize_names (); /* Storage for the entries. */ auto_obstack m_storage; + /* List of all entries. */ std::vector<cooked_index_entry *> m_entries; + /* If we found an entry with 'is_main' set, store it here. */ cooked_index_entry *m_main = nullptr; + /* The addrmap. This maps address ranges to dwarf2_per_cu objects. */ addrmap_fixed *m_addrmap = nullptr; + /* Storage for canonical names. */ gdb::string_set m_names; + + /* True if at least one entry in this shard has a parent link that requires + deferred resolution. */ + bool m_have_deferred_parents = false; }; using cooked_index_shard_up = std::unique_ptr<cooked_index_shard>; diff --git a/gdb/dwarf2/cooked-index.c b/gdb/dwarf2/cooked-index.c index 167e39ffc89a..f834f054da0e 100644 --- a/gdb/dwarf2/cooked-index.c +++ b/gdb/dwarf2/cooked-index.c @@ -74,31 +74,73 @@ cooked_index::set_contents () m_state->set (cooked_state::MAIN_AVAILABLE); - /* This is run after finalization is done -- but not before. If - this task were submitted earlier, it would have to wait for - finalization. However, that would take a slot in the global - thread pool, and if enough such tasks were submitted at once, it - would cause a livelock. */ - gdb::task_group finalizers ([this] () + /* Start the first step of index finalization. */ + this->start_resolve_deferred_parents (); +} + +/* See cooked-index.h. */ + +void +cooked_index::start_resolve_deferred_parents () +{ + gdb::task_group group ([this] () { - m_state->set (cooked_state::FINALIZED); - m_state->write_to_cache (index_for_writing ()); - m_state->set (cooked_state::CACHE_DONE); + this->start_canonicalize_names (); }); - for (auto &shard : m_shards) + /* Arrange to call resolve_deferred_parents on each shard that has at least + one deferred parent link. */ + for (const cooked_index_shard_up &shard : m_shards) { - auto this_shard = shard.get (); - const parent_map_map *parent_maps = m_state->get_parent_map_map (); - finalizers.add_task ([this, this_shard, parent_maps] () - { - scoped_time_it time_it ("DWARF finalize worker", - m_state->m_per_command_time); - this_shard->finalize (parent_maps); - }); + if (!shard->m_have_deferred_parents) + continue; + + group.add_task ([this, this_shard = shard.get ()] () + { + scoped_time_it time_it ("DWARF resolve deferred parents worker", + m_state->m_per_command_time); + + this_shard->resolve_deferred_parents (m_state->get_parent_map_map ()); + }); } - finalizers.start (); + group.start (); +} + +/* See cooked-index.h. */ + +void +cooked_index::start_canonicalize_names () +{ + gdb::task_group group ([this] () + { + /* The index is considered finalized (fully usable) at this point. */ + m_state->set (cooked_state::FINALIZED); + this->write_to_cache (); + }); + + /* Arrange to call canonicalize_names on each shard. */ + for (const cooked_index_shard_up &shard : m_shards) + { + group.add_task ([this, this_shard = shard.get ()] () + { + scoped_time_it time_it ("DWARF canonicalize names worker", + m_state->m_per_command_time); + + this_shard->canonicalize_names (); + }); + } + + group.start (); +} + +/* See cooked-index.h. */ + +void +cooked_index::write_to_cache () +{ + m_state->write_to_cache (index_for_writing ()); + m_state->set (cooked_state::CACHE_DONE); } cooked_index::~cooked_index () @@ -190,7 +232,7 @@ cooked_index::get_main () const if ((entry->flags & IS_MAIN) != 0) { /* This should be kept in sync with - cooked_index_shard::finalize. Note that there, C + cooked_index_shard::canonicalize_names. Note that there, C requires canonicalization -- but that is only for types, 'main' doesn't count. Similarly, C++ requires canonicalization, but again "main" is an diff --git a/gdb/dwarf2/cooked-index.h b/gdb/dwarf2/cooked-index.h index 2e177cb62dd2..758e399706ba 100644 --- a/gdb/dwarf2/cooked-index.h +++ b/gdb/dwarf2/cooked-index.h @@ -70,14 +70,17 @@ . | . if main thread calls... v . compute_main_name cooked_index::set_contents - . | / | \ - . v / | \ - . wait (MAIN_AVAILABLE) finalization - . | \ | / - . v \ | / - . done state = FINALIZED - . | - . v + . | | + . v v + . wait (MAIN_AVAILABLE) resolve deferred parents + . | | + . v v + . done canonicalize names + . | + . v + . state = FINALIZED + . | + . v . maybe write to index cache . state = CACHE_DONE . ~cooked_index_worker @@ -91,7 +94,10 @@ . | . v . use the index -*/ + + The steps between set_contents and FINALIZED can be thought of as the + "index finalization", where we fix up a number of things that couldn't + be done during the parallel scan. */ class cooked_index : public dwarf_scanner_base { @@ -170,6 +176,18 @@ class cooked_index : public dwarf_scanner_base { wait (cooked_state::CACHE_DONE); } private: + /* Start the "resolve deferred parents" step of index finalization. */ + void start_resolve_deferred_parents (); + + /* Start the "canonicalize names" step of index finalization. + + This step must run after "resolve deferred parents", because it depends on + the parents being set. */ + void start_canonicalize_names (); + + /* Execute the "write to cache" step at the end of index + finalization. */ + void write_to_cache (); /* The vector of cooked_index objects. This is stored because the entries are stored on the obstacks in those objects. */ diff --git a/gdb/testsuite/gdb.dwarf2/ada-forward-spec-deferred-grandparent.exp b/gdb/testsuite/gdb.dwarf2/ada-forward-spec-deferred-grandparent.exp new file mode 100644 index 000000000000..24a55e5fbae3 --- /dev/null +++ b/gdb/testsuite/gdb.dwarf2/ada-forward-spec-deferred-grandparent.exp @@ -0,0 +1,81 @@ +# 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/>. + +# This test reproduces a case that caused a crash in the DWARF indexer, where +# we were trying to compute the full name of an index entry whose parent's +# parent link was not yet resolved. + +load_lib dwarf.exp + +# This test can only be run on targets which support DWARF-2 and use gas. +require dwarf2_support + +standard_testfile main.c -debug.S + +set asm_file [standard_output_file $srcfile2] +Dwarf::assemble $asm_file { + declare_labels v_decl pkg_decl myint + + cu {} { + DW_TAG_compile_unit { + DW_AT_language @DW_LANG_Ada95 + } { + # The definition of variable "v". Its DW_AT_specification + # points forward, so its parent link is deferred. + # + # The crash would happen when the index attempted to create a + # "full name" entry for this variable. + DW_TAG_variable { + DW_AT_specification %$v_decl + DW_AT_location { + DW_OP_const1u 23 + DW_OP_stack_value + } SPECIAL_expr + } + + # The definition of package "pkg". Its DW_AT_specification + # points forward, so its parent link is also deferred. + DW_TAG_module { + DW_AT_specification %$pkg_decl + } { + v_decl: DW_TAG_variable { + DW_AT_name v + DW_AT_type :$myint + DW_AT_declaration 1 DW_FORM_flag_present + } + } + + pkg_decl: DW_TAG_module { + DW_AT_name pkg + DW_AT_declaration 1 DW_FORM_flag_present + } {} + + myint: DW_TAG_base_type { + DW_AT_byte_size 4 DW_FORM_sdata + DW_AT_encoding @DW_ATE_signed + DW_AT_name myint + } + } + } +} + +if {[build_executable "failed to build executable" ${testfile} \ + [list $srcfile $asm_file] {nodebug}]} { + return +} + +clean_restart $testfile + +gdb_test "with language ada -- print pkg.v" " = 23" -- 2.55.0 ^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH 1/2] gdb: split cooked index finalization into separate steps 2026-09-24 4:59 ` [PATCH 1/2] gdb: split cooked index finalization into separate steps Simon Marchi @ 2026-09-24 10:16 ` Andrew Burgess 2026-09-24 14:24 ` Simon Marchi 0 siblings, 1 reply; 36+ messages in thread From: Andrew Burgess @ 2026-09-24 10:16 UTC (permalink / raw) To: Simon Marchi, gdb-patches; +Cc: Simon Marchi Simon Marchi <simon.marchi@efficios.com> writes: > This patch refactors the existing cooked index "finalize" step into > multiple steps. This is a preparatory patch for the following patch, > which will add some more steps. I believe that using this pattern will > help keep the code readable and easy to understand, despite the > increasing amount of work done. > > The following patch introduces a step that needs to run after the > deferred parents have been resolved, but before the name > canonicalization happens, so factor out the deferred parents code into a > step of its own. > > The code that remains in cooked_index_shard::finalize is all about name > canonicalization (if we consider the Ada stuff to be a special case of > that), so rename that step to cooked_index_shard::canonicalize_names(). > > For clarity, move the "write to cache" code out of the "done" callback > to make it clear that it is logically a separate step. > > So as of this patch, we have the steps: > > - resolve deferred parents > - canonicalize names > - write to cache > > The term "finalization" now refers to all these steps. Or, depending on > your definition, it could refer to all but "write to cache", since the > index reaches the FINALIZED state (i.e. becomes fully usable) just > before that one. > > Here are the implementation details: > > - The first step is called by cooked_index::set_contents(), then each > step is responsible for calling the next one (thus forming a chain), > until we are done. > > - Steps that involve per-shard parallel processing use gdb::task_group, > the "done" callback of the task_group starts the next task. > > A special case of that is: if some steps are able to detect ahead of > time that it's unnecessary to run the step for a given shard, they > can omit adding a task for that shard. > > An even more special case of that is: if no tasks are added to a task > group, the "done" callback for that task group is invoked > immediately when the group is started. This allows easily and > cheaply skipping entire steps when they are not needed. > > This is used in this patch for the new "resolve deferred parents" > step. Each shard records whether it has at least one deferred parent > during the initial scan. If a shard has none (which is a common > case), then cooked_index_shard::resolve_deferred_parents would > iterate the index entries for nothing. The step therefore omits > adding a task for that shard in that case. If no shards have any > deferred parents that require resolving, we go directly to the next > step with very little cost. > > During review, Claude pointed out that my change actually fixes a > potential problem. The current code resolves the deferred parent of an > entry in the same loop where it potentially calls "full_name" for that > entry (to create the special Ada entries). "full_name" walks up the > parent chain, and thus requires all grand-parents to be resolved. > Depending on the order of DIEs in the file, it might not be true. > Having the "resolve deferred parents" step run completely before the > name canonicalization step avoids this. > > I built a gdb.dwarf2 test case around the DWARF structure it proposed to > hit the bug. It is perhaps not something we expect from real-world > producers, but it's also not completely unthinkable. It hits this > assert when the patch is not applied: > > gdb/dwarf2/cooked-index-entry.h:217: internal-error: get_parent: Assertion `(flags & IS_PARENT_DEFERRED) == 0' failed. > > ... and passes with it. Simon, Thanks for doing this. My reworked series (unposted) had a patch just like this, but your addition of the new test is a bonus I didn't have, so I think yours is better. I only had one minor suggestion from my take on this change, see below... > + > +void > +cooked_index_shard::canonicalize_names () > { > gdb::unordered_set<const cooked_index_entry *, > cooked_index_entry_name_ptr_hash, > @@ -216,13 +236,6 @@ cooked_index_shard::finalize (const parent_map_map *parent_maps) > > for (cooked_index_entry *entry : m_entries) > { > - if ((entry->flags & IS_PARENT_DEFERRED) != 0) > - { > - const cooked_index_entry *new_parent > - = parent_maps->find (entry->get_deferred_parent ()); > - entry->resolve_parent (new_parent); > - } > - I replaced this block with: /* Deferred parents should not reach this point. */ gdb_assert ((entry->flags & IS_PARENT_DEFERRED) == 0); I think the assert is worth keeping. With that: Approved-By: Andrew Burgess <aburgess@redhat.com> Thanks, Andrew ^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH 1/2] gdb: split cooked index finalization into separate steps 2026-09-24 10:16 ` Andrew Burgess @ 2026-09-24 14:24 ` Simon Marchi 0 siblings, 0 replies; 36+ messages in thread From: Simon Marchi @ 2026-09-24 14:24 UTC (permalink / raw) To: Andrew Burgess, gdb-patches On 9/24/26 6:16 AM, Andrew Burgess wrote: > Simon Marchi <simon.marchi@efficios.com> writes: > >> This patch refactors the existing cooked index "finalize" step into >> multiple steps. This is a preparatory patch for the following patch, >> which will add some more steps. I believe that using this pattern will >> help keep the code readable and easy to understand, despite the >> increasing amount of work done. >> >> The following patch introduces a step that needs to run after the >> deferred parents have been resolved, but before the name >> canonicalization happens, so factor out the deferred parents code into a >> step of its own. >> >> The code that remains in cooked_index_shard::finalize is all about name >> canonicalization (if we consider the Ada stuff to be a special case of >> that), so rename that step to cooked_index_shard::canonicalize_names(). >> >> For clarity, move the "write to cache" code out of the "done" callback >> to make it clear that it is logically a separate step. >> >> So as of this patch, we have the steps: >> >> - resolve deferred parents >> - canonicalize names >> - write to cache >> >> The term "finalization" now refers to all these steps. Or, depending on >> your definition, it could refer to all but "write to cache", since the >> index reaches the FINALIZED state (i.e. becomes fully usable) just >> before that one. >> >> Here are the implementation details: >> >> - The first step is called by cooked_index::set_contents(), then each >> step is responsible for calling the next one (thus forming a chain), >> until we are done. >> >> - Steps that involve per-shard parallel processing use gdb::task_group, >> the "done" callback of the task_group starts the next task. >> >> A special case of that is: if some steps are able to detect ahead of >> time that it's unnecessary to run the step for a given shard, they >> can omit adding a task for that shard. >> >> An even more special case of that is: if no tasks are added to a task >> group, the "done" callback for that task group is invoked >> immediately when the group is started. This allows easily and >> cheaply skipping entire steps when they are not needed. >> >> This is used in this patch for the new "resolve deferred parents" >> step. Each shard records whether it has at least one deferred parent >> during the initial scan. If a shard has none (which is a common >> case), then cooked_index_shard::resolve_deferred_parents would >> iterate the index entries for nothing. The step therefore omits >> adding a task for that shard in that case. If no shards have any >> deferred parents that require resolving, we go directly to the next >> step with very little cost. >> >> During review, Claude pointed out that my change actually fixes a >> potential problem. The current code resolves the deferred parent of an >> entry in the same loop where it potentially calls "full_name" for that >> entry (to create the special Ada entries). "full_name" walks up the >> parent chain, and thus requires all grand-parents to be resolved. >> Depending on the order of DIEs in the file, it might not be true. >> Having the "resolve deferred parents" step run completely before the >> name canonicalization step avoids this. >> >> I built a gdb.dwarf2 test case around the DWARF structure it proposed to >> hit the bug. It is perhaps not something we expect from real-world >> producers, but it's also not completely unthinkable. It hits this >> assert when the patch is not applied: >> >> gdb/dwarf2/cooked-index-entry.h:217: internal-error: get_parent: Assertion `(flags & IS_PARENT_DEFERRED) == 0' failed. >> >> ... and passes with it. > > Simon, > > Thanks for doing this. My reworked series (unposted) had a patch just > like this, but your addition of the new test is a bonus I didn't have, > so I think yours is better. I only had one minor suggestion from my > take on this change, see below... > >> + >> +void >> +cooked_index_shard::canonicalize_names () >> { >> gdb::unordered_set<const cooked_index_entry *, >> cooked_index_entry_name_ptr_hash, >> @@ -216,13 +236,6 @@ cooked_index_shard::finalize (const parent_map_map *parent_maps) >> >> for (cooked_index_entry *entry : m_entries) >> { >> - if ((entry->flags & IS_PARENT_DEFERRED) != 0) >> - { >> - const cooked_index_entry *new_parent >> - = parent_maps->find (entry->get_deferred_parent ()); >> - entry->resolve_parent (new_parent); >> - } >> - > > I replaced this block with: > > /* Deferred parents should not reach this point. */ > gdb_assert ((entry->flags & IS_PARENT_DEFERRED) == 0); > > I think the assert is worth keeping. With that: > > Approved-By: Andrew Burgess <aburgess@redhat.com> Good idea, applied locally. Simon ^ permalink raw reply [flat|nested] 36+ messages in thread
* [PATCH 2/2] gdb: resolve class name via DW_AT_signature in cooked index 2026-09-16 11:38 ` [PATCHv4] " Andrew Burgess ` (2 preceding siblings ...) 2026-09-24 4:59 ` [PATCH 1/2] gdb: split cooked index finalization into separate steps Simon Marchi @ 2026-09-24 4:59 ` Simon Marchi 2026-09-24 10:39 ` Andrew Burgess 3 siblings, 1 reply; 36+ messages in thread From: Simon Marchi @ 2026-09-24 4:59 UTC (permalink / raw) To: gdb-patches; +Cc: Andrew Burgess From: Andrew Burgess <aburgess@redhat.com> This commit fixes PR gdb/33447, an issue where looking up qualified member function names was not working for C++ binaries compiled with Clang when using the -fdebug-types-section flag. Before this commit we would see this behaviour: (gdb) print base1::a_function There is no field named a_function When what we expect to see is: (gdb) print base1::a_function $1 = {void (const base1 * const)} 0x403060 <base1::a_function() const> The problem is that the cooked index is unable to determine the name of the parent class `base1` in this case, and so decides not to index `base1` or any of its child DIEs, which includes its member functions. The problem was discovered by running gdb.cp/cpexprs-debug-types.exp with Clang: make check-gdb TESTS=gdb.cp/cpexprs-debug-types.exp \ RUNTESTFLAGS='CXX_FOR_TARGET=clang++ CC_FOR_TARGET=clang' The cpexprs-debug-types.exp test forces use of the '-fdebug-types-section' flag, which is not on by default. With this flag, class definitions are placed in type units, and the compile unit contains only a declaration stub for each class. Both clang++ and g++ emit these stubs, but they differ in one detail: GCC includes DW_AT_name on the stub, while clang++ does not, the stub carries only DW_AT_declaration and DW_AT_signature. The class name is only available in the type unit, reachable by following the signature. For example, Clang emits this in a CU: <1><2e7d>: DW_TAG_class_type DW_AT_declaration : 1 DW_AT_signature : 0x3abb... <2><2eac>: DW_TAG_subprogram DW_AT_name : a_function DW_AT_declaration : 1 The definition for a_function is elsewhere in the same CU: <1><3142>: DW_TAG_subprogram DW_AT_specification: <0x2eac> And in a TU elsewhere: Compilation Unit @ offset 0xd2e: ... snip ... Signature: 0x3abb... <0><d46>: Abbrev Number: 1 (DW_TAG_type_unit) ... snip ... <1><d51>: Abbrev Number: 30 (DW_TAG_class_type) ... snip ... <d57> DW_AT_name : (indexed string: 0xaa): base1 To find the DW_AT_name the cooked index needs to look up the type within the TU. Without the name the cooked indexer skips indexing `base1` as well as its children. This look up used to work; it works in GDB 17. PR gdb/33447 incorrectly identifies commit c879f4dc3e317cf6353a45a803ecf00d577a13d8 as the commit that introduced the regression. This is actually the last working commit. The problem was introduced by the next commit in the same series: commit 86ac8c546235a67d6a6bb29476a3a9ac8f7a620a Date: Thu Jan 2 15:28:18 2025 -0700 Convert lookup_symbol_in_objfile Prior to this commit GDB's symbol lookup had two phases, a search through already expanded symtabs, and a search via lookup_symbol_via_quick_fns. After the above commit only lookup_symbol_via_quick_fns remains. The lookup_symbol_via_quick_fns lookup, which relies on the indexer, was always broken, but the first phase, searching via expanded symtabs, could correctly find the type name via the signature. An initial attempt to solve this problem tried to fix this problem within cooked_indexer::scan_attributes, calling lookup_signatured_type and finding the name that way. However, there were three problems with this approach: 1. Possible thread safety issues; calling lookup_signatured_type for a DWO file ends up calling lookup_dwo_signatured_type, which can call add_type_unit and finalize_all_units, which modify state that is shared between parser threads. 2. The dwarf2_per_cu::type_offset_in_section for a TU is only set when the cutu_reader is constructed to parse that TU. Calling lookup_signatured_type doesn't fully parse the TU, it just finds the TU. The original code relied on type_offset_in_section being valid in order to then parse the TU and extract the name. This would break if the CU was processed before the TU. 3. For skeletonless TUs, these are not indexed until late in the indexing process, after all the CUs have been indexed. This means that if the TU referenced by the signature was skeletonless then the original approach would fail to find it. The new approach presented here is modeled more along the lines of the deferred parent handling that already exists within the indexer. The following changes have been made: 1. abbrev.c: Add DW_AT_signature to the set of attributes that mark a DIE as "interesting" in abbrev_table::read. Without this, the unnamed class stub was classified as uninteresting at the abbreviation level and scan_attributes was never called for it. 2. cooked-indexer.c (cooked_indexer::scan_attributes): Capture the DW_AT_signature attribute. 3. cooked-index-entry.h: Allow a cooked_index_entry to hold a deferred name, in the same way it can already hold a deferred parent. The name member becomes a union, cooked_index_entry_name_ref, holding either the resolved name or the signature of the DIE that carries the real name. The new IS_NAME_DEFERRED flag says which member is active, mirroring IS_PARENT_DEFERRED and cooked_index_entry_ref. cooked_index_entry also gets other methods mirroring the deferred parent mechanism: name(), name_is_deferred(), get_deferred_name() and resolve_name(). name() asserts that the name is no longer deferred, so any attempt to read the name too early is caught. All readers of the old public 'name' member are updated to call name(). 4. cooked-indexer.c (cooked_indexer::index_dies): There are two different jobs done here: (a) If a DIE has no name, but does have a signature, then create the entry anyway, storing the signature in the name union and setting IS_NAME_DEFERRED. (b) If we are indexing a TU, make a record of the signature, and the name of the primary type within the TU. This builds a signature to name map. 5. cooked-index-worker.c (cooked_index_worker::done_reading): Merge together all of the signature to name maps built in (4b). 6. cooked-index-shard.c (cooked_index_shard::resolve_deferred_names): A new function, look through all of the cooked_index_entry objects in the shard for those with IS_NAME_DEFERRED, and use the signature to name map created in (5) to look up the name and update the entry. resolve_deferred_names returns a bool, true if any deferred name couldn't be resolved. 7. cooked-index-shard.c (cooked_index_shard::prune_nameless_entries): A new function, look through all of the cooked_index_entry objects in the shard, if the entry's parent still has a deferred name, break the parent relationship. If the entry itself still has a deferred name, remove it from the index. This prevents a nameless entry from being found via the get_parent method. After this point every entry in the index has a resolved name, so name() can be called on any of them. 8. cooked-index.c (cooked_index::wait): After waiting, if we have completed finalization then emit any complaints that came from the finalization phase. 9. cooked-index.c: Add the "resolve deferred names" and "prune nameless entries" steps to the index finalization steps, implemented with the functions mentioned in points 6 and 7. The "resolve deferred names" step is only applied to shards that have m_have_deferred_names set. The "prune nameless entries" step is only run if cooked_index::m_have_nameless_entries is set. 10. cooked-index-entry.c (cooked_index_entry::write_scope): Add an assert that the entry's name is resolved. Entries that don't satisfy this should have been removed during finalization. Added three new DWARF assembler tests. gdb.dwarf2/sig-type-unnamed-class.exp gdb.dwarf2/sig-type-unnamed-class-dwo.exp: These reproduce the problem case; there's a nameless declaration stub that references its full type via DW_AT_signature. To match the Clang output as much as possible the member function definition is separate and makes use of DW_AT_specification. The -dwo test places the DWARF into a DWO file and uses a skeletonless TU. gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp: This one tests some invalid DWARF, the type DIE references a signature that doesn't exist. In this case GDB discards the cooked_index_entries whose name could not be resolved, and orphans their children. I think this is fine though, the DWARF is corrupted in this case. Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=33447 Change-Id: I1e0ba20e58af169abbf99e97c14c788164f0d1b2 --- gdb/dwarf2/abbrev.c | 1 + gdb/dwarf2/cooked-index-entry.c | 13 +- gdb/dwarf2/cooked-index-entry.h | 75 ++++++++- gdb/dwarf2/cooked-index-shard.c | 105 ++++++++++-- gdb/dwarf2/cooked-index-shard.h | 47 +++++- gdb/dwarf2/cooked-index-worker.c | 9 + gdb/dwarf2/cooked-index-worker.h | 30 +++- gdb/dwarf2/cooked-index.c | 101 +++++++++++- gdb/dwarf2/cooked-index.h | 34 +++- gdb/dwarf2/cooked-indexer.c | 60 +++++-- gdb/dwarf2/cooked-indexer.h | 1 + gdb/dwarf2/index-write.c | 2 +- .../sig-type-unnamed-class-bad-sig.exp | 155 ++++++++++++++++++ .../gdb.dwarf2/sig-type-unnamed-class-dwo.exp | 143 ++++++++++++++++ .../gdb.dwarf2/sig-type-unnamed-class.exp | 106 ++++++++++++ 15 files changed, 827 insertions(+), 55 deletions(-) create mode 100644 gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp create mode 100644 gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-dwo.exp create mode 100644 gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp diff --git a/gdb/dwarf2/abbrev.c b/gdb/dwarf2/abbrev.c index 44d5c87a5f79..e99acc5e752d 100644 --- a/gdb/dwarf2/abbrev.c +++ b/gdb/dwarf2/abbrev.c @@ -160,6 +160,7 @@ abbrev_table::read (struct dwarf2_section_info *section, case DW_AT_specification: case DW_AT_abstract_origin: case DW_AT_extension: + case DW_AT_signature: has_specification_or_origin = true; break; diff --git a/gdb/dwarf2/cooked-index-entry.c b/gdb/dwarf2/cooked-index-entry.c index 8c324f77904b..ef7d96262a23 100644 --- a/gdb/dwarf2/cooked-index-entry.c +++ b/gdb/dwarf2/cooked-index-entry.c @@ -175,7 +175,7 @@ cooked_index_entry::full_name (struct obstack *storage, cooked_index_full_name_flag name_flags, const char *default_sep) const { - const char *local_name = ((name_flags & FOR_MAIN) != 0) ? name : canonical; + const char *local_name = ((name_flags & FOR_MAIN) != 0) ? name () : canonical; if ((flags & IS_LINKAGE) != 0 || get_parent () == nullptr) return local_name; @@ -197,8 +197,8 @@ cooked_index_entry::full_name (struct obstack *storage, cooked_index_shard::canonicalize_names. */ if ((name_flags & FOR_ADA_LINKAGE_NAME) != 0) { - if (strstr (name, "__") != nullptr) - return name; + if (strstr (name (), "__") != nullptr) + return name (); sep = "__"; break; } @@ -233,10 +233,15 @@ cooked_index_entry::write_scope (struct obstack *storage, { if (get_parent () != nullptr) get_parent ()->write_scope (storage, sep, flags); + + /* Any entry with an unresolved name will have been filtered out by + cooked_index_shard::prune_nameless_entries. */ + gdb_assert (!name_is_deferred ()); + /* When computing the Ada linkage name, the entry might not have been canonicalized yet, so use the 'name'. */ const char *local_name = ((flags & (FOR_MAIN | FOR_ADA_LINKAGE_NAME)) != 0 - ? name + ? name () : canonical); obstack_grow (storage, local_name, strlen (local_name)); obstack_grow (storage, sep, strlen (sep)); diff --git a/gdb/dwarf2/cooked-index-entry.h b/gdb/dwarf2/cooked-index-entry.h index 60ea581cbbe5..a2e007b7cf69 100644 --- a/gdb/dwarf2/cooked-index-entry.h +++ b/gdb/dwarf2/cooked-index-entry.h @@ -47,6 +47,8 @@ enum cooked_index_flag_enum : unsigned char /* True if this is a function that has DW_AT_inline set in a way that indicates it was inlined. */ IS_INLINED = 64, + /* True is m_name.deferred has a value rather than m_name.resolved. */ + IS_NAME_DEFERRED = 128, }; DEF_ENUM_FLAGS_TYPE (enum cooked_index_flag_enum, cooked_index_flag); @@ -79,6 +81,31 @@ union cooked_index_entry_ref parent_map::addr_type deferred; }; +/* Type representing either a resolved or deferred cooked_index_entry + name. A deferred name is held as the DW_AT_signature of the DIE + that carries the real name. */ + +union cooked_index_entry_name_ref +{ + cooked_index_entry_name_ref (ULONGEST deferred_) + { + deferred = deferred_; + } + + cooked_index_entry_name_ref (const char *resolved_) + { + resolved = resolved_; + } + + const char *resolved; + ULONGEST deferred; +}; + +/* Type that maps DW_AT_signature values for a TU to the name of the + primary type within the TU. */ + +using signature_to_name_map = gdb::unordered_map<ULONGEST, const char *>; + /* Return a string representation of FLAGS. */ std::string to_string (cooked_index_flag flags); @@ -94,15 +121,16 @@ struct cooked_index_entry : public allocate_on_obstack<cooked_index_entry> { cooked_index_entry (sect_offset die_offset_, enum dwarf_tag tag_, cooked_index_flag flags_, - enum language lang_, const char *name_, + enum language lang_, + cooked_index_entry_name_ref name_, cooked_index_entry_ref parent_entry_, dwarf2_per_cu *per_cu_) - : name (name_), - tag (tag_), + : tag (tag_), flags (flags_), lang (lang_), die_offset (die_offset_), per_cu (per_cu_), + m_name (name_), m_parent_entry (parent_entry_) { } @@ -225,6 +253,36 @@ struct cooked_index_entry : public allocate_on_obstack<cooked_index_entry> return m_parent_entry.deferred; } + /* Return the entry's name. This may be the name or the linkage + name -- two entries are created for DIEs which have both + attributes. It points to a string that outlives this entry, + which in practice means the mapped DWARF or the storage of one of + the cooked_index_shard objects. */ + const char *name () const + { + gdb_assert ((flags & IS_NAME_DEFERRED) == 0); + return m_name.resolved; + } + + /* Return true if this entry's name still has to be resolved. */ + bool name_is_deferred () const + { return (flags & IS_NAME_DEFERRED) != 0; } + + /* Return the signature of the DIE holding this entry's name. */ + ULONGEST get_deferred_name () const + { + gdb_assert ((flags & IS_NAME_DEFERRED) != 0); + return m_name.deferred; + } + + /* Resolve deferred name to NAME. */ + void resolve_name (const char *name) + { + gdb_assert ((flags & IS_NAME_DEFERRED) != 0); + flags = flags & ~IS_NAME_DEFERRED; + m_name.resolved = name; + } + /* Force the language to be set to the CU's language. This may only be called when the language is unknown, which can only happen with .gdb_index. This method is const because it is called from @@ -252,12 +310,7 @@ struct cooked_index_entry : public allocate_on_obstack<cooked_index_entry> defined in some CU that is included by many other CUs. */ iteration_status visit_defining_cus (per_cu_callback callback) const; - /* The name as it appears in DWARF. This always points into one of - the mapped DWARF sections. Note that this may be the name or the - linkage name -- two entries are created for DIEs which have both - attributes. */ - const char *name; - /* The canonical name. This may be equal to NAME. */ + /* The canonical name. This may be equal to the name. */ const char *canonical = nullptr; /* The DWARF tag. */ enum dwarf_tag tag; @@ -284,6 +337,10 @@ struct cooked_index_entry : public allocate_on_obstack<cooked_index_entry> void write_scope (struct obstack *storage, const char *sep, cooked_index_full_name_flag flags) const; + /* The entry's name, or the signature by which it can be found. See + IS_NAME_DEFERRED and the accessors above. */ + cooked_index_entry_name_ref m_name; + /* The parent entry. This is NULL for top-level entries. Otherwise, it points to the parent entry, such as a namespace or class. */ diff --git a/gdb/dwarf2/cooked-index-shard.c b/gdb/dwarf2/cooked-index-shard.c index a69b29faa62f..b288cdc16199 100644 --- a/gdb/dwarf2/cooked-index-shard.c +++ b/gdb/dwarf2/cooked-index-shard.c @@ -20,6 +20,8 @@ #include "dwarf2/cooked-index-shard.h" #include "dwarf2/tag.h" #include "dwarf2/index-common.h" +#include "dwarf2/read.h" +#include "dwarf2/error.h" #include "cp-support.h" #include "c-lang.h" #include "ada-lang.h" @@ -48,7 +50,7 @@ cooked_index_shard::create (sect_offset die_offset, enum dwarf_tag tag, cooked_index_flag flags, enum language lang, - const char *name, + cooked_index_entry_name_ref name, cooked_index_entry_ref parent_entry, dwarf2_per_cu *per_cu) { @@ -75,7 +77,8 @@ cooked_index_shard::create (sect_offset die_offset, cooked_index_entry * cooked_index_shard::add (sect_offset die_offset, enum dwarf_tag tag, cooked_index_flag flags, enum language lang, - const char *name, cooked_index_entry_ref parent_entry, + cooked_index_entry_name_ref name, + cooked_index_entry_ref parent_entry, dwarf2_per_cu *per_cu) { cooked_index_entry *result = create (die_offset, tag, flags, lang, name, @@ -85,6 +88,9 @@ cooked_index_shard::add (sect_offset die_offset, enum dwarf_tag tag, if ((flags & IS_PARENT_DEFERRED) != 0) m_have_deferred_parents = true; + if ((flags & IS_NAME_DEFERRED) != 0) + m_have_deferred_names = true; + /* An explicitly-tagged main program should always override the implicit "main" discovery. */ if ((flags & IS_MAIN) != 0) @@ -107,7 +113,8 @@ cooked_index_shard::add (sect_offset die_offset, enum dwarf_tag tag, && parent_entry.resolved == nullptr && m_main == nullptr && language_may_use_plain_main (lang) - && streq (name, "main")) + && (flags & IS_NAME_DEFERRED) == 0 + && streq (name.resolved, "main")) m_main = result; return result; @@ -125,10 +132,10 @@ cooked_index_shard::handle_gnat_encoded_entry characters are left as-is. This is done to make name matching a bit simpler; and for wide characters, it means the choice of Ada source charset does not affect the indexer directly. */ - std::string canonical = ada_decode (entry->name, false, false); + std::string canonical = ada_decode (entry->name (), false, false); if (canonical.empty ()) { - entry->canonical = entry->name; + entry->canonical = entry->name (); return; } std::vector<std::string_view> names = split_name (canonical.c_str (), @@ -151,7 +158,7 @@ cooked_index_shard::handle_gnat_encoded_entry last = create (entry->die_offset, DW_TAG_module, IS_SYNTHESIZED, language_ada, new_name, parent, entry->per_cu); - last->canonical = last->name; + last->canonical = last->name (); new_entries.push_back (last); *slot = last; } @@ -175,7 +182,7 @@ struct cooked_index_entry_name_ptr_hash std::uint64_t operator () (const cooked_index_entry *entry) const noexcept { - return ankerl::unordered_dense::hash<const char *> () (entry->name); + return ankerl::unordered_dense::hash<const char *> () (entry->name ()); } }; @@ -186,12 +193,44 @@ struct cooked_index_entry_name_ptr_eq bool operator () (const cooked_index_entry *a, const cooked_index_entry *b) const noexcept { - return a->name == b->name; + return a->name () == b->name (); } }; /* See cooked-index-shard.h. */ +bool +cooked_index_shard::resolve_deferred_names + (const signature_to_name_map &sig_names) +{ + bool have_nameless_entries = false; + for (cooked_index_entry *entry : m_entries) + { + if (!entry->name_is_deferred ()) + continue; + + const ULONGEST signature = entry->get_deferred_name (); + + if (const auto it = sig_names.find (signature); + it != sig_names.end ()) + entry->resolve_name (it->second); + else + { + have_nameless_entries = true; + complaint (_(DWARF_ERROR_PREFIX + "Cannot find signatured DIE %s referenced from DIE " + "at %s [in module %s]"), + hex_string (signature), + sect_offset_str (entry->die_offset), + entry->per_cu->per_bfd ()->filename ()); + } + } + + return have_nameless_entries; +} + +/* See cooked-index-shard.h. */ + void cooked_index_shard::resolve_deferred_parents (const parent_map_map *parent_maps) @@ -209,6 +248,38 @@ cooked_index_shard::resolve_deferred_parents /* See cooked-index-shard.h. */ +void +cooked_index_shard::prune_nameless_entries () +{ + for (cooked_index_entry *entry : m_entries) + { + /* Remove a parent reference if the parent has no name. This + leaves ENTRY as an orphan, but this only happens if the DWARF + is corrupted and we failed to find a name for the parent. We + can safely check the parent's name at this point because all + deferred names and parent links will have been resolved in + all shards before this is called on any shard. */ + if (const cooked_index_entry *parent = entry->get_parent (); + parent != nullptr && parent->name_is_deferred ()) + entry->set_parent (nullptr); + } + + /* If we failed to resolve the name of an entry via its signature + then remove the entry from the m_entries vector. This should be + rare, and should only happen when we have corrupted DWARF. The + entries still live on the obstack, so parent pointers are still + valid, but removing entries from the index means we don't try to + search them when looking for index hits. */ + m_entries.erase (std::remove_if (m_entries.begin (), m_entries.end (), + [] (const cooked_index_entry *e) + { + return e->name_is_deferred (); + }), + m_entries.end ()); +} + +/* See cooked-index-shard.h. */ + void cooked_index_shard::canonicalize_names () { @@ -236,21 +307,25 @@ cooked_index_shard::canonicalize_names () for (cooked_index_entry *entry : m_entries) { + /* Entries without a name are filtered out during the call to + prune_nameless_entries. */ + gdb_assert (!entry->name_is_deferred ()); + /* Note that this code must be kept in sync with cooked_index::get_main -- if canonicalization is required here, then a check might be required there. */ gdb_assert (entry->canonical == nullptr); if ((entry->flags & IS_LINKAGE) != 0) - entry->canonical = entry->name; + entry->canonical = entry->name (); else if (entry->lang == language_ada) { /* Newer versions of GNAT emit DW_TAG_module and use a hierarchical structure. In this case, we don't need to do any extra work. This can be detected by looking for a GNAT-encoded name. */ - if (strstr (entry->name, "__") == nullptr) + if (strstr (entry->name (), "__") == nullptr) { - entry->canonical = entry->name; + entry->canonical = entry->name (); /* If the entry does not have a parent, then there's nothing extra to do here -- the entry itself is @@ -297,10 +372,10 @@ cooked_index_shard::canonicalize_names () name. */ gdb::unique_xmalloc_ptr<char> canon_name = (entry->lang == language_cplus - ? cp_canonicalize_string (entry->name) - : c_canonicalize_name (entry->name)); + ? cp_canonicalize_string (entry->name ()) + : c_canonicalize_name (entry->name ())); if (canon_name == nullptr) - entry->canonical = entry->name; + entry->canonical = entry->name (); else entry->canonical = m_names.insert (std::move (canon_name)); } @@ -312,7 +387,7 @@ cooked_index_shard::canonicalize_names () } } else - entry->canonical = entry->name; + entry->canonical = entry->name (); } /* Make sure any new Ada entries end up in the results. This isn't diff --git a/gdb/dwarf2/cooked-index-shard.h b/gdb/dwarf2/cooked-index-shard.h index 9b9ffa7a76e3..ca4e415ed326 100644 --- a/gdb/dwarf2/cooked-index-shard.h +++ b/gdb/dwarf2/cooked-index-shard.h @@ -26,6 +26,7 @@ #include "addrmap.h" #include "gdbsupport/iterator-range.h" #include "gdbsupport/string-set.h" +#include "complaints.h" /* An index of interesting DIEs. This is "cooked", in contrast to a mapped .debug_names or .gdb_index, which are "raw". An entry in @@ -44,7 +45,7 @@ class cooked_index_shard Entries are owned by this object. The new item is returned. */ cooked_index_entry *add (sect_offset die_offset, enum dwarf_tag tag, cooked_index_flag flags, enum language lang, - const char *name, + cooked_index_entry_name_ref name, cooked_index_entry_ref parent_entry, dwarf2_per_cu *per_cu); @@ -105,7 +106,7 @@ class cooked_index_shard enum dwarf_tag tag, cooked_index_flag flags, enum language lang, - const char *name, + cooked_index_entry_name_ref name, cooked_index_entry_ref parent_entry, dwarf2_per_cu *per_cu); @@ -118,16 +119,50 @@ class cooked_index_shard (cooked_index_entry *entry, htab_t gnat_entries, std::vector<cooked_index_entry *> &new_entries); + /* Use SIG_NAMES to resolve the deferred names of entries in this shard. + + Return true if any deferred name could not be resolved. */ + bool resolve_deferred_names (const signature_to_name_map &sig_names); + /* Use PARENT_MAPS to resolve the deferred parent links of entries in this shard. */ void resolve_deferred_parents (const parent_map_map *parent_maps); + /* Remove index entries that have no name (for which we failed to + resolve the name in resolve_deferred_names). Break any parent link + pointing to an entry with no name. */ + void prune_nameless_entries (); + /* Compute the canonical name for the entries in this shard. Due to how Ada name lookups work, this function may also create new index entries with full names. */ void canonicalize_names (); + /* Called after each step of the finalization process. Store + COMPLAINTS so they can be reported later on the main thread. */ + void merge_finalize_complaints (complaint_collection &&complaints) + { + if (m_finalize_complaints.empty ()) + m_finalize_complaints = std::move (complaints); + else + { + /* The current version of gdb::unordered_set doesn't support + the merge method that std::unordered_set supports. If we + update gdb::unordered_set then we could switch this to use + merge(). */ + m_finalize_complaints.insert (complaints.begin (), complaints.end ()); + } + } + + /* Return the set of complaints emitted during the finalization + process. We move these complaints out of the shard as these are + only emitted once, and don't need to be stored beyond that. */ + complaint_collection release_finalize_complaints () + { + return std::move (m_finalize_complaints); + } + /* Storage for the entries. */ auto_obstack m_storage; @@ -143,9 +178,17 @@ class cooked_index_shard /* Storage for canonical names. */ gdb::string_set m_names; + /* True if at least one entry in this shard has a name that requires + deferred resolution. */ + bool m_have_deferred_names = false; + /* True if at least one entry in this shard has a parent link that requires deferred resolution. */ bool m_have_deferred_parents = false; + + /* Any complaints emitted while finalizing the index are stored + here until they can be emitted on the main thread. */ + complaint_collection m_finalize_complaints; }; using cooked_index_shard_up = std::unique_ptr<cooked_index_shard>; diff --git a/gdb/dwarf2/cooked-index-worker.c b/gdb/dwarf2/cooked-index-worker.c index 723e027172e8..054b25ba1479 100644 --- a/gdb/dwarf2/cooked-index-worker.c +++ b/gdb/dwarf2/cooked-index-worker.c @@ -249,6 +249,15 @@ cooked_index_worker::done_reading () m_all_parents_map.add_map (*one_result.get_parent_map ()); } + { + scoped_time_it time_it ("DWARF add signature name map", m_per_command_time); + + /* Combine all of the signature to name maps. */ + for (cooked_index_worker_result &one_result : m_results) + for (const auto &[sig, name] : one_result.get_sig_name_map ()) + m_all_sig_names_map.emplace (sig, name); + } + /* Update all the CU inclusion information. */ for (auto &item : m_results) item.invert_cu_inclusions (); diff --git a/gdb/dwarf2/cooked-index-worker.h b/gdb/dwarf2/cooked-index-worker.h index 1799c0b9e08b..d62ed01faac1 100644 --- a/gdb/dwarf2/cooked-index-worker.h +++ b/gdb/dwarf2/cooked-index-worker.h @@ -68,7 +68,7 @@ class cooked_index_worker_result cooked-index.h. The new entry is returned. */ cooked_index_entry *add (sect_offset die_offset, enum dwarf_tag tag, cooked_index_flag flags, enum language lang, - const char *name, + cooked_index_entry_name_ref name, cooked_index_entry_ref parent_entry, dwarf2_per_cu *per_cu) { @@ -83,6 +83,15 @@ class cooked_index_worker_result return m_shard->add (name); } + /* Called when processing TUs to record that the primary type within + a type unit with SIGNATURE, was called NAME. This information + will be used during finalization to fix-up the name of any + entries carrying the IS_NAME_DEFERRED flag. */ + void add_signatured_type_name (ULONGEST signature, const char *name) + { + m_sig_name_map.emplace (signature, name); + } + /* Install the current addrmap into the shard being constructed, then transfer ownership of the index to the caller. */ cooked_index_shard_up release_shard () @@ -153,6 +162,12 @@ class cooked_index_worker_result discovered. */ void invert_cu_inclusions (); + /* The signature to name map for this worker. */ + const signature_to_name_map &get_sig_name_map () const + { + return m_sig_name_map; + } + private: /* The abbrev table cache used by this indexer. */ abbrev_table_cache m_abbrev_table_cache; @@ -188,6 +203,9 @@ class cooked_index_worker_result /* Parent map for each CU that is read. */ parent_map m_parent_map; + /* Signature to name map for the primary type in a TU. */ + signature_to_name_map m_sig_name_map; + /* A writeable addrmap being constructed by this scanner. */ addrmap_mutable m_addrmap; @@ -272,6 +290,12 @@ class cooked_index_worker return &m_all_parents_map; } + /* Return the map containing the complete signature to name information. */ + const signature_to_name_map &get_sig_name_map () const + { + return m_all_sig_names_map; + } + protected: /* Let cooked_index call the 'set' and 'write_to_cache' methods. */ @@ -319,6 +343,10 @@ class cooked_index_worker parent relationships. */ parent_map_map m_all_parents_map; + /* Map from signature to name of primary type within a TU. This is + the combined map, built after all the workers have finished. */ + signature_to_name_map m_all_sig_names_map; + /* Current state of this object. */ cooked_state m_state = cooked_state::INITIAL; /* Mutex and condition variable used to synchronize. */ diff --git a/gdb/dwarf2/cooked-index.c b/gdb/dwarf2/cooked-index.c index f834f054da0e..c9e1ae136dae 100644 --- a/gdb/dwarf2/cooked-index.c +++ b/gdb/dwarf2/cooked-index.c @@ -58,7 +58,23 @@ cooked_index::wait (cooked_state desired_state, bool allow_quit) if (m_state == nullptr) return; - if (m_state->wait (desired_state, allow_quit)) + bool done = m_state->wait (desired_state, allow_quit); + + /* Emit any cached complaints if we have finalized and we are on the + main thread. Check for the requested state or the DONE flag + here, we might have only asked for MAIN_AVAILABLE, but if the + workers are quick then they might be done, in which case we + should emit the complaints now. */ + if (!m_finalize_complaints_emitted + && is_main_thread () + && (desired_state >= cooked_state::FINALIZED || done)) + { + m_finalize_complaints_emitted = true; + for (const auto &shard : m_shards) + re_emit_complaints (shard->release_finalize_complaints ()); + } + + if (done) { /* Only the main thread can modify this. */ gdb_assert (is_main_thread ()); @@ -75,7 +91,41 @@ cooked_index::set_contents () m_state->set (cooked_state::MAIN_AVAILABLE); /* Start the first step of index finalization. */ - this->start_resolve_deferred_parents (); + this->start_resolve_deferred_names (); +} + +/* See cooked-index.h. */ + +void +cooked_index::start_resolve_deferred_names () +{ + gdb::task_group group ([this] () + { + this->start_resolve_deferred_parents (); + }); + + /* Arrange to call resolve_deferred_names on each shard that has + deferred names. */ + for (const cooked_index_shard_up &shard : m_shards) + { + if (!shard->m_have_deferred_names) + continue; + + group.add_task ([this, this_shard = shard.get ()] () + { + complaint_interceptor complaint_handler; + + scoped_time_it time_it ("DWARF resolve deferred names worker", + m_state->m_per_command_time); + + if (this_shard->resolve_deferred_names (m_state->get_sig_name_map ())) + m_have_nameless_entries.store (true); + + this_shard->merge_finalize_complaints (complaint_handler.release ()); + }); + } + + group.start (); } /* See cooked-index.h. */ @@ -85,7 +135,7 @@ cooked_index::start_resolve_deferred_parents () { gdb::task_group group ([this] () { - this->start_canonicalize_names (); + this->start_prune_nameless_entries (); }); /* Arrange to call resolve_deferred_parents on each shard that has at least @@ -109,6 +159,39 @@ cooked_index::start_resolve_deferred_parents () /* See cooked-index.h. */ +void +cooked_index::start_prune_nameless_entries () +{ + gdb::task_group group ([this] () + { + this->start_canonicalize_names (); + }); + + /* Remove index entries whose name we could not resolve. + + If there is any nameless entry, this step needs to run on all the shards, + because there could be children of a nameless entry in other shards, + whose parent links we want to break. + + This step normally only runs in case there is something wrong with the + DWARF info. */ + if (m_have_nameless_entries.load ()) + for (const cooked_index_shard_up &shard : m_shards) + { + group.add_task ([this, this_shard = shard.get ()] () + { + scoped_time_it time_it ("DWARF prune nameless entries worker", + m_state->m_per_command_time); + + this_shard->prune_nameless_entries (); + }); + } + + group.start (); +} + +/* See cooked-index.h. */ + void cooked_index::start_canonicalize_names () { @@ -124,10 +207,14 @@ cooked_index::start_canonicalize_names () { group.add_task ([this, this_shard = shard.get ()] () { + complaint_interceptor complaint_handler; + scoped_time_it time_it ("DWARF canonicalize names worker", m_state->m_per_command_time); this_shard->canonicalize_names (); + + this_shard->merge_finalize_complaints (complaint_handler.release ()); }); } @@ -239,7 +326,7 @@ cooked_index::get_main () const exception. */ if ((entry->lang != language_ada && entry->lang != language_cplus) - || streq (entry->name, "main")) + || streq (entry->name (), "main")) { /* There won't be one better than this. */ return entry; @@ -289,7 +376,7 @@ cooked_index::dump (gdbarch *arch) gdb_printf (" [%zu] ((cooked_index_entry *) %p)\n", i++, entry); gdb_printf (" name: %ps\n", styled_string (style, - entry->name)); + entry->name ())); gdb_printf (" canonical: %ps\n", styled_string (style, entry->canonical)); gdb_printf (" qualified: %ps\n", @@ -305,7 +392,7 @@ cooked_index::dump (gdbarch *arch) entry->get_deferred_parent ()); else if (entry->get_parent () != nullptr) gdb_printf (" parent: ((cooked_index_entry *) %p) [%s]\n", - entry->get_parent (), entry->get_parent ()->name); + entry->get_parent (), entry->get_parent ()->name ()); else gdb_printf (" parent: ((cooked_index_entry *) 0)\n"); @@ -315,7 +402,7 @@ cooked_index::dump (gdbarch *arch) const cooked_index_entry *main_entry = this->get_main (); if (main_entry != nullptr) gdb_printf (" main: ((cooked_index_entry *) %p) [%s]\n", main_entry, - main_entry->name); + main_entry->name ()); else gdb_printf (" main: ((cooked_index_entry *) 0)\n"); diff --git a/gdb/dwarf2/cooked-index.h b/gdb/dwarf2/cooked-index.h index 758e399706ba..83387b547474 100644 --- a/gdb/dwarf2/cooked-index.h +++ b/gdb/dwarf2/cooked-index.h @@ -31,6 +31,8 @@ #include "dwarf2/cooked-index-shard.h" #include "dwarf2/cooked-index-worker.h" +#include <atomic> + /* The main index of DIEs. The index is created by multiple threads. The overall process is @@ -72,10 +74,16 @@ . compute_main_name cooked_index::set_contents . | | . v v - . wait (MAIN_AVAILABLE) resolve deferred parents + . wait (MAIN_AVAILABLE) resolve deferred names . | | . v v - . done canonicalize names + . done resolve deferred parents + . | + . v + . prune nameless entries + . | + . v + . canonicalize names . | . v . state = FINALIZED @@ -176,13 +184,23 @@ class cooked_index : public dwarf_scanner_base { wait (cooked_state::CACHE_DONE); } private: + /* Start the "resolve deferred names" step of index finalization. */ + void start_resolve_deferred_names (); + /* Start the "resolve deferred parents" step of index finalization. */ void start_resolve_deferred_parents (); + /* Start the "prune nameless entries" step of index finalization. + + This step must run after "resolve deferred names" and "resolve deferred + parents", because it relies on names and parents being set. */ + void start_prune_nameless_entries (); + /* Start the "canonicalize names" step of index finalization. This step must run after "resolve deferred parents", because it depends on - the parents being set. */ + the parents being set, and after "prune nameless entries", because it + requires all entries to have a name. */ void start_canonicalize_names (); /* Execute the "write to cache" step at the end of index @@ -197,6 +215,16 @@ class cooked_index : public dwarf_scanner_base that the state is CACHE_DONE -- it's important to note that only the main thread may change the value of this pointer. */ cooked_index_worker_up m_state; + + /* Any complaints raised during finalization are held within the + shards in M_SHARDS. Once the main thread has waited for + finalization to be complete then the cached complaints are + emitted, and this flag is set to true. */ + bool m_finalize_complaints_emitted = false; + + /* True if some deferred name could not be resolved during the "resolve + deferred names" step. */ + std::atomic<bool> m_have_nameless_entries = false; }; /* An implementation of quick_symbol_functions for the cooked DWARF diff --git a/gdb/dwarf2/cooked-indexer.c b/gdb/dwarf2/cooked-indexer.c index 581c0eb07635..23029bc4f13c 100644 --- a/gdb/dwarf2/cooked-indexer.c +++ b/gdb/dwarf2/cooked-indexer.c @@ -157,6 +157,7 @@ cooked_indexer::scan_attributes (dwarf2_per_cu *scanning_per_cu, parent_map::addr_type *maybe_defer, bool *is_enum_class, bool *is_inlined, + std::optional<ULONGEST> *signature, bool for_specification) { bool is_declaration = false; @@ -234,6 +235,16 @@ cooked_indexer::scan_attributes (dwarf2_per_cu *scanning_per_cu, attr.get_ref_die_offset () }; break; + case DW_AT_signature: + /* The DW_AT_signature could also be a direct reference to a + type DIE. We don't currently try to capture those + signatures as right now we're only capturing this in + order to handle the fact that some TUs are parsed after + the parallel CU parsing. */ + if (attr.form == DW_FORM_ref_sig8) + signature->emplace (attr.as_signature ()); + break; + case DW_AT_external: if (attr.as_boolean ()) *flags &= ~IS_STATIC; @@ -380,7 +391,7 @@ cooked_indexer::scan_attributes (dwarf2_per_cu *scanning_per_cu, scan_attributes (scanning_per_cu, new_reader, new_info_ptr, new_info_ptr, new_abbrev, name, linkage_name, flags, nullptr, parent_entry, maybe_defer, - is_enum_class, is_inlined, true); + is_enum_class, is_inlined, signature, true); } if (!for_specification) @@ -547,6 +558,7 @@ cooked_indexer::index_dies (cutu_reader *reader, const cooked_index_entry *this_parent_entry = parent_entry; bool is_enum_class = false; bool is_inlined = false; + std::optional<ULONGEST> signature; /* The scope of a DW_TAG_entry_point cooked_index_entry is the one of its surrounding subroutine. */ @@ -556,7 +568,7 @@ cooked_indexer::index_dies (cutu_reader *reader, = scan_attributes (reader->cu ()->per_cu, reader, info_ptr, info_ptr, abbrev, &name, &linkage_name, &flags, &sibling, &this_parent_entry, &defer, &is_enum_class, - &is_inlined, false); + &is_inlined, &signature, false); /* A DW_TAG_entry_point inherits its static/extern property from the enclosing subroutine. */ if (abbrev->tag == DW_TAG_entry_point) @@ -611,19 +623,33 @@ cooked_indexer::index_dies (cutu_reader *reader, cooked_index_entry *this_entry = nullptr; /* Always use the reader's CU for the entry CU. */ dwarf2_per_cu *cu_for_entry = reader->cu ()->per_cu; - if (name != nullptr) + + /* If scan_attributes failed to find a name then we might still + want to create an entry if we think we might later find a + name via the signature. In that case the entry holds the + signature until the name is resolved. */ + if (name != nullptr || signature.has_value ()) { + cooked_index_flag entry_flags = flags; + cooked_index_entry_name_ref name_ref = name; + cooked_index_entry_ref parent_ref = this_parent_entry; + + if (name == nullptr) + { + name_ref = signature.value (); + entry_flags |= IS_NAME_DEFERRED; + } + if (defer != 0) - this_entry - = m_index_storage->add (this_die, abbrev->tag, - flags | IS_PARENT_DEFERRED, - m_language, name, - defer, cu_for_entry); - else - this_entry - = m_index_storage->add (this_die, abbrev->tag, flags, - m_language, name, - this_parent_entry, cu_for_entry); + { + parent_ref = defer; + entry_flags |= IS_PARENT_DEFERRED; + } + + this_entry + = m_index_storage->add (this_die, abbrev->tag, entry_flags, + m_language, name_ref, parent_ref, + cu_for_entry); } else if (this_parent_entry != nullptr) { @@ -637,6 +663,14 @@ cooked_indexer::index_dies (cutu_reader *reader, m_die_range_map->add_entry (addr, addr, this_parent_entry); } + /* If THIS_DIE is the primary type within a TU, and has a valid + name, then add an entry mapping the signature to the name. */ + if (const signatured_type *st = cu_for_entry->as_signatured_type (); + name != nullptr + && st != nullptr + && this_die == st->type_offset_in_section) + m_index_storage->add_signatured_type_name (st->signature, name); + if (linkage_name != nullptr) { /* We only want this to be "main" if it has a linkage name diff --git a/gdb/dwarf2/cooked-indexer.h b/gdb/dwarf2/cooked-indexer.h index 563742c90f6a..393ef9fca5ac 100644 --- a/gdb/dwarf2/cooked-indexer.h +++ b/gdb/dwarf2/cooked-indexer.h @@ -86,6 +86,7 @@ class cooked_indexer parent_map::addr_type *maybe_defer, bool *is_enum_class, bool *is_inlined, + std::optional<ULONGEST> *signature, bool for_specification); /* Handle DW_TAG_imported_unit, by scanning the DIE to find diff --git a/gdb/dwarf2/index-write.c b/gdb/dwarf2/index-write.c index 2b92b2cae472..4712f08515c7 100644 --- a/gdb/dwarf2/index-write.c +++ b/gdb/dwarf2/index-write.c @@ -697,7 +697,7 @@ class debug_names if ((entry->flags & IS_SYNTHESIZED) != 0) return; - m_name_to_value_set[entry->name].emplace_back (entry); + m_name_to_value_set[entry->name ()].emplace_back (entry); } /* Build all the tables. All symbols must be already inserted. diff --git a/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp new file mode 100644 index 000000000000..39a221a1b755 --- /dev/null +++ b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp @@ -0,0 +1,155 @@ +# 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/>. + +# The test sets up a type DIE without a DW_AT_name, the type DIE links +# to a type within a TU via DW_AT_signature. Normally GDB would +# use the DW_AT_signature to find the name of the type DIE. +# +# However, in this test there is no type DIE with the specified +# signature! +# +# This test checks that GDB can emit a complaint when this situation is +# encountered. The test also checks that trying to find a method +# within the type using the qualified name will fail. + +load_lib dwarf.exp + +# This test can only be run on targets which support DWARF-2 and use gas. +require dwarf2_support + +standard_testfile main-foo.c .S + +# Build the test program using VERSION for the CU/TU DWARF version. +proc run_test { version } { + # Create the DWARF. + set asm_file [standard_output_file $::srcfile2] + Dwarf::assemble { + filename $asm_file + add_dummy_cus 0 + } { + upvar version version + + get_func_info foo + get_func_info main + + declare_labels method_decl + + tu { version $version } 0xdeadbeefdeadbeef the_type { + DW_TAG_type_unit { + DW_AT_language @DW_LANG_C_plus_plus + } { + the_type: DW_TAG_class_type { + DW_AT_name the_type + DW_AT_byte_size 1 sdata + } + } + } + + cu { version $version } { + compile_unit { + DW_AT_language @DW_LANG_C_plus_plus + } { + DW_TAG_class_type { + DW_AT_declaration 1 flag + DW_AT_signature 0xdeadbeef01234567 ref_sig8 + } { + method_decl: DW_TAG_subprogram { + DW_AT_name method + DW_AT_linkage_name _ZN8the_type6methodEv + DW_AT_declaration 1 flag + } + } + + DW_TAG_subprogram { + DW_AT_specification %$method_decl + DW_AT_low_pc $foo_start DW_FORM_addr + DW_AT_high_pc $foo_end DW_FORM_addr + } + + DW_TAG_subprogram { + DW_AT_name main + DW_AT_low_pc $main_start DW_FORM_addr + DW_AT_high_pc $main_end DW_FORM_addr + } + } + } + } + + set testfile_name ${::testfile}-${version} + set binfile_name [standard_output_file $testfile_name] + if { [build_executable "failed to build" $testfile_name \ + [list $asm_file $::srcfile] {nodebug}] } { + return + } + + clean_restart $testfile_name + + set expect_complaint true + # If we have a .gdb_index already then the part of this test that + # checks for a warning when loading the executable is not going to + # work as the warning will have already been generated when the + # index was added. + if {[get_index_type $testfile_name] == "gdb"} { + set expect_complaint false + } + + clean_restart + + set host_binfile [gdb_remote_download host $binfile_name] + if { [is_remote host] } { + # For some remote host boards gdb_remote_download returns an + # absolute path, but for others it returns a relative path. + # The path reported in the complaint message is always + # absolute. Handle this with an optional prefix pattern. + set binfile_re "\[^\r\n\]*[string_to_regexp $host_binfile]" + } elseif {[section_get $binfile_name ".gnu_debuglink"] ne ""} { + set binfile_re "\[^\r\n\]+[string_to_regexp ${testfile_name}.debug]" + } else { + set binfile_re [string_to_regexp $binfile_name] + } + + # Load the executable and check we see the expected complaint from + # the DWARF indexer. + gdb_test_no_output "maint set dwarf synchronous on" + gdb_test_no_output "set complaints 100" + set saw_complaint false + gdb_test_multiple "file $host_binfile" "file command" -lbl { + -re "^\r\nDuring symbol reading: DWARF Error: Cannot find signatured DIE 0xdeadbeef01234567 referenced from DIE at $::hex \\\[in module $binfile_re\\\](?=\r\n)" { + set saw_complaint true + exp_continue + } + -re "^\r\n$::gdb_prompt $" { + gdb_assert { $saw_complaint == $expect_complaint } $gdb_test_name + } + } + + if { ![readnow] } { + # Check that GDB is unable to find this via the index. If we + # load the full symbols then GDB is able to resolve this print + # via the linkage name. + # + # It is unfortunate that there is a difference in behaviour + # depending on whether GDB is using the index or not, but + # remember, the DWARF is corrupted at this point, so it's + # probably OK if things don't fully work, so long as GDB + # doesn't crash. + gdb_test "print the_type::method" \ + "^There is no field named method" + } +} + +foreach_with_prefix version { 4 5 } { + run_test $version +} diff --git a/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-dwo.exp b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-dwo.exp new file mode 100644 index 000000000000..bc6420d7aa9d --- /dev/null +++ b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-dwo.exp @@ -0,0 +1,143 @@ +# 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/>. + +# Like sig-type-unnamed-class.exp, but with split DWARF. The type +# unit in the DWO file is skeletonless, which is the norm when using +# split DWARF. +# +# Check that the cooked index correctly resolves the parent of a member +# function when the class declaration stub in the DWO compile unit has +# DW_AT_signature but no DW_AT_name, and the referenced type unit is +# skeletonless. + +load_lib dwarf.exp + +# This test can only be run on targets which support DWARF-2 and use gas. +require dwarf2_support + +standard_testfile main-foo.c -dw.S + +# Build the test program using VERSION for the CU/TU DWARF version. + +proc run_test { version } { + set asm_file [standard_output_file $::srcfile2] + + # Setup some state based on DWARF version. + if { $version == 5 } { + set dwo_cu_opts [list fission 1 version 5 dwo_id 0xF00D] + set skel_cu_opts [list version 5 dwo_id 0xF00D] + set dwo_name_attr DW_AT_dwo_name + } else { + set dwo_cu_opts [list fission 1 version 4] + set skel_cu_opts [list version 4] + set dwo_name_attr DW_AT_GNU_dwo_name + } + + set testfile_name ${::testfile}-${version} + + Dwarf::assemble $asm_file { + upvar version version + upvar dwo_cu_opts dwo_cu_opts + upvar skel_cu_opts skel_cu_opts + upvar dwo_name_attr dwo_name_attr + upvar testfile_name testfile_name + + declare_labels method_decl + + get_func_info foo + + # Type unit in the DWO file. This is skeletonless: there is no + # corresponding skeleton TU in the main file. + tu { + fission 1 + version $version + } 0xdeadbeef01234567 the_type { + type_unit { + DW_AT_language @DW_LANG_C_plus_plus + } { + the_type: DW_TAG_class_type { + DW_AT_name the_type + DW_AT_byte_size 1 DW_FORM_sdata + } + } + } + + set debug_addr_base [debug_addr_label] + + # Compile unit in the DWO file. Contains a class declaration + # stub with DW_AT_signature but no DW_AT_name, replicating what + # Clang emits with -fdebug-types-section. The class name must + # be resolved by following the signature to the type unit. + cu $dwo_cu_opts { + compile_unit { + DW_AT_language @DW_LANG_C_plus_plus + DW_AT_name ${::srcfile} + DW_AT_comp_dir . + if { $version == 4 } { + DW_AT_GNU_dwo_id 0xF00D DW_FORM_data8 + } + } { + DW_TAG_class_type { + DW_AT_declaration 1 DW_FORM_flag + DW_AT_signature 0xdeadbeef01234567 DW_FORM_ref_sig8 + } { + method_decl: DW_TAG_subprogram { + DW_AT_name method + DW_AT_linkage_name _ZN8the_type6methodEv + DW_AT_declaration 1 DW_FORM_flag + } + } + + DW_TAG_subprogram { + DW_AT_specification :$method_decl + DW_AT_low_pc $foo_start DW_FORM_GNU_addr_index + DW_AT_high_pc $foo_end DW_FORM_GNU_addr_index + } + } + } + + # Skeleton CU in the main file. + cu $skel_cu_opts { + compile_unit { + $dwo_name_attr ${testfile_name}-dw.dwo DW_FORM_strp + DW_AT_comp_dir . + DW_AT_GNU_addr_base $debug_addr_base + if { $version == 4 } { + DW_AT_GNU_dwo_id 0xF00D DW_FORM_data8 + } + } {} + } + } + + set binfile_name [standard_output_file $testfile_name] + set obj [standard_output_file "${testfile_name}-dw.o"] + if {[build_executable_and_dwo_files "build exec and dwo" $binfile_name \ + {nodebug} \ + [list $asm_file {nodebug split-dwo} $obj] \ + [list $::srcfile {nodebug}]]} { + return + } + + clean_restart $testfile_name + + # Check that GDB was able to find the parent for "method", and as + # a result, can correctly find this field of "the_type". + gdb_test "print the_type::method" \ + [string_to_regexp " <the_type::method()>"] +} + +foreach_with_prefix version { 4 5 } { + run_test $version +} diff --git a/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp new file mode 100644 index 000000000000..6f39e2c823d5 --- /dev/null +++ b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp @@ -0,0 +1,106 @@ +# 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/>. + +# Check that the cooked index correctly resolves the parent of a member +# function when the class declaration stub in the compile unit has +# DW_AT_signature but no DW_AT_name. +# +# This replicates what Clang emits with -fdebug-types-section. The +# compile unit contains an unnamed DW_TAG_class_type declaration with +# only DW_AT_declaration, DW_AT_signature, and child member function +# declarations. +# +# The class name must be resolved by following the signature to the +# type unit. Without this, the member function definitions (which use +# DW_AT_specification to point at the child declarations) end up with +# no parent, and qualified lookup fails. + +load_lib dwarf.exp + +# This test can only be run on targets which support DWARF-2 and use gas. +require dwarf2_support + +standard_testfile main-foo.c .S + +# Build the test program using VERSION for the CU/TU DWARF version. +proc run_test { version } { + # Create the DWARF. + set asm_file [standard_output_file $::srcfile2] + Dwarf::assemble { + filename $asm_file + add_dummy_cus 0 + } { + upvar version version + + get_func_info foo + get_func_info main + + declare_labels method_decl + + tu { version $version } 0xdeadbeef01234567 the_type { + DW_TAG_type_unit { + DW_AT_language @DW_LANG_C_plus_plus + } { + the_type: DW_TAG_class_type { + DW_AT_name the_type + DW_AT_byte_size 1 sdata + } + } + } + + cu { version $version } { + compile_unit { + DW_AT_language @DW_LANG_C_plus_plus + } { + DW_TAG_class_type { + DW_AT_declaration 1 flag + DW_AT_signature 0xdeadbeef01234567 ref_sig8 + } { + method_decl: DW_TAG_subprogram { + DW_AT_name method + DW_AT_linkage_name _ZN8the_type6methodEv + DW_AT_declaration 1 flag + } + } + + DW_TAG_subprogram { + DW_AT_specification %$method_decl + DW_AT_low_pc $foo_start DW_FORM_addr + DW_AT_high_pc $foo_end DW_FORM_addr + } + + DW_TAG_subprogram { + DW_AT_name main + DW_AT_low_pc $main_start DW_FORM_addr + DW_AT_high_pc $main_end DW_FORM_addr + } + } + } + } + + if { [prepare_for_testing "failed to prepare" ${::testfile}-${version} \ + [list $asm_file $::srcfile] {nodebug}] } { + return + } + + # Check that GDB was able to find the parent for "method", and as + # a result, can correctly find this field of "the_type". + gdb_test "print the_type::method" \ + [string_to_regexp " <the_type::method()>"] +} + +foreach_with_prefix version { 4 5 } { + run_test $version +} -- 2.55.0 ^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH 2/2] gdb: resolve class name via DW_AT_signature in cooked index 2026-09-24 4:59 ` [PATCH 2/2] gdb: resolve class name via DW_AT_signature in cooked index Simon Marchi @ 2026-09-24 10:39 ` Andrew Burgess 2026-09-24 15:07 ` Simon Marchi 0 siblings, 1 reply; 36+ messages in thread From: Andrew Burgess @ 2026-09-24 10:39 UTC (permalink / raw) To: Simon Marchi, gdb-patches Simon, Thanks for working on this. This looks great, I have a couple of minor comments. Simon Marchi <simon.marchi@efficios.com> writes: > From: Andrew Burgess <aburgess@redhat.com> I'd be fine if you wanted to change yourself to be the author, you've put a lot of work into this iteration. But I feel you should at least add a Co-Authored-By tag if nothing else :) > diff --git a/gdb/dwarf2/cooked-index-entry.h b/gdb/dwarf2/cooked-index-entry.h > index 60ea581cbbe5..a2e007b7cf69 100644 > --- a/gdb/dwarf2/cooked-index-entry.h > +++ b/gdb/dwarf2/cooked-index-entry.h > @@ -47,6 +47,8 @@ enum cooked_index_flag_enum : unsigned char > /* True if this is a function that has DW_AT_inline set in a way > that indicates it was inlined. */ > IS_INLINED = 64, > + /* True is m_name.deferred has a value rather than m_name.resolved. */ Typo: 'True IF m_name.deferred ....' > + IS_NAME_DEFERRED = 128, In cooked-index-entry.c you need to update: std::string to_string (cooked_index_flag flags) which converts these flags to strings. > + > +/* See cooked-index.h. */ > + > void > cooked_index::start_canonicalize_names () > { > @@ -124,10 +207,14 @@ cooked_index::start_canonicalize_names () > { > group.add_task ([this, this_shard = shard.get ()] () > { > + complaint_interceptor complaint_handler; > + I think you should add the complaint_interceptor to cooked_index::start_resolve_deferred_parents and cooked_index::start_prune_nameless_entries too. It's not needed in those cases, but it's not needed here either. My reasoning is that adding it is cheap, especially if no complaints are emitted, and it's done in a worker thread, so we notice the impact even less. But, if we ever add a complaint in the future then you need to remember to add the complaint_interceptor, or hope someone catches it during review. Given we're adding the infrastructure now, lets just add the complaint_interceptor to all cases, then we're covered for future growth. With these fixes: Approved-By: Andrew Burgess <aburgess@redhat.com> Thanks, Andrew ^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH 2/2] gdb: resolve class name via DW_AT_signature in cooked index 2026-09-24 10:39 ` Andrew Burgess @ 2026-09-24 15:07 ` Simon Marchi 0 siblings, 0 replies; 36+ messages in thread From: Simon Marchi @ 2026-09-24 15:07 UTC (permalink / raw) To: Andrew Burgess, gdb-patches On 9/24/26 6:39 AM, Andrew Burgess wrote: > > Simon, > > Thanks for working on this. This looks great, I have a couple of minor > comments. > > Simon Marchi <simon.marchi@efficios.com> writes: > >> From: Andrew Burgess <aburgess@redhat.com> > > I'd be fine if you wanted to change yourself to be the author, you've > put a lot of work into this iteration. But I feel you should at least > add a Co-Authored-By tag if nothing else :) Done (Co-Authored-By). >> diff --git a/gdb/dwarf2/cooked-index-entry.h b/gdb/dwarf2/cooked-index-entry.h >> index 60ea581cbbe5..a2e007b7cf69 100644 >> --- a/gdb/dwarf2/cooked-index-entry.h >> +++ b/gdb/dwarf2/cooked-index-entry.h >> @@ -47,6 +47,8 @@ enum cooked_index_flag_enum : unsigned char >> /* True if this is a function that has DW_AT_inline set in a way >> that indicates it was inlined. */ >> IS_INLINED = 64, >> + /* True is m_name.deferred has a value rather than m_name.resolved. */ > > Typo: 'True IF m_name.deferred ....' Done. >> + IS_NAME_DEFERRED = 128, > > In cooked-index-entry.c you need to update: > > std::string to_string (cooked_index_flag flags) > > which converts these flags to strings. Done. >> + >> +/* See cooked-index.h. */ >> + >> void >> cooked_index::start_canonicalize_names () >> { >> @@ -124,10 +207,14 @@ cooked_index::start_canonicalize_names () >> { >> group.add_task ([this, this_shard = shard.get ()] () >> { >> + complaint_interceptor complaint_handler; >> + > > I think you should add the complaint_interceptor to > cooked_index::start_resolve_deferred_parents and > cooked_index::start_prune_nameless_entries too. > > It's not needed in those cases, but it's not needed here either. > > My reasoning is that adding it is cheap, especially if no complaints are > emitted, and it's done in a worker thread, so we notice the impact even > less. > > But, if we ever add a complaint in the future then you need to remember > to add the complaint_interceptor, or hope someone catches it during > review. > > Given we're adding the infrastructure now, lets just add the > complaint_interceptor to all cases, then we're covered for future > growth. I agree, done. > With these fixes: > > Approved-By: Andrew Burgess <aburgess@redhat.com> > > Thanks, > Andrew Thanks for the review, I will push it later today (to both master and gdb-18-branch). Hopefully Tom has some time to give it a quick look today, just to get another pair of eyes on it. Simon ^ permalink raw reply [flat|nested] 36+ messages in thread
end of thread, other threads:[~2026-09-25 2:34 UTC | newest] Thread overview: 36+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2026-08-19 10:03 [PATCH] [GDB 18] gdb: resolve class name via DW_AT_signature in cooked index Andrew Burgess 2026-08-21 17:07 ` Tom Tromey 2026-08-28 21:30 ` [PATCHv2] " Andrew Burgess 2026-09-01 13:31 ` [PATCHv3] " Andrew Burgess 2026-09-10 16:11 ` Simon Marchi 2026-09-11 19:18 ` Tom Tromey 2026-09-12 2:06 ` Simon Marchi 2026-09-14 13:23 ` Andrew Burgess 2026-09-14 14:57 ` Simon Marchi 2026-09-14 15:38 ` Tom Tromey 2026-09-15 10:25 ` Andrew Burgess 2026-09-15 15:01 ` Tom Tromey 2026-09-15 15:55 ` Simon Marchi 2026-09-15 17:21 ` Simon Marchi 2026-09-16 11:40 ` Andrew Burgess 2026-09-16 11:45 ` Andrew Burgess 2026-09-16 11:48 ` Andrew Burgess 2026-09-14 15:36 ` Tom Tromey 2026-09-10 16:18 ` Simon Marchi 2026-09-16 11:38 ` [PATCHv4] " Andrew Burgess 2026-09-22 4:26 ` Simon Marchi 2026-09-23 13:47 ` Simon Marchi 2026-09-24 4:59 ` [PATCH 0/2] " Simon Marchi 2026-09-24 5:05 ` Simon Marchi 2026-09-24 15:09 ` [PATCH v6 0/3] gdb/dwarf: " Simon Marchi 2026-09-24 15:09 ` [PATCH v6 1/3] gdb/dwarf: split cooked index finalization into separate steps Simon Marchi 2026-09-24 15:09 ` [PATCH v6 2/3] gdb/dwarf: resolve class name via DW_AT_signature in cooked index Simon Marchi 2026-09-24 15:09 ` [PATCH v6 3/3] gdb/dwarf: add cooked_index_entry::parent_is_deferred Simon Marchi 2026-09-24 20:53 ` Andrew Burgess 2026-09-25 2:34 ` Simon Marchi 2026-09-24 4:59 ` [PATCH 1/2] gdb: split cooked index finalization into separate steps Simon Marchi 2026-09-24 10:16 ` Andrew Burgess 2026-09-24 14:24 ` Simon Marchi 2026-09-24 4:59 ` [PATCH 2/2] gdb: resolve class name via DW_AT_signature in cooked index Simon Marchi 2026-09-24 10:39 ` Andrew Burgess 2026-09-24 15:07 ` Simon Marchi
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox