首页 > C语言 > 常见函数调用约定

常见函数调用约定

本文转载自《Reversing:逆向工程揭密》一书。该书描述的是在逆向与反逆向之间展开的一场旷日持久的拉锯战。

C.1.3 调用约定

调用约定(Calling Conventions)定义了程序中调用函数的方式。调用约定之所以与我们这里讨论的堆栈相关,是因为调用约定决定了在函数调用的时候数据(比如说参数)在堆栈中的组织方式。理解调用约定对你来说非常重要,因为在逆向过程中你会不时地遇到函数调用,准确地辨识所用的调用约定是哪一种将有助于你理解你正在解读的程序。

在讨论各个调用约定之前,我们先来讨论一下函数调用要用到的两条基本的指令:CALL指令和RET指令。CALL指令将当前的指令指针(这个指针指向紧接在CALL指令后面的那条指令)压入堆栈,然后执行一条无条件转移指令转移到新的代码地址。

RET是与CALL指令配合使用的指令,在绝大多数函数中它是最后一条指令。RET指令弹出返回地址(就是早些时候CALL指令压入堆栈的地址)并将其加载到EIP寄存器中,然后从这个地址开始继续执行。

接下来的几个小节中我们将讨论几种最常见的调用约定,及其它们在汇编语言中是怎样实现的。

cdecl调用约定

cdecl调用约定是C和C++语言中的标准调用约定。其特点是允许函数接收可变数量的参数。cdecl调用约定可以做到参数数量可变,是因为由主调函数负责在函数调用之后恢复堆栈指针。另外,与其他调用约定相比,cdecl函数是按照相反的顺序接收参数的。第一个参数被首先压入堆栈,最后一个参数最后压入堆栈。(代码疯子注:好像没有这回事吧,在Visual Studio上测试一下,也是最后一个参数先入栈的,从右至左)识别cdecl调用约定的方法非常简单:如果函数接收了一个或多个参数,并且以一个简单的不带任何操作数的RET指令收尾的话,这个函数很可能是采用cdecl调用约定。

fastcall调用约定

顾名思义,fastcall是一种相对比较高效的调用约定:它使用寄存器来给被调函数传递前两个参数,其余的参数通过堆栈传递给被调函数。最初,fastcall是Microsoft公司专用的调用约定,但是现在大多数主流编译器都支持支持这种调用了,所以你可以在更多的现代程序中碰见它。fastcall调用约定通常使用ECX寄存器和EDX寄存器来分别存放第一个参数和第二个参数。

stdcall调用约定

stdcall调用约定在windows系统中是非常常见的,因为windows系统函数和API都使用这种调用约定。stdcall调用约定在参数传递的方式和顺序上与cdecl调用约定相反。使用stdcall调用约定的函数接收参数的顺序与使用cdecl调用约定的函数的相反,即stdcall中最后一个参数最先压入堆栈。stdcall与cdecl另一个重要的区别在于:在stdcall中被调函数负责清栈,而在cdecl中是由主调函数负责清栈的。stdcall中函数使用RET指令清栈,这是RET指令带有一个操作数,该操作数指明在EIP跳回主要函数之前需要释放的堆栈空间的字节数。这就是说,stdcall调用约定中RET指令带的操作数往往就意味着函数一共传入几个参数。(操作数除以4=参数个数)这是在逆向工程中识别stdcall调用约定的一个重要的特征,并可以据此判断出函数所接收的参数的个数。

C++类成员调用约定(thiscall)

当C++程序中的类方法所接受的参数的个数是固定的时候,Microsoft和Intel编译器会使用这种调用约定。一种快速识别这种调用约定的技巧是:使用这种调用约定的函数指令流将在ecx寄存器中写入一个有效指针,并往堆栈中压入参数,但不使用edx寄存器。原因是每个C++的类方法都必须接收一个类指针(就是this指针),并可能较频繁的使用该指针。编译器则使用这种高效的技巧来传递和存储这个特殊的参数。

对于参数个数不确定的类方法,编译器就将使用cdecl调用约定,并把this指针作为第一个参数首先压入堆栈。

———[Update 2012/07/08]———
Delphi中函数参数传递方式:
前三个参数通过寄存器eax,edx,ecx传递,超过三个参数部分放在堆栈传递,堆栈传递方式先压入最后的参数,即从右至左。
函数自己恢复堆栈。


觉得文章还不错?点击此处对作者进行打赏!


本文地址: 程序人生 >> 常见函数调用约定
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!


更多



  1. 本文目前尚无任何评论.