函數呼叫的完整過程包含四個關鍵階段:參數入棧 → 函數跳轉 → 保護現場 → 恢復現場。編譯器利用三個專用暫存器管理執行流程:
假設呼叫 g_func(1, 2, 3),對應組合語言指令流程如下:
push 3 ; 參數 3
push 2 ; 參數 2
push 1 ; 參數 1
此時 ESP 指向棧頂,記憶體依序儲存 1, 2, 3。呼叫慣例採用右向左壓棧,便於支援可變參數函數(如 printf)。
call 0x00401005 ; 呼叫跳轉指令
; 0x00401005 處為 jmp g_func 實際入口 0x00401030
call 指令隱含執行:將下一條指令位址(返回位址)壓入棧,再跳轉。此例中返回位址為 0x00401093(add 指令)。
push ebp ; 保存呼叫者 EBP
mov ebp, esp ; 設定新棧幀基址
sub esp, 0x48 ; 配置區域變數空間(含 0x40 位元組間隔 + 2 個 int 區域變數 8 位元組)
push ebx ; 保存被呼叫者需保留的暫存器
push esi
push edi
關鍵細節:
sub esp, 0x48 中的 0x40 (64 bytes) 為固定間隔區,隔離呼叫者與被呼叫者棧區;剩餘 8 bytes 供區域變數 x, y 使用。ebx, esi, edi 為被呼叫者保存暫存器,呼叫慣例要求被呼叫者保持不變。[呼叫者區域變數...]
[參數 1][參數 2][參數 3] ← 呼叫者壓入
[返回位址 0x00401093] ← call 隱含壓入
[舊 EBP] ← push ebp
[區域變數 x][區域變數 y] ← sub esp 配置
[間隔區 64 bytes]
[舊 EBX][舊 ESI][舊 EDI] ← 被呼叫者保存
↑
ESP (當前棧頂)
EBP (當前棧底)
pop edi ; 還原暫存器
pop esi
pop ebx
mov esp, ebp ; 釋放區域變數空間
pop ebp ; 還原呼叫者 EBP
ret ; 彈出返回位址跳轉,等價於 pop eip
ret 指令從棧頂取出返回位址載入 EIP,控制權回到呼叫者。呼叫者隨後執行 add esp, 0xC 清理參數空間(__cdecl 慣例由呼叫者清棧)。
| 暫存器 | 角色 | 呼叫慣例責任 |
|---|---|---|
| EAX, ECX, EDX | 易失性暫存器 | 呼叫者保存 |
| EBX, ESI, EDI | 非易失性暫存器 | 被呼叫者保存 |
| EBP | 棧幀基址 | 被呼叫者保存/建立 |
| ESP | 棧頂指標 | 硬體/指令自動管理 |
| EIP | 指令指標 | call/ret/jmp 控制 |
