函數調用堆棧過程
調用約定
函數的調用約定不少,常見的包括__stdcall,__cdecl,__fastcall,__thiscall等等。
主要的區別在於約束的三個事件,一個是參數傳遞是從左開始呢仍是從右開始,還有就是堆棧清理的清理方是調用者仍是被調用者。另外來講不一樣的函數調用約定函數產生的符號名稱不一樣。windows
舉個栗子,對於cdecl,參數是從右到左傳遞,堆棧平衡是由調用函數來執行的;而win32API通常使用的是stdcall,參數一樣是採用了從右往左傳遞,而函數的堆棧平衡則是由被調用函數執行(不支持可變參數);fastcall參數直接放入寄存器而非棧中,規定前兩個參數分別放入ecx和edx中,當寄存器用完時候參數才按照從右往左的順序壓入堆棧。函數
調用約定 |
使用場景 |
_cdecl |
c調用約定 |
_stdcall |
windows標準調用約定 |
_fastcall |
快速調用約定 |
_thiscall |
C++成員函數調用約定 |
壓棧過程
int add(int a, int b)
{
return a+b;
}
int main()
{
int a = 1;
int b = 2;
int res = add(a,b);
return 0;
}
- 首先從main函數初始,ebp和esp分別存放函數的棧底地址和棧頂地址,此時ebp-4便是a,ebp-8則是b的地址。
- 而後調用函數add,第一先將參數從右往左依次入棧,push在調用方的函數棧當中,也就是說此時esp往裏開闢了兩個參數
- 執行call指令,首先將下一條指令地址進行入棧,
-
隨後開闢新棧,進行現場保護
this
- 這裏省略了現場保護的過程,主要作的就是push了多個寄存器(例如edi,ebx,edi)的值,在完成後還原現場進行pop,對程序沒有什麼其餘影響這裏省略。
- 將edi置爲棧頂地址,ecx置爲11h,eax置爲0CCCCCCCCh。
- 從edi開始循環拷貝eax,也就是將整個棧內初始化爲0CCCCCCCCh,也就是常見的「燙」。
- 執行add函數
開闢一個臨時變量,值是(a)(ebp+8) + (b)(ebp+0Ch),將這個值放入eax中。
-
執行完成後回退棧針
spa
- 首先mov esp,ebp 將add的函數棧回退。
- 隨後pop,將[ebp]的值彈出給ebp,也就是ebp彈回退到main函數的棧底。
- 執行ret指令,將下一條指令的地址彈出給eip寄存器。
- 隨後在main函數的函數棧中回退形參變量所佔用的內存 add esp+8。
那麼再來看看其餘狀況
上面的返回值是一個int類型,也就是C的內置類型,經過eax寄存器帶出。3d
若是是一個double或者long long呢?那麼能夠經過eax、edx兩個寄存器帶出。code
若是是一個自定義類型呢?其實也是相似的:blog
- 首先在參數傳參過程當中不能直接去push一個寄存器了,而如今是經過開闢內存後,將自定義類型的實參b的地址放入esi中,循環賦給實參。
例如說自定義類型的b參數
- 參數傳遞完成以後,再來看看返回值,返回值首先會在壓入全部的形參以後,將main函數中返回值(臨時量)的地址壓入參數。
- 對返回值的操做也是相似的,經過esi和edi、ecx,循環拷貝到main函數的函數棧之中。
- 臨時量返回值的地址是最後纔會壓棧的,那麼它的地址必定是ebx+8