何为调用约定
调用约定指的是函数在调用时会按照不同规则,翻译成不同的汇编代码。这和参数的压栈顺序和栈的清理方式相关,也就是说不同的调用约定,这些方式会做相应改变。一般编译器是以默认的调用约定编译一份代码,但当一个项目使用不同调用约定的库会产生链接错误。
何为函数导出名
同一个函数,在不同的编译器编译出来的符号名是不一样的,程序目标文件链接的时候不知道源程序的函数名,而是通过目标文件(.obj)中寻找相应的函数符号表。在下面中会指出不同调用约定对应的函数导出名。
三种调用约定
(1)__fastcall
特点:快
参数传递方式:前两个参数-寄存器,剩余参数-栈(右到左)
栈的清理者:被调函数
函数导出名:
按C的编译方式:@函数名@参数字节数
按C++的编译方式:
(2)__cdecl
特点:C语言调用约定,文件比__stdcall大
参数传递方式:栈(右到左)
栈的清理者:调用者
函数导出名:
按C的编译方式:_函数名
按C++的编译方式:规则同下面的_stdcall调用约定,只是参数表的开始标识由上面的"@@YG"变为"@@YA"。
(3)__stdcall
特点:标准调用约定, Pascal程序的缺省调用方式,通常用于Win32 Api中
参数传递方式:栈(右到左)
栈的清理者:被调用者
函数导出名:
按C的编译方式:_函数名@参数字节数
按C++的编译方式:
1)、以"?"标识函数名的开始,后跟函数名;
2)、函数名后面以"@@YG"标识参数表的开始,后跟参数表;
3)、参数表以代号表示:
X--void ,
D--char,
E--unsigned char,
F--short,
H--int,
I--unsigned int,
J--long,
K--unsigned long,
M--float,
N--double,
_N--bool,
PA--表示指针,后面的代号表明指针类型,如果相同类型的指针连续出现,以"0"代替,一个"0"代表一次重复;
4)、参数表的第一项为该函数的返回值类型,其后依次为参数的数据类型,指针标识在其所指数据类型前;
5)、参数表后以"@Z"标识整个名字的结束,如果该函数无参数,则以"Z"标识结束。
上面这段代码,源文件后缀必须是.c,同时使用windows系统的dumpbin工具即可获得对应目标文件的内容。
int __cdecl f1(int a) { return a +1; } int __stdcall f2(int b) { return b +1; } int __fastcall f3(int c) { return c +1; } int main() { //函数导出名为_f1、_f2@4、@f3@4 int i = f1(1); int j = f2(2); int k = f3(3); return0; } @f3@4: 00000000:55 push ebp 00000001:8B EC mov ebp,esp 00000003:81 EC CC 000000 sub esp,0CCh 00000009:53 push ebx 0000000A:56 push esi 0000000B:57 push edi 0000000C:51 push ecx 0000000D:8D BD 34 FF FF FF lea edi,[ebp+FFFFFF34h] 00000013: B9 33000000 mov ecx,33h 00000018: B8 CC CC CC CC mov eax,0CCCCCCCCh 0000001D: F3 AB rep stos dword ptr es:[edi] 0000001F:59 pop ecx 00000020:894D F8 mov dword ptr [ebp-8],ecx 00000023:8B45 F8 mov eax,dword ptr [ebp-8] 00000026:83 C0 01 add eax,1 00000029:5F pop edi 0000002A:5E pop esi 0000002B:5B pop ebx 0000002C:8B E5 mov esp,ebp 0000002E:5D pop ebp 0000002F: C3 ret _f1: 00000000:55 push ebp 00000001:8B EC mov ebp,esp 00000003:81 EC C0 000000 sub esp,0C0h 00000009:53 push ebx 0000000A:56 push esi 0000000B:57 push edi 0000000C:8D BD 40 FF FF FF lea edi,[ebp+FFFFFF40h] 00000012: B9 30000000 mov ecx,30h 00000017: B8 CC CC CC CC mov eax,0CCCCCCCCh 0000001C: F3 AB rep stos dword ptr es:[edi] 0000001E:8B4508 mov eax,dword ptr [ebp+8] 00000021:83 C0 01 add eax,1 00000024:5F pop edi 00000025:5E pop esi 00000026:5B pop ebx 00000027:8B E5 mov esp,ebp 00000029:5D pop ebp 0000002A: C3 ret _f2@4: 00000000:55 push ebp 00000001:8B EC mov ebp,esp 00000003:81 EC C0 000000 sub esp,0C0h 00000009:53 push ebx 0000000A:56 push esi 0000000B:57 push edi 0000000C:8D BD 40 FF FF FF lea edi,[ebp+FFFFFF40h] 00000012: B9 30000000 mov ecx,30h 00000017: B8 CC CC CC CC mov eax,0CCCCCCCCh 0000001C: F3 AB rep stos dword ptr es:[edi] 0000001E:8B4508 mov eax,dword ptr [ebp+8] 00000021:83 C0 01 add eax,1 00000024:5F pop edi 00000025:5E pop esi 00000026:5B pop ebx 00000027:8B E5 mov esp,ebp 00000029:5D pop ebp 0000002A: C2 0400 ret 4 _main: 00000000:55 push ebp 00000001:8B EC mov ebp,esp 00000003:81 EC E4 000000 sub esp,0E4h 00000009:53 push ebx 0000000A:56 push esi 0000000B:57 push edi 0000000C:8D BD 1C FF FF FF lea edi,[ebp-0E4h] 00000012: B9 39000000 mov ecx,39h 00000017: B8 CC CC CC CC mov eax,0CCCCCCCCh 0000001C: F3 AB rep stos dword ptr es:[edi] 0000001E:6A01 push 1 00000020: E8 00000000 call _f1 00000025:83 C4 04 add esp,4 00000028:8945 F8 mov dword ptr [ebp-8],eax 0000002B:6A02 push 2 0000002D: E8 00000000 call _f2@4 00000032:8945 EC mov dword ptr [ebp-14h],eax 00000035: B9 03000000 mov ecx,3 0000003A: E8 00000000 call @f3@4 0000003F:8945 E0 mov dword ptr [ebp-20h],eax 00000042:33 C0 xor eax,eax 00000044:5F pop edi 00000045:5E pop esi 00000046:5B pop ebx 00000047:81 C4 E4 000000 add esp,0E4h 0000004D:3B EC cmp ebp,esp 0000004F: E8 00000000 call __RTC_CheckEsp 00000054:8B E5 mov esp,ebp 00000056:5D pop ebp 00000057: C3 ret
相关命令是:
1 //把获得obj函数导出名,存储到d:\1.txt文件 2 dumpbin OBJ文件路径/all /rawdata:none > d:\1.txt 3 4 //获得汇编代码,存储到d:\2.txt 5 dumpbin OBJ文件路径/disasm d:\2.txt
C编译的函数如何在C++中使用
解决办法是采用extern "C"修饰符。使用方法是,把该修饰符添加到调用约定必须是__cdecl的C函数前,如DriverEntry,windows驱动函数入口函数规定为_DriverEntry@8,因此用C++编译器
一些库是用C编译而成的,而在C++平台想要使用这些库,我们可以这样引入C库函数头文件
-
1 extern"C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,IN PUNICODE_STRING pRegistry) 2 { 3 //do something 4 return STATUS_SUCCESS; 5 }
#ifdef __cplusplus extern"C" { #endif #include<NTDDK.h> #ifdef __cplusplus } #endif
参考链接: