飘云阁

 找回密码
 加入我们

QQ登录

只需一步,快速开始

查看: 6125|回复: 2

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

[复制链接]
  • TA的每日心情
    无聊
    2016-10-10 10:27
  • 签到天数: 26 天

    [LV.4]偶尔看看III

    发表于 2015-1-7 12:33:29 | 显示全部楼层 |阅读模式
    本帖最后由 Crack_Qs 于 2015-1-7 12:36 编辑

    文章作者:Crack_Qs[4st][PDG]
    编译模式: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
    //////////////////////////////////////////////////////////////////////////////
    编译器会为函数开辟函数自己的栈桢,空函数(无参数、无变量)源码如下:
    1. int fun()
    2. {
    3.       return 1;
    4. }
    复制代码

    汇编:
    000000013F421120   40 57                    push        rdi       //保存环境
    000000013F421122   B8 01 00 00 00     mov         eax,1     //由此可见x64下返回值依然由RAX寄存器传出
    000000013F421127   5F                         pop         rdi       //恢复环境
    000000013F421128   C3                         ret                   //返回上一层调用

    再简单了解过函数框架后,探究下变量的存储方式:
    1. int fun()
    2. {
    3.     int nTest1 = 16;
    4.     int nTest2 = 16;
    5.     int nTest3 = 16;
    6.     int nTest4 = 16;
    7.     int nTest5 = 16;
    8.     return 1;
    9. }
    复制代码
    汇编:
    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  .???????       <- 参数列表尾部

    文档版:
    初探x64参数变量及栈空间布局.rar (2.42 KB, 下载次数: 5, 售价: 1 枚飘云币)

    PYG19周年生日快乐!
  • TA的每日心情
    奋斗
    2016-1-13 12:25
  • 签到天数: 3 天

    [LV.2]偶尔看看I

    发表于 2015-1-7 12:57:31 | 显示全部楼层
    {:soso_e113:}给力的开始

    点评

    紧跟Q神步伐  详情 回复 发表于 2015-1-7 13:19
    PYG19周年生日快乐!
  • TA的每日心情
    无聊
    2016-10-10 10:27
  • 签到天数: 26 天

    [LV.4]偶尔看看III

     楼主| 发表于 2015-1-7 13:19:21 | 显示全部楼层

    紧跟Q神步伐
    PYG19周年生日快乐!
    您需要登录后才可以回帖 登录 | 加入我们

    本版积分规则

    快速回复 返回顶部 返回列表