Crack_Qs 发表于 2015-1-7 12:33:29

初探x64参数变量及栈空间布局

本帖最后由 Crack_Qs 于 2015-1-7 12:36 编辑

文章作者:Crack_Qs
编译模式:Debug
测试平台:Winodws 7 x64
编译环境:Microsoft Visual Studio Ultimate 2013 (12.0.30723.00) Update 3

关于x64论坛已有其他兄弟分析,我整理下自己的记录。非干货科普文,不喜勿喷。

如果本文中有遗漏部分请参考以下帖子,如果发现错误请反馈给我,万分感谢。
x64传参规则研究
https://www.chinapyg.com/thread-74565-1-1.html
x64 传参再分析
https://www.chinapyg.com/thread-74910-1-1.html
X64 传参规则的研究
https://www.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 000000013F7F33F5   C7 04 24 10 00 00 00            mov         dword ptr ,10h                        //第一个变量
000000013F7F33FC   C7 44 24 04 10 00 00 00         mov         dword ptr ,10h               //第二个变量
000000013F7F3404   C7 44 24 08 10 00 00 00         mov         dword ptr ,10h               //第三个变量
000000013F7F340C   C7 44 24 0C 10 00 00 00         mov         dword ptr ,10h               //第四个变量
000000013F7F3414   C7 44 24 10 10 00 00 00         mov         dword ptr ,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 ,7                                     //第七个参数由rsp+0x30
000000013F9F3456   66 C7 44 24 28 06 00                mov         word ptr ,6                                    //第六个参数由rsp+0x28
000000013F9F345D   C7 44 24 20 05 00 00 00          mov         dword ptr ,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 ,r9d            //因为call指令的关系RSP变动,所以此处依然是RSP+0x20000000013FFA33E5   44 88 44 24 18                                 mov         byte ptr ,r8b            
000000013FFA33EA   66 89 54 24 10                                 mov         word ptr ,dx
000000013FFA33EF   89 4C 24 08                                    mov         dword ptr ,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
000000013FFA3407   8B 4C 24 20                                    mov         ecx,dword ptr             //默认取第一个参数
000000013FFA340B   C7 04 24 10 00 00 00                     mov         dword ptr ,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

栈空间布局:
0x000000000020FDD010 00 00 00 cc cc cc cc....????       <- 局部变量空间
0x000000000020FDD8cc cc cc cc cc cc cc cc????????
0x000000000020FDE030 fe 20 00 00 00 00 000? .....       <- push edi
0x000000000020FDE87c 34 fa 3f 01 00 00 00|4??....       <- ret rip
0x000000000020FDF001 00 00 00 cc cc cc cc....????       <- 参数列表头部
0x000000000020FDF802 00 cc cc cc cc cc cc..??????       <-
0x000000000020FE0003 cc cc cc cc cc cc cc.???????       <-
0x000000000020FE0804 00 00 00 cc cc cc cc....????       <-
0x000000000020FE1005 00 00 00 cc cc cc cc....????       <-
0x000000000020FE1806 00 cc cc cc cc cc cc..??????       <-
0x000000000020FE2007 cc cc cc cc cc cc cc.???????       <- 参数列表尾部

文档版:


small-q 发表于 2015-1-7 12:57:31

{:soso_e113:}给力的开始

Crack_Qs 发表于 2015-1-7 13:19:21

small-q 发表于 2015-1-7 12:57
给力的开始

紧跟Q神步伐
页: [1]
查看完整版本: 初探x64参数变量及栈空间布局