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; 20+ 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] 20+ 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; 20+ 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] 20+ 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; 20+ 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] 20+ 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; 20+ 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] 20+ 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; 20+ 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] 20+ 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; 20+ 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] 20+ 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; 20+ 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] 20+ 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; 20+ 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] 20+ 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; 20+ 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] 20+ 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; 20+ 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] 20+ 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; 20+ 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] 20+ 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; 20+ 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] 20+ 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; 20+ 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] 20+ 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; 20+ 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] 20+ 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; 20+ 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] 20+ 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; 20+ 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] 20+ 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
  2 siblings, 0 replies; 20+ 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] 20+ 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; 20+ 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] 20+ 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; 20+ 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] 20+ 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; 20+ 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] 20+ messages in thread

end of thread, other threads:[~2026-09-16 11:48 UTC | newest]

Thread overview: 20+ 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

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox