### 函数调用时的流程，参数如何传入以及寄存器、栈的变化

**函数调用前的准备 (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 在返回前额外弹出多少字节的栈空间