-
-
[原创]函数调用约定
-
发表于:
2024-8-5 18:49
2766
-
关于C语言的函数调用约定,主要关注四个问题:
- 参数的传递方向
- 参数的传递媒介
- 谁负责栈平衡?
- 返回值的存储
针对以上四个问题,在微软VC++平台上,提供了三种调用约定:__stdcall,__cdecl和__fastcall。
PS:以下内容仅适用于32位x86平台
__stdcall
这是VC++的约定,也就是微软的约定。
- 参数从右向左传递
- 使用栈传参
- 被调用方清理参数
- 返回值一般在处理器中
先准备一段C代码:
1 2 3 4 5 6 7 8 9 10 | int __stdcall func( int x, int y, int z, int m)
{
return x + y + z + m;
}
int main( void )
{
func(4, 5, 6, 7);
return 0;
}
|
断点调试到调用func处,打开反汇编窗口可见,从右向左依次向栈中压入7,6,5,4四个参数:
再断点执行到func返回处可见,结果保存在寄存器eax中作为返回值。
在执行ret指令时指定了一个操作数0x10,也就是过程返回后还要将栈指针寄存器esp加16字节(4个int型参数的长度)以恢复栈平衡,这里体现了被调用者清理参数空间。
__cdecl
这是C标准调用约定(C declaration)。
- 参数从右向左传递
- 使用栈传参
- 调用方清理参数
- 返回值一般在处理器中
将函数定义改为__cdecl
1 2 3 4 5 6 7 8 9 10 | int __cdecl func( int x, int y, int z, int m)
{
return x + y + z + m;
}
int main( void )
{
func(4, 5, 6, 7);
return 0;
}
|
断点调试到调用func处,打开反汇编窗口可见,从右向左依次向栈中压入7,6,5,4四个参数,并且在call指令之后,也就是过程调用返回后,将栈指针寄存器esp加0x10(16字节,4个int型参数的长度),恢复栈平衡,这里体现出由调用者清理参数空间。
PS:如果函数是变参函数,则只能使用__cdecl,因为只有调用者知道传入了几个参数,也只能由调用者恢复栈平衡。
再断点执行到func返回之前,可见结果是保存到寄存器eax中作为返回值的。
__fastcall
一个小众的调用约定
- 左数前两个参数使用处理器传参,其它参数从右向左使用栈传参
- 被调用方清理参数空间
- 返回值一般在处理器中
因为前两个参数使用寄存器传参,相对快些,故名曰fast。
将func定义改为__fastcall
1 2 3 4 5 6 7 8 9 10 | int __fastcall func( int x, int y, int z, int m)
{
return x + y + z + m;
}
int main( void )
{
func(4, 5, 6, 7);
return 0;
}
|
断点执行到调用func处,打开反汇编窗口可见,先从右向左向栈中压入7,6,再使用寄存器edx,ecx分别传递参数5,4:
断点执行到func返回处可见,最终结果保存在寄存器eax中作为返回值,并且在执行ret指令时指定了操作数8(m,z两个参数的长度)以恢复栈平衡。
x64架构的传参
上述所有传参方式均是针对32位x86平台的,后续的x64架构新增了R8~R15共8个寄存器,缓解了32位平台寄存器紧缺的问题,所以在x64架构中,无论是哪种调用约定,都优先使用寄存器传参。
将Visual Studio修改为x64:
再看反汇编代码,4个参数均使用寄存器传参:
[课程]FART 脱壳王!加量不加价!FART作者讲授!
最后于 2024-8-7 06:31
被米龙·0xFFFE编辑
,原因: 修改一些错误