1 __cdecl

程序代码:

void __cdecl demo_cdecl(int x, int y, int z, int w) 
{
    int sum = x + y + z + w;
}
int main ()
{
    demo_cdecl(1, 2, 3, 4);
    return 0;
}

在32位的Windows XP上使用VC6.0编译,用x32dbg进行动态调试:

x86的容器 x86c_寄存器


此时栈帧情况如下:

x86的容器 x86c_函数返回_02


然后继续运行,进入demo_cdecl函数栈帧,这里在执行CALL的时候,实际上已经将CALL语句的下一句ADD ESP,10所在地址00401085压栈作为返回地址:

x86的容器 x86c_寄存器_03


demo_cdecl函数如下:

x86的容器 x86c_堆栈_04


在执行MOV DWORD PTR SS:[EBP - 4], EAX语句后栈和寄存器情况如下:

  • 栈:
  • 寄存器:

被调用函数执行完RET后寄存器内容如下:

x86的容器 x86c_函数返回_05


执行完ADD ESP, 10后调用方将堆栈清理:

x86的容器 x86c_二进制_06

2 __stdcall

程序代码:

void __stdcall demo_stdcall(int x, int y, int z, int w) 
{
    int sum = x + y + z + w;
}
int main ()
{
    demo_stdcall(1, 2, 3, 4);
    return 0;
}

与cdecl的区别就是stdcall的堆栈是由被调用方清理的,因此这里只介绍有所区别的地方。

首先可以看到调用方在CALL语句后面并没有堆栈清理的语句:

x86的容器 x86c_寄存器_07


然后转入被调用函数,函数栈帧的其他操作与cdecl一致,只是最后的RET语句变为了RET 10语句,也就是在执行完该语句后,被调用方清理了16个字节的栈帧(也就是入栈的4个参数所占用的栈帧):

x86的容器 x86c_x86的容器_08


函数返回后,堆栈已被清理,调用方直接从返回地址开始继续运行即可:

x86的容器 x86c_函数返回_09

3 __fastcall

程序代码:

void __fastcall demo_fastcall(int x, int y, int z, int w) 
{
    int sum = x + y + z + w;
}
int main ()
{
    demo_fastcall(1, 2, 3, 4);
    return 0;
}

调用者的代码如下:

x86的容器 x86c_二进制_10


执行CALL语句的栈帧及寄存器状态如下:

x86的容器 x86c_堆栈_11


x86的容器 x86c_堆栈_12


进入被调用的函数后,代码如下:

x86的容器 x86c_寄存器_13


首先将EBP入栈,这里需要关注一下EBP的值为0019FED4,因为后续寻找参数时都是基于EBP的,如下:

x86的容器 x86c_堆栈_14


当运行下面两句代码后,将EDX和EXC中的前两个参数值分别压入了栈中EBP-8和EBP-4的位置:

MOV DWORD PTR SS:[EBP - 8], EDX
MOV DWORD PTR SS:[EBP - 4], ECX

x86的容器 x86c_寄存器_15


所以当前4个参数所在的位置如下:

x86的容器 x86c_x86的容器_16


这样就很好理解下面的累和操作了:

x86的容器 x86c_堆栈_17


x86的容器 x86c_二进制_18

fastcall的堆栈清理也是有被调用方完成的,可以看到被调用函数的最后一句为RET 8,即在函数返回时即清理在调用前压入栈中的后两个参数(8个字节)。

x86的容器 x86c_二进制_19


函数返回后调用方不需要进行堆栈清理操作,直接从返回地址继续运行即可。

4 __thiscall

程序代码:

class CSum
{
public:
    int Add(int a, int b)
    {
        return a + b;
    }
};

void main()
{
    CSum sum;
    sum.Add(1, 2);
}

x86的容器 x86c_二进制_20


然后进入被调用方,代码如下:

x86的容器 x86c_寄存器_21


在执行完加分操作后寄存器状态如下:

x86的容器 x86c_堆栈_22


thiscall也是由被调用方进行堆栈清理,在执行完RET 8后,清理8字节的堆栈并将返回地址POP到EIP中,调用方在函数返回后直接从返回地址继续运行:

x86的容器 x86c_堆栈_23