Add files via upload

This commit is contained in:
雾島风起時
2025-09-25 04:02:19 +08:00
committed by GitHub
parent 6d955ffc2a
commit 49832f115f
25 changed files with 1001 additions and 0 deletions
+36
View File
@@ -0,0 +1,36 @@
### 函数调用时的流程,参数如何传入以及寄存器、栈的变化
**函数调用前的准备 (Caller)**
在调用函数前,调用方(caller)会进行以下准备:
1. **参数传递**
- **寄存器优先**:对于前几个参数(通常是前4个,具体数量取决于调用约定),它们会被放入特定的通用寄存器中。在 x64 `fastcall` 约定下,参数会依次放入 **RCX, RDX, R8, R9** 寄存器。这种方式非常快,因为它避免了昂贵的内存操作
- **栈传递**:如果参数数量超过了寄存器的限制,剩下的参数就会被从右到左(或从左到右,取决于具体约定)压入栈中
2. **栈帧对齐**:为了保证性能,特别是对于一些高级指令集(如 SSE、AVX),栈帧需要对齐到特定的字节边界(通常是16字节)。调用方会确保在调用 `call` 指令前,栈指针 **RSP** 是对齐的
3. **调用指令**:最后,调用方会执行 `call` 指令。`call` 指令有两个主要作用:
- 将**下一条指令的地址**(即函数的返回地址)压入栈中
- 跳转到被调用函数(callee)的入口地址
**函数执行过程中的变化 (Callee)**
一旦 `call` 指令将控制权转移给被调用函数(callee),它会做以下几件事:
1. **保存旧栈帧**:函数的第一条指令通常是 `push rbp`。这将调用方函数的基址寄存器 **RBP** 的值压入栈中,保存了调用方的栈帧信息
2. **建立新栈帧**:接下来,函数会执行 `mov rbp, rsp`。这会将栈指针 **RSP** 的当前值复制到 **RBP**,从而建立起当前函数的栈帧。从现在开始,所有局部变量和参数都可以通过 **RBP** 加上或减去一个偏移量来访问
3. **局部变量分配**:如果函数有局部变量,它会通过 `sub rsp, [size]` 指令在栈上分配空间。这个操作会使栈指针 **RSP** 向低地址方向移动,为局部变量腾出空间
4. **保存非易失性寄存器**
- **易失性寄存器(Volatile / Caller-saved**`RAX`, `RCX`, `RDX`, `R8`-`R11`等。这些寄存器被认为是临时的,调用方**不指望**它们在函数返回后保持原值
- **非易失性寄存器(Non-volatile / Callee-saved**`RBX`, `RBP`, `RDI`, `RSI`, `R12`-`R15`等。这些寄存器被认为需要保持其值不变。如果被调用函数需要使用它们,就必须在使用前将它们的值压入栈中,并在返回前恢复
5. **执行函数主体**:现在,函数开始执行其核心逻辑。它可以使用传递进来的参数(通过寄存器或栈),也可以使用自己栈上的局部变量
**函数返回时的清理 (Callee & Caller)**
当函数执行完毕,准备返回时,会进行以下清理工作:
1. **恢复栈指针**:函数会执行 `mov rsp, rbp`。这个指令会将 **RSP** 的值恢复到进入函数时的状态,从而**释放所有局部变量**所占用的栈空间
2. **恢复旧栈帧**:接着,函数会执行 `pop rbp`。这会将调用方保存的 **RBP** 值从栈中弹出并恢复到 **RBP** 寄存器中,从而恢复到调用方的栈帧
3. **返回指令**:最后,函数执行 `ret` 指令。`ret` 指令的作用是:
- 从栈中弹出返回地址
-**RIP**(指令指针寄存器)的值设置为弹出的返回地址,从而将程序的控制权交还给调用方的下一条指令
4. **参数清理**:在某些调用约定(如 `cdecl`)中,调用方需要负责清理栈上用于参数传递的空间。然而在 `fastcall` 等现代约定中,由于参数主要通过寄存器传递,这个步骤变得简化。如果参数是通过栈传递的,`ret` 指令后面通常会带一个立即数,告诉 CPU 在返回前额外弹出多少字节的栈空间