调用约定

  编译器:VS2015

   调用约定的直观认识:

  __stdcall是微软自己搞出来的,

  c、c++的默认调用方式是_cdecl,

  另外还有优化的__fastcall(通过寄存器传递参数)

  以及c++的,thiscall(不能直接使用)

  还有naked call

  (可参考http://blog.csdn.net/ghevinn/article/details/43668825)

0x01   x86  calling convention

0x01   x86平台下常用三种调用约定,__cdecl、__stdcall、__fastcall

    

     1.__cdecl

    __cdecl是C/C++的默认调用约定,如果不显示声明调用约定的情况下,就是该调用约定.

    

#include "stdafx.h"
int __cdecl SHCdeclCallTest(int , int , int , int , int );

int main()
{
	SHCdeclCallTest(1, 2, 3, 4, 5);
    return 0;
}


int __cdecl SHCdeclCallTest(int v1, int v2, int v3, int v4, int v5)
{
	return v1 + v2 + v3 + v4 + v5;
}

  

  反汇编看出__cdecl参数从右至左入栈,然后由调用者(caller)清理栈区。

  2.__stdcall 标准调用

#include "stdafx.h"
int __stdcall SHCdeclCallTest(int , int , int , int , int );

int main()
{
	SHCdeclCallTest(1, 2, 3, 4, 5);
    return 0;
}


int __stdcall SHCdeclCallTest(int v1, int v2, int v3, int v4, int v5)
{
	return v1 + v2 + v3 + v4 + v5;
}

  

  反汇编看出__stdcall参数跟__cdecl入栈方式相同,从右至左入栈,被调用者(callee)清理栈区。

  3.__fastcall

int __fastcall SHCdeclCallTest(int v1, int v2, int v3, int v4, int v5)
{
	return v1 + v2 + v3 + v4 + v5;
}

  

  __fastcall参数从右至左入栈,前两个参数被分别放进了ecx、edx寄存器,如果少于或等于两个参数,会先将参数放入寄存器。同样由被调用者(callee)清理栈区。

0x02  x64  calling convention

    微软的C编译器在目标平台为x64的时候,就不再支持__stdcall ,__fastcall关键字,无需再考虑调用协议。

    x64位平台下只有一种__fastcall的调用约定,前4参数则先放入ecx、edx、r8、r9寄存器,更多的参数放入栈区,由调用者(caller)清理栈区空间。

    第一点值得注意的是,在64位下,编译器还是为前4个参数预留了栈区空间(每个栈空间大小为8字节,共32字节大小),然后将寄存器的值放入所预留的栈区空间,这是为了防止在传递参数的过程中,寄存器需要接收其他的值而导致参数无法传递,或者其他值无法接收的情况。

    第二点值得注意的是,x64平台下与通过 PUSH 和 POP 指令在堆栈中显式添加和移除参数的 x86 编译器不同,x64 代码生成器会预留足够的堆栈空间。在进入某个函数之后,就使用sub rsp,n来开辟一个栈空间,除了给自己保存内部变量之外,也给自己所调用的函数传递参数用,这样参数就不用一个个地push,而可以直接用mov指令来填写,函数内的每个被调用函数也不再需要去每一次得恢复栈空间了,直接函数结束时再一次性恢复栈空间。

0x03   x86下存在不同调用约定的理由

     1.可变参数的情况(比如printf()函数参数不定长),其他调用约定也在32位平台下全部会由编译器修正为__cdecl。(stdcall 无法处理变长参数,)

   2.混合语言函数调用的情况,如果是C调用C,它当然知道参数与返回值是如何压栈;但如果是vb或pascal调用C,它怎么知道参数与返回值是如何压栈的?那么怎么清栈?所以无法使用cdecl,只能使用stdcall。

unix主要使用C作为程序语言,但在Windows上vb或pascal与C使用一样广泛,所以,必须考虑不同语言间如何调用问题。(大多数API都采用__stdcall调用规范,这是因为几乎所有的语言都支持__stdcall调用. 所以在跨(开发)平台的调用中,我们都使用__stdcall(虽然有时是以WINAPI的样子出现)。)

    

原文地址:https://www.cnblogs.com/lsh123/p/7598700.html