(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
Run Code Online (Sandbox Code Playgroud)
gdb如何优化我的价值?
Pau*_*l R 57
这意味着您使用eg编译,gcc -O3并且gcc优化器发现您的某些变量在某种程度上是冗余的,这使得它们可以被优化掉.在这种特殊情况下,您似乎有三个变量a,b,c具有相同的值,并且可能它们都可以被别名化为单个变量.编译并禁用优化,例如gcc -O0,如果您想要查看此类变量(在任何情况下,这通常都是调试版本的好主意).
Cir*_*四事件 14
带有反汇编分析的最小可运行示例
像往常一样,我喜欢看一些拆解,以便更好地了解正在发生的事情。
在这种情况下,我们得到的见解是,如果一个变量被优化以存储仅在寄存器而非堆栈,然后将其注册在被覆盖的话,就显示为<optimized out>如由R.提到。
当然,这只有在不再需要相关变量时才会发生,否则程序将失去其值。因此,往往会发生在函数开始时您可以看到变量值的情况,但在最后它变为<optimized out>.
我们经常对此感兴趣的一个典型案例是函数参数本身,因为它们是:
这种理解实际上有一个具体的应用:当使用反向调试时,您可能能够简单地通过回到它们的最后使用点来恢复感兴趣的变量的值:How do I view the value of an <optimized out> variable in 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;
}
Run Code Online (Sandbox Code Playgroud)
编译并运行:
gcc -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
gdb -q -nh main.out
Run Code Online (Sandbox Code Playgroud)
然后在 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>
Run Code Online (Sandbox Code Playgroud)
要了解发生了什么,请记住 x86 Linux 调用约定:i386 和 x86-64 上的 UNIX 和 Linux 系统调用的调用约定是什么,您应该知道:
由此我们推断:
add $0x1,%edi
Run Code Online (Sandbox Code Playgroud)
对应于:
i += 1;
Run Code Online (Sandbox Code Playgroud)
因为i是 的第一个参数f1,因此存储在 RDI 中。
现在,当我们在这两个地方时:
i += 1;
j += f2(i);
Run Code Online (Sandbox Code Playgroud)
RDI 的值没有被修改,因此 GDB 可以随时在这些行中查询它。
但是,一旦f2调用:
i程序中不再需要的值lea 0x1(%rax),%edi确实EDI = j + RAX + 1,这两个:
j = 1f2调用的第一个参数RDI = j因此,当到达以下行时:
k += f2(j);
Run Code Online (Sandbox Code Playgroud)
以下两条指令都/可能已经修改了 RDI,这是唯一i被存储的地方(f2可以将其用作临时寄存器,并lea明确将其设置为 RAX + 1):
0x00005555555546c3 <+3>: callq 0x5555555546b0 <f2>
0x00005555555546c8 <+8>: lea 0x1(%rax),%edi
Run Code Online (Sandbox Code Playgroud)
因此 RDI 不再包含 的值i。事实上, 的价值i已经完全丧失了!因此,唯一可能的结果是:
$3 = <optimized out>
Run Code Online (Sandbox Code Playgroud)
类似的事情发生在 的值上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
Run Code Online (Sandbox Code Playgroud)
然后反汇编看起来像:
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
Run Code Online (Sandbox Code Playgroud)
从这个可怕的反汇编中,我们看到 RDI 的值在程序执行的一开始就被移到了堆栈中:
mov %edi,-0x14(%rbp)
Run Code Online (Sandbox Code Playgroud)
然后在需要时从内存中检索到寄存器中,例如:
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)
Run Code Online (Sandbox Code Playgroud)
j初始化时立即推送到堆栈中的情况基本上相同:
0x000055555555467e <+11>: c7 45 f4 01 00 00 00 movl $0x1,-0xc(%rbp)
Run Code Online (Sandbox Code Playgroud)
因此,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;
}
Run Code Online (Sandbox Code Playgroud)
然后我们观察到这有效地扩展了 的可见性,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
Run Code Online (Sandbox Code Playgroud)
编译器通过i += i在第一条指令处存储在 RDX 中来完成。
在 Ubuntu 18.04、GCC 7.4.0、GDB 8.1.0 中测试。
来自https://idlebox.net/2010/apidocs/gdb-7.0.zip/gdb_9.html
未保存在其堆栈帧中的参数值显示为“值优化输出”。
我猜你是用 -O(somevalue) 编译的,并且正在访问发生优化的函数中的变量 a、b、c。
| 归档时间: |
|
| 查看次数: |
89166 次 |
| 最近记录: |