Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
From: Andrew Burgess <aburgess@redhat.com>
To: gdb-patches@sourceware.org
Cc: Andrew Burgess <aburgess@redhat.com>
Subject: [PATCHv2] gdb: resolve class name via DW_AT_signature in cooked index
Date: Fri, 28 Aug 2026 22:30:01 +0100	[thread overview]
Message-ID: <295672ce0ea0bf20911fbbc997f38fe9f62b19be.1787952498.git.aburgess@redhat.com> (raw)
In-Reply-To: <3ea45eb5b7eba98d5b0261a7648720a56fe6d578.1787133721.git.aburgess@redhat.com>

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


      parent reply	other threads:[~2026-08-28 21:30 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-19 10:03 [PATCH] [GDB 18] " Andrew Burgess
2026-08-21 17:07 ` Tom Tromey
2026-08-28 21:30 ` Andrew Burgess [this message]

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=295672ce0ea0bf20911fbbc997f38fe9f62b19be.1787952498.git.aburgess@redhat.com \
    --to=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