<gdb>中的<value optimized out>是什么意思?</gdb>

125
(gdb) n
134   a = b = c = 0xdeadbeef + ((uint32_t)length) + initval;
(gdb) n
(gdb) p a
$30 = <value optimized out>
(gdb) p b
$31 = <value optimized out>
(gdb) p c
$32 = 3735928563

gdb如何优化掉我的值?


1
可能是gdb行为:值被优化掉的重复问题。 - user
a、b和c是指针吗? - Darshan L
6个回答

119
这意味着你使用了例如 gcc -O3 进行编译,并且gcc优化器发现了一些变量在某种程度上是冗余的,可以进行优化处理。 在这种情况下,您似乎有三个变量 a、b、c 具有相同的值,可以将它们都指向一个变量。 如果您想查看这些变量(这通常对于调试构建是一个好主意),请禁用优化进行编译,例如 gcc -O0

1
但是这里的 a 不是多余的,它后面还需要使用.. 177 case 3: a+=k[0]&0xffffff; break; - gdb
6
如果您想进一步分析,需要发布所有相关代码。 - Paul R
3
优化器会尽可能地将临时变量保存在寄存器中。如果多个变量具有相同的值,它们也可以被别名到同一个寄存器中,直到其中一个变量被修改为止,并可能分配到另一个寄存器中。因此,在优化后的代码中,您的变量的生命周期可能与源代码中看起来不同。如果不想被这种行为搞糊涂,请关闭优化。 - Paul R
3
新版本的gcc有一个选项 -Og,它只应用那些不会损害调试能力的优化——非常有用(还可查看 -gdwarf4man gcc)。此外,如果你不想让编译器优化某个变量但又不想为整个构建禁用优化,可以将其临时定义为 volatile。以上信息来自这里:http://ask.xmodulo.com/print-optimized-out-value-gdb.html。 - kavadias
14
@kavadias,“-Og”选项可能会导致变量被优化掉成为问题的根源!请看我在这里的答案:https://dev59.com/jrzpa4cB1Zd3GeqPP7Td#63386263。因此,如果您遇到任何错误,提示“<optimized out>”或“Can't take address of "var" which isn't an lvalue.”,那么您必须使用“-O0”而不是“-Og”! - Gabriel Staples
显示剩余2条评论

22
使用反汇编分析的最小可运行示例 通常,我喜欢查看一些反汇编代码以更好地理解正在发生的事情。
在这种情况下,我们得到的洞见是,如果一个变量被优化为仅存储在寄存器中而不是堆栈中,然后它所在的寄存器被覆盖,那么它会显示为<optimized out>,如R.所提到的
当然,只有在该变量不再需要时才会发生这种情况,否则程序将失去其价值。因此,在函数开始时可以看到变量值,但在结束时它变为<optimized out>
我们通常感兴趣的是函数参数本身,因为它们是:
  • 始终在函数的开头定义
  • 可能在计算更多中间值时不再使用于函数结尾。
  • 往往被进一步的函数子调用覆盖,这些函数子调用必须设置相同的寄存器以满足调用约定
这种理解实际上有一个具体的应用:当使用反向调试时,您可能只需回退到感兴趣变量的最后使用点,就能够恢复其值:如何在C++中查看优化掉的变量的值?

main.c

#include <stdio.h>

int __attribute__((noinline)) f3(int i) {
    return i + 1;
}

int __attribute__((noinline)) f2(int i) {
    return f3(i) + 1;
}

int __attribute__((noinline)) f1(int i) {
    int j = 1, k = 2, l = 3;
    i += 1;
    j += f2(i);
    k += f2(j);
    l += f2(k);
    return l;
}

int main(int argc, char *argv[]) {
    printf("%d\n", f1(argc));
    return 0;
}

编译并运行:

gcc -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
gdb -q -nh main.out

然后在GDB内部,我们有以下会话:

Breakpoint 1, f1 (i=1) at main.c:13
13          i += 1;
(gdb) disas
Dump of assembler code for function f1:
=> 0x00005555555546c0 <+0>:     add    $0x1,%edi
   0x00005555555546c3 <+3>:     callq  0x5555555546b0 <f2>
   0x00005555555546c8 <+8>:     lea    0x1(%rax),%edi
   0x00005555555546cb <+11>:    callq  0x5555555546b0 <f2>
   0x00005555555546d0 <+16>:    lea    0x2(%rax),%edi
   0x00005555555546d3 <+19>:    callq  0x5555555546b0 <f2>
   0x00005555555546d8 <+24>:    add    $0x3,%eax
   0x00005555555546db <+27>:    retq   
End of assembler dump.
(gdb) p i
$1 = 1
(gdb) p j
$2 = 1
(gdb) n
14          j += f2(i);
(gdb) disas
Dump of assembler code for function f1:
   0x00005555555546c0 <+0>:     add    $0x1,%edi
=> 0x00005555555546c3 <+3>:     callq  0x5555555546b0 <f2>
   0x00005555555546c8 <+8>:     lea    0x1(%rax),%edi
   0x00005555555546cb <+11>:    callq  0x5555555546b0 <f2>
   0x00005555555546d0 <+16>:    lea    0x2(%rax),%edi
   0x00005555555546d3 <+19>:    callq  0x5555555546b0 <f2>
   0x00005555555546d8 <+24>:    add    $0x3,%eax
   0x00005555555546db <+27>:    retq   
End of assembler dump.
(gdb) p i
$3 = 2
(gdb) p j
$4 = 1
(gdb) n
15          k += f2(j);
(gdb) disas
Dump of assembler code for function f1:
   0x00005555555546c0 <+0>:     add    $0x1,%edi
   0x00005555555546c3 <+3>:     callq  0x5555555546b0 <f2>
   0x00005555555546c8 <+8>:     lea    0x1(%rax),%edi
=> 0x00005555555546cb <+11>:    callq  0x5555555546b0 <f2>
   0x00005555555546d0 <+16>:    lea    0x2(%rax),%edi
   0x00005555555546d3 <+19>:    callq  0x5555555546b0 <f2>
   0x00005555555546d8 <+24>:    add    $0x3,%eax
   0x00005555555546db <+27>:    retq   
End of assembler dump.
(gdb) p i
$5 = <optimized out>
(gdb) p j
$6 = 5
(gdb) n
16          l += f2(k);
(gdb) disas
Dump of assembler code for function f1:
   0x00005555555546c0 <+0>:     add    $0x1,%edi
   0x00005555555546c3 <+3>:     callq  0x5555555546b0 <f2>
   0x00005555555546c8 <+8>:     lea    0x1(%rax),%edi
   0x00005555555546cb <+11>:    callq  0x5555555546b0 <f2>
   0x00005555555546d0 <+16>:    lea    0x2(%rax),%edi
=> 0x00005555555546d3 <+19>:    callq  0x5555555546b0 <f2>
   0x00005555555546d8 <+24>:    add    $0x3,%eax
   0x00005555555546db <+27>:    retq   
End of assembler dump.
(gdb) p i
$7 = <optimized out>
(gdb) p j
$8 = <optimized out>

为了理解正在发生的事情,请记住x86 Linux调用约定:i386和x86-64上UNIX和Linux系统调用的调用约定是什么,您应该知道:

  • RDI包含第一个参数
  • RDI可能在函数调用中被破坏
  • RAX包含返回值

由此我们可以推断出:

add    $0x1,%edi

对应于:

i += 1;

因为if1的第一个参数,因此存储在RDI中。

现在,当我们同时处于:

i += 1;
j += f2(i);

RDI的值没有被修改,因此GDB可以在这些行中的任何时候查询它。

但是,一旦调用f2

  • 程序不再需要i的值
  • lea 0x1(%rax),%edi执行EDI = j + RAX + 1,它同时:
    • 初始化j = 1
    • 设置下一个f2调用的第一个参数为RDI = j

因此,当到达以下行时:

k += f2(j);

以下两个指令都可能已经修改了RDI,这是唯一存储i的地方(f2可能会将其用作临时寄存器,而lea则明确将其设置为RAX + 1):

   0x00005555555546c3 <+3>:     callq  0x5555555546b0 <f2>
   0x00005555555546c8 <+8>:     lea    0x1(%rax),%edi

因此,RDI不再包含i的值。实际上,i的值完全丢失了!因此,唯一可能的结果是:

$3 = <optimized out>

类似的事情也会发生在变量j的值上,尽管在调用k += f2(j);后仅仅过了一行,j就变得不必要了。

考虑变量j也可以让我们了解GDB是多么聪明。值得注意的是,在i += 1;时,变量j的值还没有出现在任何寄存器或内存地址中,而GDB必须仅基于调试信息元数据就已经知道了它的值。

-O0分析

如果我们使用编译选项-O0而不是-O3

gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c

那么反汇编看起来会像这样:

11      int __attribute__((noinline)) f1(int i) {
=> 0x0000555555554673 <+0>:     55      push   %rbp
   0x0000555555554674 <+1>:     48 89 e5        mov    %rsp,%rbp
   0x0000555555554677 <+4>:     48 83 ec 18     sub    $0x18,%rsp
   0x000055555555467b <+8>:     89 7d ec        mov    %edi,-0x14(%rbp)

12          int j = 1, k = 2, l = 3;
   0x000055555555467e <+11>:    c7 45 f4 01 00 00 00    movl   $0x1,-0xc(%rbp)
   0x0000555555554685 <+18>:    c7 45 f8 02 00 00 00    movl   $0x2,-0x8(%rbp)
   0x000055555555468c <+25>:    c7 45 fc 03 00 00 00    movl   $0x3,-0x4(%rbp)

13          i += 1;
   0x0000555555554693 <+32>:    83 45 ec 01     addl   $0x1,-0x14(%rbp)

14          j += f2(i);
   0x0000555555554697 <+36>:    8b 45 ec        mov    -0x14(%rbp),%eax
   0x000055555555469a <+39>:    89 c7   mov    %eax,%edi
   0x000055555555469c <+41>:    e8 b8 ff ff ff  callq  0x555555554659 <f2>
   0x00005555555546a1 <+46>:    01 45 f4        add    %eax,-0xc(%rbp)

15          k += f2(j);
   0x00005555555546a4 <+49>:    8b 45 f4        mov    -0xc(%rbp),%eax
   0x00005555555546a7 <+52>:    89 c7   mov    %eax,%edi
   0x00005555555546a9 <+54>:    e8 ab ff ff ff  callq  0x555555554659 <f2>
   0x00005555555546ae <+59>:    01 45 f8        add    %eax,-0x8(%rbp)

16          l += f2(k);
   0x00005555555546b1 <+62>:    8b 45 f8        mov    -0x8(%rbp),%eax
   0x00005555555546b4 <+65>:    89 c7   mov    %eax,%edi
   0x00005555555546b6 <+67>:    e8 9e ff ff ff  callq  0x555555554659 <f2>
   0x00005555555546bb <+72>:    01 45 fc        add    %eax,-0x4(%rbp)

17          return l;
   0x00005555555546be <+75>:    8b 45 fc        mov    -0x4(%rbp),%eax

18      }
   0x00005555555546c1 <+78>:    c9      leaveq 
   0x00005555555546c2 <+79>:    c3      retq 

从这个可怕的反汇编中,我们可以看到在程序执行的最开始,RDI的值被移动到栈上:

mov    %edi,-0x14(%rbp)

当需要时,它会从内存中检索到寄存器中,例如:

14          j += f2(i);
   0x0000555555554697 <+36>:    8b 45 ec        mov    -0x14(%rbp),%eax
   0x000055555555469a <+39>:    89 c7   mov    %eax,%edi
   0x000055555555469c <+41>:    e8 b8 ff ff ff  callq  0x555555554659 <f2>
   0x00005555555546a1 <+46>:    01 45 f4        add    %eax,-0xc(%rbp)

当初始化j时,基本上会发生相同的情况,它会立即被推送到堆栈中:

   0x000055555555467e <+11>:    c7 45 f4 01 00 00 00    movl   $0x1,-0xc(%rbp)

因此,GDB很容易在任何时候找到这些变量的值:它们始终存在于内存中!
这也让我们对为什么无法避免优化代码中的有了一些见解:由于寄存器数量有限,唯一的方法是将不需要的寄存器实际推送到内存中,这将在一定程度上削弱-O3的好处。
延长i的生命周期
如果我们编辑f1使其返回l + i,如下所示:
int __attribute__((noinline)) f1(int i) {
    int j = 1, k = 2, l = 3;
    i += 1;
    j += f2(i);
    k += f2(j);
    l += f2(k);
    return l + i;
}

然后我们观察到,这实际上将 i 的可见性延伸到函数的末尾。

这是因为我们强制GCC使用一个额外的变量来保留 i 直到最后:

   0x00005555555546c0 <+0>:     lea    0x1(%rdi),%edx
   0x00005555555546c3 <+3>:     mov    %edx,%edi
   0x00005555555546c5 <+5>:     callq  0x5555555546b0 <f2>
   0x00005555555546ca <+10>:    lea    0x1(%rax),%edi
   0x00005555555546cd <+13>:    callq  0x5555555546b0 <f2>
   0x00005555555546d2 <+18>:    lea    0x2(%rax),%edi
   0x00005555555546d5 <+21>:    callq  0x5555555546b0 <f2>
   0x00005555555546da <+26>:    lea    0x3(%rdx,%rax,1),%eax
   0x00005555555546de <+30>:    retq

编译器通过将i += i存储在RDX中的第一条指令来执行。

在Ubuntu 18.04、GCC 7.4.0和GDB 8.1.0中测试。


6
它并没有改变原始变量名,只是你的编译器这样做了,但仍然存在原始变量名的调试符号。

6

2

您需要关闭编译器优化。

如果您在gdb中对特定变量感兴趣,可以将该变量声明为“volatile”,并重新编译代码。这将使编译器关闭该变量的编译器优化。

volatile int quantity = 0;


0
只需运行“export COPTS ='-g -O0';”并重新构建您的代码。 重建后,使用gdb进行调试。 您将不会看到这样的错误。 谢谢。

据我所知,COPTS不是gcc接受的环境变量,假设正在使用gcc - sappjw
不要忘记在编译命令中添加“$COPTS”。 - Akib Azmain Turja

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接