https://gcc.gnu.org/bugzilla/show_bug.cgi?id=128622

            Bug ID: 128622
           Summary: [LTO] Function-local static missing from info locals
                    when its definition and function body come from
                    different TUs
           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-TU testcase below, LTO selects the prevailing definition of a
function-local static (transform(int)::x) from a.o and the out-of-line function
body (transform(int)) from b.o. Stopping inside transform, GDB can evaluate
'print x', but omits x from 'info locals' and from GDB/MI '-stack-list-locals'
/ '-stack-list-variables'. Building with -fno-lto lists x in the active scope.

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

Both inputs use -O2 -g -gdwarf-5 -flto, and a.C adds -fno-strict-aliasing.
During LGEN, a.C inlines transform and retains its static x, but no out-of-line
transform definition. b.C takes transform's address and retains both
definitions. With link order a.o b.o and -flto-partition=one, repro.res
records:

  _ZZ9transformiE1x   a.o   transform(int)::x
  _Z9transformi       b.o   transform(int)

Stopping at h.h:6 in the out-of-line transform:

  With -flto:                   With -fno-lto:
    (gdb) info locals             (gdb) info locals
    result = 10                   result = 10
    (gdb) print x                 x = 10
    $1 = 10                       (gdb) print x
                                  $1 = 10

In late DWARF, the address-bearing DIE for x is attached to a separate
rangeless subprogram stub rather than to the executing function DIE (arrows
denote DW_AT_abstract_origin):

  DW_TAG_subprogram [code range]     -> b.C's early transform
    DW_TAG_formal_parameter (n)      -> b.C's n
    DW_TAG_variable (result)         -> b.C's result

  DW_TAG_subprogram [no code range]  -> a.C's early transform
    DW_TAG_variable (x) [DW_OP_addr] -> a.C's x

With different per-TU options, SCC merging keeps the two function declarations
distinct. free_lang_data removes x from BLOCK_VARS, and its DECL_CONTEXT
remains a.C's transform. flush_limbo_die_list attaches x's late DIE to that
function, which has no selected body. The resulting subprogram DIE has no code
range and is skipped by GDB. This is the cross-TU counterpart of the
DECL_CONTEXT placement issue in PR124864 comment 5.

Varying link order vs. per-TU options separates symbol selection from
declaration merging:

  Link / compile configuration             Prevailing (fn / x)  Fn decls merge 
GDB in out-of-line transform
  ---------------------------------------  -------------------  -------------- 
-------------------------------------------------
  a.o b.o (a.o with -fno-strict-aliasing)  b.o / a.o            no             
info locals omits x (this PR)
  b.o a.o (a.o with -fno-strict-aliasing)  b.o / b.o            no             
info locals lists result = 10, x = 10
  a.o b.o (matching options on both)       b.o / a.o            yes            
x = 10 listed; n and result duplicated (PR128616)

Reproducer

--- h.h ---
inline int transform (int n)
{
  static volatile int x = 7;
  int result = x + n;
  x = result;
  asm volatile ("nop" : "+r" (result) : : "memory");
  return result;
}
__attribute__((noipa)) int local_value (int);

--- a.C ---
#include "h.h"
int local_value (int n) { return transform (n); }

--- b.C ---
#include "h.h"
int (*volatile pointer) (int) = transform;

int main ()
{
  int a = transform (1);
  int b = pointer (2);
  int c = local_value (3);
  return a + b + c != 31;
}

--- run.sh ---
#!/bin/sh
set -eu
export LC_ALL=C
mode=${1:--flto}

"${CXX:-g++}" -O2 -g -gdwarf-5 "$mode" -fno-strict-aliasing -c a.C -o a.o
"${CXX:-g++}" -O2 -g -gdwarf-5 "$mode" -c b.C -o b.o
"${CXX:-g++}" -O2 -g -gdwarf-5 "$mode" -flto-partition=one \
    -save-temps -no-pie a.o b.o -o repro

"${GDB:-gdb}" -q -nx -batch -iex 'set debuginfod enabled off' \
    -ex 'break *_Z9transformi' -ex run \
    -ex 'advance h.h:6' \
    -ex 'info args' -ex 'info locals' -ex 'print x' repro

Run with:
  CXX=g++ GDB=gdb sh run.sh -flto
  readelf --debug-dump=info repro
  cat repro.res
  CXX=g++ GDB=gdb sh run.sh -fno-lto

Reply via email to