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

            Bug ID: 105036
           Summary: Missing variables when debugging due to overlapping
                    ranges after unrolling, instruction scheduling, and
                    inlining at -O3
           Product: gcc
           Version: 12.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: debug
          Assignee: unassigned at gcc dot gnu.org
          Reporter: assaiante at diag dot uniroma1.it
  Target Milestone: ---

In this minimized C example, variables i and j that index the two nested loops
are missing when putting a breakpoint on the statement in the inner loop and
executing it for the very first time. Then, they become visible when executing
it at iteration (0,1) and still are when about to exit the outer loop, as
expected with unrolling.

This behavior appears to be caused by i and j’s DWARF location info, for the
(0,0) iteration only, overlapping with DWARF location info for the inlined
function foo. We observe the issue at -O3 with the root cause likely being
schedule-insns2, combined with the effects of inlining foo and loop unrolling.
We also noted that if we shorten the loop counts in the example, for instance
to have i stopping at 3 and j at 4, the issue is no longer present.

Please find below a detailed analysis for -O3 on x64 and a quick assessment on
past gcc versions where the issue is sometimes not present.

$ cat a.c
volatile int a;
int b;
unsigned c[35];
int d;
void foo() {
  b = 3;
  for (; b >= -10; b = b - 4) {
    d++;
  }
}
int main() {
  int i, j;
  foo();
  i = 0;
  for (; i < 5; i++) {
    j = 0;
    for (; j < 7; j++)
      a = c[i * 5 + j];
  }
}

GCC and GDB version (GCC commit id: 500d3f0a302):
$ gcc --version
gcc (GCC) 12.0.0 20211227 (experimental)
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ gdb --version
GNU gdb (GDB) 11.2
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

GDB trace:
$ gcc -O3 -g a.c -o opt
$ gdb -q opt
Reading symbols from opt...
(gdb) b 18
Breakpoint 1 at 0x4003b6: /home/stepping/50/reduce/a.c:18. (2 locations)
(gdb) r
Starting program: /home/stepping/50/reduce/opt 

Breakpoint 1, foo () at test.c:18
18            a = c[i * 5 + j];
(gdb) info locals
No locals.
(gdb) n

Breakpoint 1, main () at test.c:18
18            a = c[i * 5 + j];
(gdb) info locals
i = 0
j = 1
(gdb) n
15        for (; i < 5; i++) {
(gdb) info locals
i = 5
j = 7

GDB trace with instruction stepping: 
$ gdb -q opt
Reading symbols from opt...
(gdb) b 18
Breakpoint 1 at 0x4003b6: /home/stepping/50/reduce/a.c:18. (2 locations)
(gdb) r
Starting program: /home/stepping/50/reduce/opt 

Breakpoint 1, foo () at test.c:18
18            a = c[i * 5 + j];
(gdb) info loc
No locals.
(gdb) si
0x00000000004003bd in main () at test.c:18
18            a = c[i * 5 + j];
(gdb) info loc
i = 0
j = 0
(gdb) si

Breakpoint 1, main () at test.c:18
18            a = c[i * 5 + j];
(gdb) info loc
i = 0
j = 1

We can see how in the source-level stepping GDB trace, when the breakpoint at
line 18 is hit for the first time, variables i and j are not available due to
the location overlap. Instead, when stepping through instructions, we see the
correct value of i and j from the second instruction associated with the first
execution of line 18 (since the first one is the only one overlapping).

ASM at -O3 (shortened):
00000000004003a0 <main>:
  4003a0:       8b 05 da 0c 20 00       mov    0x200cda(%rip),%eax        #
601080 <c>
  4003a6:       8b 15 e8 0c 20 00       mov    0x200ce8(%rip),%edx        #
601094 <c+0x14>
  4003ac:       c7 05 56 0d 20 00 f3    movl   $0xfffffff3,0x200d56(%rip)      
 # 60110c <b>
  4003b3:       ff ff ff 
  4003b6:       83 05 a3 0c 20 00 04    addl   $0x4,0x200ca3(%rip)        #
601060 <d>
  4003bd:       89 05 4d 0d 20 00       mov    %eax,0x200d4d(%rip)        #
601110 <a>
  4003c3:       8b 05 bb 0c 20 00       mov    0x200cbb(%rip),%eax        #
601084 <c+0x4>
        [ . . . ]
  400519:       8b 05 c9 0b 20 00       mov    0x200bc9(%rip),%eax        #
6010e8 <c+0x68>
  40051f:       89 05 eb 0b 20 00       mov    %eax,0x200beb(%rip)        #
601110 <a>
  400525:       31 c0                   xor    %eax,%eax
  400527:       c3                      retq   
  400528:       0f 1f 84 00 00 00 00    nopl   0x0(%rax,%rax,1)
  40052f:       00 


>From the ASM we see that function foo is inlined within the function main and
all the loops are fully unrolled.

DWARF info at -O3:
0x000000a0:   DW_TAG_subprogram
                DW_AT_external  (true)
                DW_AT_name      ("main")
                DW_AT_decl_file ("/home/stepping/50/reduce/a.c")
                DW_AT_decl_line (12)
                DW_AT_decl_column       (0x05)
                DW_AT_type      (0x0000003d "int")
                DW_AT_low_pc    (0x00000000004003a0)
                DW_AT_high_pc   (0x0000000000400528)
                DW_AT_frame_base        (DW_OP_call_frame_cfa)
                DW_AT_call_all_calls    (true)
                DW_AT_sibling   (0x00000115)

0x000000c2:     DW_TAG_variable
                  DW_AT_name    ("i")
                  DW_AT_decl_column     (0x07)
                  DW_AT_type    (0x0000003d "int")
                  DW_AT_location        (0x00000018: 
                     [0x00000000004003b6, 0x0000000000400405): DW_OP_lit0,
DW_OP_stack_value
                     [ . . . ]
                     [0x0000000000400525, 0x0000000000400528): DW_OP_lit5,
DW_OP_stack_value)
                  DW_AT_unknown_2137    (0x0000000c)

0x000000d2:     DW_TAG_variable
                  DW_AT_name    ("j")
                  DW_AT_decl_column     (0x0a)
                  DW_AT_type    (0x0000003d "int")
                  DW_AT_location        (0x0000009f: 
                     [0x00000000004003b6, 0x00000000004003c3): DW_OP_lit0,
DW_OP_stack_value
                     [ . . . ]
                     [0x0000000000400525, 0x0000000000400528): DW_OP_lit7,
DW_OP_stack_value)
                  DW_AT_unknown_2137    (0x0000004f)

0x000000e0:     DW_TAG_inlined_subroutine
                  DW_AT_abstract_origin (0x000000f6 "foo")
                  DW_AT_entry_pc        (0x00000000004003a0)
                  DW_AT_unknown_2138    (0x03)
                  DW_AT_ranges  (0x0000000c
                     [0x00000000004003a0, 0x00000000004003a0)
                     [0x00000000004003b6, 0x00000000004003bd))
                  DW_AT_call_file       ("/home/stepping/50/reduce/test.c")
                  DW_AT_call_line       (13)
                  DW_AT_call_column     (0x03)


>From the DWARF info at -O3, the first entry in the location definition of
variables i and j is defined as:
[0x00000000004003b6, 0x0000000000400405): for variable i, with value 0 
[0x00000000004003b6, 0x00000000004003c3): for variable j, with value 0
Those values represent the variables upon entering for the first time the body
of the nested loop that has been unrolled.

The range of addresses that represents the inlined foo function is:
[0x00000000004003b6, 0x00000000004003bd).

We can see that these ranges overlap, causing the variables i and j to not be
available during the debugging of the first iteration of the nested loop in the
main function, since according to debug info, at such address, the program is
inside function foo where variables i and j do not exist. 

Through some testing we found out that the optimization that ultimately makes
the variables disappear is -fschedule-insns2. By disabling it with
-fno-schedule-insns2, the ranges of the variables are correctly defined and do
not overlap with the location of the inlined function. About inlining, we can
see how some instructions of function main are scheduled before instructions
from the inlined function foo and this does not happen when the flag is
disabled.

ASM with -fno-schedule-insns2 (shortened):
00000000004003a0 <main>:
  4003a0:       83 05 b9 0c 20 00 04    addl   $0x4,0x200cb9(%rip)        #
601060 <d>
  4003a7:       c7 05 5b 0d 20 00 f3    movl   $0xfffffff3,0x200d5b(%rip)      
 # 60110c <b>
  4003ae:       ff ff ff 
  4003b1:       8b 05 c9 0c 20 00       mov    0x200cc9(%rip),%eax        #
601080 <c>
  4003b7:       89 05 53 0d 20 00       mov    %eax,0x200d53(%rip)        #
601110 <a>
[ . . . ]  
  40051f:       89 05 eb 0b 20 00       mov    %eax,0x200beb(%rip)        #
601110 <a>
  400525:       31 c0                   xor    %eax,%eax
  400527:       c3                      retq   
  400528:       0f 1f 84 00 00 00 00    nopl   0x0(%rax,%rax,1)
  40052f:       00

DWARF info with -fno-schedule-insn2 (shortened):
0x000000a0:   DW_TAG_subprogram
                DW_AT_external  (true)
                DW_AT_name      ("main")
                DW_AT_decl_file ("/home/stepping/50/reduce/a.c")
                DW_AT_decl_line (12)
                DW_AT_decl_column       (0x05)
                DW_AT_type      (0x0000003d "int")
                DW_AT_low_pc    (0x00000000004003a0)
                DW_AT_high_pc   (0x0000000000400528)
                DW_AT_frame_base        (DW_OP_call_frame_cfa)
                DW_AT_call_all_calls    (true)
                DW_AT_sibling   (0x0000011b)

0x000000c2:     DW_TAG_variable
                  DW_AT_name    ("i")
                  DW_AT_decl_column     (0x07)
                  DW_AT_type    (0x0000003d "int")
                  DW_AT_location        (0x00000018: 
                     [0x00000000004003b1, 0x0000000000400405): DW_OP_lit0,
DW_OP_stack_value
                     [ . . . ]
                     [0x0000000000400525, 0x0000000000400528): DW_OP_lit5,
DW_OP_stack_value)
                  DW_AT_unknown_2137    (0x0000000c)

0x000000d2:     DW_TAG_variable
                  DW_AT_name    ("j")
                  DW_AT_decl_column     (0x0a)
                  DW_AT_type    (0x0000003d "int")
                  DW_AT_location        (0x0000009f: 
                     [0x00000000004003b1, 0x00000000004003bd): DW_OP_lit0,
DW_OP_stack_value
                     [ . . . ]
                     [0x0000000000400525, 0x0000000000400528): DW_OP_lit7,
DW_OP_stack_value)
                  DW_AT_unknown_2137    (0x0000004f)

0x000000e0:     DW_TAG_inlined_subroutine
                  DW_AT_abstract_origin (0x00000102 "foo")
                  DW_AT_entry_pc        (0x00000000004003a0)
                  DW_AT_unknown_2138    (0x03)
                  DW_AT_low_pc  (0x00000000004003a0)
                  DW_AT_high_pc (0x00000000004003b1)
                  DW_AT_call_file       ("/home/stepping/50/reduce/a.c")
                  DW_AT_call_line       (13)
                  DW_AT_call_column     (0x03)

In the DWARF info with -fno-schedule-insns2, the locations of both variables
and inlined subroutine do not overlap anymore.

We have tested the program with older gcc versions. Here is a recap of the
behavior encountered on each one of them:
gcc-{6,7}: variables i and j are marked as optimized out and function foo is
inlined;
gcc-{8,9}: variables i and j are visible and function foo in inlined;
gcc-{10,11}: variables i and j not visible at first iteration (just like in the
500d3f0a302 git version that we tested)

>From an analysis of DWARF info generated by older versions where the issue is
not present, we think that those can be ignored since the DWARF info about the
inlined function location was not generated at all (there was only an attribute
on the function’s DIE telling that it had been inlined), so no overlap was
possible.

Reply via email to