Assembly: Function Call
這篇主要介紹,在 C/C++ 呼叫函數的時候,在組合語言中他是怎麼運作的。
Environment
為了方便,拿 32 bit 的機器當例子,並且用 Microsoft 的 cdecl convention 解釋。
主要是參考《introduction to 80x86 assembly language and computer architecture, second edition》的 Chapter 6。
Terms
如果不懂組合語言的話,可以稍微看一下名詞解釋:
CPU Registers:
- EIP (Instruction Pointer): 即 PC (Program Counter),紀錄下一個要執行的指令位址
- ESP (Stack Pointer): 指向 stack 中最底端的位址(Stack 是由高位址向低位址增長)
- EBP (Base Pointer): 輔助
ESP
用,例如計算參數,詳細請見正文
Code
先上這篇要講解的範例 code:
int f(int a, int b)
{
return a + b;
}
int main()
{
f(3, 5);
}
很簡單的一個小程式,本篇重點放在 call f(3, 5)
的流程。
Procedure
Push parameters and return address
首先,這個函數需要傳遞兩個參數,也就是這裡的 3, 5,那個傳遞的機制是怎麼運作的呢? 大家應該還記得記憶體有分成 heap
, stack
, data
…等區域,而傳遞參數的部份就是在 stack
裡面進行的。
第一步,先把 3, 5 push 進 stack 上(要注意stack是往低位址增長的),順序是先push 5 在 push 3,也就是從最後一個參數 push 到第一個。
再來,要 push return address
,這樣在跑完 function 的時候,它才知道下一個要執行的指令在哪裡,才能回到原本的地方繼續流程。
push 完這三個值/址後,執行 call
這個指令,讓指令指標(EIP
)指向下一個要跑的指令,即f
這個函式。
Base Pointer and Stack Pointer
進入函式後,首先要先把 EBP
這個東西 push 起來,因為我們會等一下更改到 EBP
的值。 之後把 EBP
設成目前 stack 最頂端的位址(ESP
)。
這個 EBP
是用來計算剛剛傳入參數的位址的,如果單純用 stack 頂端位址(ESP
)計算,因為他在執行過程中可能改變( ESP 恆指向 stack 頂端),所以需要用 EBP
來算。
In the function
再來把 CPU register裡等等需要的東西先push起來,接著就是跑這個 function 的指令了,這裡不是本篇討論的重點,故略過。
在回到 main
function 之前,要先把 EBP
pop 回去,才能執行 ret
指令,根據目前在 stack 頂端的 return address 回到原本的流程。
圖大致像這樣,不過本例中沒有區域變數(local variable):
Pop parameters
回去之後還沒結束!不要忘了那兩個參數還佔用著 stack,所以要讓 ESP
往後退那兩個值佔用的空間。
在 cdecl 的規定下,把 stack 裡面的參數移出這個步驟,是 caller 的工作;然而在 stdcall 的規定裡, 這個步驟由 callee 完成。
這樣就執行完了,最後 return 0 結束 main
function。