首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

函數呼叫堆疊幀深度解析:x86 呼叫慣例、參數傳遞、返回機制與 GDB 實戰觀察

     編輯:關於C語言

函數呼叫是程式執行的核心機制。本文基於 x86 (32 位) cdecl 呼叫慣例,詳細剖析呼叫指令 call、堆疊幀建立/銷毀、參數右至左壓棧、返回值 EAX 傳遞、leave/ret 返回序列,並提供 objdump 反組譯與 GDB 寄存器觀察實戰紀錄,幫助開發者建立底層執行模型。

呼叫指令 call 雙重任務

  1. 將下一條指令位址(返回位址)壓入堆疊。
  2. 修改 EIP 指向被呼叫函數入口。

被呼叫函數序曲:建立堆疊幀

push ebp          ; 保存呼叫者 EBP
mov  ebp, esp     ; 設定新幀基址
sub  esp, N       ; 分配區域變數空間 (N bytes)

此時堆疊佈局(高位址→低位址):

[呼叫者 EBP]          ← EBP
[返回位址]            ← EBP+4
[參數 1]              ← EBP+8
[參數 2]              ← EBP+12
...
[區域變數]            ← EBP-x

參數傳遞規則 (cdecl)

  • 右至左壓棧:foo(a, b, c) 先壓 c,再 b,最後 a,使 a 位於 EBP+8 固定偏移,支援可變參數。
  • 呼叫者清理堆疊:call 返回後,呼叫者執行 add esp, 12 彈出參數。

返回值傳遞

  • 整數/指標/浮點(x87):EAX (ST(0))。
  • 結構體:通常由呼叫者分配空間並傳遞隱藏指標參數。

尾聲:銷毀堆疊幀與返回

mov  eax, [ebp-4]   ; 取得返回值 (若有)
leave               ; 等價於 mov esp,ebp / pop ebp
ret                 ; 彈出返回位址至 EIP,呼叫者繼續執行

實戰:objdump 反組譯對照 C 原始碼


// C 代碼
int bar(int c, int d) { int e = c+d; return e; }
int foo(int a, int b) { return bar(a, b); }
int main() { foo(2, 3); return 0; }

// 編譯:gcc -g -O0 test.c -o test
// 反組譯:objdump -d -M intel -S test

關鍵片段(地址略):

bar:
    push ebp
    mov  ebp, esp
    sub  esp, 0x10
    mov  eax, DWORD PTR [ebp+0xc]   ; d
    mov  edx, DWORD PTR [ebp+0x8]   ; c
    lea  eax, [edx+eax]             ; c+d
    mov  DWORD PTR [ebp-0x4], eax   ; e
    mov  eax, DWORD PTR [ebp-0x4]   ; 返回值
    leave
    ret

foo:
    push ebp
    mov  ebp, esp
    sub  esp, 0x8
    mov  eax, DWORD PTR [ebp+0xc]   ; b
    mov  DWORD PTR [esp+0x4], eax   ; 壓參數 d
    mov  eax, DWORD PTR [ebp+0x8]   ; a
    mov  DWORD PTR [esp], eax       ; 壓參數 c
    call bar
    leave
    ret

main:
    push ebp
    mov  ebp, esp
    sub  esp, 0x8
    mov  DWORD PTR [esp+0x4], 0x3   ; 3
    mov  DWORD PTR [esp], 0x2       ; 2
    call foo
    mov  eax, 0
    leave
    ret

GDB 實戰觀察堆疊幀鏈

(gdb) break bar
(gdb) run
(gdb) info registers
esp 0xbffff718
ebp 0xbffff728   ; bar 幀基址

(gdb) up        ; 切換到 foo 幀
(gdb) info registers
esp 0xbffff730
ebp 0xbffff738   ; foo 幀基址

(gdb) up        ; 切換到 main 幀
(gdb) info registers
esp 0xbffff740
ebp 0xbffff748   ; main 幀基址

觀察:EBP 串連形成呼叫鏈,[EBP] 存放上層 EBP,[EBP+4] 存放返回位址。

返回過程逐指令還原

  1. mov eax, [ebp-4]:取區域變數 e 至 EAX。
  2. leave:mov esp, ebp (棄區域變數) → pop ebp (恢復呼叫者 EBP)。
  3. ret:pop eip (跳回呼叫者 call 後指令) → esp+=4。

層層 leave/ret 逐級返回至 main。

常見呼叫慣例對照

慣例參數順序堆疊清理名稱修飾用途
cdecl右→左呼叫者_funcC/C++ 預設
stdcall右→左被呼叫者_func@NWindows API
fastcall前兩參 ECX/EDX,其餘右→左被呼叫者@func@N優化調用
thiscallthis 在 ECX,其餘右→左被呼叫者同上C++ 成員函數

總結

理解堆疊幀結構、參數佈局、返回序列,是除錯崩潰、分析核心轉儲、編寫組語介面、實現協程/異常處理的基石。建議配合 gcc -S 查看組語、objdump -S 對照源碼、GDB info frame/bt 觀察實時堆疊,深化體感認知。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved