示例1
1 #include <stdio.h>
2
3 int sub(int d, int e) {
4 return d - e;
5 }
6 int sum(int a, int b) {
7 int c = sub(100, 9);
8 return a + b + c;
9 }
10
11 int main() {
12 int a = 12;
13 int b = 98;
14 int sum_result = sum(a, b);
15 return 0;
16 }
加上-O1或者-O3编译,我们发现,汇编代码变得非常简洁
00000000004004e7 <_Z3subii>:
4004e7: 89 f8 mov %edi,%eax
4004e9: 29 f0 sub %esi,%eax
4004eb: c3 retq
00000000004004ec <_Z3sumii>:
4004ec: 8d 44 37 5b lea 0x5b(%rdi,%rsi,1),%eax
4004f0: c3 retq
00000000004004f1 <main>:
4004f1: b8 c9 00 00 00 mov $0xc9,%eax
4004f6: c3 retq
4004f7: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1)
4004fe: 00 00
可以看到,编译器进行了一些编译优化-常量折叠、尾递归优化等,main函数直接设置了ax寄存器,返回了!这个例子可以大概看到编译优化的强大
因此,想要在进行了编译优化的代码里进行gdb,就不是一件容易得事情,这在我们后面的示例2中可以窥见。示例2对示例1进行了简单的修改,使得main函数不会直接设置一个立即数返回
示例2
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int sub(int d, int e) {
5 return d - e;
6 }
7 int sum(int a, int b) {
8 int c = sub(100, 9);
9 return a + c / b;
10 }
11
12 int main(int argc, char* argv[]) {
13 volatile int a = atoi(argv[1]); // 12
14 volatile int b = atoi(argv[2]);
15 int sum_result = sum(a, b);
16 printf("%d", sum_result);
17 return 0;
18 }
这里我们家volatile是为了保证a和b在栈上看一下对应的汇编
0000000000400587 <main>:
400587: 53 push %rbx
400588: 48 83 ec 10 sub $0x10,%rsp
40058c: 48 89 f3 mov %rsi,%rbx
40058f: 48 8b 7e 08 mov 0x8(%rsi),%rdi
400593: ba 0a 00 00 00 mov $0xa,%edx
400598: be 00 00 00 00 mov $0x0,%esi
40059d: e8 de fe ff ff callq 400480 <strtol@plt>
4005a2: 89 44 24 0c mov %eax,0xc(%rsp)
4005a6: 48 8b 7b 10 mov 0x10(%rbx),%rdi
4005aa: ba 0a 00 00 00 mov $0xa,%edx
4005af: be 00 00 00 00 mov $0x0,%esi
4005b4: e8 c7 fe ff ff callq 400480 <strtol@plt>
4005b9: 89 44 24 08 mov %eax,0x8(%rsp)
4005bd: 8b 4c 24 08 mov 0x8(%rsp),%ecx
4005c1: 8b 74 24 0c mov 0xc(%rsp),%esi
4005c5: b8 5b 00 00 00 mov $0x5b,%eax
4005ca: 99 cltd
4005cb: f7 f9 idiv %ecx
4005cd: 01 c6 add %eax,%esi
4005cf: bf 80 06 40 00 mov $0x400680,%edi
4005d4: b8 00 00 00 00 mov $0x0,%eax
4005d9: e8 82 fe ff ff callq 400460 <printf@plt>
4005de: b8 00 00 00 00 mov $0x0,%eax
4005e3: 48 83 c4 10 add $0x10,%rsp
4005e7: 5b pop %rbx
4005e8: c3 retq
4005e9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
通过汇编代码可以看到,
1. main函数仍然没有直接调用sub或者sum函数,而是将他们在main函数中进行了展开
2. rbp寄存器没有用作栈底寄存器,这在编译优化后的代码中是常见的
这里我们输入一个12 0,让其在函数sum中产生一个coredump,这时候看一下core栈
两个函数栈,#0函数栈中,我们发现a和b变量都被optimized out了
那么只能从main函数中查看,仔细看main函数,发现调用了两次strtol,即a、b转为int类型后分别保存在了rsp+8和rsp+0x0c的位置,那么我们可以查看下这两个地址放的是什么
正是我们输入的两个值
示例代码比较简单,实际生产环境要复杂的多,对此gcc的建议是在开发前期需要较多debug时,使用O0优化级别,便于调试,后续再切换到O1、O3等高等级的优化,详细可以参考 gnu-Debugging Optimized Code