程序代码:
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进行动态调试:
此时栈帧情况如下:
然后继续运行,进入demo_cdecl函数栈帧,这里在执行CALL的时候,实际上已经将CALL语句的下一句ADD ESP,10所在地址00401085压栈作为返回地址:
demo_cdecl函数如下:
在执行MOV DWORD PTR SS:[EBP - 4], EAX语句后栈和寄存器情况如下:
被调用函数执行完RET后寄存器内容如下:
执行完ADD ESP, 10后调用方将堆栈清理:
程序代码:
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语句后面并没有堆栈清理的语句:
然后转入被调用函数,函数栈帧的其他操作与cdecl一致,只是最后的RET语句变为了RET 10语句,也就是在执行完该语句后,被调用方清理了16个字节的栈帧(也就是入栈的4个参数所占用的栈帧):
函数返回后,堆栈已被清理,调用方直接从返回地址开始继续运行即可:
程序代码:
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;
}
调用者的代码如下:
执行CALL语句的栈帧及寄存器状态如下:
进入被调用的函数后,代码如下:
首先将EBP入栈,这里需要关注一下EBP的值为0019FED4,因为后续寻找参数时都是基于EBP的,如下:
当运行下面两句代码后,将EDX和EXC中的前两个参数值分别压入了栈中EBP-8和EBP-4的位置:
MOV DWORD PTR SS:[EBP - 8], EDX
MOV DWORD PTR SS:[EBP - 4], ECX
所以当前4个参数所在的位置如下:
这样就很好理解下面的累和操作了:
fastcall的堆栈清理也是有被调用方完成的,可以看到被调用函数的最后一句为RET 8,即在函数返回时即清理在调用前压入栈中的后两个参数(8个字节)。
函数返回后调用方不需要进行堆栈清理操作,直接从返回地址继续运行即可。
程序代码:
class CSum
{
public:
int Add(int a, int b)
{
return a + b;
}
};
void main()
{
CSum sum;
sum.Add(1, 2);
}
然后进入被调用方,代码如下:
在执行完加分操作后寄存器状态如下:
thiscall也是由被调用方进行堆栈清理,在执行完RET 8后,清理8字节的堆栈并将返回地址POP到EIP中,调用方在函数返回后直接从返回地址继续运行: