<value optimize out> 在 gdb 中是啥意思?

Posted

技术标签:

【中文标题】<value optimize out> 在 gdb 中是啥意思?【英文标题】:What does <value optimized out> mean in gdb?<value optimize out> 在 gdb 中是什么意思? 【发布时间】:2011-07-26 18:04:28 【问题描述】:
(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 如何优化我的值??

【问题讨论】:

gdb behavior : value optimized out的可能重复 a、b 和 c 是指针吗?? 【参考方案1】:

这意味着您使用例如编译gcc -O3 和 gcc 优化器发现您的某些变量在某种程度上是多余的,因此可以优化它们。在这种特殊情况下,您似乎拥有三个具有相同值的变量 a、b、c,并且可能它们都可以别名为单个变量。在禁用优化的情况下编译,例如gcc -O0,如果您想查看此类变量(无论如何,这对于调试构建来说通常是一个好主意)。

【讨论】:

但是这里a不是多余的,需要后面用到..177 case 3 : a+=k[0]&amp;0xffffff; break; 如果您想进一步分析,您需要发布所有相关代码。 优化器将尽可能将临时变量保存在寄存器中。如果它们都具有相同的值,它还可以将多个变量别名到同一个寄存器,直到其中一个被修改,此时它可能被分配到不同的寄存器。因此,优化代码中变量的生命周期可能与源代码中显示的不同。如果您不想被这种行为弄糊涂,请关闭优化。 较新的 gcc 有一个选项 -Og 它只应用那些不会影响可调试性的优化——非常有用(对于 -gdwarf4 也是 man gcc)。此外,您可以临时将您不想丢失的变量定义为volatile,如果您不想对其进行编译器优化,但又不想禁用整个构建的优化!这两个信息都来自这里:ask.xmodulo.com/print-optimized-out-value-gdb.html @kavadias,-Og 选项可能正是导致变量优化的问题!在这里查看我的答案:***.com/a/63386263/4561887。因此,如果您遇到任何错误,例如 &lt;optimized out&gt;Can't take address of "var" which isn't an lvalue.,那么您必须使用 -O0 而不是 -Og!【参考方案2】:

带有反汇编分析的最小可运行示例

像往常一样,我喜欢看一些反汇编来更好地了解发生了什么。

在这种情况下,我们得到的见解是,如果一个变量被优化为存储only in a register rather than the stack,然后它所在的寄存器被覆盖,那么它显示为&lt;optimized out&gt; 和mentioned by R.。

当然,只有在不再需要相关变量时才会发生这种情况,否则程序将失去其价值。因此,往往会在函数的开头看到变量值,但在结尾处变成&lt;optimized out&gt;

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

总是在函数的开头定义 可能不会在函数末尾使用,因为要计算更多中间值。 倾向于被进一步的函数子调用覆盖,这些函数子调用必须设置完全相同的寄存器以满足调用约定

这种理解其实有一个具体的应用:在使用reverse debugging时,你也许可以通过简单地退回到它们的最后使用点来恢复感兴趣的变量的值:How do I view the value of an <optimized out> variable in 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 调用约定:What are the calling conventions for UNIX & Linux system calls on i386 and x86-64 你应该知道:

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 很容易随时找到这些变量的值:它们始终存在于内存中!

这也让我们了解了为什么在优化代码中无法避免&lt;optimized out&gt;:由于寄存器的数量是有限的,唯一的方法就是将不需要的寄存器实际推送到内存中,这将部分抵消了-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 中测试。

【讨论】:

【参考方案3】:

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

【讨论】:

【参考方案4】:

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

未保存在其堆栈帧中的参数值显示为“优化值”。

我猜你是用 -O(somevalue) 编译的,并且在一个发生优化的函数中访问变量 a,b,c

【讨论】:

【参考方案5】:

您需要关闭编译器优化。

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

volatile int 数量 = 0;

【讨论】:

【参考方案6】:

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

【讨论】:

AFAICT COPTS 不是gcc 接受的环境变量,假设正在使用gcc 不要忘记将$COPTS 附加到您的编译命令中。

以上是关于<value optimize out> 在 gdb 中是啥意思?的主要内容,如果未能解决你的问题,请参考以下文章

gdb optimized out

Optimal Value Functions and Optimal Policy

<c:out> 标签与 EL表达式 ${} 区别

jstl core 库 之 out set remove

UVA 10304 Optimal Binary Search Tree

Optimize Prime Sieve