C語言從原則上來說,只能在函數內執行代碼。
所以任何 text 段都對應有自己的幀棧。
本文主要談一下 call leave ret 三條與函數調用緊密相關的指令。
call 指令
call 的不同形式
call Label 所謂直接跳轉
call *operand 所謂間接跳轉
080483f7 <caller>: 804840c: e8 dc ff ff ff call 80483ed <callee>
上邊代碼段中 caller 中 call 80483ed <callee>
就是直接跳轉
call 之前的准備
080483f7 <caller>: 80483fa: 83 ec 08 sub $0x8,%esp 80483fd: c7 44 24 04 1c a0 04 movl $0x804a01c,0x4(%esp) 8048404: 08 8048405: c7 04 24 01 00 00 00 movl $0x1,(%esp)
gcc ABI約定被調函數的參數保存在調用者的棧幀(frame)上,所以 caller 需要將 callee 的參數放在自己的棧幀上。這個過程分兩步完成。
- 開棧。
將棧指針向下(棧由高位向下擴展)移動 8 bytes。這是因為兩個參數一個是指針類型,一個是整數類型,均需要 4 bytes 來存儲。事實上由於對齊的要求,即使參數類型小於 4 bytes 編譯器還是會為其分配 4 bytes 的棧空間, - 反向保存參數。
gcc ABI規定,反向保存參數,故棧頂保存最后一個參數。如果參數類型大於 4 bytes,IA32 需要用兩條movl
指令來傳遞參數。
值得注意的是,ABI只規定了參數在棧上存儲的空間順序,並沒有規定參數壓入棧中的時間順序
call 干了什么
存儲返回地址。
call
指令將 (%eip)
對應指令之后的那條指令的起始地址放在棧上,也就是把 %eip + n
放在 (%esp)
,其中 n 為 (%eip)
中指令的長度。然后跳轉到 call
的操作數所指的地址。
call之后發生了什么
080483ed <callee>: 80483ed: 55 push %ebp // sub $0x4,%esp // mov %ebp,(%esp) 80483ee: 89 e5 mov %esp,%ebp 80483f0: 83 ec 2c sub $0x2c,%esp 8048405: c7 45 e8 01 00 00 00 movl $0x1,-0x18(%ebp) 804840c: c7 45 ec 02 00 00 00 movl $0x2,-0x14(%ebp) 8048413: c7 45 f0 03 00 00 00 movl $0x3,-0x10(%ebp) 804841a: c7 45 f4 04 00 00 00 movl $0x4,-0xc(%ebp) 8048421: c7 45 f8 05 00 00 00 movl $0x5,-0x8(%ebp) 8048428: c7 45 fc 06 00 00 00 movl $0x6,-0x4(%ebp)
- 切換棧幀。
被調函數首先將舊的棧底指針%ebp
壓到自己的棧幀上,然后以其地址(而非內容)作為自己的棧底指針的內容,此時新的棧幀已經形成了,由於%esp == %ebp
,故新的棧幀暫時沒有使用棧內存。 - 開棧。
當局部變量數量太大時,編譯器會選擇將局部變量放在棧幀上。gcc的ABI約定,函數棧幀的大小必須 16 bytes 對齊,所以sub
指令所減去的16進制數以c結尾(棧幀上已經有上一幀%ebp
) 。 - 初始化局部變量。
這里對局部變量的初始化是以棧底指針為基准的,此處值得注意的是(%ebp)
中存儲的是上一幀的%ebp
leave 指令
8048411: c9 leave
leave
所做的工作是還原上一幀的棧底指針與棧頂指針,等效於
mov %ebp,%esp // 把棧頂指針置為本幀的棧底(同時也是存儲上一幀棧底指針內容的地址),
popl %ebp // 還原上一幀的棧底指針,此時 %esp 指向返回地址
ret 指令
8048412: c3 ret
ret
所做的工作是彈出棧頂的返回地址,並跳轉到此地址。此時 %esp
指向調用函數所存儲的被調函數的最后一個參數。
雜記
一個完整的棧幀上會有什么?
從底到頂依次是:
1. 上一幀的 `%ebp` 1. ABI 約定被調用者保存(如果有)的調用者的三個寄存器的內容 `%ebx` `%esi` `%edi` 2. 局部變量 2. 對齊空白 3. ABI 約定調用者保存(如果有)的自己的三個寄存器的內容 `%eax` `%edx` `%ecx` 3. 所調用的函數的參數 3. 返回地址(本幀的 %esp所指,下一幀的 0x4(%ebp))