x86下的C语言函数调用栈分析

标签: 二进制

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进行动态调试:
在这里插入图片描述
此时栈帧情况如下:
在这里插入图片描述
然后继续运行,进入demo_cdecl函数栈帧,这里在执行CALL的时候,实际上已经将CALL语句的下一句ADD ESP,10所在地址00401085压栈作为返回地址:
在这里插入图片描述
demo_cdecl函数如下:
在这里插入图片描述
在执行MOV DWORD PTR SS:[EBP - 4], EAX语句后栈和寄存器情况如下:

  • 栈:在这里插入图片描述
  • 寄存器:
    在这里插入图片描述

被调用函数执行完RET后寄存器内容如下:
在这里插入图片描述
执行完ADD ESP, 10后调用方将堆栈清理:
在这里插入图片描述

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语句后面并没有堆栈清理的语句:
在这里插入图片描述
然后转入被调用函数,函数栈帧的其他操作与cdecl一致,只是最后的RET语句变为了RET 10语句,也就是在执行完该语句后,被调用方清理了16个字节的栈帧(也就是入栈的4个参数所占用的栈帧):
在这里插入图片描述
函数返回后,堆栈已被清理,调用方直接从返回地址开始继续运行即可:
在这里插入图片描述

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;
}

调用者的代码如下:
在这里插入图片描述
执行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个字节)。
在这里插入图片描述
函数返回后调用方不需要进行堆栈清理操作,直接从返回地址继续运行即可。

4 __thiscall

程序代码:

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中,调用方在函数返回后直接从返回地址继续运行:
在这里插入图片描述

版权声明:本文为qq_42181428原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_42181428/article/details/106970360