Say what you do and do what you say

0%

x86_64 calling conventions

在分析异常调用栈需要了解参数传递细节,在这里整理一下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结构化异常处理。

参考资料

  1. X86_calling_conventions
  2. The 64 bit x86 C Calling Convention