Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
From: Simon Marchi <simon.marchi@efficios.com>
To: gdb-patches@sourceware.org
Cc: Andrew Burgess <aburgess@redhat.com>,
	Simon Marchi <simon.marchi@efficios.com>
Subject: [PATCH v6 2/3] gdb/dwarf: resolve class name via DW_AT_signature in cooked index
Date: Thu, 24 Sep 2026 11:09:10 -0400	[thread overview]
Message-ID: <20260924151048.204777-3-simon.marchi@efficios.com> (raw)
In-Reply-To: <20260924151048.204777-1-simon.marchi@efficios.com>

From: Andrew Burgess <aburgess@redhat.com>

This commit fixes PR gdb/33447, an issue where looking up qualified
member function names was not working for C++ binaries compiled with
Clang when using the -fdebug-types-section flag.

Before this commit we would see this behaviour:

  (gdb) print base1::a_function
  There is no field named a_function

When what we expect to see is:

  (gdb) print base1::a_function
  $1 = {void (const base1 * const)} 0x403060 <base1::a_function() const>

The problem is that the cooked index is unable to determine the name
of the parent class `base1` in this case, and so decides not to index
`base1` or any of its child DIEs, which includes its member functions.

The problem was discovered by running gdb.cp/cpexprs-debug-types.exp
with Clang:

  make check-gdb TESTS=gdb.cp/cpexprs-debug-types.exp \
    RUNTESTFLAGS='CXX_FOR_TARGET=clang++ CC_FOR_TARGET=clang'

The cpexprs-debug-types.exp test forces use of the
'-fdebug-types-section' flag, which is not on by default.  With this
flag, class definitions are placed in type units, and the compile unit
contains only a declaration stub for each class.  Both clang++ and g++
emit these stubs, but they differ in one detail: GCC includes
DW_AT_name on the stub, while clang++ does not, the stub carries only
DW_AT_declaration and DW_AT_signature.  The class name is only
available in the type unit, reachable by following the signature.

For example, Clang emits this in a CU:

  <1><2e7d>: DW_TAG_class_type
     DW_AT_declaration : 1
     DW_AT_signature   : 0x3abb...
  <2><2eac>: DW_TAG_subprogram
     DW_AT_name        : a_function
     DW_AT_declaration : 1

The definition for a_function is elsewhere in the same CU:

  <1><3142>: DW_TAG_subprogram
     DW_AT_specification: <0x2eac>

And in a TU elsewhere:

   Compilation Unit @ offset 0xd2e:
    ... snip ...
    Signature:     0x3abb...
  <0><d46>: Abbrev Number: 1 (DW_TAG_type_unit)
     ... snip ...
  <1><d51>: Abbrev Number: 30 (DW_TAG_class_type)
     ... snip ...
     <d57>   DW_AT_name        : (indexed string: 0xaa): base1

To find the DW_AT_name the cooked index needs to look up the type
within the TU.  Without the name the cooked indexer skips indexing
`base1` as well as its children.

This look up used to work; it works in GDB 17.  PR gdb/33447
incorrectly identifies commit c879f4dc3e317cf6353a45a803ecf00d577a13d8
as the commit that introduced the regression.  This is actually the
last working commit.  The problem was introduced by the next commit in
the same series:

  commit 86ac8c546235a67d6a6bb29476a3a9ac8f7a620a
  Date:   Thu Jan 2 15:28:18 2025 -0700

      Convert lookup_symbol_in_objfile

Prior to this commit GDB's symbol lookup had two phases, a search
through already expanded symtabs, and a search via
lookup_symbol_via_quick_fns.  After the above commit only
lookup_symbol_via_quick_fns remains.

The lookup_symbol_via_quick_fns lookup, which relies on the indexer,
was always broken, but the first phase, searching via expanded
symtabs, could correctly find the type name via the signature.

An initial attempt to solve this problem tried to fix this problem
within cooked_indexer::scan_attributes, calling lookup_signatured_type
and finding the name that way.  However, there were three problems
with this approach:

  1. Possible thread safety issues; calling lookup_signatured_type for
     a DWO file ends up calling lookup_dwo_signatured_type, which can
     call add_type_unit and finalize_all_units, which modify state
     that is shared between parser threads.

  2. The dwarf2_per_cu::type_offset_in_section for a TU is only set
     when the cutu_reader is constructed to parse that TU.  Calling
     lookup_signatured_type doesn't fully parse the TU, it just finds
     the TU.  The original code relied on type_offset_in_section being
     valid in order to then parse the TU and extract the name.  This
     would break if the CU was processed before the TU.

  3. For skeletonless TUs, these are not indexed until late in the
     indexing process, after all the CUs have been indexed.  This
     means that if the TU referenced by the signature was skeletonless
     then the original approach would fail to find it.

The new approach presented here is modeled more along the lines of the
deferred parent handling that already exists within the indexer.  The
following changes have been made:

1. abbrev.c: Add DW_AT_signature to the set of attributes that mark a
   DIE as "interesting" in abbrev_table::read.  Without this,
   the unnamed class stub was classified as uninteresting at the
   abbreviation level and scan_attributes was never called for it.

2. cooked-indexer.c (cooked_indexer::scan_attributes): Capture the
   DW_AT_signature attribute.

3. cooked-index-entry.h: Allow a cooked_index_entry to hold a deferred
   name, in the same way it can already hold a deferred parent.

   The name member becomes a union, cooked_index_entry_name_ref, holding
   either the resolved name or the signature of the DIE that carries the
   real name.  The new IS_NAME_DEFERRED flag says which member is
   active, mirroring IS_PARENT_DEFERRED and cooked_index_entry_ref.

   cooked_index_entry also gets other methods mirroring the deferred
   parent mechanism: name(), name_is_deferred(), get_deferred_name() and
   resolve_name().  name() asserts that the name is no longer deferred,
   so any attempt to read the name too early is caught.  All readers of
   the old public 'name' member are updated to call name().

4. cooked-indexer.c (cooked_indexer::index_dies): There are two
   different jobs done here:

   (a) If a DIE has no name, but does have a signature, then create the
       entry anyway, storing the signature in the name union and setting
       IS_NAME_DEFERRED.

   (b) If we are indexing a TU, make a record of the signature, and the
       name of the primary type within the TU.  This builds a signature
       to name map.

5. cooked-index-worker.c (cooked_index_worker::done_reading): Merge
   together all of the signature to name maps built in (4b).

6. cooked-index-shard.c (cooked_index_shard::resolve_deferred_names):

   A new function, look through all of the cooked_index_entry objects in
   the shard for those with IS_NAME_DEFERRED, and use the signature to
   name map created in (5) to look up the name and update the entry.

   resolve_deferred_names returns a bool, true if any deferred name
   couldn't be resolved.

7. cooked-index-shard.c (cooked_index_shard::prune_nameless_entries):

   A new function, look through all of the cooked_index_entry objects in
   the shard, if the entry's parent still has a deferred name, break the
   parent relationship.  If the entry itself still has a deferred name,
   remove it from the index.

   This prevents a nameless entry from being found via the get_parent
   method.  After this point every entry in the index has a resolved
   name, so name() can be called on any of them.

8. cooked-index.c (cooked_index::wait): After waiting, if we have
   completed finalization then emit any complaints that came from the
   finalization phase.

9. cooked-index.c: Add the "resolve deferred names" and "prune nameless
   entries" steps to the index finalization steps, implemented with the
   functions mentioned in points 6 and 7.

   The "resolve deferred names" step is only applied to shards that have
   m_have_deferred_names set.

   The "prune nameless entries" step is only run if
   cooked_index::m_have_nameless_entries is set.

10. cooked-index-entry.c (cooked_index_entry::write_scope): Add an
    assert that the entry's name is resolved.  Entries that don't
    satisfy this should have been removed during finalization.

Added three new DWARF assembler tests.

  gdb.dwarf2/sig-type-unnamed-class.exp
  gdb.dwarf2/sig-type-unnamed-class-dwo.exp:

   These reproduce the problem case; there's a nameless declaration stub
   that references its full type via DW_AT_signature.  To match the
   Clang output as much as possible the member function definition is
   separate and makes use of DW_AT_specification.  The -dwo test places
   the DWARF into a DWO file and uses a skeletonless TU.

  gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp:

   This one tests some invalid DWARF, the type DIE references a
   signature that doesn't exist.  In this case GDB discards the
   cooked_index_entries whose name could not be resolved, and orphans
   their children.  I think this is fine though, the DWARF is corrupted
   in this case.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=33447
Co-Authored-By: Simon Marchi <simon.marchi@efficios.com>
Change-Id: I1e0ba20e58af169abbf99e97c14c788164f0d1b2
---
 gdb/dwarf2/abbrev.c                           |   1 +
 gdb/dwarf2/cooked-index-entry.c               |  14 +-
 gdb/dwarf2/cooked-index-entry.h               |  75 ++++++++-
 gdb/dwarf2/cooked-index-shard.c               | 105 ++++++++++--
 gdb/dwarf2/cooked-index-shard.h               |  47 +++++-
 gdb/dwarf2/cooked-index-worker.c              |   9 +
 gdb/dwarf2/cooked-index-worker.h              |  30 +++-
 gdb/dwarf2/cooked-index.c                     | 105 +++++++++++-
 gdb/dwarf2/cooked-index.h                     |  34 +++-
 gdb/dwarf2/cooked-indexer.c                   |  60 +++++--
 gdb/dwarf2/cooked-indexer.h                   |   1 +
 gdb/dwarf2/index-write.c                      |   2 +-
 .../sig-type-unnamed-class-bad-sig.exp        | 155 ++++++++++++++++++
 .../gdb.dwarf2/sig-type-unnamed-class-dwo.exp | 143 ++++++++++++++++
 .../gdb.dwarf2/sig-type-unnamed-class.exp     | 106 ++++++++++++
 15 files changed, 832 insertions(+), 55 deletions(-)
 create mode 100644 gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp
 create mode 100644 gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-dwo.exp
 create mode 100644 gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp

diff --git a/gdb/dwarf2/abbrev.c b/gdb/dwarf2/abbrev.c
index 44d5c87a5f79..e99acc5e752d 100644
--- a/gdb/dwarf2/abbrev.c
+++ b/gdb/dwarf2/abbrev.c
@@ -160,6 +160,7 @@ abbrev_table::read (struct dwarf2_section_info *section,
 	    case DW_AT_specification:
 	    case DW_AT_abstract_origin:
 	    case DW_AT_extension:
+	    case DW_AT_signature:
 	      has_specification_or_origin = true;
 	      break;
 
diff --git a/gdb/dwarf2/cooked-index-entry.c b/gdb/dwarf2/cooked-index-entry.c
index 8c324f77904b..44b065157b80 100644
--- a/gdb/dwarf2/cooked-index-entry.c
+++ b/gdb/dwarf2/cooked-index-entry.c
@@ -35,6 +35,7 @@ to_string (cooked_index_flag flags)
     MAP_ENUM_FLAG (IS_PARENT_DEFERRED),
     MAP_ENUM_FLAG (IS_SYNTHESIZED),
     MAP_ENUM_FLAG (IS_INLINED),
+    MAP_ENUM_FLAG (IS_NAME_DEFERRED),
   };
 
   return flags.to_string (mapping);
@@ -175,7 +176,7 @@ cooked_index_entry::full_name (struct obstack *storage,
 			       cooked_index_full_name_flag name_flags,
 			       const char *default_sep) const
 {
-  const char *local_name = ((name_flags & FOR_MAIN) != 0) ? name : canonical;
+  const char *local_name = ((name_flags & FOR_MAIN) != 0) ? name () : canonical;
 
   if ((flags & IS_LINKAGE) != 0 || get_parent () == nullptr)
     return local_name;
@@ -197,8 +198,8 @@ cooked_index_entry::full_name (struct obstack *storage,
 	 cooked_index_shard::canonicalize_names.  */
       if ((name_flags & FOR_ADA_LINKAGE_NAME) != 0)
 	{
-	  if (strstr (name, "__") != nullptr)
-	    return name;
+	  if (strstr (name (), "__") != nullptr)
+	    return name ();
 	  sep = "__";
 	  break;
 	}
@@ -233,10 +234,15 @@ cooked_index_entry::write_scope (struct obstack *storage,
 {
   if (get_parent () != nullptr)
     get_parent ()->write_scope (storage, sep, flags);
+
+  /* Any entry with an unresolved name will have been filtered out by
+     cooked_index_shard::prune_nameless_entries.  */
+  gdb_assert (!name_is_deferred ());
+
   /* When computing the Ada linkage name, the entry might not have
      been canonicalized yet, so use the 'name'.  */
   const char *local_name = ((flags & (FOR_MAIN | FOR_ADA_LINKAGE_NAME)) != 0
-			    ? name
+			    ? name ()
 			    : canonical);
   obstack_grow (storage, local_name, strlen (local_name));
   obstack_grow (storage, sep, strlen (sep));
diff --git a/gdb/dwarf2/cooked-index-entry.h b/gdb/dwarf2/cooked-index-entry.h
index 60ea581cbbe5..2637b8255258 100644
--- a/gdb/dwarf2/cooked-index-entry.h
+++ b/gdb/dwarf2/cooked-index-entry.h
@@ -47,6 +47,8 @@ enum cooked_index_flag_enum : unsigned char
   /* True if this is a function that has DW_AT_inline set in a way
      that indicates it was inlined.  */
   IS_INLINED = 64,
+  /* True if m_name.deferred has a value rather than m_name.resolved.  */
+  IS_NAME_DEFERRED = 128,
 };
 DEF_ENUM_FLAGS_TYPE (enum cooked_index_flag_enum, cooked_index_flag);
 
@@ -79,6 +81,31 @@ union cooked_index_entry_ref
   parent_map::addr_type deferred;
 };
 
+/* Type representing either a resolved or deferred cooked_index_entry
+   name.  A deferred name is held as the DW_AT_signature of the DIE
+   that carries the real name.  */
+
+union cooked_index_entry_name_ref
+{
+  cooked_index_entry_name_ref (ULONGEST deferred_)
+  {
+    deferred = deferred_;
+  }
+
+  cooked_index_entry_name_ref (const char *resolved_)
+  {
+    resolved = resolved_;
+  }
+
+  const char *resolved;
+  ULONGEST deferred;
+};
+
+/* Type that maps DW_AT_signature values for a TU to the name of the
+   primary type within the TU.  */
+
+using signature_to_name_map = gdb::unordered_map<ULONGEST, const char *>;
+
 /* Return a string representation of FLAGS.  */
 
 std::string to_string (cooked_index_flag flags);
@@ -94,15 +121,16 @@ struct cooked_index_entry : public allocate_on_obstack<cooked_index_entry>
 {
   cooked_index_entry (sect_offset die_offset_, enum dwarf_tag tag_,
 		      cooked_index_flag flags_,
-		      enum language lang_, const char *name_,
+		      enum language lang_,
+		      cooked_index_entry_name_ref name_,
 		      cooked_index_entry_ref parent_entry_,
 		      dwarf2_per_cu *per_cu_)
-    : name (name_),
-      tag (tag_),
+    : tag (tag_),
       flags (flags_),
       lang (lang_),
       die_offset (die_offset_),
       per_cu (per_cu_),
+      m_name (name_),
       m_parent_entry (parent_entry_)
   {
   }
@@ -225,6 +253,36 @@ struct cooked_index_entry : public allocate_on_obstack<cooked_index_entry>
     return m_parent_entry.deferred;
   }
 
+  /* Return the entry's name.  This may be the name or the linkage
+     name -- two entries are created for DIEs which have both
+     attributes.  It points to a string that outlives this entry,
+     which in practice means the mapped DWARF or the storage of one of
+     the cooked_index_shard objects.  */
+  const char *name () const
+  {
+    gdb_assert (!name_is_deferred ());
+    return m_name.resolved;
+  }
+
+  /* Return true if this entry's name still has to be resolved.  */
+  bool name_is_deferred () const
+  { return (flags & IS_NAME_DEFERRED) != 0; }
+
+  /* Return the signature of the DIE holding this entry's name.  */
+  ULONGEST get_deferred_name () const
+  {
+    gdb_assert (name_is_deferred ());
+    return m_name.deferred;
+  }
+
+  /* Resolve deferred name to NAME.  */
+  void resolve_name (const char *name)
+  {
+    gdb_assert (name_is_deferred ());
+    flags = flags & ~IS_NAME_DEFERRED;
+    m_name.resolved = name;
+  }
+
   /* Force the language to be set to the CU's language.  This may only
      be called when the language is unknown, which can only happen
      with .gdb_index.  This method is const because it is called from
@@ -252,12 +310,7 @@ struct cooked_index_entry : public allocate_on_obstack<cooked_index_entry>
      defined in some CU that is included by many other CUs.  */
   iteration_status visit_defining_cus (per_cu_callback callback) const;
 
-  /* The name as it appears in DWARF.  This always points into one of
-     the mapped DWARF sections.  Note that this may be the name or the
-     linkage name -- two entries are created for DIEs which have both
-     attributes.  */
-  const char *name;
-  /* The canonical name.  This may be equal to NAME.  */
+  /* The canonical name.  This may be equal to the name.  */
   const char *canonical = nullptr;
   /* The DWARF tag.  */
   enum dwarf_tag tag;
@@ -284,6 +337,10 @@ struct cooked_index_entry : public allocate_on_obstack<cooked_index_entry>
   void write_scope (struct obstack *storage, const char *sep,
 		    cooked_index_full_name_flag flags) const;
 
+  /* The entry's name, or the signature by which it can be found.  See
+     IS_NAME_DEFERRED and the accessors above.  */
+  cooked_index_entry_name_ref m_name;
+
   /* The parent entry.  This is NULL for top-level entries.
      Otherwise, it points to the parent entry, such as a namespace or
      class.  */
diff --git a/gdb/dwarf2/cooked-index-shard.c b/gdb/dwarf2/cooked-index-shard.c
index 51b2eacd46d8..af8c3e9527ac 100644
--- a/gdb/dwarf2/cooked-index-shard.c
+++ b/gdb/dwarf2/cooked-index-shard.c
@@ -20,6 +20,8 @@
 #include "dwarf2/cooked-index-shard.h"
 #include "dwarf2/tag.h"
 #include "dwarf2/index-common.h"
+#include "dwarf2/read.h"
+#include "dwarf2/error.h"
 #include "cp-support.h"
 #include "c-lang.h"
 #include "ada-lang.h"
@@ -48,7 +50,7 @@ cooked_index_shard::create (sect_offset die_offset,
 			    enum dwarf_tag tag,
 			    cooked_index_flag flags,
 			    enum language lang,
-			    const char *name,
+			    cooked_index_entry_name_ref name,
 			    cooked_index_entry_ref parent_entry,
 			    dwarf2_per_cu *per_cu)
 {
@@ -75,7 +77,8 @@ cooked_index_shard::create (sect_offset die_offset,
 cooked_index_entry *
 cooked_index_shard::add (sect_offset die_offset, enum dwarf_tag tag,
 			 cooked_index_flag flags, enum language lang,
-			 const char *name, cooked_index_entry_ref parent_entry,
+			 cooked_index_entry_name_ref name,
+			 cooked_index_entry_ref parent_entry,
 			 dwarf2_per_cu *per_cu)
 {
   cooked_index_entry *result = create (die_offset, tag, flags, lang, name,
@@ -85,6 +88,9 @@ cooked_index_shard::add (sect_offset die_offset, enum dwarf_tag tag,
   if ((flags & IS_PARENT_DEFERRED) != 0)
     m_have_deferred_parents = true;
 
+  if (result->name_is_deferred ())
+    m_have_deferred_names = true;
+
   /* An explicitly-tagged main program should always override the
      implicit "main" discovery.  */
   if ((flags & IS_MAIN) != 0)
@@ -107,7 +113,8 @@ cooked_index_shard::add (sect_offset die_offset, enum dwarf_tag tag,
 	   && parent_entry.resolved == nullptr
 	   && m_main == nullptr
 	   && language_may_use_plain_main (lang)
-	   && streq (name, "main"))
+	   && !result->name_is_deferred ()
+	   && streq (result->name (), "main"))
     m_main = result;
 
   return result;
@@ -125,10 +132,10 @@ cooked_index_shard::handle_gnat_encoded_entry
      characters are left as-is.  This is done to make name matching a
      bit simpler; and for wide characters, it means the choice of Ada
      source charset does not affect the indexer directly.  */
-  std::string canonical = ada_decode (entry->name, false, false);
+  std::string canonical = ada_decode (entry->name (), false, false);
   if (canonical.empty ())
     {
-      entry->canonical = entry->name;
+      entry->canonical = entry->name ();
       return;
     }
   std::vector<std::string_view> names = split_name (canonical.c_str (),
@@ -151,7 +158,7 @@ cooked_index_shard::handle_gnat_encoded_entry
 	  last = create (entry->die_offset, DW_TAG_module,
 			 IS_SYNTHESIZED, language_ada, new_name, parent,
 			 entry->per_cu);
-	  last->canonical = last->name;
+	  last->canonical = last->name ();
 	  new_entries.push_back (last);
 	  *slot = last;
 	}
@@ -175,7 +182,7 @@ struct cooked_index_entry_name_ptr_hash
 
   std::uint64_t operator () (const cooked_index_entry *entry) const noexcept
   {
-    return ankerl::unordered_dense::hash<const char *> () (entry->name);
+    return ankerl::unordered_dense::hash<const char *> () (entry->name ());
   }
 };
 
@@ -186,12 +193,44 @@ struct cooked_index_entry_name_ptr_eq
   bool operator () (const cooked_index_entry *a,
 		    const cooked_index_entry *b) const noexcept
   {
-    return a->name == b->name;
+    return a->name () == b->name ();
   }
 };
 
 /* See cooked-index-shard.h.  */
 
+bool
+cooked_index_shard::resolve_deferred_names
+	(const signature_to_name_map &sig_names)
+{
+  bool have_nameless_entries = false;
+  for (cooked_index_entry *entry : m_entries)
+    {
+      if (!entry->name_is_deferred ())
+	continue;
+
+      const ULONGEST signature = entry->get_deferred_name ();
+
+      if (const auto it = sig_names.find (signature);
+	  it != sig_names.end ())
+	entry->resolve_name (it->second);
+      else
+	{
+	  have_nameless_entries = true;
+	  complaint (_(DWARF_ERROR_PREFIX
+		       "Cannot find signatured DIE %s referenced from DIE "
+		       "at %s [in module %s]"),
+		     hex_string (signature),
+		     sect_offset_str (entry->die_offset),
+		     entry->per_cu->per_bfd ()->filename ());
+	}
+    }
+
+  return have_nameless_entries;
+}
+
+/* See cooked-index-shard.h.  */
+
 void
 cooked_index_shard::resolve_deferred_parents
 	(const parent_map_map *parent_maps)
@@ -209,6 +248,38 @@ cooked_index_shard::resolve_deferred_parents
 
 /* See cooked-index-shard.h.  */
 
+void
+cooked_index_shard::prune_nameless_entries ()
+{
+  for (cooked_index_entry *entry : m_entries)
+    {
+      /* Remove a parent reference if the parent has no name.  This
+	 leaves ENTRY as an orphan, but this only happens if the DWARF
+	 is corrupted and we failed to find a name for the parent.  We
+	 can safely check the parent's name at this point because all
+	 deferred names and parent links will have been resolved in
+	 all shards before this is called on any shard.  */
+      if (const cooked_index_entry *parent = entry->get_parent ();
+	  parent != nullptr && parent->name_is_deferred ())
+	entry->set_parent (nullptr);
+    }
+
+  /* If we failed to resolve the name of an entry via its signature
+     then remove the entry from the m_entries vector.  This should be
+     rare, and should only happen when we have corrupted DWARF.  The
+     entries still live on the obstack, so parent pointers are still
+     valid, but removing entries from the index means we don't try to
+     search them when looking for index hits.  */
+  m_entries.erase (std::remove_if (m_entries.begin (), m_entries.end (),
+				   [] (const cooked_index_entry *e)
+				   {
+				     return e->name_is_deferred ();
+				   }),
+		   m_entries.end ());
+}
+
+/* See cooked-index-shard.h.  */
+
 void
 cooked_index_shard::canonicalize_names ()
 {
@@ -239,21 +310,25 @@ cooked_index_shard::canonicalize_names ()
       /* Deferred parents should not reach this point.  */
       gdb_assert ((entry->flags & IS_PARENT_DEFERRED) == 0);
 
+      /* Entries without a name are filtered out during the call to
+	 prune_nameless_entries.  */
+      gdb_assert (!entry->name_is_deferred ());
+
       /* Note that this code must be kept in sync with
 	 cooked_index::get_main -- if canonicalization is required
 	 here, then a check might be required there.  */
       gdb_assert (entry->canonical == nullptr);
       if ((entry->flags & IS_LINKAGE) != 0)
-	entry->canonical = entry->name;
+	entry->canonical = entry->name ();
       else if (entry->lang == language_ada)
 	{
 	  /* Newer versions of GNAT emit DW_TAG_module and use a
 	     hierarchical structure.  In this case, we don't need to
 	     do any extra work.  This can be detected by looking for a
 	     GNAT-encoded name.  */
-	  if (strstr (entry->name, "__") == nullptr)
+	  if (strstr (entry->name (), "__") == nullptr)
 	    {
-	      entry->canonical = entry->name;
+	      entry->canonical = entry->name ();
 
 	      /* If the entry does not have a parent, then there's
 		 nothing extra to do here -- the entry itself is
@@ -300,10 +375,10 @@ cooked_index_shard::canonicalize_names ()
 		 name.  */
 	      gdb::unique_xmalloc_ptr<char> canon_name
 		= (entry->lang == language_cplus
-		   ? cp_canonicalize_string (entry->name)
-		   : c_canonicalize_name (entry->name));
+		   ? cp_canonicalize_string (entry->name ())
+		   : c_canonicalize_name (entry->name ()));
 	      if (canon_name == nullptr)
-		entry->canonical = entry->name;
+		entry->canonical = entry->name ();
 	      else
 		entry->canonical = m_names.insert (std::move (canon_name));
 	    }
@@ -315,7 +390,7 @@ cooked_index_shard::canonicalize_names ()
 	    }
 	}
       else
-	entry->canonical = entry->name;
+	entry->canonical = entry->name ();
     }
 
   /* Make sure any new Ada entries end up in the results.  This isn't
diff --git a/gdb/dwarf2/cooked-index-shard.h b/gdb/dwarf2/cooked-index-shard.h
index 9b9ffa7a76e3..ca4e415ed326 100644
--- a/gdb/dwarf2/cooked-index-shard.h
+++ b/gdb/dwarf2/cooked-index-shard.h
@@ -26,6 +26,7 @@
 #include "addrmap.h"
 #include "gdbsupport/iterator-range.h"
 #include "gdbsupport/string-set.h"
+#include "complaints.h"
 
 /* An index of interesting DIEs.  This is "cooked", in contrast to a
    mapped .debug_names or .gdb_index, which are "raw".  An entry in
@@ -44,7 +45,7 @@ class cooked_index_shard
      Entries are owned by this object.  The new item is returned.  */
   cooked_index_entry *add (sect_offset die_offset, enum dwarf_tag tag,
 			   cooked_index_flag flags, enum language lang,
-			   const char *name,
+			   cooked_index_entry_name_ref name,
 			   cooked_index_entry_ref parent_entry,
 			   dwarf2_per_cu *per_cu);
 
@@ -105,7 +106,7 @@ class cooked_index_shard
 			      enum dwarf_tag tag,
 			      cooked_index_flag flags,
 			      enum language lang,
-			      const char *name,
+			      cooked_index_entry_name_ref name,
 			      cooked_index_entry_ref parent_entry,
 			      dwarf2_per_cu *per_cu);
 
@@ -118,16 +119,50 @@ class cooked_index_shard
        (cooked_index_entry *entry, htab_t gnat_entries,
 	std::vector<cooked_index_entry *> &new_entries);
 
+  /* Use SIG_NAMES to resolve the deferred names of entries in this shard.
+
+     Return true if any deferred name could not be resolved.  */
+  bool resolve_deferred_names (const signature_to_name_map &sig_names);
+
   /* Use PARENT_MAPS to resolve the deferred parent links of entries in this
      shard.  */
   void resolve_deferred_parents (const parent_map_map *parent_maps);
 
+  /* Remove index entries that have no name (for which we failed to
+     resolve the name in resolve_deferred_names).  Break any parent link
+     pointing to an entry with no name.  */
+  void prune_nameless_entries ();
+
   /* Compute the canonical name for the entries in this shard.
 
      Due to how Ada name lookups work, this function may also create new index
      entries with full names.  */
   void canonicalize_names ();
 
+  /* Called after each step of the finalization process.  Store
+     COMPLAINTS so they can be reported later on the main thread.  */
+  void merge_finalize_complaints (complaint_collection &&complaints)
+  {
+    if (m_finalize_complaints.empty ())
+      m_finalize_complaints = std::move (complaints);
+    else
+      {
+	/* The current version of gdb::unordered_set doesn't support
+	   the merge method that std::unordered_set supports.  If we
+	   update gdb::unordered_set then we could switch this to use
+	   merge().  */
+	m_finalize_complaints.insert (complaints.begin (), complaints.end ());
+      }
+  }
+
+  /* Return the set of complaints emitted during the finalization
+     process.  We move these complaints out of the shard as these are
+     only emitted once, and don't need to be stored beyond that.  */
+  complaint_collection release_finalize_complaints ()
+  {
+    return std::move (m_finalize_complaints);
+  }
+
   /* Storage for the entries.  */
   auto_obstack m_storage;
 
@@ -143,9 +178,17 @@ class cooked_index_shard
   /* Storage for canonical names.  */
   gdb::string_set m_names;
 
+  /* True if at least one entry in this shard has a name that requires
+     deferred resolution.  */
+  bool m_have_deferred_names = false;
+
   /* True if at least one entry in this shard has a parent link that requires
      deferred resolution.  */
   bool m_have_deferred_parents = false;
+
+  /* Any complaints emitted while finalizing the index are stored
+     here until they can be emitted on the main thread.  */
+  complaint_collection m_finalize_complaints;
 };
 
 using cooked_index_shard_up = std::unique_ptr<cooked_index_shard>;
diff --git a/gdb/dwarf2/cooked-index-worker.c b/gdb/dwarf2/cooked-index-worker.c
index 723e027172e8..054b25ba1479 100644
--- a/gdb/dwarf2/cooked-index-worker.c
+++ b/gdb/dwarf2/cooked-index-worker.c
@@ -249,6 +249,15 @@ cooked_index_worker::done_reading ()
       m_all_parents_map.add_map (*one_result.get_parent_map ());
   }
 
+  {
+    scoped_time_it time_it ("DWARF add signature name map", m_per_command_time);
+
+    /* Combine all of the signature to name maps.  */
+    for (cooked_index_worker_result &one_result : m_results)
+      for (const auto &[sig, name] : one_result.get_sig_name_map ())
+	m_all_sig_names_map.emplace (sig, name);
+  }
+
   /* Update all the CU inclusion information.  */
   for (auto &item : m_results)
     item.invert_cu_inclusions ();
diff --git a/gdb/dwarf2/cooked-index-worker.h b/gdb/dwarf2/cooked-index-worker.h
index 1799c0b9e08b..d62ed01faac1 100644
--- a/gdb/dwarf2/cooked-index-worker.h
+++ b/gdb/dwarf2/cooked-index-worker.h
@@ -68,7 +68,7 @@ class cooked_index_worker_result
      cooked-index.h.  The new entry is returned.  */
   cooked_index_entry *add (sect_offset die_offset, enum dwarf_tag tag,
 			   cooked_index_flag flags, enum language lang,
-			   const char *name,
+			   cooked_index_entry_name_ref name,
 			   cooked_index_entry_ref parent_entry,
 			   dwarf2_per_cu *per_cu)
   {
@@ -83,6 +83,15 @@ class cooked_index_worker_result
     return m_shard->add (name);
   }
 
+  /* Called when processing TUs to record that the primary type within
+     a type unit with SIGNATURE, was called NAME.  This information
+     will be used during finalization to fix-up the name of any
+     entries carrying the IS_NAME_DEFERRED flag.  */
+  void add_signatured_type_name (ULONGEST signature, const char *name)
+  {
+    m_sig_name_map.emplace (signature, name);
+  }
+
   /* Install the current addrmap into the shard being constructed,
      then transfer ownership of the index to the caller.  */
   cooked_index_shard_up release_shard ()
@@ -153,6 +162,12 @@ class cooked_index_worker_result
      discovered.  */
   void invert_cu_inclusions ();
 
+  /* The signature to name map for this worker.  */
+  const signature_to_name_map &get_sig_name_map () const
+  {
+    return m_sig_name_map;
+  }
+
 private:
   /* The abbrev table cache used by this indexer.  */
   abbrev_table_cache m_abbrev_table_cache;
@@ -188,6 +203,9 @@ class cooked_index_worker_result
   /* Parent map for each CU that is read.  */
   parent_map m_parent_map;
 
+  /* Signature to name map for the primary type in a TU.  */
+  signature_to_name_map m_sig_name_map;
+
   /* A writeable addrmap being constructed by this scanner.  */
   addrmap_mutable m_addrmap;
 
@@ -272,6 +290,12 @@ class cooked_index_worker
     return &m_all_parents_map;
   }
 
+  /* Return the map containing the complete signature to name information.  */
+  const signature_to_name_map &get_sig_name_map () const
+  {
+    return m_all_sig_names_map;
+  }
+
 protected:
 
   /* Let cooked_index call the 'set' and 'write_to_cache' methods.  */
@@ -319,6 +343,10 @@ class cooked_index_worker
      parent relationships.  */
   parent_map_map m_all_parents_map;
 
+  /* Map from signature to name of primary type within a TU.  This is
+     the combined map, built after all the workers have finished.  */
+  signature_to_name_map m_all_sig_names_map;
+
   /* Current state of this object.  */
   cooked_state m_state = cooked_state::INITIAL;
   /* Mutex and condition variable used to synchronize.  */
diff --git a/gdb/dwarf2/cooked-index.c b/gdb/dwarf2/cooked-index.c
index bf1ea5223671..96996ebc4603 100644
--- a/gdb/dwarf2/cooked-index.c
+++ b/gdb/dwarf2/cooked-index.c
@@ -58,7 +58,23 @@ cooked_index::wait (cooked_state desired_state, bool allow_quit)
   if (m_state == nullptr)
     return;
 
-  if (m_state->wait (desired_state, allow_quit))
+  bool done = m_state->wait (desired_state, allow_quit);
+
+  /* Emit any cached complaints if we have finalized and we are on the
+     main thread.  Check for the requested state or the DONE flag
+     here, we might have only asked for MAIN_AVAILABLE, but if the
+     workers are quick then they might be done, in which case we
+     should emit the complaints now.  */
+  if (!m_finalize_complaints_emitted
+      && is_main_thread ()
+      && (desired_state >= cooked_state::FINALIZED || done))
+    {
+      m_finalize_complaints_emitted = true;
+      for (const auto &shard : m_shards)
+	re_emit_complaints (shard->release_finalize_complaints ());
+    }
+
+  if (done)
     {
       /* Only the main thread can modify this.  */
       gdb_assert (is_main_thread ());
@@ -75,7 +91,40 @@ cooked_index::set_contents ()
   m_state->set (cooked_state::MAIN_AVAILABLE);
 
   /* Start the first step of index finalization.  */
-  this->start_resolve_deferred_parents ();
+  this->start_resolve_deferred_names ();
+}
+
+/* See cooked-index.h.  */
+
+void
+cooked_index::start_resolve_deferred_names ()
+{
+  gdb::task_group group ([this] ()
+    {
+      this->start_resolve_deferred_parents ();
+    });
+
+  /* Arrange to call resolve_deferred_names on each shard that has
+     deferred names.  */
+  for (const cooked_index_shard_up &shard : m_shards)
+    {
+      if (!shard->m_have_deferred_names)
+	continue;
+
+      group.add_task ([this, this_shard = shard.get ()] ()
+	{
+	  complaint_interceptor complaint_handler;
+	  scoped_time_it time_it ("DWARF resolve deferred names worker",
+				  m_state->m_per_command_time);
+
+	  if (this_shard->resolve_deferred_names (m_state->get_sig_name_map ()))
+	    m_have_nameless_entries.store (true);
+
+	  this_shard->merge_finalize_complaints (complaint_handler.release ());
+	});
+    }
+
+  group.start ();
 }
 
 /* See cooked-index.h.  */
@@ -85,7 +134,7 @@ cooked_index::start_resolve_deferred_parents ()
 {
   gdb::task_group group ([this] ()
     {
-      this->start_canonicalize_names ();
+      this->start_prune_nameless_entries ();
     });
 
   /* Arrange to call resolve_deferred_parents on each shard that has at least
@@ -97,10 +146,12 @@ cooked_index::start_resolve_deferred_parents ()
 
       group.add_task ([this, this_shard = shard.get ()] ()
 	{
+	  complaint_interceptor complaint_handler;
 	  scoped_time_it time_it ("DWARF resolve deferred parents worker",
 				  m_state->m_per_command_time);
 
 	  this_shard->resolve_deferred_parents (m_state->get_parent_map_map ());
+	  this_shard->merge_finalize_complaints (complaint_handler.release ());
 	});
     }
 
@@ -109,6 +160,44 @@ cooked_index::start_resolve_deferred_parents ()
 
 /* See cooked-index.h.  */
 
+void
+cooked_index::start_prune_nameless_entries ()
+{
+  gdb::task_group group ([this] ()
+    {
+      this->start_canonicalize_names ();
+    });
+
+  /* Remove index entries whose name we could not resolve.
+
+     If there is any nameless entry, this step needs to run on all the shards,
+     because there could be children of a nameless entry in other shards,
+     whose parent links we want to break.
+
+     This step normally only runs in case there is something wrong with the
+     DWARF info.  */
+  if (m_have_nameless_entries.load ())
+    for (const cooked_index_shard_up &shard : m_shards)
+      {
+	group.add_task ([this, this_shard = shard.get ()] ()
+	  {
+	    complaint_interceptor complaint_handler;
+
+	    scoped_time_it time_it ("DWARF prune nameless entries worker",
+				    m_state->m_per_command_time);
+
+	    this_shard->prune_nameless_entries ();
+
+	    this_shard->merge_finalize_complaints
+	      (complaint_handler.release ());
+	  });
+      }
+
+  group.start ();
+}
+
+/* See cooked-index.h.  */
+
 void
 cooked_index::start_canonicalize_names ()
 {
@@ -124,10 +213,12 @@ cooked_index::start_canonicalize_names ()
     {
       group.add_task ([this, this_shard = shard.get ()] ()
 	{
+	  complaint_interceptor complaint_handler;
 	  scoped_time_it time_it ("DWARF canonicalize names worker",
 				  m_state->m_per_command_time);
 
 	  this_shard->canonicalize_names ();
+	  this_shard->merge_finalize_complaints (complaint_handler.release ());
 	});
     }
 
@@ -239,7 +330,7 @@ cooked_index::get_main () const
 		 exception.  */
 	      if ((entry->lang != language_ada
 		   && entry->lang != language_cplus)
-		  || streq (entry->name, "main"))
+		  || streq (entry->name (), "main"))
 		{
 		  /* There won't be one better than this.  */
 		  return entry;
@@ -289,7 +380,7 @@ cooked_index::dump (gdbarch *arch)
 
       gdb_printf ("    [%zu] ((cooked_index_entry *) %p)\n", i++, entry);
       gdb_printf ("    name:       %ps\n", styled_string (style,
-							  entry->name));
+							  entry->name ()));
       gdb_printf ("    canonical:  %ps\n", styled_string (style,
 							  entry->canonical));
       gdb_printf ("    qualified:  %ps\n",
@@ -305,7 +396,7 @@ cooked_index::dump (gdbarch *arch)
 		    entry->get_deferred_parent ());
       else if (entry->get_parent () != nullptr)
 	gdb_printf ("    parent:     ((cooked_index_entry *) %p) [%s]\n",
-		    entry->get_parent (), entry->get_parent ()->name);
+		    entry->get_parent (), entry->get_parent ()->name ());
       else
 	gdb_printf ("    parent:     ((cooked_index_entry *) 0)\n");
 
@@ -315,7 +406,7 @@ cooked_index::dump (gdbarch *arch)
   const cooked_index_entry *main_entry = this->get_main ();
   if (main_entry != nullptr)
     gdb_printf ("  main: ((cooked_index_entry *) %p) [%s]\n", main_entry,
-		  main_entry->name);
+		  main_entry->name ());
   else
     gdb_printf ("  main: ((cooked_index_entry *) 0)\n");
 
diff --git a/gdb/dwarf2/cooked-index.h b/gdb/dwarf2/cooked-index.h
index 758e399706ba..83387b547474 100644
--- a/gdb/dwarf2/cooked-index.h
+++ b/gdb/dwarf2/cooked-index.h
@@ -31,6 +31,8 @@
 #include "dwarf2/cooked-index-shard.h"
 #include "dwarf2/cooked-index-worker.h"
 
+#include <atomic>
+
 /* The main index of DIEs.
 
    The index is created by multiple threads.  The overall process is
@@ -72,10 +74,16 @@
    .   compute_main_name         cooked_index::set_contents
    .          |                              |
    .          v                              v
-   .   wait (MAIN_AVAILABLE)      resolve deferred parents
+   .   wait (MAIN_AVAILABLE)      resolve deferred names
    .          |                              |
    .          v                              v
-   .        done                     canonicalize names
+   .        done                   resolve deferred parents
+   .                                         |
+   .                                         v
+   .                                prune nameless entries
+   .                                         |
+   .                                         v
+   .                                  canonicalize names
    .                                         |
    .                                         v
    .                                 state = FINALIZED
@@ -176,13 +184,23 @@ class cooked_index : public dwarf_scanner_base
   { wait (cooked_state::CACHE_DONE); }
 
 private:
+  /* Start the "resolve deferred names" step of index finalization.  */
+  void start_resolve_deferred_names ();
+
   /* Start the "resolve deferred parents" step of index finalization.  */
   void start_resolve_deferred_parents ();
 
+  /* Start the "prune nameless entries" step of index finalization.
+
+     This step must run after "resolve deferred names" and "resolve deferred
+     parents", because it relies on names and parents being set.  */
+  void start_prune_nameless_entries ();
+
   /* Start the "canonicalize names" step of index finalization.
 
      This step must run after "resolve deferred parents", because it depends on
-     the parents being set.  */
+     the parents being set, and after "prune nameless entries", because it
+     requires all entries to have a name.  */
   void start_canonicalize_names ();
 
   /* Execute the "write to cache" step at the end of index
@@ -197,6 +215,16 @@ class cooked_index : public dwarf_scanner_base
      that the state is CACHE_DONE -- it's important to note that only
      the main thread may change the value of this pointer.  */
   cooked_index_worker_up m_state;
+
+  /* Any complaints raised during finalization are held within the
+     shards in M_SHARDS.  Once the main thread has waited for
+     finalization to be complete then the cached complaints are
+     emitted, and this flag is set to true.  */
+  bool m_finalize_complaints_emitted = false;
+
+  /* True if some deferred name could not be resolved during the "resolve
+     deferred names" step.  */
+  std::atomic<bool> m_have_nameless_entries = false;
 };
 
 /* An implementation of quick_symbol_functions for the cooked DWARF
diff --git a/gdb/dwarf2/cooked-indexer.c b/gdb/dwarf2/cooked-indexer.c
index 581c0eb07635..23029bc4f13c 100644
--- a/gdb/dwarf2/cooked-indexer.c
+++ b/gdb/dwarf2/cooked-indexer.c
@@ -157,6 +157,7 @@ cooked_indexer::scan_attributes (dwarf2_per_cu *scanning_per_cu,
 				 parent_map::addr_type *maybe_defer,
 				 bool *is_enum_class,
 				 bool *is_inlined,
+				 std::optional<ULONGEST> *signature,
 				 bool for_specification)
 {
   bool is_declaration = false;
@@ -234,6 +235,16 @@ cooked_indexer::scan_attributes (dwarf2_per_cu *scanning_per_cu,
 		     attr.get_ref_die_offset () };
 	  break;
 
+	case DW_AT_signature:
+	  /* The DW_AT_signature could also be a direct reference to a
+	     type DIE.  We don't currently try to capture those
+	     signatures as right now we're only capturing this in
+	     order to handle the fact that some TUs are parsed after
+	     the parallel CU parsing.  */
+	  if (attr.form == DW_FORM_ref_sig8)
+	    signature->emplace (attr.as_signature ());
+	  break;
+
 	case DW_AT_external:
 	  if (attr.as_boolean ())
 	    *flags &= ~IS_STATIC;
@@ -380,7 +391,7 @@ cooked_indexer::scan_attributes (dwarf2_per_cu *scanning_per_cu,
 	scan_attributes (scanning_per_cu, new_reader, new_info_ptr,
 			 new_info_ptr, new_abbrev, name, linkage_name,
 			 flags, nullptr, parent_entry, maybe_defer,
-			 is_enum_class, is_inlined, true);
+			 is_enum_class, is_inlined, signature, true);
     }
 
   if (!for_specification)
@@ -547,6 +558,7 @@ cooked_indexer::index_dies (cutu_reader *reader,
       const cooked_index_entry *this_parent_entry = parent_entry;
       bool is_enum_class = false;
       bool is_inlined = false;
+      std::optional<ULONGEST> signature;
 
       /* The scope of a DW_TAG_entry_point cooked_index_entry is the one of
 	 its surrounding subroutine.  */
@@ -556,7 +568,7 @@ cooked_indexer::index_dies (cutu_reader *reader,
 	= scan_attributes (reader->cu ()->per_cu, reader, info_ptr, info_ptr,
 			   abbrev, &name, &linkage_name, &flags, &sibling,
 			   &this_parent_entry, &defer, &is_enum_class,
-			   &is_inlined, false);
+			   &is_inlined, &signature, false);
       /* A DW_TAG_entry_point inherits its static/extern property from
 	 the enclosing subroutine.  */
       if (abbrev->tag == DW_TAG_entry_point)
@@ -611,19 +623,33 @@ cooked_indexer::index_dies (cutu_reader *reader,
       cooked_index_entry *this_entry = nullptr;
       /* Always use the reader's CU for the entry CU.  */
       dwarf2_per_cu *cu_for_entry = reader->cu ()->per_cu;
-      if (name != nullptr)
+
+      /* If scan_attributes failed to find a name then we might still
+	 want to create an entry if we think we might later find a
+	 name via the signature.  In that case the entry holds the
+	 signature until the name is resolved.  */
+      if (name != nullptr || signature.has_value ())
 	{
+	  cooked_index_flag entry_flags = flags;
+	  cooked_index_entry_name_ref name_ref = name;
+	  cooked_index_entry_ref parent_ref = this_parent_entry;
+
+	  if (name == nullptr)
+	    {
+	      name_ref = signature.value ();
+	      entry_flags |= IS_NAME_DEFERRED;
+	    }
+
 	  if (defer != 0)
-	    this_entry
-	      = m_index_storage->add (this_die, abbrev->tag,
-				      flags | IS_PARENT_DEFERRED,
-				      m_language, name,
-				      defer, cu_for_entry);
-	  else
-	    this_entry
-	      = m_index_storage->add (this_die, abbrev->tag, flags,
-				      m_language, name,
-				      this_parent_entry, cu_for_entry);
+	    {
+	      parent_ref = defer;
+	      entry_flags |= IS_PARENT_DEFERRED;
+	    }
+
+	  this_entry
+	    = m_index_storage->add (this_die, abbrev->tag, entry_flags,
+				    m_language, name_ref, parent_ref,
+				    cu_for_entry);
 	}
       else if (this_parent_entry != nullptr)
 	{
@@ -637,6 +663,14 @@ cooked_indexer::index_dies (cutu_reader *reader,
 	  m_die_range_map->add_entry (addr, addr, this_parent_entry);
 	}
 
+      /* If THIS_DIE is the primary type within a TU, and has a valid
+	 name, then add an entry mapping the signature to the name.  */
+      if (const signatured_type *st = cu_for_entry->as_signatured_type ();
+	  name != nullptr
+	  && st != nullptr
+	  && this_die == st->type_offset_in_section)
+	m_index_storage->add_signatured_type_name (st->signature, name);
+
       if (linkage_name != nullptr)
 	{
 	  /* We only want this to be "main" if it has a linkage name
diff --git a/gdb/dwarf2/cooked-indexer.h b/gdb/dwarf2/cooked-indexer.h
index 563742c90f6a..393ef9fca5ac 100644
--- a/gdb/dwarf2/cooked-indexer.h
+++ b/gdb/dwarf2/cooked-indexer.h
@@ -86,6 +86,7 @@ class cooked_indexer
 				   parent_map::addr_type *maybe_defer,
 				   bool *is_enum_class,
 				   bool *is_inlined,
+				   std::optional<ULONGEST> *signature,
 				   bool for_specification);
 
   /* Handle DW_TAG_imported_unit, by scanning the DIE to find
diff --git a/gdb/dwarf2/index-write.c b/gdb/dwarf2/index-write.c
index 2b92b2cae472..4712f08515c7 100644
--- a/gdb/dwarf2/index-write.c
+++ b/gdb/dwarf2/index-write.c
@@ -697,7 +697,7 @@ class debug_names
     if ((entry->flags & IS_SYNTHESIZED) != 0)
       return;
 
-    m_name_to_value_set[entry->name].emplace_back (entry);
+    m_name_to_value_set[entry->name ()].emplace_back (entry);
   }
 
   /* Build all the tables.  All symbols must be already inserted.
diff --git a/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp
new file mode 100644
index 000000000000..39a221a1b755
--- /dev/null
+++ b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-bad-sig.exp
@@ -0,0 +1,155 @@
+# Copyright 2026 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# The test sets up a type DIE without a DW_AT_name, the type DIE links
+# to a type within a TU via DW_AT_signature.  Normally GDB would
+# use the DW_AT_signature to find the name of the type DIE.
+#
+# However, in this test there is no type DIE with the specified
+# signature!
+#
+# This test checks that GDB can emit a complaint when this situation is
+# encountered.  The test also checks that trying to find a method
+# within the type using the qualified name will fail.
+
+load_lib dwarf.exp
+
+# This test can only be run on targets which support DWARF-2 and use gas.
+require dwarf2_support
+
+standard_testfile main-foo.c .S
+
+# Build the test program using VERSION for the CU/TU DWARF version.
+proc run_test { version } {
+    # Create the DWARF.
+    set asm_file [standard_output_file $::srcfile2]
+    Dwarf::assemble {
+	filename $asm_file
+	add_dummy_cus 0
+    } {
+	upvar version version
+
+	get_func_info foo
+	get_func_info main
+
+	declare_labels method_decl
+
+	tu { version $version } 0xdeadbeefdeadbeef the_type {
+	    DW_TAG_type_unit {
+		DW_AT_language @DW_LANG_C_plus_plus
+	    } {
+		the_type: DW_TAG_class_type {
+		    DW_AT_name the_type
+		    DW_AT_byte_size 1 sdata
+		}
+	    }
+	}
+
+	cu { version $version } {
+	    compile_unit {
+		DW_AT_language @DW_LANG_C_plus_plus
+	    } {
+		DW_TAG_class_type {
+		    DW_AT_declaration 1 flag
+		    DW_AT_signature 0xdeadbeef01234567 ref_sig8
+		} {
+		    method_decl: DW_TAG_subprogram {
+			DW_AT_name method
+			DW_AT_linkage_name _ZN8the_type6methodEv
+			DW_AT_declaration 1 flag
+		    }
+		}
+
+		DW_TAG_subprogram {
+		    DW_AT_specification %$method_decl
+		    DW_AT_low_pc $foo_start DW_FORM_addr
+		    DW_AT_high_pc $foo_end DW_FORM_addr
+		}
+
+		DW_TAG_subprogram {
+		    DW_AT_name main
+		    DW_AT_low_pc $main_start DW_FORM_addr
+		    DW_AT_high_pc $main_end DW_FORM_addr
+		}
+	    }
+	}
+    }
+
+    set testfile_name ${::testfile}-${version}
+    set binfile_name [standard_output_file $testfile_name]
+    if { [build_executable "failed to build" $testfile_name \
+	      [list $asm_file $::srcfile] {nodebug}] } {
+	return
+    }
+
+    clean_restart $testfile_name
+
+    set expect_complaint true
+    # If we have a .gdb_index already then the part of this test that
+    # checks for a warning when loading the executable is not going to
+    # work as the warning will have already been generated when the
+    # index was added.
+    if {[get_index_type $testfile_name] == "gdb"} {
+	set expect_complaint false
+    }
+
+    clean_restart
+
+    set host_binfile [gdb_remote_download host $binfile_name]
+    if { [is_remote host] } {
+	# For some remote host boards gdb_remote_download returns an
+	# absolute path, but for others it returns a relative path.
+	# The path reported in the complaint message is always
+	# absolute.  Handle this with an optional prefix pattern.
+	set binfile_re "\[^\r\n\]*[string_to_regexp $host_binfile]"
+    } elseif {[section_get $binfile_name ".gnu_debuglink"] ne ""} {
+	set binfile_re "\[^\r\n\]+[string_to_regexp ${testfile_name}.debug]"
+    } else {
+	set binfile_re [string_to_regexp $binfile_name]
+    }
+
+    # Load the executable and check we see the expected complaint from
+    # the DWARF indexer.
+    gdb_test_no_output "maint set dwarf synchronous on"
+    gdb_test_no_output "set complaints 100"
+    set saw_complaint false
+    gdb_test_multiple "file $host_binfile" "file command" -lbl {
+	-re "^\r\nDuring symbol reading: DWARF Error: Cannot find signatured DIE 0xdeadbeef01234567 referenced from DIE at $::hex \\\[in module $binfile_re\\\](?=\r\n)" {
+	    set saw_complaint true
+	    exp_continue
+	}
+	-re "^\r\n$::gdb_prompt $" {
+	    gdb_assert { $saw_complaint == $expect_complaint } $gdb_test_name
+	}
+    }
+
+    if { ![readnow] } {
+	# Check that GDB is unable to find this via the index.  If we
+	# load the full symbols then GDB is able to resolve this print
+	# via the linkage name.
+	#
+	# It is unfortunate that there is a difference in behaviour
+	# depending on whether GDB is using the index or not, but
+	# remember, the DWARF is corrupted at this point, so it's
+	# probably OK if things don't fully work, so long as GDB
+	# doesn't crash.
+	gdb_test "print the_type::method" \
+	    "^There is no field named method"
+    }
+}
+
+foreach_with_prefix version { 4 5 } {
+    run_test $version
+}
diff --git a/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-dwo.exp b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-dwo.exp
new file mode 100644
index 000000000000..bc6420d7aa9d
--- /dev/null
+++ b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class-dwo.exp
@@ -0,0 +1,143 @@
+# Copyright 2026 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# Like sig-type-unnamed-class.exp, but with split DWARF.  The type
+# unit in the DWO file is skeletonless, which is the norm when using
+# split DWARF.
+#
+# Check that the cooked index correctly resolves the parent of a member
+# function when the class declaration stub in the DWO compile unit has
+# DW_AT_signature but no DW_AT_name, and the referenced type unit is
+# skeletonless.
+
+load_lib dwarf.exp
+
+# This test can only be run on targets which support DWARF-2 and use gas.
+require dwarf2_support
+
+standard_testfile main-foo.c -dw.S
+
+# Build the test program using VERSION for the CU/TU DWARF version.
+
+proc run_test { version } {
+    set asm_file [standard_output_file $::srcfile2]
+
+    # Setup some state based on DWARF version.
+    if { $version == 5 } {
+	set dwo_cu_opts [list fission 1 version 5 dwo_id 0xF00D]
+	set skel_cu_opts [list version 5 dwo_id 0xF00D]
+	set dwo_name_attr DW_AT_dwo_name
+    } else {
+	set dwo_cu_opts [list fission 1 version 4]
+	set skel_cu_opts [list version 4]
+	set dwo_name_attr DW_AT_GNU_dwo_name
+    }
+
+    set testfile_name ${::testfile}-${version}
+
+    Dwarf::assemble $asm_file {
+	upvar version version
+	upvar dwo_cu_opts dwo_cu_opts
+	upvar skel_cu_opts skel_cu_opts
+	upvar dwo_name_attr dwo_name_attr
+	upvar testfile_name testfile_name
+
+	declare_labels method_decl
+
+	get_func_info foo
+
+	# Type unit in the DWO file.  This is skeletonless: there is no
+	# corresponding skeleton TU in the main file.
+	tu {
+	    fission 1
+	    version $version
+	} 0xdeadbeef01234567 the_type {
+	    type_unit {
+		DW_AT_language @DW_LANG_C_plus_plus
+	    } {
+		the_type: DW_TAG_class_type {
+		    DW_AT_name the_type
+		    DW_AT_byte_size 1 DW_FORM_sdata
+		}
+	    }
+	}
+
+	set debug_addr_base [debug_addr_label]
+
+	# Compile unit in the DWO file.  Contains a class declaration
+	# stub with DW_AT_signature but no DW_AT_name, replicating what
+	# Clang emits with -fdebug-types-section.  The class name must
+	# be resolved by following the signature to the type unit.
+	cu $dwo_cu_opts {
+	    compile_unit {
+		DW_AT_language @DW_LANG_C_plus_plus
+		DW_AT_name ${::srcfile}
+		DW_AT_comp_dir .
+		if { $version == 4 } {
+		    DW_AT_GNU_dwo_id 0xF00D DW_FORM_data8
+		}
+	    } {
+		DW_TAG_class_type {
+		    DW_AT_declaration 1 DW_FORM_flag
+		    DW_AT_signature 0xdeadbeef01234567 DW_FORM_ref_sig8
+		} {
+		    method_decl: DW_TAG_subprogram {
+			DW_AT_name method
+			DW_AT_linkage_name _ZN8the_type6methodEv
+			DW_AT_declaration 1 DW_FORM_flag
+		    }
+		}
+
+		DW_TAG_subprogram {
+		    DW_AT_specification :$method_decl
+		    DW_AT_low_pc $foo_start DW_FORM_GNU_addr_index
+		    DW_AT_high_pc $foo_end DW_FORM_GNU_addr_index
+		}
+	    }
+	}
+
+	# Skeleton CU in the main file.
+	cu $skel_cu_opts {
+	    compile_unit {
+		$dwo_name_attr ${testfile_name}-dw.dwo DW_FORM_strp
+		DW_AT_comp_dir .
+		DW_AT_GNU_addr_base $debug_addr_base
+		if { $version == 4 } {
+		    DW_AT_GNU_dwo_id 0xF00D DW_FORM_data8
+		}
+	    } {}
+	}
+    }
+
+    set binfile_name [standard_output_file $testfile_name]
+    set obj [standard_output_file "${testfile_name}-dw.o"]
+    if {[build_executable_and_dwo_files "build exec and dwo" $binfile_name \
+	     {nodebug} \
+	     [list $asm_file {nodebug split-dwo} $obj] \
+	     [list $::srcfile {nodebug}]]} {
+	return
+    }
+
+    clean_restart $testfile_name
+
+    # Check that GDB was able to find the parent for "method", and as
+    # a result, can correctly find this field of "the_type".
+    gdb_test "print the_type::method" \
+	[string_to_regexp " <the_type::method()>"]
+}
+
+foreach_with_prefix version { 4 5 } {
+    run_test $version
+}
diff --git a/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp
new file mode 100644
index 000000000000..6f39e2c823d5
--- /dev/null
+++ b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp
@@ -0,0 +1,106 @@
+# Copyright 2026 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# Check that the cooked index correctly resolves the parent of a member
+# function when the class declaration stub in the compile unit has
+# DW_AT_signature but no DW_AT_name.
+#
+# This replicates what Clang emits with -fdebug-types-section.  The
+# compile unit contains an unnamed DW_TAG_class_type declaration with
+# only DW_AT_declaration, DW_AT_signature, and child member function
+# declarations.
+#
+# The class name must be resolved by following the signature to the
+# type unit.  Without this, the member function definitions (which use
+# DW_AT_specification to point at the child declarations) end up with
+# no parent, and qualified lookup fails.
+
+load_lib dwarf.exp
+
+# This test can only be run on targets which support DWARF-2 and use gas.
+require dwarf2_support
+
+standard_testfile main-foo.c .S
+
+# Build the test program using VERSION for the CU/TU DWARF version.
+proc run_test { version } {
+    # Create the DWARF.
+    set asm_file [standard_output_file $::srcfile2]
+    Dwarf::assemble {
+	filename $asm_file
+	add_dummy_cus 0
+    } {
+	upvar version version
+
+	get_func_info foo
+	get_func_info main
+
+	declare_labels method_decl
+
+	tu { version $version } 0xdeadbeef01234567 the_type {
+	    DW_TAG_type_unit {
+		DW_AT_language @DW_LANG_C_plus_plus
+	    } {
+		the_type: DW_TAG_class_type {
+		    DW_AT_name the_type
+		    DW_AT_byte_size 1 sdata
+		}
+	    }
+	}
+
+	cu { version $version } {
+	    compile_unit {
+		DW_AT_language @DW_LANG_C_plus_plus
+	    } {
+		DW_TAG_class_type {
+		    DW_AT_declaration 1 flag
+		    DW_AT_signature 0xdeadbeef01234567 ref_sig8
+		} {
+		    method_decl: DW_TAG_subprogram {
+			DW_AT_name method
+			DW_AT_linkage_name _ZN8the_type6methodEv
+			DW_AT_declaration 1 flag
+		    }
+		}
+
+		DW_TAG_subprogram {
+		    DW_AT_specification %$method_decl
+		    DW_AT_low_pc $foo_start DW_FORM_addr
+		    DW_AT_high_pc $foo_end DW_FORM_addr
+		}
+
+		DW_TAG_subprogram {
+		    DW_AT_name main
+		    DW_AT_low_pc $main_start DW_FORM_addr
+		    DW_AT_high_pc $main_end DW_FORM_addr
+		}
+	    }
+	}
+    }
+
+    if { [prepare_for_testing "failed to prepare" ${::testfile}-${version} \
+	      [list $asm_file $::srcfile] {nodebug}] } {
+	return
+    }
+
+    # Check that GDB was able to find the parent for "method", and as
+    # a result, can correctly find this field of "the_type".
+    gdb_test "print the_type::method" \
+	[string_to_regexp " <the_type::method()>"]
+}
+
+foreach_with_prefix version { 4 5 } {
+    run_test $version
+}
-- 
2.55.0


  parent reply	other threads:[~2026-09-24 15:12 UTC|newest]

Thread overview: 36+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-19 10:03 [PATCH] [GDB 18] gdb: " Andrew Burgess
2026-08-21 17:07 ` Tom Tromey
2026-08-28 21:30 ` [PATCHv2] " Andrew Burgess
2026-09-01 13:31   ` [PATCHv3] " Andrew Burgess
2026-09-10 16:11     ` Simon Marchi
2026-09-11 19:18       ` Tom Tromey
2026-09-12  2:06         ` Simon Marchi
2026-09-14 13:23           ` Andrew Burgess
2026-09-14 14:57             ` Simon Marchi
2026-09-14 15:38               ` Tom Tromey
2026-09-15 10:25               ` Andrew Burgess
2026-09-15 15:01                 ` Tom Tromey
2026-09-15 15:55                   ` Simon Marchi
2026-09-15 17:21                     ` Simon Marchi
2026-09-16 11:40                       ` Andrew Burgess
2026-09-16 11:45                     ` Andrew Burgess
2026-09-16 11:48                   ` Andrew Burgess
2026-09-14 15:36             ` Tom Tromey
2026-09-10 16:18     ` Simon Marchi
2026-09-16 11:38     ` [PATCHv4] " Andrew Burgess
2026-09-22  4:26       ` Simon Marchi
2026-09-23 13:47         ` Simon Marchi
2026-09-24  4:59       ` [PATCH 0/2] " Simon Marchi
2026-09-24  5:05         ` Simon Marchi
2026-09-24 15:09         ` [PATCH v6 0/3] gdb/dwarf: " Simon Marchi
2026-09-24 15:09           ` [PATCH v6 1/3] gdb/dwarf: split cooked index finalization into separate steps Simon Marchi
2026-09-24 15:09           ` Simon Marchi [this message]
2026-09-24 15:09           ` [PATCH v6 3/3] gdb/dwarf: add cooked_index_entry::parent_is_deferred Simon Marchi
2026-09-24 20:53             ` Andrew Burgess
2026-09-25  2:34               ` Simon Marchi
2026-09-24  4:59       ` [PATCH 1/2] gdb: split cooked index finalization into separate steps Simon Marchi
2026-09-24 10:16         ` Andrew Burgess
2026-09-24 14:24           ` Simon Marchi
2026-09-24  4:59       ` [PATCH 2/2] gdb: resolve class name via DW_AT_signature in cooked index Simon Marchi
2026-09-24 10:39         ` Andrew Burgess
2026-09-24 15:07           ` Simon Marchi

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260924151048.204777-3-simon.marchi@efficios.com \
    --to=simon.marchi@efficios.com \
    --cc=aburgess@redhat.com \
    --cc=gdb-patches@sourceware.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox