Windows Function Call Convention-Windows函数调用约定

来源:互联网 发布:applocale类似的软件 编辑:程序博客网 时间:2024/05/17 18:49


        在程序开发中,函数调用是靠栈来实现的,同时,编译器帮助我们做了压栈和恢复堆栈工作,这样我们在调用函数时不需要再进压栈退栈。但是由于存在不同的语言,其调用约定也是多种多样的。这样在调用函数时,我们就需要提前通知编译器,告知其以何种方式编译代码。上图中调用约定,便体现了约定的多样性。而这种通知机制,便是函数调用约定(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位的参数并不进行高位零扩展,也就是高位是无法预测的垃圾数据。

0 0
原创粉丝点击