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

来源:互联网 发布:55寸国产电视 知乎 编辑:程序博客网 时间:2024/06/10 12:40

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        30040107A   push        10040107C   call        @ILT+10(AddInt) (0040100f)

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

0040104A   ret         8

这一句就相当于

retadd 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,30040107D   mov         ecx,100401082   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        90040107A   push        70040107C   mov         edx,300401081   mov         ecx,100401086   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        30040B46A   push        10040B46C   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        30040103A   push        10040103C   lea         eax,[ebp-4]0040103F   push        eax00401040   call        @ILT+20(Example::AddInt) (00401019)00401045   add         esp,0Ch
3.4.2 __stdcall
15:       a.AddInt(1, 3);00401038   push        30040103A   push        10040103C   lea         eax,[ebp-4]0040103F   push        eax00401040   call        @ILT+0(Example::AddInt) (00401005)
3.4.3 __fastcall
15:       a.AddInt(1, 3);00401038   push        30040103A   mov         edx,10040103F   lea         ecx,[ebp-4]00401042   call        @ILT+10(Example::AddInt) (0040100f)

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