]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
[gdb/symtab] Fix DW_TAG_member regression
authorTom de Vries <tdevries@suse.de>
Wed, 3 Dec 2025 08:07:30 +0000 (09:07 +0100)
committerTom de Vries <tdevries@suse.de>
Wed, 3 Dec 2025 08:07:30 +0000 (09:07 +0100)
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 = <optimized out>^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 <optimized out> 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 <tom@tromey.com>
PR symtab/33415
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=33415

gdb/dwarf2/abbrev.c
gdb/dwarf2/cooked-indexer.c
gdb/dwarf2/index-write.c
gdb/dwarf2/tag.h
gdb/testsuite/gdb.dwarf2/static-const-member.exp [new file with mode: 0644]

index 3aed737838d1b9c6ea250a65a00e01123cec3c5d..df8a4baba05c90e9f1a898e35ee76f27aa83b53a 100644 (file)
@@ -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<get_compile_file_tempdir()::<lambda()> >
+            ::__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))
        {
index 913ff77f8900f6baabc9a4678a3127d3b8bc377d..19361b255172a4ccff06a3c0a6e78106024590db 100644 (file)
@@ -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.  */
index 175f1a03a94053837d15a250111810e5ca8b3bb3..d011cc59e3f7a4efe95ca9ec452ea007047cabc8 100644 (file)
@@ -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;
index 9b5c77554da74364254a8960516974f297164cdc..40936131938847c72e5a187830afdbb1ba5ab38e 100644 (file)
@@ -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 (file)
index 0000000..d328829
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+
+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" \
+    " = <optimized out>"
+
+# Variant without external.
+gdb_test \
+    "print A::ddd" \
+    " = <optimized out>"