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

            Bug ID: 128207
           Summary: [debug] AArch64: incorrect debug information for local
                    variables at function end with -O2
           Product: gcc
           Version: 17.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: debug
          Assignee: unassigned at gcc dot gnu.org
          Reporter: MrEven132 at outlook dot com
  Target Milestone: ---

Debugger: GNU GDB 17.2

At the closing brace of compute_result, GDB prints the initial values of two
local variables with -O2, although first and second are explicitly assigned 10
and 20 before the function returns. With -O1, they are displayed as 10 and 20,
respectively.

Disabling -fschedule-insns2 removes the problem. Enabling it at -O1 introduces
the same symptom.

Testcase
========

Save the following as case.c:

static unsigned input_value = 0x10003;
static unsigned result;
static short low_bits(unsigned value) { return value & 32767; }
static unsigned compute_result(void)
{
    int first = 1;
    int second = 2;
    result = low_bits(input_value);
    first = 10;
    second = 20;
    return first;
}
int main(void)
{
    compute_result();
    return result != 3;
}

Compilation
===========

For an AArch64 target:

gcc -g -gdwarf-5 -gno-statement-frontiers -fno-inline -O1 case.c -o O1.elf
gcc -g -gdwarf-5 -gno-statement-frontiers -fno-inline -O2 case.c -o O2.elf

Both executables finish normally with exit status zero.

The -gno-statement-frontiers option allows GDB's source-line breakpoint to
select the relevant line-table entries. With the default statement-frontier
information, GDB reports "No compiled code for line 12" for this testcase. The
.text sections are byte-for-byte identical with and without this option at both
optimization levels.

Debugger reproduction
=====================

Open each executable in GDB and set the same source-line breakpoint:

gdb -q O1.elf
(gdb) break case.c:12
(gdb) run
(gdb) frame
(gdb) info line *$pc
(gdb) print first
(gdb) print second
(gdb) continue

Repeat with O2.elf. No column or instruction address is specified when setting
the breakpoint.

Relevant captured output for -O1:

Breakpoint 1 at 0x40062c: file case.c, line 12.
(gdb) frame
#0  compute_result () at case.c:12
12      }
(gdb) info line *$pc
Line 12 of "case.c" starts at address 0x40062c <compute_result+32> and ends at
0x400638 <main>.
(gdb) print first
$3 = 10
(gdb) print second
$4 = 20

For -O2:

Breakpoint 1 at 0x400684: file case.c, line 12.
(gdb) frame
#0  compute_result () at case.c:12
12      }
(gdb) info line *$pc
Line 12 of "case.c" starts at address 0x400684 <compute_result+28> and ends at
0x400688 <compute_result+32>.
(gdb) print first
$3 = 1
(gdb) print second
$4 = 2

Absolute source paths have been shortened to case.c above. The $3/$4 labels
come from the capture script, which also reads each variable before printing
the complete diagnostic transcript; these labels do not affect the values.
GDB's actual frame position is case.c:12 in both cases, not merely the
requested breakpoint line. The result was reproduced three times for each
optimization level, with normal exit status zero. No column equality is claimed
for GDB.

Expected result
===============

At the closing brace, first should be displayed as 10 and second as 20: their
assignments on lines 9 and 10 precede the return statement and closing brace.

Instruction ordering and DWARF
=============================

The relevant -O2 instruction sequence is:

0x400680: sxth w0, w0                // line 8
0x400684: ldp  x29, x30, [sp], #16   // line 12; breakpoint
0x400688: str  w0, [x1, #0x2c]       // line 8; store to result
0x40068c: ret                       // line 12

The stack-frame restoration has been scheduled before the store belonging to
line 8. However, the location lists retain the initial values until after that
store:

first:
  DW_AT_location:
    [0x400668, 0x40068c): DW_OP_lit1, DW_OP_stack_value
    [0x40068c, 0x400690): DW_OP_lit10, DW_OP_stack_value

second:
  DW_AT_location:
    [0x400668, 0x40068c): DW_OP_lit2, DW_OP_stack_value
    [0x40068c, 0x400690): DW_OP_lit20, DW_OP_stack_value

Consequently, the debugger displays the initial constants at 0x400684, despite
displaying the function's closing brace.

Without -gno-statement-frontiers, the line-table entry for 0x400684 is 12:1
with is_stmt=0. The added option permits a normal GDB line breakpoint there;
the instruction addresses and the variable location ranges above are unchanged.
At the later instruction 0x40068c, the location lists describe first as 10 and
second as 20.

This appears to involve the interaction between scheduled instruction source
locations and the placement of variable-update debug records. The variable
expressions themselves are constants, so the observed values do not depend on
evaluating registers or memory.

Optimization controls
=====================

All rows use -g -gdwarf-5 -gno-statement-frontiers -fno-inline, with a GDB
breakpoint at case.c:12 and an independently checked actual frame line of 12:

Options                         first    second
-O1                             10       20
-O2                             1        2
-O2 -fno-schedule-insns2         10       20
-O1 -fschedule-insns2            1        2

O2 line table
=============

The following is the complete decoded line-table row output from the O2
executable used for the GDB reproduction above, compiled with -g -gdwarf-5
-gno-statement-frontiers -fno-inline -O2.

Command:

llvm-dwarfdump --debug-line O2.elf

The line table uses DWARF version 5. File indices 0 and 1 both refer to case.c.
Rows are shown in their original order, including both address sequences:

Address            Line   Column File   ISA Discriminator OpIndex Flags
------------------ ------ ------ ------ --- ------------- ------- -------------
0x0000000000400660      3     39      1   0             0       0  is_stmt
0x0000000000400660      3     63      1   0             0       0  is_stmt
0x0000000000400664      3     63      1   0             0       0 
0x0000000000400668      4     17      1   0             0       0  is_stmt
0x0000000000400668      4     17      1   0             0       0 
0x000000000040066c      8     12      1   0             1       0  is_stmt
0x0000000000400670      8     14      1   0             0       0  is_stmt
0x0000000000400674      4     17      1   0             0       0  is_stmt
0x0000000000400678      8     14      1   0             0       0  is_stmt
0x000000000040067c      4     17      1   0             0       0  is_stmt
0x000000000040067c      8     14      1   0             0       0  is_stmt
0x0000000000400684     12      1      1   0             0       0  is_stmt
0x0000000000400688      8     14      1   0             1       0  is_stmt
0x000000000040068c     12      1      1   0             0       0  is_stmt
0x0000000000400690     12      1      1   0             0       0  is_stmt
end_sequence
0x0000000000400500     14      1      1   0             0       0  is_stmt
0x0000000000400508     15      5      1   0             0       0  is_stmt
0x000000000040050c     16     19      1   0             0       0  is_stmt
0x0000000000400510     17      1      1   0             0       0  is_stmt
0x0000000000400514     16     19      1   0             0       0  is_stmt
0x000000000040051c     17      1      1   0             0       0  is_stmt
0x0000000000400524     17      1      1   0             0       0  is_stmt
end_sequence

At 0x400684, the row identifies line 12, column 1: this is the ldp instruction
where the GDB breakpoint stops. The next row, at 0x400688, returns to line 8,
column 14 for the store to result. The ret at 0x40068c is again line 12, column
1. Both line-12 instruction rows have is_stmt set in this build. The 0x400690
end_sequence row is the end of the function's address sequence, not an
additional instruction. These rows expose the source-location order 12 -> 8 ->
12 near the end of compute_result.

Reply via email to