汇编与gdb(二)

138 阅读3分钟

接上篇juejin.cn/post/744119…

示例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栈

图片.png

两个函数栈,#0函数栈中,我们发现a和b变量都被optimized out了

图片.png

那么只能从main函数中查看,仔细看main函数,发现调用了两次strtol,即a、b转为int类型后分别保存在了rsp+8和rsp+0x0c的位置,那么我们可以查看下这两个地址放的是什么

图片.png

正是我们输入的两个值

示例代码比较简单,实际生产环境要复杂的多,对此gcc的建议是在开发前期需要较多debug时,使用O0优化级别,便于调试,后续再切换到O1、O3等高等级的优化,详细可以参考 gnu-Debugging Optimized Code

gcc.gnu.org/onlinedocs/…