考慮函式呼叫:func(1, 2)
需要把1和2這兩個引數進行傳遞,這裡的引數傳遞可以通過兩種方式:
1.引數入棧(記憶體)
2.引數傳遞到暫存器
大多數情況下,也是c\c++的預設形式是通過棧進行傳遞,因為雖然暫存器傳遞方式快但是暫存器數量有限
引數壓入棧中(記憶體),cs:ip指向下一條指令位址需要進行跳轉到函式入口就需要進行原位址的儲存,這也是通過壓入棧中來解決
注意這裡,由於傳遞哪些引數呼叫者是知道的,所以引數壓入必須由呼叫者來進行。
通過call指令來跳轉同時把原位址壓棧儲存
跳轉後,進入到函式的入口位址,必須做的兩件事情:
將bp暫存器(棧底位址也叫棧基址)設定為sp位址,因為sp始終指向棧頂位置當進行函式呼叫時,需要建立乙個新的棧幀(stack frame)這時將基址設定為棧頂即可,但是同時原始的棧基址必須入棧進行儲存,所以將先進行push壓入bp內容,這是通過sub指令進行
預留棧空間給區域性變數,也就是將sp內容進行設定,設定的值由編譯器根據區域性變數,臨時變數數量自動計算
這兩件事情做完後,進行函式體的執行,然後進入到函式返回階段
函式的返回需要進行2件事情:
將sp恢復為bp內容,因為在函式進入之初,sp給了bp,然後進行預留空間導致sp在函式退出階段,和bp中間差了預留空間的長度。所以為了進行「平衡」必須恢復到函式進入之初
恢復bp內容為上乙個棧幀基址,由於在進入之初push了bp所以只需要pop即可
最後進行ret,彈出返回位址並置cs:ip
這時已經返回到呼叫者的位置,由於之前引數進行了壓棧,所以需要把引數都彈出,也就是把sp加上引數占用的空間即可
維護了乙個函式呼叫的資訊,乙個函式的棧幀並不包含傳入的引數,傳入的引數在上乙個棧幀後面
有3種呼叫約定:
呼叫約定
引數入棧順序
引數清理負責
備註__cdecl
從右向左
呼叫者負責,呼叫者負責就是呼叫者需要把sp位址退回到引數之前,由於每次這種約定每次呼叫的地方都會多一條指令,所以會導致**膨脹
c\c++預設呼叫約定,由於呼叫者負責堆疊平衡所以支援可變引數…
__stdcall
從右向左
由於被呼叫者負責清理,所以不能支援可變引數,windows api大多都是該呼叫約定
__fastcall
前兩個引數利用暫存器進行引數傳遞,後面引數利用棧
被呼叫者
由於借助暫存器,所以速度較快
堆疊平衡指能正確維護sp暫存器也就是棧頂的位置,例如在函式即將退出之際,由於引數的傳遞利用了棧,所以函式呼叫完成後,必須把引數占用的空間給「還」回去。也就是sp指標加上乙個數值,到達第乙個引數之前的記憶體位置。
不同的函式呼叫約定,進行堆疊平衡的角色時不同的,參考上表
利用ax暫存器進行返回值傳遞
由於可變引數壓入引數的個數不固定,如果讓被呼叫者負責進行堆疊平衡,由於被呼叫者並不知道壓入引數的個數,所以無法進行堆疊平衡。因為ret指令後面得使用乙個常數,由於是常數所以必須事先確定引數個數
而cdecl是呼叫者負責平衡,所以常數可以任意,只需放在函式呼叫返回後即可
int __fastcall add
(int a,
int b,
int d)
彙編**:
push ebp
mov ebp,esp
sub esp,0e4h
...mov esp,ebp
pop ebp
ret 4
如上,由於前兩個引數通過暫存器傳遞,所以ret只需加上4個位元組即可完成堆疊平衡,這裡是被呼叫者進行平衡
再看呼叫的地方:
push 4
mov edx,3
mov ecx,2
call add (02b1361h)
mov dword ptr [a],eax
如上push 4把引數(常量引數)入棧,前面兩個引數使用暫存器傳遞,從右向左
int __fastcall add
(int a,
int b,
int d)
由於fastcall和stdcall都是被呼叫這負責推展平衡,所以彙編**類似
int __cdecl add
(int a,
int b,
int d)
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret
如上,函式返回的ret指令不加引數,直接返回
push 4
push 3
push 2
call add (0117136bh)
add esp,0ch
mov dword ptr [a],eax
呼叫者這裡使用add指令來進行堆疊平衡
非常詳細的呼叫過程分析
函式呼叫過程(棧幀)
眾所周知,程式每呼叫乙個函式,系統都會為其開闢一塊空間,當它返回時,才收回這塊空間。程式崩潰有一部分原因就是因為無限制的呼叫函式,卻沒有及時返回,導致記憶體空間不夠。為了更好的維護這一塊空間 通常稱為棧空間 我們需要了解兩個暫存器,乙個為 esp 指向棧頂的指標 乙個為 ebp 指向棧底的指標 棧空...
函式棧幀(呼叫過程)
函式棧幀就是在呼叫函式是為其在棧空間上開闢了一段空間,指向過程呼叫,乙個過程呼叫包括將資料 以過程引數和返回值的形式 和控制從 的一部分傳遞到另一部分。我們以以下 為例講解整個函式呼叫過程 int my add int x,int y int main 一 呼叫main 函式 我們從main 函式的...
函式的呼叫過程 棧幀
在談棧幀之前,我們必須要先知道c c 程式記憶體的分配情況。乙個由c c 編譯的程式占用的記憶體分為以下幾個部分 1 棧區 stack 由編譯器自動分配釋放 存放為執行函式而分配的局 部變數 函式引數 返回資料 返回位址等。其操作方式類似於資料結構中的 棧。2 堆區 heap 一般由程式設計師分配釋...