通过VC学习反汇编——函数调用:调用约定

3 调用约定

调用约定决定了以下内容:函数参数的压栈顺序、由调用者还是被调用者平衡堆栈。

3.1 __cdecl

__cdecl是C和C++程序的默认调用约定:参数通过堆栈来传递,从右向左依次入栈,由调用者平衡堆栈。
同样的代码,我们在AddInt函数前面加上__cdecl调用约定

int __cdecl AddInt(int a, int b)
{
 int c = a+b;
 return c;
}

F5运行断下后,按“ALT+F8”打开反汇编窗口,没有任何变化,和没加__cdecl一样,说明默认调用约定就是__cdecl。

3.2 __stdcall

同样的代码,我们在AddInt函数前面加上__stdcall调用约定

int __stdcall AddInt(int a, int b)
{
 int c = a+b;
 return c;
}

再反汇编看看main函数里,调用的过程如下

9:        int x = AddInt(1, 3);
00401078   push        3
0040107A   push        1
0040107C   call        @ILT+10(AddInt) (0040100f)

参数还是一样从右向左依次入栈,但是没有了add esp,8这句,那谁来平衡堆栈呢?看看AddInt函数的反汇编的最后一条指令:

0040104A   ret         8

这一句就相当于

ret
add esp,8

所以__stdcall的调用约定是参数通过堆栈来传递,从右向左依次入栈,由被调用者平衡堆栈。
一般Windows API函数都是__stdcall,在Windef.h中可以找到如下的定义:

#define WINAPI      __stdcall
3.3 __fastcall

同样的代码,我们在AddInt函数前面加上__fastcall调用约定

int __fastcall AddInt(int a, int b)
{
 int c = a+b;
 return c;
}

再反汇编看看main函数里,调用的过程如下

9:        int x = AddInt(1, 3); 
00401078   mov         edx,3
0040107D   mov         ecx,1
00401082   call        @ILT+0(AddInt) (00401005)

可以看到,两个参数分别用ECX和EDX传递。如果更多参数会怎么样呢?

int __fastcall AddInt(int a, int b, int c, int d)
{
  int e = a+b+c+d;
  return e;
}

调用的过程如下:

9:        int x = AddInt(1, 3, 7, 9);
00401078   push        9
0040107A   push        7
0040107C   mov         edx,3
00401081   mov         ecx,1
00401086   call        @ILT+15(AddInt) (00401014)

可以看出来,__fastcall的调用约定是:第一个参数通过ECX传递,第二个参数通过EDX传递,第三个参数起从右向左依次入栈,由被调用者平衡堆栈。

3.4 类的成员函数

对于类的成员函数来说,除了要传递普通的参数,还有一个隐藏的参数——this指针。

class Example
{
public:
	int AddInt(int a, int b)
	{
		int c = a+b;
		return c;
	}
};

int main()
{
	Example a;

	a.AddInt(1, 3);

	return 0;
}
反汇编看一下调用的过程:
16:       a.AddInt(1, 3);
0040B468   push        3
0040B46A   push        1
0040B46C   lea         ecx,[ebp-4]
0040B46F   call        @ILT+5(Example::AddInt) (0040100a)

在调用之前,多了一个指令lea  ecx,[ebp-4],这一句实际上就是将this指针传递给ECX,可以看出来,类成员函数的默认调用约定是:参数通过堆栈来传递,从右向左依次入栈,由被调用者平衡堆栈栈,this指针通过ECX传递。除了this指针,其他都和__stdcall相同。

所以我们只要看到call调用前,某个地址传递给了ECX,就可以知道十有八九调用的是一个类成员函数。

值得注意的是VC编译器默认使用ECX传递this指针,但是Borland C++编译器却是用EAX,不同的编译器处理的方式不一样。

 

更进一步,如果指定类的成员函数调用约定为__cdecl、__stdcall或者是__fastcall,会是什么情况呢?

3.4.1 __cdecl
15:       a.AddInt(1, 3);
00401038   push        3
0040103A   push        1
0040103C   lea         eax,[ebp-4]
0040103F   push        eax
00401040   call        @ILT+20(Example::AddInt) (00401019)
00401045   add         esp,0Ch
3.4.2 __stdcall
15:       a.AddInt(1, 3);
00401038   push        3
0040103A   push        1
0040103C   lea         eax,[ebp-4]
0040103F   push        eax
00401040   call        @ILT+0(Example::AddInt) (00401005)
3.4.3 __fastcall
15:       a.AddInt(1, 3);
00401038   push        3
0040103A   mov         edx,1
0040103F   lea         ecx,[ebp-4]
00401042   call        @ILT+10(Example::AddInt) (0040100f)

可以看出,如果指定了调用约定,实际上编译器把this指针当成函数的第一个参数进行处理了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值