初探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  .???????       <- 参数列表尾部

时间: 2024-10-17 11:02:45

初探x64参数变量及栈空间布局的相关文章

什么变量在堆内存里存放,什么变量在栈内存里存放

什么变量在堆内存里存放,什么变量在栈内存里存放 堆和栈的区别 (stack and heap) 一般认为在c中分为这几个存储区 1栈 - 有编译器自动分配释放 2堆 - 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收 3全局区(静态区),全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域. 程序结束释放. 4另外还有一个专门放常量的地方. - 程序结束释放 在函数体中定义的变量通常是在栈上,用mal

《转载》什么变量在堆内存里存放,什么变量在栈内存里存放

堆和栈的区别 (stack and heap) 一般认为在c中分为这几个存储区 1栈 - 有编译器自动分配释放 2堆 - 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收 3全局区(静态区),全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域. 程序结束释放. 4另外还有一个专门放常量的地方. - 程序结束释放 在函数体中定义的变量通常是在栈上,用malloc, calloc, realloc等分配内

Shell 环境变量 &amp; 参数变量

环境变量 $HOME 当前用户的家目录 $PATH 以冒号分隔的用来搜索命令的目录列表 $PS1 命令提示符,通常是$字符,但在bash中,可以使用一些更复杂的值.例如,字符串[\[email protected]\h \w]$就是一个流行的默认值,它给出用户名.机器名和当前目录名,当然也包括一个$提示符. $SP2 二级提示符,用来提示后续的输入,通常是>字符 $IFS 输入域提示符.当shell读取输入时,它给出用来分隔单词的一组字符,它们通常时空格.制表符和换行符 $0 shell脚本的名

函数参数的压栈顺序

(Owed by: 春夜喜雨 http://blog.csdn.net/chunyexiyu 转载请标明来源) 先来看一道面试题: 设int arr[]={1,2,3,4 }; int *ptr=arr; printf("%d,%d",*ptr,*(++ptr)); 面试题的答案是: 2, 2 这个面试题为什么会这样呢? 原因就跟函数的压栈顺序有关,先压栈哪一个,哪一个就会被先计算,后压栈哪一个,那一个就会被后计算. 对于常见的C++程序,像缺省_cdecl或使用_stdcall的函数压栈顺序都

创建参数变量

先创建一个参数类package TestHelloWorld; public class Constant { public static String name="张申123123"; public static String pwd="jd_zhangshen"; public static String jd_login_title="京东-欢迎登录"; }再在主程序中引用参数变量if (window.getTitle().equals(C

?Swift语言中为外部参数设置默认值可变参数常量参数变量参数输入输出参数

Swift语言中为外部参数设置默认值可变参数常量参数变量参数输入输出参数 7.4.4  为外部参数设置默认值 开发者也可以对外部参数设置默认值.这时,调用的时候,也可以省略参数传递本文选自Swift1.2语言快速入门v2.0. [示例7-11]以下的代码就为外部参数toString.withJoiner设置了默认的参数"Swift"和"---".代码如下: import Foundation func join(string s1: String, toString

shell脚本程序中的部分常用环境变量和参数变量的说明以及简单shell脚本示例

环境变量 $HOME 当前用户的家目录 $PATH 以冒号分隔的用来搜索命令的目录列表 $PS1 命令提示符,通常是$字符,但在bash中,可以使用一些更复杂的值.例如,字符串[\[email protected]\h\w]$就是一个流行的默认值,它给出用户名/机器名和当前的目录名,当然也包括一个$提示符. $PS2 二级提示符,用来表示后续的输入,通常是 > 字符. $IFS 输入域分隔符.当shell读取输入时,它给出用来分隔单词的一组字符,他们通常是空格,制表符和换行符. $0 shell

ASP.NET Core中使用GraphQL - 第五章 字段, 参数, 变量

ASP.NET Core中使用GraphQL ASP.NET Core中使用GraphQL - 第一章 Hello World ASP.NET Core中使用GraphQL - 第二章 中间件 ASP.NET Core中使用GraphQL - 第三章 依赖注入 ASP.NET Core中使用GraphQL - 第四章 GrahpiQL 字段# 我们已经很好的理解了GraphQL中的字段.在之前HelloWorldQuery的例子中,我们添加了2个字段hello和howdy. 它们都是标量字段.正

C 提高1 内存四区 变量本质 栈开口方向 指针铁律1

C 提高第一天复习 内存四区,变量常量的本质,函数调用模型,栈开口方向,指针铁律1,指针是一种数据类型 C 提高学员标准:写一个标准的冒泡排序 选择法或者冒泡法排序 在一个函数内排序 通过函数调用的方式排序 数组做函数参数的技术盲点和推演 #include <stdio.h> #include <stdlib.h> #include <string.h> int main() { int i = 0; int j = 0; int tmp = 0; int a[] =