Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
* [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-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

* 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 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-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

* [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: [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: [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

* [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

* [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 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

* 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 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 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

* 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

* [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

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