https://gcc.gnu.org/bugzilla/show_bug.cgi?id=128619
Bug ID: 128619
Summary: [LTO] Constructor/destructor origin chains cause
duplicate parameters and locals in GDB
Product: gcc
Version: 17.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: debug
Assignee: unassigned at gcc dot gnu.org
Reporter: luolongjuna at gmail dot com
Target Milestone: ---
In the two single-TU C++ testcases below, compiling with -flto -g produces
multi-hop or mismatched DW_AT_abstract_origin chains on constructor/destructor
clones, causing GDB to list parameters and local variables twice. Building the
same sources with -fno-lto lists each parameter and local once.
Reproduced on x86_64 GNU/Linux with GCC 17.0.0 (g:bd921def14a4), GDB 17.2 and
binutils 2.46.1, and with Ubuntu GCC 13.3.0.
GCC 17 build configuration:
--enable-languages=c,c++ --disable-bootstrap --disable-multilib
--disable-nls --disable-werror --enable-checking=yes
--without-isl --with-system-zlib
A. Inlined destructor (V::~V at a.C:8, 'info args' and 'info locals')
With -flto: With -fno-lto:
this = <optimized out> this = <synthetic pointer>
this = <optimized out> keep = 7
keep = 7
keep = <optimized out>
B. Out-of-line constructor (K::K at a.C:8, 'info args')
With -flto: With -fno-lto:
this = <optimized out> this = 0x...
n = <optimized out> n = 7
this = <optimized out>
n = <optimized out>
In both cases, the concrete parameter DIEs have valid locations: evaluated
individually, 'this' gives <synthetic pointer> in Case A and an address in Case
B, and n gives 7. In GDB 17.2, 'info args' looks each argument up by name and
finds the inherited parameter symbol, which has no location.
In both LTO builds, the concrete instance refers to the clone's early DIE. That
DIE in turn refers to the abstract destructor (D4) or constructor (C4). The
children's origin chains end at children of D4 or C4, as shown below:
Case A (inlined V::~V):
inlined_subroutine ---------> early D2 clone -> abstract ~V (D4)
formal_parameter (this) --------------------> D4's this
lexical_block ------------------------------> D4's block (keep)
Case B (out-of-line K::K):
concrete subprogram --------> early C2 clone -> abstract K (C4)
formal_parameter (this) --> C2's this ------> C4's this
formal_parameter (n) -----> C2's n ---------> C4's n
GDB's inherit_abstract_dies (see PR40573) chases each child's
DW_AT_abstract_origin chain to its final target under D4/C4, but compares that
target's parent against the instance's immediate origin (early D2/C2). With
'set complaints 100', GDB reports on both LTO binaries:
Child DIE ... and its abstract origin ... have different parents
and inherits a second set of children from the clone's early DIE.
In Case A, the non-LTO inline instance refers directly to D4. With LTO,
free_lang_data_in_decl clears D2's DECL_ABSTRACT_ORIGIN because D4 is a method.
The inline instance then refers to D2, while its parameter and lexical block
still refer to D4's children.
In Case B, gen_subprogram_die reuses the existing C2 DIE without LTO and adds
the code range to it and locations to its parameters. With LTO,
maybe_create_die_with_external_ref creates separate late DIEs for the function
and parameters, each referring to its corresponding early C2 DIE. The function
and parameter chains consequently gain one level. PR124864's constructor
testcase (-flto -g) hits this same C2 path and also lists 'this' twice.
Reproducers
Save Case A and Case B in separate directories with the shared run.sh below.
=== Case A: a.C ===
volatile int sink;
struct V
{
int value;
~V ()
{
int keep = value;
asm volatile ("nop" : "+r" (keep) : : "memory");
sink = keep;
}
};
__attribute__((noipa)) void destroy (int n)
{
V value { n };
}
int main ()
{
destroy (7);
return sink != 7;
}
=== Case B: a.C ===
volatile int sink;
struct K
{
int v;
__attribute__((noinline, noclone)) K (int n)
{
asm volatile ("nop" : "+r" (n) : : "memory");
v = n;
sink = n;
}
};
int main ()
{
K k (7);
return sink != 7 || k.v != 7;
}
--- run.sh ---
#!/bin/sh
set -eu
export LC_ALL=C
mode=${1:--flto}
"${CXX:-g++}" -O2 -g -gdwarf-5 "$mode" -flto-partition=one \
-save-temps -no-pie a.C -o repro
"${GDB:-gdb}" -q -nx -batch -iex 'set debuginfod enabled off' \
-ex 'break a.C:8' -ex run \
-ex 'info args' -ex 'info locals' repro
Run in each directory:
CXX=g++ GDB=gdb sh run.sh -flto
readelf --debug-dump=info repro
gdb -q -nx -batch -iex 'set complaints 100' -ex 'break a.C:8' repro 2>&1 |
grep 'different parents'
CXX=g++ GDB=gdb sh run.sh -fno-lto