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.