调用约定
允许使用VARARG:表示参数个数可以是不确定
约定类型 | _cdecl(C类规范) | pascal | stdcall | Fastcall |
---|---|---|---|---|
参数传递 | 从右到左 | 从左到右 | 从右到左 | 寄存器传参与栈 |
平衡者 | 调用者 | 子程序 | 子程序 | 子程序 |
允许使用VARARG | 是 | 否 | 否 |
x86 下的调用约定
_cdecl,_stdcall,_fastcall
int Test(int a,int b,int c,int d,int e)
{
return a + b + c + d + e;
}
int main()
{
Test(1,2,3,4,5);
}
- _cdecl C++下的默认调用约定
Test(1,2,3,4,5);
00A71891 push 5
00A71893 push 4
00A71895 push 3
00A71897 push 2 //第二个参宿
00A71899 push 1 //第一个参宿
00A7189B call Test (0A71398h)
00A718A0 add esp,14h //平栈由调用者执行
_cdecl从右到左进行压栈操作,由调用者进行压栈操作
- _stdcall
_stdcall 是WindowsApi默认的调用约定
Test(1,2,3,4,5);
00271891 push 5
00271893 push 4
00271895 push 3
00271897 push 2
00271899 push 1
0027189B call Test (02713ACh) //由被调用者平栈
Test_Call
int _stdcall Test(int a,int b,int c,int d,int e)
{
00F41760 push ebp
00F41761 mov ebp,esp
00F41763 sub esp,0C0h
00F41769 push ebx
00F4176A push esi
00F4176B push edi
00F4176C mov edi,ebp
00F4176E xor ecx,ecx
00F41770 mov eax,0CCCCCCCCh
00F41775 rep stos dword ptr es:[edi]
00F41777 mov ecx,offset _99047FA2_源@cpp (0F4C029h)
00F4177C call @__CheckForDebuggerJustMyCode@4 (0F4130Ch)
return a + b + c + d + e;
00F41781 mov eax,dword ptr [a]
00F41784 add eax,dword ptr [b]
00F41787 add eax,dword ptr [c]
00F4178A add eax,dword ptr [d]
00F4178D add eax,dword ptr [e]
}
00F41790 pop edi
00F41791 pop esi
00F41792 pop ebx
00F41793 add esp,0C0h
00F41799 cmp ebp,esp
00F4179B call __RTC_CheckEsp (0F41235h)
00F417A0 mov esp,ebp
00F417A2 pop ebp
00F417A3 ret 14h //平栈操作
_stdcall 是从右到左进行压栈,但是平栈操作由被被调用者执行
- _fastcall
008F1891 push 6
008F1893 push 5
008F1895 push 4
008F1897 push 3
008F1899 mov edx,2
008F189E mov ecx,1
008F18A3 call Test (08F13C0h)
_fastcall 比较特殊他会,从左往右压入参数,但是前两个参数会被赋值到寄存器中,然后由被调者自己平栈
int _fastcall Test(int a,int b,int c,int d,int e,int w)
{
008F1760 push ebp
008F1761 mov ebp,esp
008F1763 sub esp,0D8h
008F1769 push ebx
008F176A push esi
008F176B push edi
008F176C push ecx
008F176D lea edi,[ebp-18h]
008F1770 mov ecx,6
008F1775 mov eax,0CCCCCCCCh
008F177A rep stos dword ptr es:[edi]
008F177C pop ecx
008F177D mov dword ptr [b],edx
008F1780 mov dword ptr [a],ecx
008F1783 mov ecx,offset _99047FA2_源@cpp (08FC029h)
008F1788 call @__CheckForDebuggerJustMyCode@4 (08F130Ch)
return a + b + c + d + e;
008F178D mov eax,dword ptr [a]
008F1790 add eax,dword ptr [b]
008F1793 add eax,dword ptr [c]
008F1796 add eax,dword ptr [d]
008F1799 add eax,dword ptr [e]
}
008F179C pop edi
008F179D pop esi
008F179E pop ebx
008F179F add esp,0D8h
008F17A5 cmp ebp,esp
008F17A7 call __RTC_CheckEsp (08F1235h)
008F17AC mov esp,ebp
008F17AE pop ebp
008F17AF ret 10h //平栈。因为我多加了个参数导致和之前长度不一样了。
x64 下的调用约定
调用规则是由编译器决定的现在这是由VS平台下的调用约定,这是一种变形的_fastcall。
Test(1,2,3,4,5);
00007FF631FB187B mov dword ptr [rsp+20h],5
00007FF631FB1883 mov r9d,4
00007FF631FB1889 mov r8d,3
00007FF631FB188F mov edx,2
00007FF631FB1894 mov ecx,1
00007FF631FB1899 call Test (07FF631FB13ACh)
参数从右到左,前四个参数会放到寄存器中分别是,edx,ecx,r8,r9 寄存器。 寄存器的速度是高于栈传参的速度的
int _cdecl Test(int a,int b,int c,int d,int e)
{
00007FF631FB1760 mov dword ptr [rsp+20h],r9d
00007FF631FB1765 mov dword ptr [rsp+18h],r8d
00007FF631FB176A mov dword ptr [rsp+10h],edx
00007FF631FB176E mov dword ptr [rsp+8],ecx
00007FF631FB1772 push rbp
00007FF631FB1773 push rdi
00007FF631FB1774 sub rsp,0E8h
可以看到这个r9,r8,edx,ecx又放入了栈空间,这是为什么?
寄存器中的值放入栈预留空间,是为了防止参数传递过程中,寄存器需要接收其他值而无法传递参数,或者其他值无法接收的情况。
仔细查看会发现x64这个调用约定是没有平栈的操作的是因为
64位下一开始系统会为main()函数开辟一个很大的栈区,但是main()函数并未消耗掉这么大的栈区空间,这时候怎么办呢?子函数就会还继续利用main()函数的栈区空间,所以main()函数并不用对子函数栈区空间进行清理。
...