<value optimized =“”out =“”>在gdb中意味着什么?</value>

时间:2011-03-31 09:40:01

标签: gdb

(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如何优化我的值?

6 个答案:

答案 0 :(得分:50)

这意味着你用例如gcc -O3和gcc优化工具发现某些变量在某种程度上是多余的,这使得它们可以被优化掉。在这种特殊情况下,您似乎有三个变量a,b,c具有相同的值,并且可能它们都可以被别名化为单个变量。在禁用优化的情况下编译,例如gcc -O0,如果你想看到这样的变量(在任何情况下,这通常都是调试版本的好主意。)

答案 1 :(得分:6)

带有分解分析的最小可运行示例

和往常一样,我希望看到一些分解以便更好地了解正在发生的事情。

在这种情况下,我们获得的见解是,如果将变量优化为存储only in a register rather than the stack,然后将其所在的寄存器覆盖,则将<optimized out>显示为{{3 }}。

当然,只有在不再需要有关变量的情况下,这种情况才会发生,否则程序将失去其值。因此,在函数开始时可以看到变量值,但在结束时它变成<optimized out>

我们经常对此感兴趣的一种典型情况是函数参数本身,因为它们是:

  • 总是在函数开始时定义
  • 随着更多中间值的计算,可能不会在函数末尾被使用。
  • 倾向于被其他函数子调用覆盖,这些子调用必须设置完全相同的寄存器才能满足调用约定

这种理解实际上有一个具体的应用:使用mentioned by R.时,您可以简单地回到它们的最后使用点:reverse debugging

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调用约定:How do I view the value of an <optimized out> variable in C++?,您应该知道:

  • 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的值也发生了类似的事情,尽管j仅在调用k += f2(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可以随时轻松找到这些变量的值:它们始终存在于内存中!

这也使我们对为什么无法避免在优化的代码中使用<optimized out>有所了解:由于寄存器的数量有限,因此,唯一的方法是将不需要的寄存器实际推入内存,这将部分挫败-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中进行了测试。

答案 2 :(得分:5)

没有。你的编译器做了,但是仍然有原始变量名的调试符号。

答案 3 :(得分:3)

来自https://idlebox.net/2010/apidocs/gdb-7.0.zip/gdb_9.html

未在其堆栈帧中保存的参数值显示为“value optimized out”。

我猜你用-O(somevalue)编译并在正在进行优化的函数中访问变量a,b,c。

答案 4 :(得分:1)

您需要关闭编译器优化。

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

volatile int数量= 0;

答案 5 :(得分:0)

只需运行&#34;导出COPTS =&#39; -g -O0&#39;;&#34;并重建您的代码。重建后,使用gdb进行调试。你不会看到这样的错误。感谢。