在分析异常调用栈需要了解参数传递细节,在这里整理一下x86_64调用约定。
通过函数的汇编语句分析调用约定
栈帧布局
1 2 3 4 5 6 7 8 9 10 11
| .text foo: push %rbp mov %rsp,%rbp … [retaddr][old rbp] ^ | rbp/rsp 8(%rbp): return address 0(%rbp): old %rbp
|
上面是foo函数的汇编语句,在函数入口处,%rsp指向返回地址,重新设置%rbp指向当前栈帧。
局部变量
1 2 3 4 5 6 7 8 9 10
| foo: push %rbp mov %rsp,%rbp sub $16,%rsp # local var spaces … [retaddr][old rbp][local vars] high addr ^ ^ low addr | | rbp rsp -8(%rbp):local vars
|
如果在栈上分配局部变量,通过%rbp访问。-8(%rbp)访问第一个局部变量,依次类推访问其它局部变量。
返回地址
1 2 3 4 5 6 7 8 9 10 11 12 13
| foo: push %rbp mov %rsp,%rbp sub $16,%rsp # local var spaces ... add $16,%rsp # balance stack pop %rbp # restore stack frame pointer ret # return to caller [retaddr][old rbp][local vars] ^ ^ | | rbp rsp -8(%rbp):local vars
|
在使用完局部变量需要把对应栈空间释放,平衡堆栈,最后ret返回caller处。
参数传递
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| call conventions: void foo(int a,int b,int c,int d,int e,int f,int g); 40053c: 6a 07 pushq $0x7 # seventh use stack 40053e: 41 b9 06 00 00 00 mov $0x6,%r9d # sixth 400544: 41 b8 05 00 00 00 mov $0x5,%r8d # fifth 40054a: b9 04 00 00 00 mov $0x4,%ecx # fouth 40054f: ba 03 00 00 00 mov $0x3,%edx # third 400554: be 02 00 00 00 mov $0x2,%esi # second para 400559: bf 01 00 00 00 mov $0x1,%edi # first para %rdi ->a %rsi ->b %rdx ->c %rcx ->d %r8 ->e %r9 ->f [parameters][retaddr][old rbp] ^ | rbp/rsp 8(%rbp): return address 16(%rbp): the seventh parameter 24(%rbp): the eighth parameter
|
前6个参数使用寄存器传递,如果还有额外参数则通过堆栈传递。
以上式x86的调用约定,更正下x64的调用约定。
x64调用约定进行函数调用时栈必须是16字节对齐的,有人做过实验,只有macOS严格执行,
linux和windows似乎不严格执行该规定,即使不16字节对齐也没多大问题。
在windows上,x64调用约定使用rcx,rdx,r8,r9传参,但是需要在栈上给它们留空间,windows还有还有SEH结构化异常处理。
参考资料
- X86_calling_conventions
- The 64 bit x86 C Calling Convention