结合VC6讲下对调用约定的理解

  调用约定(Calling Convertions)定义了程序中调用函数的方式,决定了在函数调用时数据在堆栈中的组织方式。如参数的压栈顺序和堆栈清理工作。这里结合VC 6.0,根据具体的小程序讲解三种调用约定:cdecl,stdcall,fastcall。(还有一些其他的调用约定,俺就浅尝辄止了)

  首先在VC6.0中创建一个项目,程序代码如下:

ContractedBlock.gif ExpandedBlockStart.gif View Code

   
1 #include < stdio.h >
2 #include < string .h >
3
4 void
5 age_print(
6 char * name,
7 int age
8 )
9 {
10 printf( " %s is %d years old.\n " , name, age);
11 }
12
13 int main( void )
14 {
15 char name[ 128 ];
16 int age;
17
18 strcpy(name, " joe " );
19 age = 100 ;
20
21 age_print(name, age);
22
23 return 0 ;
24 }

  如何在VC6中设置调用约定呢?Project->Setting->C/C++,在category中选择Code Generation。可以看到在下面出现一个下拉框,名称是Calling convention,即可选择调用约定的方式,支持上面讲的三种方式:cdecl,stdcall,fastcall。

      我们通过VC的调试方式看每种调用约定时堆栈的情况。

1、cdecl

此种方式下的汇编代码如下所示:

ContractedBlock.gif ExpandedBlockStart.gif View Code

   
1 18 : strcpy(name, " joe " ) ;
2 0040108E push offset string " joe " ( 00422038 )
3 00401093 lea eax,[ebp-80h]
4 00401096 push eax
5 00401097 call strcpy ( 004011b0 )
6 0040109C add esp, 8
7 19 : age = 100 ;
8 0040109F mov dword ptr [ebp-84h],64h
9 20 :
10 21 : age_print(name, age) ;
11 004010A9 mov ecx,dword ptr [ebp-84h]
12 004010AF push ecx
13 004010B0 lea edx,[ebp-80h]
14 004010B3 push edx
15 004010B4 call @ILT+ 0 (_age_print) ( 00401005 )
16 004010B9 add esp, 8

  由strcpy(name, "joe");对应的汇编代码,可见name的值为ebp-80h;

  由age=100;对应的汇编代码可见age的地址为ebp-84h。

  执行到age_print(name,age)时,对应的汇编代码是首先push age,然后push name,由此可见cdecl方式下,参数是从右到左进行进栈的。最后通过call调用age_print,age_print对应的汇编语言具体如下:

ContractedBlock.gif ExpandedBlockStart.gif View Code

   
1 4 : void
2 5 : age_print(
3 6 : char* name,
4 7 : int age
5 8 : )
6 9 : {
7 00401020 push ebp
8 00401021 mov ebp,esp
9 00401023 sub esp,40h
10 00401026 push ebx
11 00401027 push esi
12 00401028 push edi
13 00401029 lea edi,[ebp-40h]
14 0040102C mov ecx,10h
15 00401031 mov eax,0CCCCCCCCh
16 00401036 rep stos dword ptr [edi]
17 10 : printf( " %s is %d years old.\n " , name, age) ;
18 00401038 mov eax,dword ptr [ebp+0Ch]
19 0040103B push eax
20 0040103C mov ecx,dword ptr [ebp+ 8 ]
21 0040103F push ecx
22 00401040 push offset string " %s is %d years old.\n " ( 0042201c )
23 00401045 call printf ( 004010f0 )
24 0040104A add esp,0Ch
25 11 : }
26 0040104D pop edi
27 0040104E pop esi
28 0040104F pop ebx
29 00401050 add esp,40h
30 00401053 cmp ebp,esp
31 00401055 call __chkesp ( 00401170 )
32 0040105A mov esp,ebp
33 0040105C pop ebp
34 0040105D ret

  由最后可以看到直接执行了ret,这说明age_print函数本身没有进行堆栈的清理工作,同时,我们可以在main的汇编代码中看到,在执行了call之后,执行了add  esp,8指令,这说明清理了两个参数的空间,由此可见cdecl的堆栈清理工作是由调用函数进行的。

  由此我们总结出cdecl调用约定的规则:参数进栈顺序是从右到左的,堆栈的清理工作是由调用者进行清理的。

  对于参数的进栈顺序,在《Reversing Secrets of Reverse Engineering》中作者是在APPEND C中这么描述的,

      The first parameter is pushed onto the stack first, and the last parameter is pushed last.

  但是结果和作者所说相反,查了下wiki:

  The cdecl calling convention is used by many C systems for the x86 architecture.[1] In cdecl, function parameters are pushed on the stack in a right-to-left order.

http://en.wikipedia.org/wiki/X86_calling_conventions#cdecl

  好吧,这个问题到此结束。

2、fastcall

  fastcall顾名思义是快速调用的意思。为什么叫这个名字?让我们来看相应的汇编代码就了然了。直接给出main函数调用age_print对应的汇编代码:

ContractedBlock.gif ExpandedBlockStart.gif View Code

   
1 21 : age_print(name, age) ;
2   004010A9 mov edx,dword ptr [ebp-84h]
3   004010AF lea ecx,[ebp-80h]
4   004010B2 call @ILT+ 10 (_age_print) ( 0040100f )

  由此可见,参数并没有进行压栈操作,而只是传递给了edx和ecx寄存器,直接使用寄存器进行存储参数,大家应该明白为什么fast了吧,fastcall的约定通常是使用ecx和edx分别接受第一个和第二个参数。

   对于stdcall,有兴趣的可以自己验证下,呵呵,就写这么多吧。

转载于:https://www.cnblogs.com/nocode/archive/2011/04/05/2005799.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值