On 6/24/26 3:47 AM, Richard Biener wrote:
On Wed, Jun 24, 2026 at 1:31 AM Vineet Gupta <[email protected]> wrote:
---
Changes since v1:
   - Richi's suggestion to not modify qualified_type in-place so follow on
     code-paths are not inadvertently affected.
---

typedef chain traversal could sometimes skip an intermediate type.
This could happen when two DIEs have a common underlying type but diverge
due to presence of additional attribute in one case and a qualifier in
other.
In the example from testcase, variable "perm" typechain misses interim u16.

              typedef unsigned short __u16;
                       typedef __u16 u16;
   __attribute__((btf_type_tag(""))) u16 a;
                               const u16 perm;

generates

   .uleb128 0x1  # (DIE (0x66) DW_TAG_variable)
   .long .LASF3  # DW_AT_name: "perm"
                 # DW_AT_decl_file (1, pr125421.c)
   .byte 0xa     # DW_AT_decl_line
   .byte 0xb     # DW_AT_decl_column
   .long 0x2f    # DW_AT_type

   .uleb128 0x4  # (DIE (0x2f) DW_TAG_const_type)
   .long 0x23    # DW_AT_type                        <-- BUG

   .uleb128 0x3  # (DIE (0x23) DW_TAG_typedef)
   .long .LASF5  # DW_AT_name: "__u16"
   .byte 0x1     # DW_AT_decl_file (pr125421.c)
   .byte 0x7     # DW_AT_decl_line
   .byte 0xf     # DW_AT_decl_column
   .long 0x34    # DW_AT_type

   .uleb128 0x5  # (DIE (0x34) DW_TAG_base_type)
   .byte 0x2     # DW_AT_byte_size
   .byte 0x5     # DW_AT_encoding
   .long .LASF6  # DW_AT_name: "short int"

What makes this issue worse is depending on the order in which the types
are specified in source, and processed, the problem may or maynot show up.
So in the test code above if lines 3 and 4 are swppaed, "perm" gets the
missing type u16 as expected.

The issue is in modified_type_die (), get_qualified_type (type) returned
pointer may not be identical to dtype: TREE_TYPE (TYPE_NAME (qualified_type))
while having the same underlying base type. And due to the failed
pointer identity test, subsequent usage of DECL_ORIGINAL_TYPE () for recursive
chain processing can peel away the needed type.

The implications are for a multi CU build, structurally similar but
non identical variants of types (due to an embedded member getting a
different chained typdef) can be generated and accumulate at link time
in the final .debug_info.

Fix is to handle qualified_type != dtype as if they were identical when
they are different variant nodes of the same underlying typedef.

This would be OK / unnoticed for usual dwarf debugging purposes.
However in BFP workflow: kernel binary linking invokes pahole to process
the dwarf and dedup it for btf generation (gcc can emit btf directly but
thats not been done currently for other reasons). The slightly different
variations of same structure due to this issue causes pahole dedup to
go into combinatiorial explosion of trying to match and failing repeatedly
in what seems like infinite recursion. The problem showed up and was excerbated
when enabling support for btf_type_tag in kernel (with gcc) for the first
time which landed reently in gcc-16.

Bootstrapped and regtested on x86 and aarch64.
LGTM.

Thanks. Pushed !

-Vineet


Thanks,
Richard.

         PR debug/125421

gcc/ChangeLog:

         * dwarf2out.cc (modified_type_die): Handle qualified_type being
         a variant of dtype as if they were identical to avoid peeling a
         typdef.
         For named types, use dentry unconditionally as that is valid for
         both cases of qualified_type == and  != dentry.

gcc/testsuite/ChangeLog:

         * gcc.dg/debug/dwarf2/pr125421.c: New Test.

Signed-off-by: Vineet Gupta <[email protected]>
---
  gcc/dwarf2out.cc                             | 15 ++++++++++++---
  gcc/testsuite/gcc.dg/debug/dwarf2/pr125421.c | 18 ++++++++++++++++++
  2 files changed, 30 insertions(+), 3 deletions(-)
  create mode 100644 gcc/testsuite/gcc.dg/debug/dwarf2/pr125421.c

diff --git a/gcc/dwarf2out.cc b/gcc/dwarf2out.cc
index 32aa3adefd7b..7ba18f6f57c2 100644
--- a/gcc/dwarf2out.cc
+++ b/gcc/dwarf2out.cc
@@ -13997,7 +13997,16 @@ modified_type_die (tree type, int cv_quals, tree 
type_attrs, bool reverse,
        tree dtype = TREE_TYPE (name);

        /* Skip the typedef for base types with DW_AT_endianity, no big deal.  
*/
-      if (qualified_type == dtype && !reverse_type)
+      if (!reverse_type
+         && (qualified_type == dtype
+             /* Pointer identity check above might fail when qualified_type
+                is a different variant node of the same typedef yet requires
+                the same handling as if they matched (see PR/125421).
+                Skip this when btf_type_tag attributes are present, as those
+                need to be handled in the else branch below.  */
+             || (TYPE_NAME (qualified_type) == name
+                 && TYPE_QUALS (qualified_type) == TYPE_QUALS (dtype)
+                 && !lookup_attribute ("btf_type_tag", type_attrs))))
         {
           tree origin = decl_ultimate_origin (name);

@@ -14009,8 +14018,8 @@ modified_type_die (tree type, int cv_quals, tree 
type_attrs, bool reverse,
                                       reverse, context_die);

           /* For a named type, use the typedef.  */
-         gen_type_die (qualified_type, context_die);
-         return lookup_type_die (qualified_type);
+         gen_type_die (dtype, context_die);
+         return lookup_type_die (dtype);
         }
        else
         {
diff --git a/gcc/testsuite/gcc.dg/debug/dwarf2/pr125421.c 
b/gcc/testsuite/gcc.dg/debug/dwarf2/pr125421.c
new file mode 100644
index 000000000000..810afeae7394
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/debug/dwarf2/pr125421.c
@@ -0,0 +1,18 @@
+/* Verify the typedef chains are preserved.
+   DW_TAG_const_type for "const u16" chains to u16 typedef and doesnot
+   skip to underlying __u16 typedef */
+
+/* { dg-do compile } */
+/* { dg-options "-gdwarf -dA" } */
+
+typedef short __u16;
+typedef __u16 u16;
+__attribute__((btf_type_tag(""))) u16 __softirq_pending;
+const u16 perm;
+
+/* The exact failing pattern is hard to encode with TCL regex machinery,
+   so resort to an indirect way: in the buggy output, const_type points
+   directly to __u16, and only one u16 typedef DIE is emitted (for the
+   btf_type_tag use). In the fixed output, const_type points to a u16
+   typedef, creating a second u16 typedef DIE.
+/* { dg-final { scan-assembler-times "(DW_AT_name: 
\"u16\"|\"u16..\"\[^\\r\\n\]*DW_AT_name)" 2 } } */
--
2.54.0


Reply via email to