WIndows 64 栈帧结构

小标题

1.x64下反汇编

2.调试64位4参数中寄存器的值

3.调试64位中5参数及多次调用函数栈时栈区结构

4.64位中少于4参数情况

1.x64下反汇编

64位下函数的调用约定全部用FASTCALL,就是前4个参数依次用rcx,rdx,r8,r9传递,多余的参数从右至左压参。

x86下栈帧是比较清楚的,但是x64位下栈帧的资料好少。然后调了下熟悉栈帧的结构。

int Add(int a,int b,int c,int d);

int _tmain(int argc, _TCHAR* argv[])
{
	int a = 0;
	Add(1,2,3,4);
	return 0;
}

int Add(int a,int b,int c,int d)
{
	int xx = a+b+c+d;
	int yy = a+b-c-d;
	int zz = -a-b+c+d;
	return xx;
}

  使用Vs2010 ,64位下调试,打开寄存器窗口,Alt+8 反汇编

可以看到首先将1,2,3,4放在寄存器中,然后调用call指令,call指令可以分解为将下一条指令压参,然后jmp到函数地址

在执行push指令的时候,RSP-8

000000013F931049  mov         r9d,4
000000013F93104F  mov         r8d,3
000000013F931055  mov         edx,2
000000013F93105A  mov         ecx,1
000000013F93105F  call        Add (13F931005h)   //指令为 push rip    ;RSP-8
                                                 //       jmp Add

  Add函数的反汇编代码如下:

可以看到前4句将寄存器中传递的参数赋值给rsp+8h,rsp+10h,rsp+18h,rsp+20h

如果多余4个参数,其余的依次放入rsp+28h 之后

然后保存前栈帧栈底,开辟栈区保存局部变量,由于是三个变量12字节,对齐内存是16字节,sub rsp,10h

初始化栈区,rep stos指令

int Add(int a,int b,int c,int d)
{
000000013F251080  mov         dword ptr [rsp+20h],r9d
000000013F251085  mov         dword ptr [rsp+18h],r8d
000000013F25108A  mov         dword ptr [rsp+10h],edx
000000013F25108E  mov         dword ptr [rsp+8],ecx
000000013F251092  push        rdi                //保存前栈底   RSP-8
000000013F251093  sub         rsp,10h            //开辟栈区 16字节 RSP-10h
000000013F251097  mov         rdi,rsp            //新栈帧栈底rdi=rsp
000000013F25109A  mov         ecx,4              //循环次数
000000013F25109F  mov         eax,0CCCCCCCCh
000000013F2510A4  rep stos    dword ptr [rdi]    //将rdi开始赋值eax中的值,循环4次
000000013F2510A6  mov         ecx,dword ptr [rsp+20h]  //此处是第一个参数a
	int xx = a+b+c+d;
000000013F2510AA  mov         eax,dword ptr [b]
000000013F2510AE  mov         ecx,dword ptr [a]
000000013F2510B2  add         ecx,eax
000000013F2510B4  mov         eax,ecx
000000013F2510B6  add         eax,dword ptr [c]
000000013F2510BA  add         eax,dword ptr [d]
000000013F2510BE  mov         dword ptr [rsp],eax  //rsp 保存 xx
	int yy = a+b-c-d;
000000013F2510C1  mov         eax,dword ptr [b]
000000013F2510C5  mov         ecx,dword ptr [a]
000000013F2510C9  add         ecx,eax
000000013F2510CB  mov         eax,ecx
000000013F2510CD  sub         eax,dword ptr [c]
000000013F2510D1  sub         eax,dword ptr [d]
000000013F2510D5  mov         dword ptr [yy],eax   //rsp+4 保存yy
	int zz = -a-b+c+d;
000000013F2510D9  mov         eax,dword ptr [a]
000000013F2510DD  neg         eax
000000013F2510DF  sub         eax,dword ptr [b]
000000013F2510E3  add         eax,dword ptr [c]
000000013F2510E7  add         eax,dword ptr [d]
000000013F2510EB  mov         dword ptr [zz],eax  //rsp+8 保存
	return xx;
000000013F2510EF  mov         eax,dword ptr [rsp]  //将返回值保存在eax寄存器中
}
000000013F2510F2  add         rsp,10h    //恢复开辟的栈区
000000013F2510F6  pop         rdi        //恢复前栈帧的栈底
000000013F2510F7  ret                    //pop rip  将之前保存的call下一条指令弹出给rip , 继续执行
                                         //RSP - 8  等于调用call之前的值

 

2.调试64位4参数中寄存器的值

下面是调试中的一些寄存器的值

此时的RSP和RDI都是_tmain函数的栈顶和栈底,alt+8反汇编

此时RSP经过 call 中的push rip 减去8,push edi 减去8,sub rsp,10h 一共减去20h

rdi赋值为rsp,为当前Add的栈底

rdi经过rep stos 指令将eax中值初始化到rdi中,共4*4字节,rdi初始化之后加10h

此时我们看内存中的情况如上图

栈帧情况如下

3.调试64位中5参数及多次调用函数栈时栈区结构

我们在试试5参数的函数调用情况,同时我们知道函数会把4个寄存器中的值赋值到栈上面的区域,要开辟4*8=20h的区域,在调试的时候没有发现对于rsp的操作

于是猜测是在上一个函数中已经开辟好了额外的空间存储参数的数据。

int _tmain(int argc, _TCHAR* argv[])
{
	int a = 0;
	Sub(1,2,3,4,5);
	return 0;
}

int Add(int a,int b,int c,int d)
{
	int xx = a+b+c+d;
	int yy = a+b-c-d;
	int zz = -a-b+c+d;
	return xx;
}

int Sub(int a,int b,int c,int d,int e)
{
	int xx = a+b+e+d;
	int yy = a+b-c-d;
	int zz = -a-b+c+d;
	Add(b,c,d,e);
	return xx;
}

  

我们使用Sub()5个参数的函数,同时在Sub函数中调用Add函数

	Sub(1,2,3,4,5);
000000013F4F2EF9  mov         dword ptr [rsp+20h],5   //当前rsp + 20 就是存储4个参数之后的位置
000000013F4F2F01  mov         r9d,4
000000013F4F2F07  mov         r8d,3
000000013F4F2F0D  mov         edx,2
000000013F4F2F12  mov         ecx,1
000000013F4F2F17  call        Sub (13F4F100Fh)  

int Sub(int a,int b,int c,int d,int e)
{
000000013FC32FB0  mov         dword ptr [rsp+20h],r9d  //当前的rsp 比之前操作的 -8 ,所以这个位置是 第五个参数的前面一个
000000013FC32FB5  mov         dword ptr [rsp+18h],r8d
000000013FC32FBA  mov         dword ptr [rsp+10h],edx
000000013FC32FBE  mov         dword ptr [rsp+8],ecx
000000013FC32FC2  push        rdi
000000013FC32FC3  sub         rsp,30h     //这个地方是调用Add函数  额外分配的20h    存储4个参数
000000013FC32FC7  mov         rdi,rsp
000000013FC32FCA  mov         ecx,0Ch
000000013FC32FCF  mov         eax,0CCCCCCCCh
000000013FC32FD4  rep stos    dword ptr [rdi]
000000013FC32FD6  mov         ecx,dword ptr [rsp+40h]

  

我在函数中调用了上面的Add()函数,结果rsp - 30 开辟栈空间,这是为了调用Add的时候 多开的4*8 保存4个寄存器中的值在栈中。

其中局部变量保存在 rsp + 20h 的地方,依次保存xx,yy,zz和没用到的4字节

在Add函数的栈区,我们果然看到了Sub开辟栈区的20h地址空间,存储了传递给寄存器的参数

4.64位中少于4参数情况

还有就是传递少于4个参数的情况,也会开辟  4*8的区域

没有传递参数也会开辟   4*8区域。

时间: 2024-11-09 03:36:07

WIndows 64 栈帧结构的相关文章

运行时栈帧结构

栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈(Virtual Machine Stack)的栈元素.栈帧存储了方法的局部变量表.操作数栈.动态连接和方法返回地址等信息.每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程.每一个栈帧都包括了局部变量表.操作数栈.动态连接.方法返回地址和一些额外的附加信息.在编译程序代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写入

通过反汇编C代码分析计算机函数调用机制和栈帧结构

备注:秋风 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 C程序代码(简单的函数调用和返回): 将C源码编译成汇编代码: 有效的汇编代码内容: 通过仔细分析汇编代码的工作过程中堆栈的变化情况,可以描绘出函数调用过程中使用的栈帧结构如下: 总结: 计算机在工作时使用程序栈来支持函数或过程调用,机器用栈传递参数.存储返回信息.保存寄存器值用于以后恢复,和存储本地变量.栈向低地址方向

C语言函数调用及栈帧结构

source:http://blog.csdn.net/qq_29403077/article/details/53205010 一.地址空间与物理内存 (1)地址空间与物理内存是两个完全不同的概念,真正的代码及数据都存在物理内存中. 物理储存器是指实际存在的具体储存器芯片,CPU在操纵物理储存器的时候都把他们当做内存来对待,把他们看成由若干个储存单元组成的逻辑储存器,这个逻辑储存器就是我们所说的地址空间. 地址空间大小与逻辑储存器大小不一定相等. (2)进程的地址空间分布 进程的地址空间包括:

Jvm(59),虚拟机字节码执行引擎----运行时栈帧结构

后面讲的所有的东西就是对前面所总览的虚拟机栈的进一步理解. 栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈(Virtual Machine Stack)[1]的栈元素.栈帧存储了方法的局部变量表.操作数栈.动态连接和方法返回地址等信息.每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程. 每一个栈帧都包括了局部变量表.操作数栈.动态连接.方法返回地址和一些额外的附加信息.在编译程序代码的时候,

深入理解Java虚拟机笔记---运行时栈帧结构

栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区的虚拟机栈(Virtual Machine Stack)的栈元素.栈帧存储了方法的局部变量表,操作数栈,动态连接和方法返回地址等信息.第一个方法从调用开始到执行完成,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程. 每一个栈帧都包括了局部变量表,操作数栈,动态连接,方法返回地址和一些额外的附加信息.在编译代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写入到了方法表的

图解系列之方法执行与栈帧结构

原文地址:http://blog.51cto.com/4837471/2324570

c函数调用过程原理及函数栈帧分析

转载自地址:http://blog.csdn.net/zsy2020314/article/details/9429707       今天突然想分析一下函数在相互调用过程中栈帧的变化,还是想尽量以比较清晰的思路把这一过程描述出来,关于c函数调用原理的理解是很重要的. 1.关于栈 首先必须明确一点也是非常重要的一点,栈是向下生长的,所谓向下生长是指从内存高地址->低地址的路径延伸,那么就很明显了,栈有栈底和栈顶,那么栈顶的地址要比栈底低.对x86体系的CPU而言,其中 ---> 寄存器ebp(

函数调用过程栈帧变化详解

http://www.cnblogs.com/fxplove/articles/2574451.html 数调用另一个词语表示叫作 过程.一个过程调用包括将数据和控制从代码的一部分传递到另一部分.另外,它还必须在进入时为过程的局部变量分配空间,并在推出时释放这些空间.而数据传递,局部变量的分配和释放通过操纵程序栈来实现.在了解本文章之前,您需要先对程序的进程空间有所了解,即对进程如何使用内存?如果你知道这些,下面的内容将是很easy的事情了.为了您的回顾还是将简单的分布图贴出来,便于您的回顾.

Linux - 函数的栈帧

栈帧(stack frame),机器用栈来传递过程参数,存储返回信息,保存寄存器用于以后恢复,以及本地存储.为单个过程(函数调用)分配的那部分栈称为栈帧.栈帧其实是两个指针寄存器, 寄存器%ebp为帧指针,而寄存器%esp为栈指针,当程序运行时,栈指针可以移动(大多数的信息的访问都是通过帧指针的).总之简单一句话,栈帧的主要作用是用来控制和保存一个过程的 所有信息的.栈帧结构如下所示: 下面,我们通过一个简单的程序来了解栈帧: 简单的函数分析,如下图: 该函数的栈帧情况: 当*p=bug,修改栈