【 聲明:著作權,歡迎轉載,請勿用於商業用途。 聯絡信箱:feixiaoxing @163.com】
在編譯器編譯檔案的時候,軟體會根據程式本身的要求對函數作不同的壓棧處理。有的壓棧是按照從左至右進行壓棧,有的壓棧是按照從右至左進行壓棧,有的不壓棧、直接用寄存器代替,有的是需要被調用函數自身自己平衡堆棧。下面,我們就可以一個一個自己看看。首先,隨便寫一個函數,
int add(int a, int b){return a + b;}
(1)從右至左壓棧
_cdelc是編譯器預設的一種壓棧方式,在函數上定義不定義其實意義不大。不過,我們願意貼上代碼說明一下,
int __cdecl add(int a, int b){return a + b;}
下面,在main函數裡面調用此add函數,我們看看對應的彙編是怎麼處理的,
14: int p = add(2, 3);00401068 push 30040106A push 20040106C call @ILT+0(_add) (00401005)00401071 add esp,800401074 mov dword ptr [ebp-4],eax15: return 1;00401077 mov eax,1
從上面的代碼來看,3先壓棧,然後是資料2,很明顯的從右向左壓棧。
(2)從左向右壓棧
其實,在windows之前的編譯器是支援從左向右進行壓棧的,但是現在不支援了。比如說,如果你輸入下面這段代碼,
int __pascal add(int a, int b){return a + b;}
此時,編譯器會給你貼上一個錯誤提示,error C4226: nonstandard extension used : '__pascal' is an obsolete keyword。提示說的很明白,__pascal是一個過時的關鍵字,現在不支援了。其實堆棧壓棧從左向右、還是從右向左其實無所謂。但是如果遇到的函數是變參的話,那麼此時就存在問題了。因為對於__pascal而言,最後一個參數不知道究竟是在ebp的哪個位移位置了?
(3)用寄存器代替壓棧
用寄存器代替資料壓棧是arm、powerpc等cpu使用的比較多的一種方法。因為用寄存器代替壓棧,主要是考慮到速度方面的原因。畢竟取資料、儲存資料相比較寄存器操作還是非常耗時間的,其此就是這兩種cpu的寄存器資源特別豐富。同樣,首先我們要用__fastcall裝飾一下函數,
int __fastcall add(int a, int b){return a + b;}
那接下來,我們看看調用的時候發生了什麼變化,
14: int p = add(2, 3);00401068 mov edx,30040106D mov ecx,200401072 call @ILT+10(_add) (0040100f)00401077 mov dword ptr [ebp-4],eax15: return 1;0040107A mov eax,1
和上面的壓棧不同,這裡用edx儲存了資料3,用eax儲存了資料eax。畢竟寄存器運算要比記憶體運算快得多。
(4)被調用和自行進行壓棧恢複
__stdcall是我們這裡講到的最後一種壓棧模式。在函數壓棧的方面,他和__cdelc是一樣的,但是關鍵就在add函數結束的位置發生了變化。首先,我們需要用__stdcall裝飾了一下函數,
int __stdcall add(int a, int b){return a + b;}
那麼此時函數彙編的時候,代碼發生了變化呢?
7: int __stdcall add(int a, int b)8: {00401020 push ebp00401021 mov ebp,esp00401023 sub esp,40h00401026 push ebx00401027 push esi00401028 push edi00401029 lea edi,[ebp-40h]0040102C mov ecx,10h00401031 mov eax,0CCCCCCCCh00401036 rep stos dword ptr [edi]9: return a + b;00401038 mov eax,dword ptr [ebp+8]0040103B add eax,dword ptr [ebp+0Ch]10: }0040103E pop edi0040103F pop esi00401040 pop ebx00401041 mov esp,ebp00401043 pop ebp00401044 ret 8
這裡的彙編代碼沒有什麼特別之處。但是最後一個ret 8是什麼意思呢?其實因為之前有兩個參數a和b,那麼8就是這兩個參數佔有的空間。此時ret b事實上就是讓ebp恢複到原來的空間。僅此而已。但是我們發現,可能這一步運行之後,ebp加了不是8,而是12,這又是為什麼呢?因為還有4個位元組的返回地址沒有加上呢。