函數調用約定(cdecl,stdcall,thiscall...等區別)

X86函數調用約定(cdecl,stdcall,fastcall,thiscall…)

調用者清理堆棧的約定:

在這些約定中,調用者自己清理堆棧上的參數(arguments),這樣就允許了可變參數列表的實現,如printf()

cdecl

cdecl(C declaration,即C聲明)是源起C語言的一種調用約定,也是C語言的事實上的標準。在x86架構上,其內容包括:

  • 函數實參在線程棧上按照從右至左的順序依次壓棧。
  • 函數結果保存在寄存器EAX/AX/AL中
  • 浮點型結果存放在寄存器ST0中
  • 編譯後的函數名前綴以一個下劃線字符
  • 調用者負責從線程棧中彈出實參(即清棧)
  • 8比特或者16比特長的整形實參提升爲32比特長。
  • 受到函數調用影響的寄存器(volatile registers):EAX, ECX, EDX, ST0 - ST7, ES, GS
  • 不受函數調用影響的寄存器: EBX, EBP, ESP, EDI, ESI, CS, DS
  • RET指令從函數被調用者返回到調用者(實質上是讀取寄存器EBP所指的線程棧之處保存的函數返回地址並加載到IP寄存器)

GCC的函數返回值都是由調用者分配空間,並把該空間的地址作爲隱式參數傳遞給被調函數,而不使用寄存器EAX。GCC自4.5版本開始,調用函數時,堆棧上的數據必須以16B對齊(之前的版本只需要4B對齊即可)。

考慮下面的C代碼片段:

int callee(int, int, int);
  int caller(void)
  {
      register int ret;
      
      ret = callee(1, 2, 3);
      ret += 5;
      return ret;
  }

在X86上,會產生如下彙編代碼:

  caller:
        pushl   %ebp
        movl    %esp,%ebp
        pushl   $3
        pushl   $2
        pushl   $1
        call    callee
        addl    $12,%esp
        addl    $5,%eax
        leave
        ret

cdecl調用約定通常作爲x86 C編譯器的默認調用規則,許多編譯器也提供了自動切換調用約定的選項。如果需要手動指定調用規則爲cdecl,編譯器可能會支持如下語法:

return_type _cdecl funct();

syscall

與cdecl類似,參數被從右到左推入堆棧中。EAX, ECX和EDX不會保留值。參數列表的大小被放置在AL寄存器中(?)。 syscall是32位OS/2 API的標準。

optlink

參數也是從右到左被推入堆棧。從最左邊開始的三個字符變元會被放置在EAX, EDX和ECX中,最多四個浮點變元會被傳入ST(0)到ST(3)中----雖然這四個參數的空間也會在參數列表的棧上保留。函數的返回值在EAX或ST(0)中。保留的寄存器有EBP, EBX, ESI和EDI。 optlink在IBM VisualAge編譯器中被使用。

被調用者清理堆棧的約定:

如果被調用者要清理棧上的參數,需要在編譯階段知道棧上有多少字節要處理。因此,此類的調用約定並不能兼容於可變參數列表,如printf()。然而,這種調用約定也許會更有效率,因爲需要解堆棧的代碼不要在每次調用時都生成一遍。 使用此規則的函數容易在asm代碼被認出,因爲它們會在返回前解堆棧。x86 ret指令允許一個可選的16位參數說明棧字節數,用來在返回給調用者之前解堆棧。代碼類似如下:

ret 12

pascal

基於Pascal語言的調用約定,參數從左至右入棧(與cdecl相反)。被調用者負責在返回前清理堆棧。 此調用約定常見在如下16-bit 平臺的編譯器:OS/2 1.x,微軟Windows 3.x,以及Borland Delphi版本1.x。

stdcall

stdcall是由微軟創建的調用約定,是Windows API的標準調用約定。非微軟的編譯器並不總是支持該調用協議。GCC編譯器如下使用:

int __attribute__((__stdcall__ )) func()

stdcall是Pascal調用約定與cdecl調用約定的折衷:被調用者負責清理線程棧,參數從右往左入棧。其他各方面基本與cdecl相同。但是編譯後的函數名後綴以符號"@",後跟傳遞的函數參數所佔的棧空間的字節長度。寄存器EAX, ECX和EDX被指定在函數中使用,返回值放置在EAX中。stdcall對於微軟Win32 API和Open Watcom C++是標準。

微軟的編譯工具規定:

PASCAL, WINAPI, APIENTRY, FORTRAN, CALLBACK, STDCALL, __far __pascal, __fortran, __stdcall

均是此規定.

調用者或被調用者清理堆棧:

thiscall

在調用C++非靜態成員函數時使用此約定。基於所使用的編譯器和函數是否使用可變參數,有兩個主流版本的thiscall。 對於GCC編譯器,thiscall幾乎與cdecl等同:調用者清理堆棧,參數從右到左傳遞。差別在於this指針,thiscall會在最後把this指針推入棧中,即相當於在函數原型中是隱式的左數第一個參數。

在微軟Visual C++編譯器中,this指針通過ECX寄存器傳遞,其餘同cdecl約定。當函數使用可變參數,此時調用者負責清理堆棧(參考cdecl)。thiscall約定只在微軟Visual C++ 2005及其之後的版本被顯式指定。其他編譯器中,thiscall並不是一個關鍵字(反彙編器如IDA使用__thiscall)。

在VS中,默認的函數調用方式是cdecl

在X86_64中

微軟x64調用約定使用%rdi, %rsi, %rdx, %rcx,%r8, %r9 六個寄存器用於存儲函數調用時的6個參數(從左到右),使用XMM0, XMM1, XMM2, XMM3來傳遞浮點變量。其他的參數直接入棧(從右至左)。整型返回值放置在RAX中,浮點返回值在XMM0中。少於64位的參數並沒有做零擴展,此時高位充斥着垃圾。

在微軟x64調用約定中,調用者的一個職責是在調用函數之前(無論實際的傳參使用多大空間),在棧上的函數返回地址之上(靠近棧頂)分配一個32字節的“影子空間”;並且在調用結束後從棧上彈掉此空間。影子空間是用來給RCX, RDX, R8和R9提供保存值的空間,即使是對於少於四個參數的函數也要分配這32個字節。

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