文章作者:Crack_Qs[4st][PDG]
编译模式:Debug
测试平台:Winodws 7 x64
编译环境:Microsoft Visual Studio Ultimate 2013 (12.0.30723.00) Update 3
关于x64论坛已有其他兄弟分析,我整理下自己的记录。非干货科普文,不喜勿喷。
如果本文中有遗漏部分请参考以下帖子,如果发现错误请反馈给我,万分感谢。
x64传参规则研究
http://bbs.chinapyg.com/thread-74565-1-1.html
x64 传参再分析
http://bbs.chinapyg.com/thread-74910-1-1.html
X64 传参规则的研究
http://bbs.chinapyg.com/thread-75685-1-1.html
//////////////////////////////////////////////////////////////////////////////
编译器会为函数开辟函数自己的栈桢,空函数(无参数、无变量)源码如下:
int fun()
{
return 1;
}
汇编:
000000013F421120 40 57 push rdi //保存环境
000000013F421122 B8 01 00 00 00 mov eax,1 //由此可见x64下返回值依然由RAX寄存器传出
000000013F421127 5F pop rdi //恢复环境
000000013F421128 C3 ret //返回上一层调用
再简单了解过函数框架后,探究下变量的存储方式:
int fun()
{
int nTest1 = 16;
int nTest2 = 16;
int nTest3 = 16;
int nTest4 = 16;
int nTest5 = 16;
return 1;
}
汇编:
000000013F7F33E0 40 57 push rdi //保存环境
000000013F7F33E2 48 83 EC 20 sub rsp,20h //局部变量的栈空间大小(以0x10增长对齐)
000000013F7F33E6 48 8B FC mov rdi,rsp //和x86一样在初始化局部变量的栈空间
000000013F7F33E9 B9 08 00 00 00 mov ecx,8 //stos的计数器,stos每次填充4个字节,此处立即数=栈空间大小/4
000000013F7F33EE B8 CC CC CC CC mov eax,0CCCCCCCCh
000000013F7F33F3 F3 AB rep stos dword ptr [rdi]
000000013F7F33F5 C7 04 24 10 00 00 00 mov dword ptr [rsp],10h //第一个变量
000000013F7F33FC C7 44 24 04 10 00 00 00 mov dword ptr [rsp+0x4],10h //第二个变量
000000013F7F3404 C7 44 24 08 10 00 00 00 mov dword ptr [rsp+0x8],10h //第三个变量
000000013F7F340C C7 44 24 0C 10 00 00 00 mov dword ptr [rsp+0xC],10h //第四个变量
000000013F7F3414 C7 44 24 10 10 00 00 00 mov dword ptr [rsp+0x10],10h //第五个变量
000000013F7F341C B8 01 00 00 00 mov eax,1 //return 1
000000013F7F3421 48 83 C4 20 add rsp,20h
000000013F7F3425 5F pop rdi //恢复环境
000000013F7F3426 C3 ret //返回上一层调用
Ps:因为x64的关系变量为指针时RSP是以0x8字节来递增
x64传参规则:
在x64中,函数的前4个参数传参规则是利用寄存器rcx(xmm0),rdx(xmm1),r8(xmm2),r9(xmm3)来传递参数,除此之外使用堆栈来进行传递,内存中%8对齐、从RSP+0x20开始。
int fun(int nArg1, short hArg2, char cArg3, long lArg4, int nArg5, short hArg6, char cArg7)
{
long nTest1 = 16;
return 1;
}
000000013F9F3451 C6 44 24 30 07 mov byte ptr [rsp+30h],7 //第七个参数由rsp+0x30
000000013F9F3456 66 C7 44 24 28 06 00 mov word ptr [rsp+28h],6 //第六个参数由rsp+0x28
000000013F9F345D C7 44 24 20 05 00 00 00 mov dword ptr [rsp+20h],5 //第五个参数由rsp+0x20
000000013F9F3465 41 B9 04 00 00 00 mov r9d,4 //第四个参数由R9D
000000013F9F346B 41 B0 03 mov r8b,3 //第三个参数由R8B
000000013F9F346E 66 BA 02 00 mov dx,2 //第二个参数由RDX
000000013F9F3472 B9 01 00 00 00 mov ecx,1 //第一个参数由RCX
000000013F9F3477 E8 B1 DB FF FF call 000000013F9F102D //CALL指令会向栈空间中压入下一条指令的虚拟地址用于返回,x64的寻址能力是8个字节,所以不再是esp-0x4,而是RSP-0x8
由此可见,如果被调用的函数参数大于4个,会在调用方的函数栈空间开辟临时位置,内存中%8对齐。
000000013FFA33E0 44 89 4C 24 20 mov dword ptr [rsp+20h],r9d //因为call指令的关系RSP变动,所以此处依然是RSP+0x20
000000013FFA33E5 44 88 44 24 18 mov byte ptr [rsp+18h],r8b
000000013FFA33EA 66 89 54 24 10 mov word ptr [rsp+10h],dx
000000013FFA33EF 89 4C 24 08 mov dword ptr [rsp+8],ecx
000000013FFA33F3 57 push rdi //个人理解(仅供参考):RDI相当于x86中的ebp
000000013FFA33F4 48 83 EC 10 sub rsp,10h
000000013FFA33F8 48 8B FC mov rdi,rsp
000000013FFA33FB B9 04 00 00 00 mov ecx,4
000000013FFA3400 B8 CC CC CC CC mov eax,0CCCCCCCCh
000000013FFA3405 F3 AB rep stos dword ptr [rdi]
000000013FFA3407 8B 4C 24 20 mov ecx,dword ptr [rsp+20h] //默认取第一个参数
000000013FFA340B C7 04 24 10 00 00 00 mov dword ptr [rsp],10h
000000013FFA3412 B8 01 00 00 00 mov eax,1
000000013FFA3417 48 83 C4 10 add rsp,10h //平栈
000000013FFA341B 5F pop rdi //恢复环境
000000013FFA341C C3 ret //通过栈空间的ret rip返回上层调用
至于为什么是从RSP+0x20开始,函数的前4个参数在函数内部需要打入栈中,而4 * 8 byte就是0x20
栈空间布局:
0x000000000020FDD0 10 00 00 00 cc cc cc cc ....???? <- 局部变量空间
0x000000000020FDD8 cc cc cc cc cc cc cc cc ????????
0x000000000020FDE0 30 fe 20 00 00 00 00 00 0? ..... <- push edi
0x000000000020FDE8 7c 34 fa 3f 01 00 00 00 |4??.... <- ret rip
0x000000000020FDF0 01 00 00 00 cc cc cc cc ....???? <- 参数列表头部
0x000000000020FDF8 02 00 cc cc cc cc cc cc ..?????? <-
0x000000000020FE00 03 cc cc cc cc cc cc cc .??????? <-
0x000000000020FE08 04 00 00 00 cc cc cc cc ....???? <-
0x000000000020FE10 05 00 00 00 cc cc cc cc ....???? <-
0x000000000020FE18 06 00 cc cc cc cc cc cc ..?????? <-
0x000000000020FE20 07 cc cc cc cc cc cc cc .??????? <- 参数列表尾部