[PATCH] [GDB 18] gdb: resolve class name via DW_AT_signature in cooked index
Andrew Burgess <[email protected]>
| Newsgroups | gmane.comp.gdb.patches |
|---|---|
| Message-ID | <3ea45eb5b7eba98d5b0261a7648720a56fe6d578.1787133721.git.aburgess@redhat.com> |
This patch fixes a regression that appeared since GDB 17, so if/when
approved I plan to merge this to both master and gdb-18-branch.
Thanks,
Andrew
---
This commit fixes PR gdb/33447, an issue where looking up qualified
member function names was not working for C++ binaries compiled with
Clang when using the -fdebug-types-section flag.
Before this commit we would see this behaviour:
(gdb) print base1::a_function
There is no field named a_function
When what we expect to see is:
(gdb) print base1::a_function
$1 = {void (const base1 * const)} 0x403060 <base1::a_function() const>
The problem is that the cooked index is unable to determine the name
of the parent class `base1` in this case, and so decides not to index
any of the member functions.
The problem was discovered by running gdb.cp/cpexprs-debug-types.exp
with Clang:
make check-gdb TESTS=gdb.cp/cpexprs-debug-types.exp \
RUNTESTFLAGS='CXX_FOR_TARGET=clang++ CC_FOR_TARGET=clang'
The cpexprs-debug-types.exp test forces use of the
'-fdebug-types-section' flag, which is not on by default. With this
flag, class definitions are placed in type units, and the compile unit
contains only a declaration stub for each class. Both clang++ and g++
emit these stubs, but they differ in one detail: GCC includes
DW_AT_name on the stub, while clang++ does not, the stub carries only
DW_AT_declaration and DW_AT_signature. The class name is only
available in the type unit, reachable by following the signature.
For example, Clang emits this in a CU:
<1><2e7d>: DW_TAG_class_type
DW_AT_declaration : 1
DW_AT_signature : 0x3abb...
<2><2eac>: DW_TAG_subprogram
DW_AT_name : a_function
DW_AT_declaration : 1
The definition for a_function is elsewhere in the same CU:
<1><3142>: DW_TAG_subprogram
DW_AT_specification: <0x2eac>
And in a TU elsewhere:
Compilation Unit @ offset 0xd2e:
... snip ...
Signature: 0x3abb...
<0><d46>: Abbrev Number: 1 (DW_TAG_type_unit)
... snip ...
<1><d51>: Abbrev Number: 30 (DW_TAG_class_type)
... snip ...
<d57> DW_AT_name : (indexed string: 0xaa): base1
To find the DW_AT_name the cooked index needs to look up the type
within the TU. Without the name the cooked indexer would skip the
children of `base1`.
This look up used to work; it works in GDB 17. PR gdb/33447
incorrectly identifies commit c879f4dc3e317cf6353a45a803ecf00d577a13d8
as the commit that introduced the regression. This is actually the
last working commit. The problem was introduced by the next commit in
the same series:
commit 86ac8c546235a67d6a6bb29476a3a9ac8f7a620a (HEAD)
Date: Thu Jan 2 15:28:18 2025 -0700
Convert lookup_symbol_in_objfile
Prior to this commit GDB's symbol lookup had two phases, a search
through already expanded symtabs, and a search via
lookup_symbol_via_quick_fns. After the above commit only
lookup_symbol_via_quick_fns remains.
The lookup_symbol_via_quick_fns lookup, which relies on the indexer,
was always broken, but the first phase, search via expanded symtabs,
could correctly find the type name via the signature.
The fix has three parts:
1. abbrev.c: Add DW_AT_signature to the set of attributes that mark a
DIE as "interesting" in has_specification_or_origin. Without this,
the unnamed class stub was classified as uninteresting at the
abbreviation level and scan_attributes was never called for it.
2. cooked-indexer.c (scan_attributes): Handle DW_AT_signature by
looking up the signatured_type via lookup_signatured_type and
constructing a section_and_offset origin pointing to the type DIE
in the type unit. Restructure the is_declaration / origin-
following control flow: change the "else if (origin)" to a
standalone "if" so that class declaration stubs marked with
IS_TYPE_DECLARATION can still follow their origin to retrieve the
class name from the type unit. Add origin.reset() in the other
declaration paths to preserve the original behaviour for non-class
declarations and Ada imports.
3. read.c/read.h: Make lookup_signatured_type externally visible so
it can be called from cooked-indexer.c.
A new DWARF assembler test gdb.dwarf2/sig-type-unnamed-class.exp
reproduces the problem case, there's a nameless declaration stub that
references its full type via DW_AT_signature. To match the Clang
output as much as possible the member function definition is separate
and makes use of DW_AT_specification.
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=33447
---
gdb/dwarf2/abbrev.c | 1 +
gdb/dwarf2/cooked-indexer.c | 27 ++++-
gdb/dwarf2/read.c | 6 +-
gdb/dwarf2/read.h | 7 ++
.../gdb.dwarf2/sig-type-unnamed-class.exp | 106 ++++++++++++++++++
5 files changed, 138 insertions(+), 9 deletions(-)
create mode 100644 gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp
diff --git a/gdb/dwarf2/abbrev.c b/gdb/dwarf2/abbrev.c
index 44d5c87a5f7..e99acc5e752 100644
--- a/gdb/dwarf2/abbrev.c
+++ b/gdb/dwarf2/abbrev.c
@@ -160,6 +160,7 @@ abbrev_table::read (struct dwarf2_section_info *section,
case DW_AT_specification:
case DW_AT_abstract_origin:
case DW_AT_extension:
+ case DW_AT_signature:
has_specification_or_origin = true;
break;
diff --git a/gdb/dwarf2/cooked-indexer.c b/gdb/dwarf2/cooked-indexer.c
index f55efc5e3a0..68222a95c49 100644
--- a/gdb/dwarf2/cooked-indexer.c
+++ b/gdb/dwarf2/cooked-indexer.c
@@ -234,6 +234,19 @@ cooked_indexer::scan_attributes (dwarf2_per_cu *scanning_per_cu,
attr.get_ref_die_offset () };
break;
+ case DW_AT_signature:
+ {
+ ULONGEST signature = attr.as_signature ();
+ signatured_type *sig_type = lookup_signatured_type (reader->cu (),
+ signature);
+ if (sig_type == nullptr)
+ complaint (_("cannot find DW_AT_signature type %s [in module %s]"),
+ hex_string (signature), bfd_get_filename (reader->abfd ()));
+ else
+ origin = { sig_type->section (), sig_type->type_offset_in_section };
+ }
+ break;
+
case DW_AT_external:
if (attr.as_boolean ())
*flags &= ~IS_STATIC;
@@ -325,13 +338,17 @@ cooked_indexer::scan_attributes (dwarf2_per_cu *scanning_per_cu,
{
*linkage_name = nullptr;
*name = nullptr;
+ origin.reset ();
}
+ else
+ origin.reset ();
}
- else if ((*name == nullptr
- || (*linkage_name == nullptr
- && tag_can_have_linkage_name (abbrev->tag))
- || (*parent_entry == nullptr && m_language != language_c))
- && origin.has_value ())
+
+ if ((*name == nullptr
+ || (*linkage_name == nullptr
+ && tag_can_have_linkage_name (abbrev->tag))
+ || (*parent_entry == nullptr && m_language != language_c))
+ && origin.has_value ())
{
cutu_reader *new_reader
= ensure_cu_exists (reader, *origin, false);
diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index ca475f53745..f69d34d51a2 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -2382,11 +2382,9 @@ lookup_dwp_signatured_type (struct dwarf2_cu *cu, ULONGEST sig)
return *sig_type_it;
}
-/* Lookup a signature based type for DW_FORM_ref_sig8.
- Returns NULL if signature SIG is not present in the table.
- It is up to the caller to complain about this. */
+/* See dwarf2/read.h. */
-static struct signatured_type *
+struct signatured_type *
lookup_signatured_type (struct dwarf2_cu *cu, ULONGEST sig)
{
dwarf2_per_objfile *per_objfile = cu->per_objfile;
diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
index 15dd2abf3a1..d3e2d9fb198 100644
--- a/gdb/dwarf2/read.h
+++ b/gdb/dwarf2/read.h
@@ -1489,4 +1489,11 @@ extern struct dwarf2_section_info *get_debug_line_section
extern bool is_ada_import_or_export (dwarf2_cu *cu, const char *name,
const char *linkagename);
+/* Lookup a signature based type for DW_FORM_ref_sig8. Returns NULL
+ if signature SIG is not present in the table of CU. It is up to
+ the caller to complain about this. */
+
+extern struct signatured_type *lookup_signatured_type (struct dwarf2_cu *cu,
+ ULONGEST sig);
+
#endif /* GDB_DWARF2_READ_H */
diff --git a/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp
new file mode 100644
index 00000000000..6f39e2c823d
--- /dev/null
+++ b/gdb/testsuite/gdb.dwarf2/sig-type-unnamed-class.exp
@@ -0,0 +1,106 @@
+# Copyright 2026 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Check that the cooked index correctly resolves the parent of a member
+# function when the class declaration stub in the compile unit has
+# DW_AT_signature but no DW_AT_name.
+#
+# This replicates what Clang emits with -fdebug-types-section. The
+# compile unit contains an unnamed DW_TAG_class_type declaration with
+# only DW_AT_declaration, DW_AT_signature, and child member function
+# declarations.
+#
+# The class name must be resolved by following the signature to the
+# type unit. Without this, the member function definitions (which use
+# DW_AT_specification to point at the child declarations) end up with
+# no parent, and qualified lookup fails.
+
+load_lib dwarf.exp
+
+# This test can only be run on targets which support DWARF-2 and use gas.
+require dwarf2_support
+
+standard_testfile main-foo.c .S
+
+# Build the test program using VERSION for the CU/TU DWARF version.
+proc run_test { version } {
+ # Create the DWARF.
+ set asm_file [standard_output_file $::srcfile2]
+ Dwarf::assemble {
+ filename $asm_file
+ add_dummy_cus 0
+ } {
+ upvar version version
+
+ get_func_info foo
+ get_func_info main
+
+ declare_labels method_decl
+
+ tu { version $version } 0xdeadbeef01234567 the_type {
+ DW_TAG_type_unit {
+ DW_AT_language @DW_LANG_C_plus_plus
+ } {
+ the_type: DW_TAG_class_type {
+ DW_AT_name the_type
+ DW_AT_byte_size 1 sdata
+ }
+ }
+ }
+
+ cu { version $version } {
+ compile_unit {
+ DW_AT_language @DW_LANG_C_plus_plus
+ } {
+ DW_TAG_class_type {
+ DW_AT_declaration 1 flag
+ DW_AT_signature 0xdeadbeef01234567 ref_sig8
+ } {
+ method_decl: DW_TAG_subprogram {
+ DW_AT_name method
+ DW_AT_linkage_name _ZN8the_type6methodEv
+ DW_AT_declaration 1 flag
+ }
+ }
+
+ DW_TAG_subprogram {
+ DW_AT_specification %$method_decl
+ DW_AT_low_pc $foo_start DW_FORM_addr
+ DW_AT_high_pc $foo_end DW_FORM_addr
+ }
+
+ DW_TAG_subprogram {
+ DW_AT_name main
+ DW_AT_low_pc $main_start DW_FORM_addr
+ DW_AT_high_pc $main_end DW_FORM_addr
+ }
+ }
+ }
+ }
+
+ if { [prepare_for_testing "failed to prepare" ${::testfile}-${version} \
+ [list $asm_file $::srcfile] {nodebug}] } {
+ return
+ }
+
+ # Check that GDB was able to find the parent for "method", and as
+ # a result, can correctly find this field of "the_type".
+ gdb_test "print the_type::method" \
+ [string_to_regexp " <the_type::method()>"]
+}
+
+foreach_with_prefix version { 4 5 } {
+ run_test $version
+}
base-commit: afa6db16e6508d8ea269557085bc7c301e824382
--
2.25.4