转载 函数调用约定 现场保存

在计算机科学中, 调用约定(Calling Conventions)是一种定义子过程从调用处接受参数以及返回结果的方法的约定。

函数调用时, 调用者依次把参数压栈, 然后调用函数, 函数被调用以后, 在堆栈中取得数据, 并进行计算. 函数计算结束以后, 或者调用者、或者函数本身修改堆栈, 使堆栈恢复原装. 在参数传递中, 有几个很重要的问题必须得到明确说明:

  • 当参数个数多于一个时, 按照什么顺序把参数压入堆栈
  • 函数调用后, 由谁来把堆栈恢复原装
  • 函数的返回值放在什么地方

CDECL

1
2
int function(int a, int b) //不加修饰默认就是 C 调用约定
int __cdecl function(int a, int b) //明确指定 C 调用约定

cdecl 是 C Declaration 的缩写,表示 C 语言默认的函数调用方法:

  • 参数从右向左压入堆栈
  • 函数本身不清理堆栈,调用者负责清理堆栈,因此 C 调用约定允许函数的参数的个数是不固定的

函数调用 function(1, 2) 调用处翻译成汇编语言将变成:

1
2
3
4
push 2;
push 1;
call function;
add esp, 8; //注意, 这里调用者再恢复堆栈

被调用函数:

1
2
3
4
5
6
7
push ebp; 保存 ebp, 该寄存器将用来保存堆栈的栈顶指针, 可以在函数退出时恢复
mov ebp, esp; 保存栈顶指针
mov eax, [ebp + 8H]; 堆栈 ebp 指向位置之前依次保存有 ebp, cs:eip, a, b, ebp + 8 指向 a
add eax, [ebp + 0CH]; 堆栈中 ebp + 12 处保存了 b
mov esp, ebp; 恢复 esp
pop ebp;
ret;

STDCALL

1
int __stdcall function(int a, int b)

stdcall 是 Standard Call 的缩写,是 C++ 的标准调用方式:

  • 参数从右向左压入堆栈
  • 函数自身修改堆栈

函数调用 function(1, 2) 调用处翻译成汇编语言将变成:

1
2
3
push 2; 第二个参数入栈
push 1; 第一个参数入栈
call function; 调用函数, 注意此时自动把 cs:eip 入栈

而对于函数自身, 则可以翻译为:

1
2
3
4
5
6
7
push ebp; 保存 ebp 寄存器, 该寄存器将用来保存堆栈的栈顶指针, 可以在函数退出时恢复
mov ebp, esp; 保存栈顶指针
mov eax, [ebp + 8H];  堆栈中 ebp 指向位置之前依次保存有 ebp, cs:eip, a, b, ebp + 8 指向 a
add eax, [ebp + 0CH]; 堆栈中 ebp + 12 处保存了 b
mov esp, ebp; 恢复 esp
pop ebp;
ret 8;

FASTCALL

1
int __fastcall function(int a, int b)

fastcall 是编译器指定的快速调用方式:

  • 函数的第一个和第二个 DWORD 参数(或者尺寸更小的)通过 ecx 和 edx 传递, 其他参数通过从右向左的顺序压栈
  • 被调用函数清理堆栈

总结

summary.webp

简单举例

cdecl 和 stdcall.webp

执行 call 之后.webp

从图中可以了解到:

  • cdecl 平衡堆栈是在 call 结束回来后 add esp,0x8(参数占用空间)
  • stdcall 平衡堆栈是在 call 内部返回时 retn 0x8

参考

 C++

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值