在
linux_x86_64
(即 64 位 Linux 系统上的 x86-64 架构)下,函数调用约定(Calling Convention)定义了函数调用时参数传递、返回值传递、寄存器使用、栈帧的构建等方面的规则。这些约定在不同的操作系统和架构上可能有所不同。在 Linux 上的 x86-64 架构中,遵循的主要是 System V ABI (Application Binary Interface),这是一种广泛使用的应用二进制接口。
1. 参数传递
在 x86_64
架构下,函数参数的传递遵循以下规则:
- 前 6 个整数参数(包括
int
、char
、long
等)通过 寄存器 传递:RDI
:第一个参数RSI
:第二个参数RDX
:第三个参数RCX
:第四个参数R8
:第五个参数R9
:第六个参数
- 额外的参数(超过 6 个参数)通过 栈 传递。
- 这些参数从栈中读取,栈的增长方向是向下(地址减小)。
2. 浮点参数传递
浮点数参数(如 float
、double
)通过 SSE 寄存器 传递:
- 前 8 个浮点数参数(
float
和double
)使用 XMM 寄存器传递,按顺序依次存放:XMM0
:第一个浮点数参数XMM1
:第二个浮点数参数- …
XMM7
:第八个浮点数参数
如果函数的参数超过了这些寄存器的数量,剩余的浮点数参数会通过栈传递。
3. 返回值传递
- 整数返回值:函数返回值通常通过
RAX
寄存器返回。RAX
会保存返回值。 - 浮点返回值:如果函数返回的是浮点数值(如
float
或double
),则使用XMM0
寄存器来存储返回值。
4. 栈帧和栈的使用
- 在每个函数调用时,调用者 会将其参数(如果超出 6 个整数或 8 个浮点数)压入栈中。
- 被调用者 负责保存其用到的寄存器,尤其是那些它会修改的寄存器(例如,
RBX
、R12
到R15
寄存器是被调用者保存的寄存器)。 - 调用者和被调用者通过约定保持栈平衡,确保栈在函数返回时是正确的。
5. 寄存器的使用约定
- RAX:返回值寄存器。函数的返回值通过此寄存器传递。
- RCX、RDX、R8、R9:用于传递函数参数(前 6 个整数参数中,
RDI
到R9
使用这 5 个寄存器)。 - R10-R15:这些寄存器是临时寄存器,调用者需要保证它们的值。如果调用函数不需要修改这些寄存器,调用者可以利用这些寄存器。
- RBX、RBP、R12-R15:这些寄存器是被调用者保存的寄存器,如果一个函数要修改这些寄存器,必须在返回之前恢复它们的值。
6. 栈对齐
在 x86_64
架构下,栈必须 按 16 字节对齐。这意味着每次函数调用时,栈指针 (rsp
) 必须是 16 的倍数。为此,调用者在调用之前可能需要调整栈指针,确保栈对齐。这个对齐要求对于向内存中传递参数(特别是浮点数参数)非常重要。
7. 调用和返回过程
函数调用:
- 参数传递:首先,将前 6 个参数传递到相应的寄存器中,如果有更多参数,调用者会将它们压入栈中。
- 保存寄存器:调用者保存需要保留的寄存器(如
RBX
、R12
到R15
)和栈帧。 - 跳转到被调用函数:执行函数调用指令(如
call
),跳转到被调用函数。 - 栈帧建立:被调用函数建立自己的栈帧,保存必要的寄存器(如
RBX
、R12
到R15
)。 - 执行函数体:函数体开始执行。
函数返回:
- 返回值传递:被调用函数将返回值放入
RAX
或XMM0
(浮点数)。 - 恢复寄存器:被调用函数恢复调用时保存的寄存器,确保返回时栈平衡。
- 跳转返回:被调用函数执行
ret
指令,跳回调用者处。
8. 总结:
在 x86_64
架构下的 Linux 系统中,函数调用约定的关键要点如下:
- 前 6 个整数参数通过 寄存器(
RDI
到R9
)传递。 - 前 8 个浮点数参数通过 SSE 寄存器(
XMM0
到XMM7
)传递。 - 如果参数超过了这些数量,剩余的参数通过 栈 传递。
- 返回值:整数通过
RAX
返回,浮点数通过XMM0
返回。 - 栈对齐:栈必须按 16 字节对齐。
- 寄存器使用:有些寄存器是调用者保存的(如
RBX
、R12
到R15
),而有些是被调用者保存的(如RBP
)。