windows32位程序包括stdcall,thiscall,fastcall,cdecl,clrcall,vectorcall,nakedcall等调用方式,x64位程序默认使用新的fastcall调用方式。
这种调用方式得益于x64平台寄存器数量的增加。
x64 fastcall调用约定
空间大于8字节的参数用参照传递,不能把一个参数分割到多个寄存器中进行传递;前四个整型或指针类型参数从左到右由RCX,RDX,R8,R9依次传递,前四个浮点类型参数从左到右由XMM0,XMM1,XMM2,XMM3依次传递。第五个和其后的参数通过栈传递,参数从右到左一次入栈。
__m128 类型, 数组和字符串不能通过值传递,只能通过指针。
结构体和union如果大小是1,2,4,8字节,用整数的传参方式;否则通过指针传递。
小于等于64位的整型或指针类型返回值由RAX传递。
浮点返回值由XMM0传递。
更大的返回值(比如结构体),由调用方在栈上分配空间,并由RCX持有该空间的指针并传递给被调用函数,因
此整型参数使用的寄存器依次右移一格,实际只可以利用3个寄存器,其余参数入栈。函数调用结束后,RAX返
回该空间的指针(就是rcx的值)。
Varargs(可变参数)也通过上述方式进行传参。
调用函数为前四个参数在调用栈上保留相应的空间,称作shadow space或spill slot。即使被调用方没有或小
于4个参数,调用函数仍然保留那么多的栈空间,这有助于在某些特殊情况下简化调用约定。
由调用函数负责清理调用栈。
除RCX,RDX,R8,R9以外,RAX、R10、R11、XMM4 和 XMM5也是易变化的(volatile)寄存器。
RBX, RBP, RDI, RSI, R12, R14, R14, and R15寄存器则必须在使用时进行保护。
在寄存器中,所有参数都是右对齐的。小于64位的参数并不进行高位零扩展,也就是高位是无法预测的垃圾数据.
参数传递示例
1 int func1(int a, int b, int c, int d, int e, double f) { 2 return 9; 3 } 4 5 int func2(int a, int b, int c, int d, int e, double f,int g) { 6 return 90; 7 } 8 9 int main() { 10 int r = func1(1, 2, 3, 4, 5, 6.6); 11 int r2 = func2(1, 2, 3, 4, 5, 6.6,7); 12 return 0; 13 }
反汇编:
1 int r2 = func2(1, 2, 3, 4, 5, 6.6,7); 2 00007FF65F263D0E mov dword ptr [rsp+30h],7 3 00007FF65F263D16 movsd xmm0,mmword ptr [__real@401a666666666666 (07FF65F269BB0h)] 4 00007FF65F263D1E movsd mmword ptr [rsp+28h],xmm0 5 00007FF65F263D24 mov dword ptr [rsp+20h],5 6 00007FF65F263D2C mov r9d,4 7 00007FF65F263D32 mov r8d,3 8 00007FF65F263D38 mov edx,2 9 00007FF65F263D3D mov ecx,1 10 00007FF65F263D42 call func2 (07FF65F261375h) 11 00007FF65F263D47 mov dword ptr [r2],eax 12 return 0; 13 00007FF65F263D4A xor eax,eax
返回结构体示例:
1 S1 func3(int a, int b, int c, int d) { 2 S1 s{6,7,8,9,11,12}; 3 return s; 4 } 5 int main() { 6 int r = func1(1, 2, 3, 4, 5, 6.6); 7 int r2 = func2(1, 2, 3, 4, 5, 6.6,7); 8 S1 r3 = func3(1, 2, 3, 4); 9 return 0; 10 }
反汇编
1 S1 func3(int a, int b, int c, int d) { 2 00007FF68A271750 mov dword ptr [rsp+20h],r9d 3 00007FF68A271755 mov dword ptr [rsp+18h],r8d 4 00007FF68A27175A mov dword ptr [rsp+10h],edx 5 00007FF68A27175E mov qword ptr [rsp+8],rcx //这里rcx存放着调用函数提前分配好的用于存放返回的结构体的指针,由于rcx用来存放返回值地址,第一个参数改由edx传递,后面的参数一次后延。 6 00007FF68A271763 push rbp 7 00007FF68A271764 push rsi 8 00007FF68A271765 push rdi 9 00007FF68A271766 sub rsp,110h 10 00007FF68A27176D lea rbp,[rsp+20h] 11 00007FF68A271772 mov rdi,rsp 12 00007FF68A271775 mov ecx,44h 13 00007FF68A27177A mov eax,0CCCCCCCCh 14 00007FF68A27177F rep stos dword ptr [rdi] 15 00007FF68A271781 mov rcx,qword ptr [rsp+138h] 16 00007FF68A271789 lea rcx,[__AD28E9E4_源@cpp (07FF68A280000h)] 17 00007FF68A271790 call __CheckForDebuggerJustMyCode (07FF68A271082h) 18 S1 s{6,7,8,9,11,12}; 19 00007FF68A271795 mov dword ptr [s],6 20 00007FF68A27179C mov dword ptr [rbp+0Ch],7 21 00007FF68A2717A3 mov dword ptr [rbp+10h],8 22 00007FF68A2717AA mov dword ptr [rbp+14h],9 23 00007FF68A2717B1 mov dword ptr [rbp+18h],0Bh 24 00007FF68A2717B8 mov dword ptr [rbp+1Ch],0Ch 25 return s; 26 00007FF68A2717BF lea rax,[s] 27 00007FF68A2717C3 mov rdi,qword ptr [rbp+110h] //rbp+110h里存放的是从rcx里传进来的调用函数分配的用户存放返回值的指针 28 00007FF68A2717CA mov rsi,rax 29 00007FF68A2717CD mov ecx,18h 30 00007FF68A2717D2 rep movs byte ptr [rdi],byte ptr [rsi] //上面几行是将s结构体的数据复制到调用函数分配好的空间里(rcx传进来的) 31 00007FF68A2717D4 mov rax,qword ptr [rbp+110h] //将返回值指针赋值给rax 32 }