近两周在做一个trouble shooting,需要对函数调用栈进行分析以找出入参和局部变量。因为在编译生成可执行程序的时候,用gcc进行了O2的优化,许多假设的函数调用栈模型都不成立了。花了一番周折,终于正确的翻译出入参和局部变量,此一旅程中的一些经验还是值得记录下来。
在32位x86系统上,函数调用栈的布局如下图所示。
栈底在高地址段,栈顶在低地址段。
从栈底到栈顶的内容分别为:
- 函数入参
- 返回地址
- 保存的寄存器值
- 被调用函数的局部变量
如果带有调试信息,则要获取上述4个部分的值很容易,对应的指令分别如下。
- info args
- info frame
- info registers
- info locals
如果没有调试信息,则可以根据这一模型并结合反汇编的结果来算出入参与局部变量的存储位置。针对32位的具体例子比较容易找到。
现在专门提一提在x86 64位下的不同,在x86 64下,因为寄存器数量增多,为了提高效率,入参基本上都是通过寄存器来传递。示例程序入下。
#include <stdlib.h> #include <stdio.h> #include <unistd.h> int demo_func(char* msg, int a, int b); int main(int argc, char** argv) { char* info = "just a demo"; int a,b; a = 320; b = 100; demo_func(info, a, b); return 0; } int demo_func(char* msg, int a, int b) { int sum; sum = a + b; return sum; }
main函数反汇编结果如下
Dump of assembler code for function main: 0x00000000004004b0 <+0>: push %rbp 0x00000000004004b1 <+1>: mov %rsp,%rbp 0x00000000004004b4 <+4>: sub $0x20,%rsp 0x00000000004004b8 <+8>: mov %edi,-0x14(%rbp) 0x00000000004004bb <+11>: mov %rsi,-0x20(%rbp) 0x00000000004004bf <+15>: movq $0x400594,-0x8(%rbp) 0x00000000004004c7 <+23>: movl $0x140,-0xc(%rbp) 0x00000000004004ce <+30>: movl $0x64,-0x10(%rbp) 0x00000000004004d5 <+37>: mov -0x10(%rbp),%edx 0x00000000004004d8 <+40>: mov -0xc(%rbp),%ecx 0x00000000004004db <+43>: mov -0x8(%rbp),%rax 0x00000000004004df <+47>: mov %ecx,%esi 0x00000000004004e1 <+49>: mov %rax,%rdi 0x00000000004004e4 <+52>: callq 0x4004f0 <demo_func> 0x00000000004004e9 <+57>: mov $0x0,%eax 0x00000000004004ee <+62>: leaveq 0x00000000004004ef <+63>: retq End of assembler dump.
注意callq 0x4004f0 <demo_func>上的三行代码,它们表示demo_func的入参,可以看出入参是通过寄存器来进行传递的。
而寄存器在demo_func中可以会被改写,于是在改写之前,这些入参会被再次保存,它们保存在stack frame中,具体位置需要根据反汇编结果进行计算。即需要根据被调用函数的反汇编代码来计算入参。
demo_func的反汇编结果如下。
Dump of assembler code for function demo_func: 0x00000000004004f0 <+0>: push %rbp 0x00000000004004f1 <+1>: mov %rsp,%rbp 0x00000000004004f4 <+4>: mov %rdi,-0x18(%rbp) 0x00000000004004f8 <+8>: mov %esi,-0x1c(%rbp) 0x00000000004004fb <+11>: mov %edx,-0x20(%rbp) 0x00000000004004fe <+14>: mov -0x20(%rbp),%eax 0x0000000000400501 <+17>: mov -0x1c(%rbp),%edx 0x0000000000400504 <+20>: add %edx,%eax 0x0000000000400506 <+22>: mov %eax,-0x4(%rbp) 0x0000000000400509 <+25>: mov -0x4(%rbp),%eax 0x000000000040050c <+28>: pop %rbp 0x000000000040050d <+29>: retq End of assembler dump.
编译时如果加上-O2优化,则反汇编结果变为
Dump of assembler code for function demo_func: 0x00000000004004b0 <+0>: lea (%rsi,%rdx,1),%eax 0x00000000004004b3 <+3>: retq End of assembler dump.
内容极度简化,在具体计算时,一定要根据反汇编的结果来进行。