博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Linux C/C++调试:快速找出GCC O2优化编译的汇编与C源码的对应关系
阅读量:4207 次
发布时间:2019-05-26

本文共 5757 字,大约阅读时间需要 19 分钟。

相信很多C/C++程序员都有过线上gdb调试程序的经历,没有或者剥离了调试信息,线上运行的也是GCC -O2编译优化后的release版本,这时就要求快速看懂汇编指令和对应C代码的对应关系。进而,找到自己需要gdb print出的关键内存信息,或针对性的打断点以及观察点等,这些调试一切的前提都是要求快速看懂反汇编指令和对应源代码的对应关系。一个函数的反汇编短则数百,长则近万行甚至更多,如何一下子找到其与C源码的对应关系呢?我通常就是快速搜索关键指令,并结合其上下文来理解推断。当然,这一切的前提是对汇编常用指令以及x86 calling convention有一定的了解。

搜索关键指令

  • 找全局变量或函数等符号;
  • 找立即数,一些特定的宏或者枚举,AT&T汇编立即数是$开头的十六进制数;
  • 找位运算、逻辑运算、乘法、加锁等特殊指令;
  • callq ,那么其前面一定是入参操作,后面是判断返回值;
  • test %rax,%rax ,判断寄存器rax是否为0,大概率是判断前面的函数调用返回值;
  • lea Load effect address, 常用来(基址+offset*单位)算地址,本质是计算一个数值,有些数据计算也会优化成这个;
  • xor %reg, %reg, 把寄存器reg清零,异或操作,相同为0,相当于赋值0,常见的优化操作;

结合上下文推断

当快速定位了一些关键指令后,接下来就是快速根据上下文进行理解。从关键指令,马上就可以推断一些寄存器存的值的意义,是一个地址,还是一个中间计算的值,那么顺着这个指令往后走,如果是函数调用往前可以看入参操作,一般情况是往后看,前面大都无法确定哪里jump过来的。对着C/C++源码,还是相对比较容易推断的。当然,如果有调试信息,那么可以用disass /m <func>来进行查看,会显示源码和汇编指令的关系,当然自己要熟悉指令,有时候优化的厉害gdb /m看的也不太准。下面用一个例子来说明:

之前写过一般文章,,里面的函数 malloc_align_page很短,源码如下:

struct mem_align {    void *origin_start;  // for free    void *start;         // data addr start, align page size    void *end;           // data addr end,   align page size    void *origin_end;};int malloc_align_page(size_t memsize, struct mem_align *mem){    if (memsize == 0 || mem == NULL)        return -1;    memset(mem, 0, sizeof(*mem));    long pagesize = sysconf(_SC_PAGE_SIZE);    if (pagesize == -1) {        perror("sysconf err");        return -1;    }    size_t datasize = memsize + pagesize * 2;    mem->origin_start = malloc(datasize);    if(mem->origin_start == NULL)        return -1;    mem->origin_end = mem->origin_start + datasize;    long mask = pagesize - 1;    mem->start = (void *)((long)(mem->origin_start + pagesize) & ~mask);    long pagenum = memsize / pagesize + 1;    mem->end = mem->start + pagesize * pagenum;    return 0;}

上面函数GCC -02编译后,反汇编如下:

(gdb) disass malloc_align_pageDump of assembler code for function malloc_align_page:   0x00000000004007b0 <+0>:     push   %r13   0x00000000004007b2 <+2>:     push   %r12   0x00000000004007b4 <+4>:     mov    %rdi,%r12   0x00000000004007b7 <+7>:     push   %rbp   0x00000000004007b8 <+8>:     push   %rbx   0x00000000004007b9 <+9>:     sub    $0x8,%rsp   0x00000000004007bd <+13>:    test   %rdi,%rdi   0x00000000004007c0 <+16>:    je     0x400858 
0x00000000004007c6 <+22>: test %rsi,%rsi 0x00000000004007c9 <+25>: mov %rsi,%rbx 0x00000000004007cc <+28>: je 0x400858
0x00000000004007d2 <+34>: movq $0x0,(%rsi) 0x00000000004007d9 <+41>: movq $0x0,0x8(%rsi) 0x00000000004007e1 <+49>: mov $0x1e,%edi 0x00000000004007e6 <+54>: movq $0x0,0x10(%rsi) 0x00000000004007ee <+62>: movq $0x0,0x18(%rsi) 0x00000000004007f6 <+70>: callq 0x400600
0x00000000004007fb <+75>: cmp $0xffffffffffffffff,%rax 0x00000000004007ff <+79>: mov %rax,%rbp 0x0000000000400802 <+82>: je 0x400860
0x0000000000400804 <+84>: lea (%r12,%rax,2),%r13 0x0000000000400808 <+88>: mov %r13,%rdi 0x000000000040080b <+91>: callq 0x4005c0
0x0000000000400810 <+96>: test %rax,%rax 0x0000000000400813 <+99>: mov %rax,(%rbx) 0x0000000000400816 <+102>: je 0x400858
0x0000000000400818 <+104>: lea (%rax,%rbp,1),%rcx 0x000000000040081c <+108>: add %rax,%r13 0x000000000040081f <+111>: mov %rbp,%rax 0x0000000000400822 <+114>: neg %rax 0x0000000000400825 <+117>: xor %edx,%edx 0x0000000000400827 <+119>: mov %r13,0x18(%rbx) 0x000000000040082b <+123>: and %rax,%rcx 0x000000000040082e <+126>: mov %r12,%rax 0x0000000000400831 <+129>: div %rbp 0x0000000000400834 <+132>: mov %rcx,0x8(%rbx) 0x0000000000400838 <+136>: add $0x1,%rax 0x000000000040083c <+140>: imul %rax,%rbp 0x0000000000400840 <+144>: xor %eax,%eax 0x0000000000400842 <+146>: add %rbp,%rcx 0x0000000000400845 <+149>: mov %rcx,0x10(%rbx) 0x0000000000400849 <+153>: add $0x8,%rsp 0x000000000040084d <+157>: pop %rbx 0x000000000040084e <+158>: pop %rbp 0x000000000040084f <+159>: pop %r12 0x0000000000400851 <+161>: pop %r13 0x0000000000400853 <+163>: retq 0x0000000000400854 <+164>: nopl 0x0(%rax) 0x0000000000400858 <+168>: mov $0xffffffff,%eax 0x000000000040085d <+173>: jmp 0x400849
0x000000000040085f <+175>: nop 0x0000000000400860 <+176>: mov $0x4008f4,%edi 0x0000000000400865 <+181>: callq 0x4005f0
0x000000000040086a <+186>: mov %ebp,%eax 0x000000000040086c <+188>: jmp 0x400849
End of assembler dump.

根据 callq fun看一下调用函数的指令,上下文前后看,往前就是调用是入参,往后就是返回值:

0x04007e1 <+49>:    mov    $0x1e,%edi // 反推,这就是第一个入参, 立即数 _SC_PAGE_SIZE0x04007e6 <+54>:    movq   $0x0,0x10(%rsi)0x04007ee <+62>:    movq   $0x0,0x18(%rsi)0x04007f6 <+70>:    callq  0x400600 
// 搜索 sysconf, 快速找到0x04007fb <+75>: cmp $0xffffffffffffffff,%rax // 比较返回值是否是-1,-1就是全F0x04007ff <+79>: mov %rax,%rbp0x0400802 <+82>: je 0x400860

根据 lea指令定位后,往下看,前面我们无法知道哪里跳过来的,但是,lea后没有无跳转,肯定接着顺序执行,往下看即可:

0x00400804 <+84>:    lea    (%r12,%rax,2),%r13 // %r13 = %r12 + %rax * 2                     // 显然对应源代码: datasize = memsize + pagesize * 2;0x0400808 <+88>:    mov    %r13,%rdi // 前面无跳转,往下执行,rdi传第一个参数0x040080b <+91>:    callq  0x4005c0 
0x0400810 <+96>: test %rax,%rax // 判断返回值是否为0

找乘除指令, 同样往下看,前面无法知道哪儿jump过来的,但是后面肯定是顺序接着执行:

0x000000000040083c <+140>:   imul   %rax,%rbp // %rbp = %rbp * %rax             // 源码只有一处乘法 mem->end = mem->start + pagesize * pagenum;0x0000000000400840 <+144>:   xor    %eax,%eax0x0000000000400842 <+146>:   add    %rbp,%rcx // %rcx = %rbp + %rcx 即 mem->end0x0000000000400845 <+149>:   mov    %rcx,0x10(%rbx) // mem->end 地址 %rbx + 0x10                    // 根据结构体,end就是struct mem_align偏移0x10字节的字段                    // 那么rbx就是mem结构体首地址了                    // 从头看,rdi第二个入参,赋给rbx,rbx就没被改写了,必然存的mem的值

就这样,关键指令定位,结合一些上下文,结合调用约定,就可以快速找出关键的指令和源码的对应关系了。

转载地址:http://cbqli.baihongyu.com/

你可能感兴趣的文章
13丨性能测试场景:如何进行场景设计
查看>>
14丨性能测试场景:如何理解业务模型
查看>>
Prometheus exporter详解
查看>>
15丨性能测试场景:如何进行监控设计
查看>>
16丨案例:性能监控工具之Grafana-Prometheus-Exporters
查看>>
九度OJ 1085:求root(N, k) (迭代)
查看>>
九度OJ 1086:最小花费 (DP)
查看>>
九度OJ 1087:约数的个数 (数字特性)
查看>>
九度OJ 1088:剩下的树 (线段树)
查看>>
九度OJ 1089:数字反转 (数字反转)
查看>>
九度OJ 1090:路径打印 (树、DFS)
查看>>
九度OJ 1091:棋盘游戏 (DP、BFS、DFS、剪枝)
查看>>
九度OJ 1092:Fibonacci (递归)
查看>>
九度OJ 1093:WERTYU (翻译)
查看>>
九度OJ 1094:String Matching(字符串匹配) (计数)
查看>>
九度OJ 1095:2的幂次方 (递归)
查看>>
九度OJ 1471-1480(10/10)
查看>>
九度OJ 1481-1490(7/10)
查看>>
九度OJ 1491-1500(5/10)
查看>>
九度OJ 1501-1510(10/10)
查看>>