在给出的关于虚拟内存的陈述中,简述虚拟内存

  在给出的关于虚拟内存的陈述中,简述虚拟内存

  本文在一个简单的汇编中理解了目录虚存的布局,进一步理解了堆栈调用和ret其他变量引用。

  虚拟内存布局关于虚拟内存中进程的布局,一个经典的解释图是:

  在一个完整的汇编程序中,首先要注意的其实是图中的堆栈部分,这是一个地址向低位增长的堆栈。

  了解一个简单的汇编如果你想分析汇编程序,一个非常有用的网站是https://godbolt.org/,它可以很容易地把程序翻译成汇编。

  网站上提供的例子有:

  //在此处键入您的代码,或者加载一个示例。

  整数平方(整数){

  返回num * num

  } x86-64 gcc 11.2编译为

  square(int):

  推送rbp

  mov rbp,rsp

  mov DWORD PTR [rbp-4],电子数据交换

  移动eax,DWORD PTR [rbp-4]

  imul eax,eax

  pop rbp

  浸水使柔软

  背景知识:

  Push代表将操作数推入堆栈(指内存中的堆栈)MOV目的地,源。mov指令的效果相当于C /Java中的赋值语句(从右值到左值)destination=sourceImul代表有符号整数乘法,有符号整数乘法pop对应push。

  仅仅知道这些指令是什么是不够的。字符rbp、rsp和DWORD PTR都有自己固定的含义。只有了解它们,我们才能理解大会在做什么。

  为了比较,我们先来看另一个简单的程序,把它翻译成编译。

  #包含stdio.h

  int main(void)

  {

  int a;

  a=972

  printf(a=%d\n ,a);

  return(0);

  }对应的程序集是:

  00000000040052d main:

  40052d: 55推rbp

  40052e: 48 89 e5 mov rbp,rsp

  400531: 48 83 ec 10 sub rsp,0x10

  400535:C7 45 fc cc 03 00 00 mov DWORD PTR[RBP-0x 4],0x3cc

  40053c: 8b 45 fc mov eax,DWORD PTR [rbp-0x4]

  40053f: 89 c6 mov esi,eax

  400541: bf e4 05 40 00 mov edi,0x4005e4

  400546: b8 00 00 00 00 mov eax,0x0

  40054b: e8 c0 fe ff ff呼叫400410 printf@plt

  400550: b8 00 00 00 00 mov eax,0x0

  400555: c9离开

  400556: c3 ret

  400557: 66 0f 1f 84 00 00 00 nop字PTR [rax rax*10x0]

  40055e: 00 00对比这两个汇编,我们发现有些东西是不变的,这就告诉我们这些东西很重要,需要理解。

  看前面几个字。

  00000000040052d main:

  40052d: 55推rbp

  40052e: 48 89 e5 mov rbp,rsp

  400531: 48 83EC10SUrsp,0x10函数第一行main指rbp和rbp这些是专用寄存器。Rbp是基址指针,指向当前堆栈帧的基址点,rsp是堆栈指针,指向当前堆栈帧的顶部。

  寄存器基址指针.它的作用是校准一个基址,在操作过程中它的值变化很小。

  rsp:寄存器堆栈指针.它的作用是校准栈顶,它的值会不断变化。因为堆栈在虚拟内存中的地址是向下增长的,所以堆栈中存储的值看起来会越来越小。

  虚拟内存中堆栈部分的初始状态:

  图中的栈上以前的值:也就是本文开头图中的命令行参数和env var。注意,这些东西实际上是栈的内容,而不是栈外的内容,所以叫做‘前值’

  push rbp指令将寄存器的值rbp压入堆栈。因为它被“推”到堆栈上,所以当前值rsp是新堆栈顶部的内存地址。和堆栈寄存器如上图所示。

  Mov rbp,rsp将堆栈指针rsp的值复制到基址指针rbp。Rsp现在指向堆栈的顶部。

  SURSP,0x10创建一个空间来存储局部变量的值。rbp和rsp之间的空间就是这个空间。请注意,这个空间足够存储我们的类型变量integer。

  sub:减去。SURSP,0x10相当于C \Java中的RSP=RSP-16;

  或者因为虚拟内存中的堆栈增长到低位地址位,所以堆栈的顶部滑动到低位地址位。

  我们只是在内存中——,在堆栈中3354,为我们的局部变量创建一个空间。这个空间叫做栈帧。每个带有局部变量的函数都将使用堆栈帧来存储这些变量。

  我们的函数汇编代码的第四行如下:

  400535:C7 45 fc cc 03 00 00 mov DWORD PTR[RBP-0x 4],0x3cc

  字是16位,DWORD是双字,32位。这正好是现代c中有符号int的长度,PTR是指针,代表地址。

  如前所述,mov相当于C \Java中的赋值,所以这里是一个赋值操作

  这一行对应于我们的C代码行:

  a=972Movordptr [RBP-0x4],0x3cc将地址rbp-4的存储器设置为972。[rbp-4]是我们的局部变量a,计算机实际上并不知道我们在代码中使用的变量的名字,它只是引用了堆栈上的内存地址。

  这是此操作后堆栈和寄存器的状态:

  现在当我们看函数的结尾时,我们会发现:

  400555: c9 leave该指令leave分为两步:将rsp设置为rbp,然后将栈顶弹出到rbp。

  因为我们rbp在进入函数时把前一个值压入堆栈,所以现在rbp被设置为前一个值rbp。

  在我们离开当前函数之前,局部变量被“释放”,前一个函数的堆栈帧被恢复。并且堆栈寄存器rbp的rsp状态被恢复到与我们进入主函数时相同的状态。

  对堆栈有更深入的了解当变量自动从堆栈中释放出来时,它们并没有被完全“销毁”。它们的值仍然在内存中,可能会被其他函数使用。

  这就是为什么写代码的时候初始化变量很重要,就像有效C中的04条款说的:使用对象之前要确保对象已经初始化。否则,当程序运行时,它们将获得堆栈上的任何值。

  考虑以下代码:

  #包含stdio.h

  无效函数1(无效)

  {

  int a;

  int b;

  int c;

  a=98

  b=972

  c=a b;

  printf(a=%d,b=%d,c=%d\n ,a,b,c);

  }

  无效函数2(无效)

  {

  int a;

  int b;

  int c;

  printf(a=%d,b=%d,c=%d\n ,a,b,c);

  }

  int main(void)

  {

  func 1();

  func 2();

  return(0);

  }输出

  a=98,b=972,c=1070

  A=98,b=972,c=1070同一个变量值func1!这是因为堆栈的工作方式。这两个函数以相同的顺序声明相同数量和类型的变量。它们的堆栈帧是相同的。func1结束时,其局部变量值所在的内存不会被清除——只有rsp会增加。

  所以当我们调用func2它的栈帧时,它和前面的栈帧func1有着完全相同的位置,局部变量的func2值和我们离开时的func1是一样的。

  注意:一个函数对应一个堆栈帧。

  对应的编译是:

  00000000040052d func1:

  40052d: 55推rbp

  40052e: 48 89 e5 mov rbp,rsp

  400531: 48 83 ec 10 sub rsp,0x10

  400535:C7 45 F4 62 00 00 00 mov DWORD PTR[RBP-0xc],0x62

  40053c: c7 45 f8 cc 03 00 00 mov双字PTR [rbp-0x8],0x3cc

  400543: 8b 45 f8 mov eax,双字PTR [rbp-0x8]

  400546: 8b 55 f4 mov edx,双字PTR [rbp-0xc]

  400549: 01 d0添加eax,edx

  40054 b:89 45 fc mov DWORD PTR[RBP-0x 4],eax

  40054e: 8b 4d fc mov ecx,DWORD PTR [rbp-0x4]

  400551: 8b 55 f8 mov edx,双字PTR [rbp-0x8]

  400554: 8b 45 f4 mov eax,DWORD PTR [rbp-0xc]

  400557: 89 c6 mov esi,eax

  400559: bf 34 06 40 00 mov edi,0x400634

  40055e: b8 00 00 00 00 mov eax,0x0

  400563: e8 a8 fe ff ff电话400410 printf@plt

  400568: c9离开

  400569: c3 ret

  00000000040056a func2:

  40056a: 55推rbp

  40056b: 48 89 e5 mov rbp,rsp

  40056e: 48 83 ec 10 sub rsp,0x10

  400572: 8b 4d fc mov ecx,DWORD PTR [rbp-0x4]

  400575: 8b 55 f8 mov edx,双字PTR [rbp-0x8]

  400578: 8b 45 f4 mov eax,DWORD PTR [rbp-0xc]

  40057b: 89 c6 mov esi,eax

  40057d: bf 34 06 40 00 mov edi,0x400634

  400582: b8 00 00 00 00 mov eax,0x0

  400587: e8 84 fe ff ff电话400410 printf@plt

  40058c: c9离开

  40058d: c3 ret

  00000000040058e主要:

  推动rbp

  40058f: 48 89 e5 mov rbp,rsp

  400592: e8 96 ff ff ff调用40052d func1

  400597: e8 ce ff ff ff调用40056a func2

  40059c: b8 00 00 00 00 mov eax,0x0

  4005a1: 5d弹出rbp

  4005a2: c3 ret

  4005 a3:66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax rax * 10x 0]

  4005aa: 00 00 0

  405ad: 0F1F00NOP DWORD PTR [RAX]你可以看到,栈帧总是以同样的方式形成的。在我们的两个函数中,堆栈帧大小是相同的,因为局部变量是相同的。

  推送rbp

  mov rbp,rsp

  SURSP,0x 10两个函数都以语句结束。

  变量a、b和C在两个函数中的引用方式相同:

  a位于存储器地址rbp-0xc。

  b位于存储器地址rbp-0x8。

  c位于内存地址rbp-0x4。

  带通话

  看上面这个有点长的汇编代码,可以发现每个函数(或者堆栈帧)都有一个ret。

  在主要部分,使用了call。现在我们来看看call和ret。

  函数调用是如何实现的?汇编中有call语句400592:E8 96 ff ff ff call 40052d func1 call语句表示指令跳转的地址,例如调用40052d func1,但是func 1执行完毕后如何退出调用并返回原处?原来调用call语句的时候会把返回地址(或者当前地址)推到栈顶。调用ret语句时,栈顶的内容,也就是返回地址,会被弹出栈外,从而正确返回main。

  Ret从堆栈中弹出返回地址并跳转到那里。当调用一个函数时,程序调用在跳转到被调用函数的第一条指令之前,使用指令按返回地址。

  这就是程序如何调用一个函数,然后从该函数返回调用函数以执行其下一条指令。

  如下图所示,调用call时,先把要返回的地址压入堆栈。

  然后调用func1形成堆栈框架。

  其他变量现在回顾本文开头的程序集。

  square(int):

  推送rbp

  mov rbp,rsp

  mov DWORD PTR [rbp-4],电子数据交换

  移动eax,DWORD PTR [rbp-4]

  imul eax,eax

  pop rbp

  ret里还有两个字符我不理解:edi和eax。

  Edi和eax和rbp、rsp一样,都是寄存器的名字。先来澄清一下x86系列寄存器中奇怪的名字。

  对寄存器的一个很好的理解是,和软件中的变量一样,它们相当于一种‘硬件变量’。

  像C变量一样,寄存器实际上有几种大小:

  rax是64位“长”型寄存器。它是在2003年向64位处理器过渡期间添加的。eax是32位的“int”大小寄存器。它是在1985年向带有80386 CPU的32位处理器过渡期间添加的。我习惯使用这种寄存器大小,因为它们也工作在32位模式,尽管我尝试对所有情况都使用较长的rax寄存器。ax是16位的“短”寄存器。它是在1979年与8086 CPU一起添加的,但至今仍用于DOS或BIOS代码中。al和ah是8位“char”大小的寄存器。al是低8位,ah是高8位。它们与1972年8008的8位寄存器非常相似。

  X64汇编代码使用16个64位寄存器。此外,其中一些寄存器的低位字节可以作为32位、16位或8位寄存器独立访问。寄存器名称如下

  以rax寄存器为例,其结构如下

  如上图所示,初始寄存器是8位。比如上图中的8位al在DOS和8086中,8位寄存器扩展为16位ax,在80386中分为高8位ah和低8位al,进一步扩展为32位eax,其中E代表扩展64位处理器使用64位rax,R代表寄存器。再看一遍,上面几段的意思已经很简单了,可以彻底理解了。

  参见破解虚拟内存:绘制VM图破解虚拟内存:堆栈、寄存器和汇编代码cs 301x86汇编x64备忘单中的寄存器布朗大学。

  来自,

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

相关文章阅读

  • office2010激活密钥大全 怎么永久激活office2010
  • project2010产品密钥免费_project2010激活密钥永久激活码
  • c语言调用退出函数 c语言退出整个程序怎么写
  • c语言中怎么给函数初始化 c语言的初始化语句
  • c语言编写函数计算平均值 c语言求平均函数
  • chatgpt是什么?为什么这么火?
  • ChatGPT为什么注册不了?OpenAI ChatGPT的账号哪里可以注册?
  • OpenAI ChatGPT怎么注册账号?ChatGPT账号注册教程
  • chatgpt什么意思,什么是ChatGPT ?
  • CAD中怎么复制图形标注尺寸不变,CAD中怎么复制图形线性不变
  • cad中怎么创建并使用脚本文件,cad怎么运行脚本
  • cad中快速计算器的功能,cad怎么快速计算
  • cad中快速修改单位的方法有哪些,cad中快速修改单位的方法是
  • cad中心点画椭圆怎么做,cad轴测图怎么画椭圆
  • CAD中常用的快捷键,cad各种快捷键的用法
  • 留言与评论(共有 条评论)
       
    验证码: