栈与栈帧
要想知道函数是怎么被调用的,需要了解栈帧和调用惯例相关知识。俞甲子2009 的“第10章 内存: 栈与堆”对相关概念有很好的介绍。本文是对相关知识的学习笔记。
附注,栈帧之间的划分边界,其实有两种不一样说法。在有些资料中 [ wikipedia; 俞甲子2009 ],callee 参数被划分在 callee 栈帧,但在 Intel 官方一些权威文档中 [ Intel ASDM, Vol.1, Ch.6; Intel X86-psABI ],callee 参数被划分在 caller 栈帧。
调用惯例
调用惯例,规定以下内容:(1) 函数参数的传递顺序和方式;(2) 栈的清理方式;(3) 名称修饰(name mangling)。常见的 x86 调用惯例列表有:cdecl(C 语言默认)、stdcall(Win32 API 标准)、fastcall、pascal。这些调用惯例如下下表所示(更加全面的列表参见 wikipedia):
调用惯例 | 栈帧清理 | 参数传递 | 名称修饰 |
---|---|---|---|
cdel | 调用者 caller | 从右至左入栈 RTL | 下划线+函数名,如 _sum |
stdcall | 被调用者 callee | 从右至左入栈 RTL | 下划线+函数名+@+参数字节数,如 _sum@8 |
fastcall | 被调用者 callee | 头两参数存入寄存器 ECX 和 EDX,其余参数从右至左入栈 RTL | @+函数名+@+参数字节数,如 @sum@8 |
pascal | 被调用者 callee | 从左至右入栈 LTR | 较为复杂,参见 pascal 文档 |
下面举例说明,cdecl 和 stdcall 两种调用惯例。
cdecl 调用惯例
cdecl,调用者负责清理堆栈(caller clean-up),参数从右至左(Right-to-Left,RTL)压入栈。举例说明 [ ref1 ref2 ]:
1 | // cdecl 调用惯例 |
编译器生成的等价汇编代码:
1 | ; 调用者清理堆栈(caller clean-up),参数 RTL 入栈 |
1 | ; sum 函数等价汇编代码 |
stdcall 调用惯例
stdcall,被调用者负责清理堆栈(callee clean-up),参数从右至左(Right-to-Left,RTL)压入栈。举例说明:
1 | // stdcall 调用惯例 |
编译器生成的等价汇编代码:
1 | ; 被调用者清理堆栈(callee clean-up),参数 RTL 入栈 |
1 | ; sum 函数等价汇编代码 |
gcc 汇编代码
hello1.c
文件内容如下:
1 | int __cdecl sum(int a, int b) { |
生成汇编代码:
1 | gcc -m32 -S -masm=intel hello1.c -o hello1.s |
生成的 hello1.s
,内容如下:
1 | .section __TEXT,__text,regular,pure_instructions |
GCC 生成的汇编代码并没有使用 push
而是通过 sub esp, 40
直接预先分配栈空间,然后使用 mov
指令将参数写进栈中,清理栈使用 add esp, 40
。逻辑上,还是符合 cdecl 调用惯例,调用者负责清理堆栈(caller clean-up),参数从右至左(Right-to-Left,RTL)压入栈。这样做的好处是,如果同时多次调用 sum
,清理栈空间的指令,只需要最后的时候调用一次就可以了。统一使用 sub esp
和 add esp
去操作 esp 值,避免 push
指令操作 esp。
现在再来看看,stdcall 调用惯例下,GCC 生成的汇编代码。把 sum
函数改为 __stdcall
,运行下面的命令:
1 | gcc -m32 -S -masm=intel hello2.c -o hello2.s |
1 | *** hello1.s Thu Feb 05 22:43:59 2018 |
反汇编代码
反汇编 objdump
、gdb
/lldb
,或者商业工具使用,IDA Pro 或者 Hopper Disassembler [ wiki ]
objdump 反汇编
1 | $ gobjdump -d -Mintel hello1 # 使用 GNU objdump |
1 | hello1: file format Mach-O 32-bit i386 |
lldb 反汇编
使用 lldb 反汇编:
1 | $ lldb hello1 |
参考资料
- 链接、装载与库,俞甲子,2009:第10章 内存: 栈与堆,豆瓣
- IDA Pro权威指南,Eagle,第2版2011:6.2.1 调用约定,豆瓣
- 2001-09 Calling Conventions Demystified https://www.codeproject.com/Articles/1388/Calling-Conventions-Demystified
- Calling Conventions https://en.wikibooks.org/wiki/X86_Disassembly/Calling_Conventions
- https://en.wikipedia.org/wiki/X86_calling_conventions