From: Tom de Vries Date: Wed, 3 Dec 2025 08:07:30 +0000 (+0100) Subject: [gdb/symtab] Fix DW_TAG_member regression X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=d03293898d81e4273ac7f9163a8186dba46db0e5;p=thirdparty%2Fbinutils-gdb.git [gdb/symtab] Fix DW_TAG_member regression On openSUSE Leap 15.6 x86_64, with gcc 7 and test-case gdb.base/condbreak-multi-context.exp I run into: ... (gdb) print aaa^M $3 = ^M (gdb) FAIL: $exp: start_before=true: scenario_1: print aaa ... This is a regression since commit 86ac8c54623 ("Convert lookup_symbol_in_objfile"). Likewise in test-cases gdb.cp/m-static.exp and gdb.cp/namespace.exp. The failure is specific to using Dwarf v4: - using target board unix/gdb:debug_flags=-gdwarf-5 fixes it - using target board unix/gdb:debug_flags=-gdwarf-4 on Tumbleweed (with gcc 15 and Dwarf v5 default) triggers it The variable we're trying to print, A::aaa is a static const int member: ... class A : public Base { public: static const int aaa = 10; ... }; ... With Dwarf v5, we have this DIE: ... <2><356>: Abbrev Number: 2 (DW_TAG_variable) <357> DW_AT_name : aaa <35c> DW_AT_linkage_name: _ZN1A3aaaE <364> DW_AT_external : 1 <364> DW_AT_accessibility: 1 (public) <364> DW_AT_declaration : 1 <364> DW_AT_const_value : 10 ... and the cooked index contains these corresponding entries: ... [45] ((cooked_index_entry *) 0x7facf0004730) name: _ZN1A3aaaE canonical: _ZN1A3aaaE qualified: _ZN1A3aaaE DWARF tag: DW_TAG_variable flags: 0x4 [IS_LINKAGE] DIE offset: 0x356 parent: ((cooked_index_entry *) 0) [52] ((cooked_index_entry *) 0x7facf0004700) name: aaa canonical: aaa qualified: A::aaa DWARF tag: DW_TAG_variable flags: 0x0 [] DIE offset: 0x356 parent: ((cooked_index_entry *) 0x7facf00046d0) [A] ... With Dwarf v4, we have instead the following DIE: ... <2><350>: Abbrev Number: 3 (DW_TAG_member) <351> DW_AT_name : aaa <35b> DW_AT_external : 1 <35b> DW_AT_accessibility: 1 (public) <35c> DW_AT_declaration : 1 <35c> DW_AT_const_value : 4 byte block: a 0 0 0 ... and there are no corresponding entries. Fix this by adding an entry: ... [47] ((cooked_index_entry *) 0x7f5a24004660) name: aaa canonical: aaa qualified: A::aaa DWARF tag: DW_TAG_member flags: 0x0 [] DIE offset: 0x350 parent: ((cooked_index_entry *) 0x7f5a24004630) [A] ... Add a regression test in the form of a dwarf assembly test-case printing the value of variable A::aaa. In the test-case, for A::aaa, DW_FORM_flag is used to encode DW_AT_declaration. In v1 of this patch that mattered (because of using has_hardcoded_declaration in abbrev_table::read), but that's no longer the case. Nevertheless, also add an A::bbb using FORM_flag_present for DW_AT_declaration (and while we're at it, DW_AT_external as well). Also add two variants, for which is printed: - A::ccc: a variant with DW_AT_location instead of DW_AT_const_value, and - A::ddd: a variant without external. This behavior is not part of the regression. I've reproduced it using a system gdb based on 14.2. It's also not specific to using the cooked index, it reproduces with -readnow as well. Tested on x86_64-linux. Approved-By: Tom Tromey PR symtab/33415 Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=33415 --- diff --git a/gdb/dwarf2/abbrev.c b/gdb/dwarf2/abbrev.c index 3aed737838d..df8a4baba05 100644 --- a/gdb/dwarf2/abbrev.c +++ b/gdb/dwarf2/abbrev.c @@ -119,6 +119,7 @@ abbrev_table::read (struct dwarf2_section_info *section, bool has_name = false; bool has_linkage_name = false; bool has_external = false; + bool has_const_value = false; /* Now read in declarations. */ int num_attrs = 0; @@ -176,6 +177,10 @@ abbrev_table::read (struct dwarf2_section_info *section, if (is_csize && cur_attr.form == DW_FORM_ref4) sibling_offset = size; break; + + case DW_AT_const_value: + has_const_value = true; + break; } switch (cur_attr.form) @@ -242,6 +247,57 @@ abbrev_table::read (struct dwarf2_section_info *section, the correct scope. */ cur_abbrev->interesting = true; } + else if (cur_abbrev->tag == DW_TAG_member && has_const_value + && has_external) + { + /* For a static const member with initializer, gcc (and likewise + clang) generates a DW_TAG_member with a DW_AT_const_value + attribute when using DWARF v4 or earlier. + + class A { + static const int aaa = 11; + }; + + <2><28>: Abbrev Number: 3 (DW_TAG_member) + <29> DW_AT_name : aaa + <34> DW_AT_external : 1 + <34> DW_AT_declaration : 1 + <34> DW_AT_const_value : 11 + + That's not the case if we move the initializer out of the class + declaration, we get a DW_TAG_variable representing the value: + + class A { + static const int aaa; + }; + const int A::aaa = 11; + + <2><28>: Abbrev Number: 3 (DW_TAG_member) + <29> DW_AT_name : aaa + <34> DW_AT_external : 1 + <34> DW_AT_declaration : 1 + <1><41>: Abbrev Number: 6 (DW_TAG_variable) + <42> DW_AT_specification: <0x28> + <48> DW_AT_linkage_name: _ZN1A3aaaE + <4c> DW_AT_location : 9 byte block: (DW_OP_addr: 0) + + We could try to cater here for the case that we have a + DW_AT_location instead of a DW_AT_const_value, but that doesn't + seem to be handled by the rest of GDB. + + I did find an example of a non-external one for std::_Function_base + ::_Base_manager > + ::__stored_locally: + + <4><27bd192>: Abbrev Number: 279 (DW_TAG_member) + <27bd194> DW_AT_name : __stored_locally + <27bd19e> DW_AT_accessibility: 2 (protected) + <27bd19f> DW_AT_declaration : 1 + <27bd19f> DW_AT_const_value : 1 byte block: 1 + + but again that doesn't seem to be handled by the rest of GDB. */ + cur_abbrev->interesting = true; + } else if (has_hardcoded_declaration && (cur_abbrev->tag != DW_TAG_variable || !has_external)) { diff --git a/gdb/dwarf2/cooked-indexer.c b/gdb/dwarf2/cooked-indexer.c index 913ff77f890..19361b25517 100644 --- a/gdb/dwarf2/cooked-indexer.c +++ b/gdb/dwarf2/cooked-indexer.c @@ -290,7 +290,8 @@ cooked_indexer::scan_attributes (dwarf2_per_cu *scanning_per_cu, that is ok. Similarly, we allow an external variable without a location; those are resolved via minimal symbols. */ if (is_declaration && !for_specification - && !(abbrev->tag == DW_TAG_variable && (*flags & IS_STATIC) == 0)) + && !((abbrev->tag == DW_TAG_variable || abbrev->tag == DW_TAG_member) + && (*flags & IS_STATIC) == 0)) { /* We always want to recurse into some types, but we may not want to treat them as definitions. */ diff --git a/gdb/dwarf2/index-write.c b/gdb/dwarf2/index-write.c index 175f1a03a94..d011cc59e3f 100644 --- a/gdb/dwarf2/index-write.c +++ b/gdb/dwarf2/index-write.c @@ -1252,6 +1252,7 @@ write_cooked_index (cooked_index *table, || entry->tag == DW_TAG_entry_point) kind = GDB_INDEX_SYMBOL_KIND_FUNCTION; else if (entry->tag == DW_TAG_variable + || entry->tag == DW_TAG_member || entry->tag == DW_TAG_constant || entry->tag == DW_TAG_enumerator) kind = GDB_INDEX_SYMBOL_KIND_VARIABLE; diff --git a/gdb/dwarf2/tag.h b/gdb/dwarf2/tag.h index 9b5c77554da..40936131938 100644 --- a/gdb/dwarf2/tag.h +++ b/gdb/dwarf2/tag.h @@ -76,6 +76,7 @@ tag_matches_domain (dwarf_tag tag, domain_search_flags search, language lang) switch (tag) { case DW_TAG_variable: + case DW_TAG_member: case DW_TAG_enumerator: case DW_TAG_constant: flags = SEARCH_VAR_DOMAIN; diff --git a/gdb/testsuite/gdb.dwarf2/static-const-member.exp b/gdb/testsuite/gdb.dwarf2/static-const-member.exp new file mode 100644 index 00000000000..d3288296b91 --- /dev/null +++ b/gdb/testsuite/gdb.dwarf2/static-const-member.exp @@ -0,0 +1,124 @@ +# Copyright 2025 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 . + +load_lib dwarf.exp + +# This test can only be run on targets which support DWARF-2 and use gas. +require dwarf2_support + +standard_testfile main.c .S + +set asm_file [standard_output_file ${srcfile2}] + +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } { + return +} + +set int_size [get_sizeof "int" -1] + +Dwarf::assemble ${asm_file} { + cu {} { + DW_TAG_compile_unit { + DW_AT_language @DW_LANG_C_plus_plus + } { + declare_labels int_label + + int_label: DW_TAG_base_type { + DW_AT_byte_size $::int_size DW_FORM_udata + DW_AT_encoding @DW_ATE_signed + DW_AT_name "int" + } + + DW_TAG_class_type { + DW_AT_name "A" + } { + DW_TAG_member { + DW_AT_name "aaa" + DW_AT_type :$int_label + DW_AT_external 1 DW_FORM_flag + DW_AT_declaration 1 DW_FORM_flag + DW_AT_accessibility 1 DW_FORM_data1 + DW_AT_const_value 10 DW_FORM_data1 + } + DW_TAG_member { + DW_AT_name "bbb" + DW_AT_type :$int_label + DW_AT_external 1 DW_FORM_flag_present + DW_AT_declaration 1 DW_FORM_flag_present + DW_AT_accessibility 1 DW_FORM_data1 + DW_AT_const_value 11 DW_FORM_data1 + } + DW_TAG_member { + DW_AT_name "ccc" + DW_AT_type :$int_label + DW_AT_external 1 DW_FORM_flag + DW_AT_declaration 1 DW_FORM_flag + DW_AT_accessibility 1 DW_FORM_data1 + DW_AT_location { + DW_OP_lit12 + } SPECIAL_expr + } + DW_TAG_member { + DW_AT_name "ddd" + DW_AT_type :$int_label + DW_AT_declaration 1 DW_FORM_flag + DW_AT_accessibility 1 DW_FORM_data1 + DW_AT_const_value 13 DW_FORM_data1 + } + } + + DW_TAG_subprogram { + MACRO_AT_func { "main" } + DW_AT_type :${int_label} + DW_AT_external 1 DW_FORM_flag + } { + } + } + } +} + +if { [prepare_for_testing "failed to prepare" $testfile \ + [list $asm_file $srcfile] {nodebug}] } { + return +} + +# Regression test for PR symtab/33415. Print the value of A::aaa in: +# +# class A +# { +# public: +# static const int aaa = 10; +# }; +# +# With DWARF 5, we get a DW_TAG_variable, but with DWARF 4, we get a +# DW_TAG_member instead. +gdb_test \ + "print A::aaa" \ + " = 10" + +# Variant with DW_FORM_flag_present instead of DW_FORM_flag. +gdb_test \ + "print A::bbb" \ + " = 11" + +# Variant with DW_AT_location instead of DW_AT_const_value. +gdb_test \ + "print A::ccc" \ + " = " + +# Variant without external. +gdb_test \ + "print A::ddd" \ + " = "