调用约定

知识来源

允许使用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()函数并不用对子函数栈区空间进行清理。