《逆向工程核心原理》读书笔记——第10章 函数调用约定

本章学习函数调用约定( Calling Convention)的相关知识。

10.1函数调用约定

  Calling Convention译成中文是“函数调用约定”,它是对函数调用时如何传递参数的一种约定。我们通过前面的学习已经知道,调用函数前要先把参数压入栈然后再传递给函数。栈就是定义在进程中的一段内存空间,向下(低地址方向)扩展,且其大小被记录在PE头中。也就是说,进程运行时确定栈内存的大小( 与malloc/new动态分配内存不同)。
提问1.函数执行完成后,栈中的参数如何处理?
回答1.不用管。
  由于只是临时使用存储在栈中的值,即使不再使用,清除工作也会浪费CPU资源。下一次再向栈存入其他值时,原有值会被自然覆盖掉,并且栈内存是固定的,所以既不能也没必要释放内存。

提问2.函数执行完毕后,ESP值如何变化?
回答2.ESP值要恢复到函数调用之前,这样可引用的栈大小才不会缩减。
  栈内存是固定的,ESP用来指示栈的当前位置,若ESP指向栈底,则无法再使用该栈。函数调用后如何处理ESP,这就是函数调用约定要解决的问题。主要的函数调用约定如下。
cdecl
stdcall
fastcall
应用程序的调试中,cdec1与stdcall的区别非常明显。不管采用哪种方式,通过栈来传递参数的基本概念都是一样的。

10.1.1 cdecl

cdecl是主要在C语言中使用的方式,调用者负责处理栈。

#include "stdio.h"

int add(int a, int b)
{
    return (a + b);
}

int main(int argc, char* argv[])
{
    return add(1, 2);
}

  使用VC++(关闭优化选项)编译代码10-1生成cdecl.exe文件后,使用OllyDbg调试。
  从图10-1中401013~40101C地址间的代码可以发现,add()函数的参数1、2以逆序方式压入栈,调用add()函数(401000)后,使用ADD ESP,8命令整理栈。调用者main()函数直接清理其压入栈的函数参数,这样的方式即是cdecl。
在这里插入图片描述

图10-1 cdecl.exe示例文件

提示
  cdecl方式的好处在于,它可以像C语言的printf()函数一样,向被调用函数传递长度可变的参数。这种长度可变的参数在其他调用约定中很难实现。

10.1.2 stdcall

  stdcall方式常用于Win 32 API,该方式由被调用者清理栈。前面讲解过C语言默认的函数调用方式为cdecl。若想使用stdcall方式编译源码,只要使用_stdcall关键字即可。

#include "stdio.h"

int _stdcall add(int a, int b)
{
    return (a + b);
}

int main(int argc, char* argv[])
{
    return add(1, 2);
}

  使用VC++(关闭优化选项)编译代码10-2生成stdcall.exe文件后,使用OllyDbg调试。从图10-2中的代码可以看到,在main()函数中调用add()函数后,省略了清理栈的代码(ADDESP,8)。
  栈的清理工作由add()函数中最后(40100A)的RETN 8命令来执行。RETN 8命令的含义为RETN+POP8字节,即返回后使ESP增加到指定大小,如图10-2所示。
在这里插入图片描述

图10-2 stdcall.exe示例文件

  像这样在被调用者add()函数内部清理栈的方式即为stdcall方式。stdcall方式的好处在于,被调用者函数内部存在着栈清理代码,与每次调用函数时都要用ADD ESP,XXX命令的cdecl方式相比,代码尺寸要小。虽然Win 32API是使用C语言编写的库,但它使用的是stdcall方式,而不是C语言默认的cdecl方式。这是为了获得更好的兼容性,使C语言之外的其他语言(Delphi(Pascal)、Visual Basic等)也能直接调用API。

10.1.3 fastcall

  fastcall方式与stdcall方式基本类似,但该方式通常会使用寄存器(而非栈内存)去传递那些需要传递给函数的部分参数(前2个)。若某函数有4个参数,则前2个参数分别使用ECX、EDX寄存器传递。
  顾名思义,fastcall方式的优势在于可以实现对函数的快速调用(从CPU的立场看,访问寄存器的速度要远比内存快得多)。单从函数调用本身来看,fastcall方式非常快,但是有时需要额外的系统开销来管理ECX、EDX寄存器。倘若调用函数前ECX与EDX中存有重要数据,那么使用它们前必须先备份。此外,如果函数本身很复杂,需要把ECX、EDX寄存器用作其他用途时,也需要将它们中的参数值存储到另外某个地方。
  前面我们学习了函数调用约定的相关知识。若想进一步学习栈与寄存器,请参考相关章节内容。

其他几种类型:

__stdcall,__cdecl,__fastcall,__thiscall,__nakedcall,__pascal,__vectorcall
参数传递顺序:
1.从右到左依次入栈:__stdcall,__cdecl,__thiscall,__fastcall
2.从左到右依次入栈:__pascal
调用堆栈清理:
1.调用者清除栈。
2.被调用函数返回后清除栈。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值