函数调用约定(Calling Convention)是对函数调用时如何传递参数的一种约定。
函数调用完毕后,ESP寄存器的值需要恢复到函数调用之前的值,从而保证可引用的栈大小不会缩减。而函数调用约定就是解决函数调用后如何处理ESP的问题的。
1、cdecl
cdecl主要是C语言中使用的方式,调用者负责处理栈。
编写简易的代码如下:
#include "stdio.h" int add(int a, int b){ return (a + b); } int main(){ return add(1, 2); }
在编译生成exe文件前先关闭优化选项才能更好地适用栈帧:
选择Release,生成exe文件,用OllyDbg打开,拉到最上面即可看到主程序:
可以看到,011D101C地址处的“add esp 8”指令,该命令直接清理其压入栈中的两个函数参数(ESP加8即ESP向下移动两个位置,如下图所示,ESP以上位置的数据将被改写而不用特意去释放内存),这样的方式称为cdecl。
cdecl方式的好处在于,可以向被调用函数传递长度可变的参数,而其他的调用约定很难实现。
2、stdcall
stdcall常用于Win32 API,由被调用者清理栈。若想使用该方式编译源码,只需使用_stdcall关键字即可。
编写简易的代码如下:
#include "stdio.h" int _stdcall add(int a, int b){ return (a + b); } int main(){ return add(1, 2); }
用OllyDbg打开:
可以看到,较cdecl方式相比,stdcall方式在main()函数处省略了清理栈的代码(add esp,8),而栈的清理工作由add()函数最后的“retn 8”来完成,其相当于“RETN + POP 8字节”,即返回后使ESP增加到指定大小。
stdcall方式的好处在于,被调用者函数内部存在着栈清理代码,与每次调用函数时都要用ADD ESP,XXX命令的cdecl方式相比,代码尺寸要小。
3、fastcall
fastcall方式与stdcall方式类似,但该方法通常会使用寄存器(而非栈内存)去传递哪些需要传递给函数的部分参数。其优势在于,可以实现对函数的快速调用(从CPU的立场看,访问寄存器的速度要远比内存的快得多)。