Windows Function Call Convention-Windows函數調用約定



        在程序開發中,函數調用是靠棧來實現的,同時,編譯器幫助我們做了壓棧和恢復堆棧工作,這樣我們在調用函數時不需要再進壓棧退棧。但是由於存在不同的語言,其調用約定也是多種多樣的。這樣在調用函數時,我們就需要提前通知編譯器,告知其以何種方式編譯代碼。上圖中調用約定,便體現了約定的多樣性。而這種通知機制,便是函數調用約定(Function Call Convention)。
        函數調用約定,本質上就是告知編譯器以何種方式將高級語言編譯爲彙編語言。
        編譯器在接收到調用約定之後,需要對函數進行以下處理:
        1)解決函數參數的壓棧順序問題。如果函數的參數多於一個,是按照由右往左的方式還是由左向右,或者其他。
        2)解決堆棧恢復的問題。也就是說,是由調用者恢復堆棧,還是由函數自身恢復堆棧。
        3)編譯器來識別函數名字的修飾約定。

<1>__cdecl方式
__cdecl調用約定方式是C/C++語言的默認處理方式,其告知編譯器,參數需要壓棧,並且是按照從右向左的方式壓棧。該約定規定由調用者(caller)負責恢復堆棧,也基於此,能夠支持可變參數函數的調用;同時,也由於這個特點,需要調用者在程序中包含棧的清理代碼,導致二進制執行文件相比於__stdcall要大的多。
在C/C++中,我們這樣使用__cdecl
int __cdecl function(int a, int b);
其中,__cdecl可以省略,因爲這是默認的方式。同時,也就是說,不寫函數調用方式的函數,均是使用__cdecl函數調用約定。

<2>__stdcall
__stdcall是pascal的處理方式,同時也用來調用Win32 API函數。該約定告知編譯器,參數需要壓棧,並且是按照從右往左的方式壓棧。但與__cdecl方式不同,該方式要求被調用的函數自身(callee)去恢復堆棧。也因此,它不支持可變參數函數,並且在調用時需要一個函數原型;同時,也導致其二進制文件較小。
在程序中,我們可以這樣使用__stdcall
int __stdcall function(int a, int b);

對於__stdcall,另外一個說明是它告知編譯器怎麼處理函數名:前加下劃線,後加@,之後跟參數總大小。如上述函數function,就變成了_function@8。

__cdecl和__stdcall的最大區別在於:__cdecl是由調用者恢復堆棧;__stdcall是由被調用函數自身恢復堆棧。

<3>__fastcall
__fastcall要求函數前兩個不大於雙字長度的參數存於ECX和EDX寄存器中,所有其他的參數被由右向左存於棧中。其他的方面與__stadcall類似。

<4>thiscall
__thiscall是C++成員函數默認的調用約定。在類的定義中,類的成員函數由於存在一個this指針,所以在處理__thiscall調用時,必須特殊處理。
在使用__thiscall時,函數的所有參數被擴展爲32bit後才進行傳輸;同時,返回值也被擴展爲32bit,並被保存在EAX寄存器中,除了一些8字節的數據結構(這些結構被保存在EDX:EAX中)。如果,這些數據結構更大,將會返回一個指針,這個指針指向EAX中保存的返回數據。在函數調用過程中,參數被由右向左保存在棧中,並且在調用完成後由被調用者去恢復堆棧。
需要注意的一點的是:1)如果參數個數確定,this指針被保存在ECX寄存器中並傳遞給函數,,並且函數自身恢復堆棧,類似於__stdcall模式;2)如果參數個數不定,this指針所在的所有參數被壓入堆棧,相當於T * const,this是其第一個參數,調用者恢復堆棧,類似於__cdecl模式。

Windows X86 平臺(VC++)中的各種調用約定

#define   CALLBACK         __stdcall   
#define   WINAPI                 __stdcall   
#define   WINAPIV              __cdecl   
#define   APIENTRY            WINAPI   
#define   APIPRIVATE        __stdcall   
#define   PASCAL              __stdcall   

調用約定:決定函數參數傳送時入棧和出棧的順序;由調用者還是被調用者把參數彈出棧;以及編譯器來識別函數名字的修飾約定。
在VC++中,各種函數的約定有以下幾個特點:
1. __cdecl
對於__cdecl約定來說,VC將函數編譯後會在函數名前面加上下劃線前綴。其是MFC的缺省調用約定。
2. __stdcall
__stdcall調用約定相當於16位動態庫中經常使用的PASCAL調用約定。在32位的VC++5.0中PASCAL調用約定不再被支持(實際上它已被定義爲__stdcall。除了__pascal外,__fortran和__syscall也不被支持),取而代之的是__stdcall調用約定。兩者實質上是一致的。
__stdcall是Pascal程序的缺省調用方式,通常用於Win32 API函數中。
3. __fastcall
VC將函數編譯後會在函數名前面加上"@"前綴,在函數名後加上"@"和參數的字節數。
4. __thiscall
thiscall僅僅用於“C++”成員函數。且thiscall不是關鍵字,不能悲催恆興源使用。
5. naked   call採用1-4的調用約定時,如果必要的話,進入函數時編譯器會產生代碼來保存ESI,EDI,EBX,EBP寄存器,退出函數時則產生代碼恢復這些寄存器的內容。naked   call不產生這樣的代碼。naked   call不是類型修飾符,故必須和_declspec共同使用。  

關鍵字__stdcall,__cdecl,__fastcall可以直接加在函數明前使用,也可以在編譯器的環境中設置。當兩者不一致,在函數名字前的關鍵字有效。三者對應的命令行參數分別爲/Gz、/Gd和/Gr。缺省狀態爲/Gd,即__cdecl。

Windows X64函數調用約定

        借PC處理器架構由x86向x64過渡之機,MS清理了windows x64平臺上的函數調用約定,由原來的數種包括stdcall,thiscall,fastcall,cdecl,pascal等,統一爲一種新的fastcall調用方式。這種調用方式得益於x64平臺寄存器數量的增加。
  
  windows x64平臺fastcall調用約定的主要特性如下:

  • 前四個整型或指針類型參數由RCX,RDX,R8,R9依次傳遞,前四個浮點類型參數由XMM0,XMM1,XMM2,XMM3依次傳遞。
  • 調用函數爲前四個參數在調用棧上保留相應的空間,稱作shadow space或spill slot。即使被調用方沒有或小於4個參數,調用函數仍然保留那麼多的棧空間,這有助於在某些特殊情況下簡化調用約定。
  • 除前四個參數以外的任何其他參數通過棧來傳遞,從右至左依次入棧。
  • 由調用函數負責清理調用棧。
  • 小於等於64位的整型或指針類型返回值由RAX傳遞。
  • 浮點返回值由XMM0傳遞。
  • 更大的返回值(比如結構體),由調用方在棧上分配空間,並有RCX持有該空間的指針並傳遞給被調用函數,因此整型參數使用的寄存器依次右移一格,實際只可以利用3個寄存器,其餘參數入棧。函數調用結束後,RAX返回該空間的指針。
  • 除RCX,RDX,R8,R9以外,RAX、R10、R11、XMM4 和 XMM5也是易變化的(volatile)寄存器。
  • RBX, RBP, RDI, RSI, R12, R14, R14, and R15寄存器則必須在使用時進行保護。
  • 在寄存器中,所有參數都是右對齊的。小於64位的參數並不進行高位零擴展,也就是高位是無法預測的垃圾數據。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章