通過一個簡單的 C 程序,編譯為 x86 匯編,逐行分析函數調用時的棧操作、參數傳遞、返回值處理、幀指針(EBP)與棧指針(ESP)的協作,建立對程序底層運行的直觀認知。
int g(int x) { return x + 3; }
int f(int x) { return g(x); }
int main(void) { return f(8) + 1; }
gcc -S -o main.s main.c -m32 # 生成 32 位匯編
main:
pushl %ebp # 保存調用者幀指針
movl %esp, %ebp # 建立新幀:EBP = ESP
subl $0x4, %esp # 分配局部變量空間(對齊/預留)
movl $8, (%esp) # 參數 8 壓棧(調用慣例:參數從右至左入棧,這裡只有一個)
call f # 跳轉到 f,壓入返回地址
addl $1, %eax # f 返回值在 EAX,加 1
leave # 恢復 ESP/EBP(等價 movl %ebp,%esp; popl %ebp)
ret # 彈出返回地址跳轉
f:
pushl %ebp
movl %esp, %ebp
subl $0x4, %esp
movl 8(%ebp), %eax # 取參數 x(EBP+8 處存放第一個參數)
movl %eax, (%esp) # 將 x 壓棧準備調用 g
call g
leave
ret
g:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax # 取參數 x
addl $3, %eax # 計算 x+3,結果留在 EAX
popl %ebp # 恢復幀指針
ret # 返回,EAX 即返回值
調用 main 時棧頂 -> [返回地址] [舊EBP] [局部變量區] <- ESP/EBP call f -> 壓入返回地址,f 建立新幀 f 內取參數 -> 從 [EBP+8] 讀取 8 call g -> 壓入返回地址,g 建立新幀 g 內計算 -> EAX = 11 g 返回 -> pop EBP, ret 回到 f f 返回 -> leave/ret 回到 main main 完成 -> EAX=12, leave/ret 回到啟動代碼
