首页 > 代码库 > 初探x64参数变量及栈空间布局

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

文章作者: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  .???????       <- 参数列表尾部

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