首页
社区
课程
招聘
[旧帖] [原创]VC6环境下C/C++ 32位函数调用约定 0.00雪花
2009-6-14 22:55 3702

[旧帖] [原创]VC6环境下C/C++ 32位函数调用约定 0.00雪花

2009-6-14 22:55
3702
俺是新来的,发贴想混个邀请码,不知是否能如愿,无论如何希望对有些人有用(我在“安全编程论坛”中看到有人讨论相关问题)。

    在此只是讨论VC6环境下C/C++ 32位函数调用约定,其它环境感兴趣的自己去发掘与验证。

    函数调用约定(call convention)涉及函数参数如何传递, 谁平栈(还有命名,返回值等问题不在此讨论), 在VC6环境下有4种调用约定, 分别说明如下:

      __cdecl        参数全部参过栈传递, 压栈顺序从右到左, 即最后一个参数先入栈; 调用者负责平栈。举例如下:
                print("",1);的汇码如下:
                        004010B6 6A 01                push        1
                        004010B8 68 7C 20 42 00       push        offset string "" (0042207c)
                        004010BD E8 7E 06 00 00       call        printf (00401740)
                        004010C2 83 C4 08             add         esp,8
                最后一句 add esp,8就是平栈的,因为调用函数print时压入了两个DWORD大小的参数。

      __stdcall        参数全部通过栈传递,压栈顺序从右到左; 被调用者负责平栈。
                CreateFileA("\\\\.\\PHYSICALDRIVE0",GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,0,OPEN_EXISTING,0,0);的实现代码如下:
                004010F4 6A 00                push        0
                004010F6 6A 00                push        0
                004010F8 6A 03                push        3
                004010FA 6A 00                push        0
                004010FC 6A 07                push        7
                004010FE 68 00 00 00 80       push        80000000h
                00401103 68 34 20 42 00       push        offset string "\\\\.\\PHYSICALDRIVE0" (00422034)
                00401108 FF 15 60 A1 42 00    call        dword ptr [__imp__CreateFileA@28 (0042a160)]
                调用CreateFileA的代码并没有去平栈,再看CreateFileA的部分代码如下
                7C801A28 8B FF                mov         edi,edi
                7C801A2A 55                   push        ebp
                7C801A2B 8B EC                mov         ebp,esp
                7C801A2D FF 75 08             push        dword ptr [ebp+8]
                7C801A30 E8 DF C6 00 00       call        7C80E114
                7C801A35 85 C0                test        eax,eax
                7C801A37 74 1E                je          7C801A57
                7C801A39 FF 75 20             push        dword ptr [ebp+20h]
                7C801A3C FF 75 1C             push        dword ptr [ebp+1Ch]
                7C801A3F FF 75 18             push        dword ptr [ebp+18h]
                7C801A42 FF 75 14             push        dword ptr [ebp+14h]
                7C801A45 FF 75 10             push        dword ptr [ebp+10h]
                7C801A48 FF 75 0C             push        dword ptr [ebp+0Ch]
                7C801A4B FF 70 04             push        dword ptr [eax+4]
                7C801A4E E8 AD ED 00 00       call        7C810800
                7C801A53 5D                   pop         ebp
                7C801A54 C2 1C 00             ret         1Ch
                可以看到, 函数返回 ret 1ch, 函数返回时弹出了1Ch字节,因为它有7个DWORD大小的参数,7*4=1Ch

       __fastcall,        前两个(如果有的话)DWORD或更小大小的参数通过REG传递,第一个在ecx中,第二个在edx中,如果还有更多的参数, 则通过栈传递, 同样是从右到左,由被调用者负责平栈
    __thiscall,        类(包括class/struct/union)非静态成员函数默认的调用约定类型,C++中不能显式声明它,这种调用约定有点象是__fastcall与__stdcall的混合体, 隐含的this指针通过ecx传递,其它参数从右到左压栈, 被调用者负责平栈。

    __cdecl类型的函数可以实现特殊的功能,即参数数量可变,如printf,其它调用类型不可以实现。
WINDOWS API大部声明为        WINAPI,实际上它是__stdcall,不过并非所有的WINDOWS API都是WINAPI调用约定的, 有许多__cdecl调用的。
    __fastcall也是比较常见的,WINDOWS 内核的许多API是__fastcall类型,写驱动的要注意。

    有一点是很少有资料提到,就是类的非静态成员调用约定不是一定是__thiscall, 你可以为一个非静态成员函数指定调用约定类型为__cdecl, __stdcall, 或__fastcall, 这种情况下,隐含的this指针总是函数的第一个参数, 对于__cdecl/__stdcall来说this指针被最后一个压入栈中,对于__fastcall来说, this指针仍然由ecx传递。所以对于类成员函数也可以实现可变参数,也可以强制要求将this指针通过栈传递。

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
点赞7
打赏
分享
最新回复 (12)
雪    币: 27
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
eggpai 2009-6-16 13:29
2
0
恩,不错,学习。
雪    币: 55
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
AmaranthF 2009-6-16 14:03
3
0
由调用者平栈的函数,windows API中貌似只有wsprintf函数了。

嘛,事实上咱自己写汇编程序的时候大部分函数都是用寄存器传递参数的……acd随便用,ebx加上两个索引寄存器偶尔也会用掉……
雪    币: 724
活跃值: (81)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
半道出家 2009-6-16 22:14
4
0
实际上也不少,kernel32, ntdll中有一堆C库函数的实现(尤其是NTDLL中有许多),它们基本上都是C调用的, VC默认使用库中的。用汇编写程序一般不链C库,需要的话则更应该调它们了。
雪    币: 55
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
AmaranthF 2009-6-17 09:59
5
0
呃,ntdll中的函数们不算api吧……那些是Windows Driver Kit里的了……
用汇编写程序,我指的是自己写函数的时候,用寄存器传递参数不需要额外的内存访问会快一些。
雪    币: 724
活跃值: (81)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
半道出家 2009-6-17 16:36
6
0
您说的对, 函数之间能通REG传递参数速度是最快的, 所以在VC中通过ECX,EDX传递参数的调用称为__fastcall,只是X86的通过寄存器不多,但除了ESP外,其它都可以用来传递参数的,不知道为什么不多用些。
  至于说NTDLL,SDK/DDK中都没有说明它,微软未向一般的开发商提供相关定义,NTDLL是NT系列平台上提供NATIVE API的模块,它是WIN32子系统(如KERNEL32/USER32/GDI32)和内核打交道的主要媒介,KERNEL32导出的一些函数直接就转向了NTDLL,如HeapAlloc,如果你反汇编XP系统上的KERNEL32.DLL,你会发现HeapAlloc被重定向到NTDLL.RtlAllocateHeap。网上有人研究NATIVE API相关类型和函数原型定义的,有些病毒或木马也在使用这些API。
雪    币: 370
活跃值: (52)
能力值: ( LV13,RANK:350 )
在线值:
发帖
回帖
粉丝
moonife 8 2009-7-14 20:37
7
0
好贴 学习了 谢谢分享
雪    币: 55
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
AmaranthF 2009-7-14 21:32
8
0
嘛,版主顶起个坟贴咱也回来看看。
关于不用c、d以外的寄存器的原因……貌似所有win32 api调用前后都不会动acd以外的寄存器……而eax又用来传递返回值,所以为了整齐也就扔掉了。
雪    币: 724
活跃值: (81)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
半道出家 2009-7-15 07:54
9
0
您提到返回值, 这是我前面没有提到的, 就翻新一下坟贴, 补充作为调用约定一部的返回值问题.

32位环境下返回值大部分情况下非常简单, 因为返回值类型多为32位整型或指针,但完整地说返回值问题要复杂些, 可以大概总结如下, 仅供参考:

        如果返回值是抽象数据类型且有构造函数(包括拷贝构造),则返回值的指针作为函数的第一个参数传递,函数通过此指针传递返回值。
        如果返回值类型大于64位,结果同上。
        如果返回值是浮点类型(float/double),返回值通过ST(0)传递。
        其它情况,如果返回值类型大小<=32位,通过eax传递,否则,通过edx:eax传递。
雪    币: 722
活跃值: (123)
能力值: ( LV12,RANK:300 )
在线值:
发帖
回帖
粉丝
轩辕小聪 7 2009-7-15 10:35
10
0
一般Win32 API调用前后,有几个寄存器是保持不变的,包括ebp、esi、edi、ebx,esp的变化跟调用规范相关,至于eax则一般用于保存返回值,ecx和edx则是不保证不变的。
雪    币: 68
活跃值: (47)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
blackgrape 2009-7-22 11:15
11
0
这个帖子写的非常好,学习了
雪    币: 34
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
KillBC 2009-9-6 20:34
12
0
ABI ~~
雪    币: 26
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
zhenyangy 2016-5-12 22:05
13
0
不错啊啊
游客
登录 | 注册 方可回帖
返回