C/C++不同函数调用方式(在汇编下调试)总结

在初学汇编调试的情况下,一开始对各种函数调用方式感觉困惑,查找网上博客介绍,如
http://hi.baidu.com/bmrs/blog/item/0d27ccfa488a3e1b6d22ebaf.html写得很详细,但是如果没有经过自己一番亲身体验,发现实在很难记住。
因此本文经过windbg单机或者双机调试,验证各种函数调用方式。
(1)首先,我们跟踪__stdcall.
main函数如下:
DWORD main()
{
                __asm
                {
                                int 3
                                push 1                  
                                call F0
                }
}
跟踪如下函数的调用:
DWORD   __stdcall   F0(DWORD a)
{
                 return   a;
}
查看反汇编代码如下:
001b:00401000 55              push    ebp //保存调用函数的ebp
001b:00401001 8bec            mov     ebp,esp//ebp作为一个浮标
001b:00401003 8b4508          mov     eax,dword ptr [ebp+8]
001b:00401006 5d                  pop     ebp //恢复调用函数的ebp
001b:00401007 c20400          ret     4 //ret imm8 根据参数的字节而定,从堆栈上弹出返回地址给eip,cs,然后给esp加上一个number值。
以上是一个参数的情况,_stdcall函数调用方式默认为我们在被调用函数头部和尾部做了一些工作,并自动恢复堆栈,也就是由被调用函数清理参数占用的堆栈。那么如果是多个参数传递的话,参数传递顺序如何呢?
main函数如下:
            int 3
              push 1
             push 2
               call F2
F2函数如下:
DWORD __stdcall F2(DWORD a,DWORD b)
{
                printf("%d/n", a);
                return(a+b);
}
查看反汇编代码如下:
00401000 55              push    ebp
00401001 8bec            mov     ebp,esp
00401003 56              push    esi
00401004 8b7508          mov     esi,dword ptr [ebp+8] ss:0023:0012ff74=00000002//从这里我们可以看出a=2
00401007 56              push    esi
00401008 68a0994000      push    4099A0h
0040100d e828000000      call    0040103a//调用printf
00401012 8b450c          mov     eax,dword ptr [ebp+0Ch]
00401015 83c408          add     esp,8
00401018 03c6            add     eax,esi
0040101a 5e              pop     esi
0040101b 5d              pop     ebp
0040101c c20800          ret     8 //两个int参数,8个字节
从 a=2,我们可以判断出参数入栈顺序方式是从右到左。
当我在写驱动函数调试的时候,反汇编用IDA查看,也是使用_stdcall调用。
(2)接下来,我们看看__cdecl的函数调用方式。
,main函数:
   int 3
    push 1
     push 2
     call F2
被调用函数如下:
DWORD    __cdecl   F1(DWORD a,DWORD b)
{
               printf("%d/n",a);
                 return   (a+b);
}
反汇编代码如下:
00401000 55              push    ebp
00401001 8bec            mov     ebp,esp
00401003 56              push    esi
00401004 8b7508          mov     esi,dword ptr [ebp+8] //2
00401007 56              push    esi
00401008 68a0994000      push    4099A0h
0040100d e828000000      call    0040103a//printf a=2
00401012 8b450c          mov     eax,dword ptr [ebp+0Ch]
00401015 83c408          add     esp,8
00401018 03c6            add     eax,esi
0040101a 5e              pop     esi
0040101b 5d              pop     ebp
0040101c c3              ret//注意这边并没有number,因为被调用函数并不知道参数的个数,所以栈的平衡由调用函数去维护。
从以上代码,我们知道参数很明显也是从右到左入栈的,但是当我们退出被调用函数的时候,查看esp如下:
0012ff74 00000002 00000001 0040122b
也就是说被调用函数退出的时候,栈没有回收传递的参数。故而我们运行到main函数如下:
00401025 e8d6ffffff      call    00401000
0040102a c3              ret
在ret的时候,本来main函数应该返回到0040122b这个地址,可是ret返回的是00000002。
No prior disassembly possible
00000002 ??              ???
因此作为调用函数的main必须回收参数占用的空间。故而,_cdecl适用于变参传递。因为在编译阶段,被调用函数不需要懂得去清理多少的堆栈空间(参数不定,ret number无法确定)
加上:add esp,8 

(3)__fastcall
被调用函数如下:
DWORD __fastcall  F3(DWORD a,DWORD b,DWORD c,DWORD d)
{
                __asm
                {
                                int 3
                                mov eax,d
                                mov ebx,c
                                add eax,ebx
                }
}
 main函数如下:
      F3(1,2,3,4);

反汇编代码:
main函数:
1!main:
01111020 6a04            push    4 //从右边参数先入栈
01111022 ba02000000      mov     edx,2//edx保存参数2
01111027 6a03            push    3
01111029 8d4aff          lea     ecx,[edx-1]//ecx保存参数1,请问为什么不是mov ecx,edx-1
0111102c e8cfffffff      call    1!F3 (01111000)
01111031 33c0            xor     eax,eax
01111033 c3              ret

被调用函数如下:
1!F3:
01111000 55              push    ebp
01111001 8bec            mov     ebp,esp
01111003 53              push    ebx
01111004 cc              int     3
01111005 8b450c          mov     eax,dword ptr [ebp+0Ch]//4
01111008 8b5d08          mov     ebx,dword ptr [ebp+8]//3
0111100b 03c3            add     eax,ebx
0111100d 5b              pop     ebx
0111100e 5d              pop     ebp
0111100f c20800          ret     8  //3,4两个参数通过栈去传递
总结如下,__fastcall名副其实,就是快速调用,利用寄存器去传递有限的(我在win7和winxp3下都是2,这个和编译器相关)的参数,
其他的参数利用栈传递,传递方式和__stdcall一样,从右向左入栈,同时由被调用函数去负责清理栈。
(4)naked
main函数:
 DIY(1,2,3);
被调用函数:
DWORD _declspec(naked) DIY( DWORD a, DWORD b, DWORD c )
{
                __asm
                {
                                push ebp
                                mov ebp,esp
                                mov eax,a
                                add eax, b
                                add eax, c
                                pop ebp
                                ret
                }      
}
反汇编代码如下:
1!main:
01321010 6a03            push    3//右边参数先入栈
01321012 6a02            push    2
01321014 6a01            push    1
01321016 e8e5ffffff      call    1!DIY (01321000)
0132101b 83c40c          add     esp,0Ch//调用函数自己清理栈
0132101e 33c0            xor     eax,eax
01321020 c3              ret

1!DIY:
01321000 cc              int     3
01321001 55              push    ebp
01321002 8bec            mov     ebp,esp
01321004 8b4508          mov     eax,dword ptr [ebp+8]
01321007 03450c          add     eax,dword ptr [ebp+0Ch]
0132100a 034510          add     eax,dword ptr [ebp+10h]
0132100d 5d              pop     ebp
0132100e c3              ret
 可以看出,naked顾名思义,裸露的,需要你为自己去维护入栈,出栈。这个和别的调用方式是不同的,别的调用方式会帮助我们保存恢复寄存器。
如果我再DIY中修改esi寄存器,那么,等我们推出函数,是否会恢复esi,我们在DIY中加入
mov esi, 44444
1!DIY:
012e1000 cc              int     3
012e1001 be9cad0000      mov     esi,0AD9Ch
012e1006 55              push    ebp
012e1007 8bec            mov     ebp,esp
012e1009 8b4508          mov     eax,dword ptr [ebp+8]
012e100c 03450c          add     eax,dword ptr [ebp+0Ch]
012e100f 034510          add     eax,dword ptr [ebp+10h]
012e1012 5d              pop     ebp
012e1013 c3              ret
我们查看寄存器发现esi从0变化为 ad9c,从而说明naked调用方式并没有为我们保存恢复寄存器。故而需要我们自己去为这个裸露的函数穿上外衣。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值