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

            Bug ID: 114144
           Summary: Variables optimized out by -Og
           Product: gcc
           Version: 14.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: debug
          Assignee: unassigned at gcc dot gnu.org
          Reporter: lukas.gra...@tu-darmstadt.de
  Target Milestone: ---

-Og seems to <optimize out> some variable values: On x86-64, function
parameters are lost after calling other functions, they were not saved (to the
stack). This could be undesired when debugging. Consider the following example:

===== foo.c ========================
int foo (int i) {
    return i + 1;
}

===== caller.c =====================
extern int foo(int);
int main(int argc, char **argv) {
    int i = foo(argc);
    int v = foo(i);
    return v;
}
====================================

   Compile on x86-64:

$ gcc -Og -g caller.c foo.c -o caller

   Debug with breakpoint after calling foo():

$ gdb caller

Reading symbols from caller...
(gdb) break caller.c:5
Breakpoint 1 at 0x401116: file caller.c, line 5.
(gdb) run
Starting program: /home/lukas/test/caller 

Breakpoint 1, main (argc=<optimised out>, argv=<optimised out>) at caller.c:5
5           return v;
(gdb) print argc
$1 = <optimised out>
(gdb) print i
$2 = <optimised out>
(gdb) print v
$3 = 3

-----------------------------------------------

For 32-bit x86 with "-m32 -Og -g" we would get argc and argv because the
calling conventions put them on the stack and not a caller-saved variable.
However, the variable i would still be <optimised out> at the breakpoint.

-----------------------------------------------
EXPECTED RESULT by compiling the same with -O0:

$ gcc -O0 -g caller.c foo.c -o caller

$ gdb caller

(gdb) break caller.c:5
Breakpoint 1 at 0x40112f: file caller.c, line 5.
(gdb) run
Starting program: /home/lukas/test/caller

Breakpoint 1, main (argc=1, argv=0x7fffffffdc98) at caller.c:5
5           return 0;
(gdb) print argc
$1 = 1
(gdb) print i
$2 = 2
(gdb) print v
$3 = 3
-----------------------------------------------

This is not a problem of the debugger gdb: By looking at the DWARF debugging
info, it turns out that argc has indeed been optimised out:


$ objdump -W caller
[...]
 <2><6d>: Abbrev Number: 1 (DW_TAG_formal_parameter)
    <6e>   DW_AT_name        : (indirect string, offset: 0x11): argc
    <72>   DW_AT_decl_file   : 1
    <72>   DW_AT_decl_line   : 2
    <72>   DW_AT_decl_column : 14
    <73>   DW_AT_type        : <0x44>
    <77>   DW_AT_location    : 0x10 (location list)
    <7b>   DW_AT_GNU_locviews: 0xc
[...]
Contents of the .debug_loclists section:
[...]
    00000010 v000000000000000 v000000000000000 views at 0000000c for:
             0000000000401106 000000000040110e (DW_OP_reg5 (rdi))
    00000015 v000000000000000 v000000000000000 views at 0000000e for:
             000000000040110e 000000000040111b (DW_OP_entry_value: (DW_OP_reg5
(rdi)); DW_OP_stack_value)
    0000001d <End of list>
[...]


Our breakpoint at location 0x401116 is inside the second range of the loclist.
However, we cannot compute "DW_OP_entry_value: (DW_OP_reg5 (rdi))" at 0x401116,
since it refers to a state at a previous location (the value of rdi at the
subprogram entry). Also, from the disasambly you can clearly see that
caller-saved registers %edi and %eax are not saved (neither to the stack nor to
callee-saved registers) before calling foo:

$ objdump -d caller
[...]
0000000000401106 <main>:
  401106:       48 83 ec 08             sub    $0x8,%rsp
  40110a:       e8 0c 00 00 00          callq  40111b <foo>
  40110f:       89 c7                   mov    %eax,%edi
  401111:       e8 05 00 00 00          callq  40111b <foo>
  401116:       48 83 c4 08             add    $0x8,%rsp
  40111a:       c3                      retq   
[...]


And if you don't like breakpoints, you could modify caller.c as follows to
automatically break by calling abort:

===== caller2.c =====================
#include <stdlib.h>
extern int foo(int);
int main(int argc, char **argv) {
    int i = foo(argc);
    int v = foo(i);
    abort();
}
=====================================

$ gcc -Og -g caller2.c foo.c -o caller2
$ gdb ./caller2
(gdb) run
Program received signal SIGABRT, Aborted.
(gdb) bt
#0  __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
#1  0x00007ffff7dcd859 in __GI_abort () at abort.c:79
#2  0x000000000040113b in main (argc=<optimised out>, argv=<optimised out>)
    at caller2.c:6

Here, too, we have argc=<optimised out>.


----------------------------------------------------------
According to the documentation of -Og:

"-Og should be the optimization level of choice for the standard
edit-compile-debug cycle, offering a reasonable level of optimization while
maintaining fast compilation and a good debugging experience."

"It is a better choice than -O0 for producing debuggable code because some
compiler passes that collect debug information are disabled at -O0."

But for many cases, -O0 currently seems to be the better choice. Because -O0
saves all values to the stack, they will not be <optimized out>. If -Og is
working as designed, then the documentation is misleading. In this case, I
would suggest to add something like the following to the documentation:

"-Og might also make the code less debuggable than -O0, because of some
optimizations for speed and size."

Observed in PR 38534.

Reply via email to