热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Enter和Leave指令

原作者不知道,转载于[360doc]移植好奇Enter的第二个参数是什么意思,今天终于知道了。enter和push。。。相比消耗更多的时钟周期,但功能多。 Leave指令很简单,相

原作者不知道,转载于[360doc] 移植好奇Enter 的第二个参数是什么意思,今天终于知道了。

enter和push。。。相比消耗更多的时钟周期,但功能多。

 

Leave指令很简单,相当于move esp,ebp和pop ebp。

  Enter SRC1,SRC2 也不复杂,只是不了解的话动态调试起来会很晕,Enter作了下面的事。

  push ebp

  mov ebp,esp

  现在栈顶上是上一个函数的基地址(就是上一个函数里的ebp,刚被压入;这个ebp很重要,习惯上进入一个子函数时,在call的时候自动压入call后面那条语句的epi,然后压入ebp来保存这个即将被改变的ebp值,然后把ebp指向当前的栈顶,这样调用这个函数领空里用到的所有本地局部变量用ebp就能找到了,所以这时ebp的值叫基址),用这个刚保存的基址,去栈的深处(也不深,也就是调用本函数的父函数的领空)把这个基址上SRC2-1个内容复制到栈顶往上的相同大小的空间里,最后再把ebp的值压上去。

  上面这段话里莫名其妙的操作正好使用了SRC2那么大的栈空间。

  sub esp,SRC1

  就走了这四步,而除了第三步,剩下那三步大家都很熟悉(如果不熟这篇文章还不是你该看的时候)并且经常自己会写的。关键是这个第三步有什么用?

  很多书上都叫第二参数是“嵌套层数”。Bingo,很正解。比如说主程序调用了proc1,而proc1调用了proc2,而proc2调用了proc3……总之突然procN要调用procM(当然N>M)中的一个局部变量,按照传统的调用子函数编写方法,这个访问实现起来简直无计可施。而如果你在proc1中用了个 enter SRC1,1 ,proc2中用了个 enter SRC1,2 ……这样就有个简单的方法了。假设procN中要被赋值的的变量是第一个,procM中要被读取的变量也是第一个,在要赋值时这样就可以:

  mov eax, DWORD PTR [ebp-4*M]

   ;把procM的基地址暂存到eax里

  push DWORD PTR [eax-4*M-4]

  ;把按这个地址找到的procM中的变量入栈(草,寄存器不够使了)

  pop DWORD PTR [ebp-4*N-4]

  ;出栈,把值赋给procN中的那个饥渴的变量。

  思考一下吧,很简单。可见要使用enter必须保证要访问的那个proc到现在所处的proc之间的一系列proc都在开头用了enter。当然,当SRC2为零时就无所谓了,而且比自己写push ebp 和 mov ebp , esp省劲的多。这样在一系列的enter作用下,到了procN时,栈中最上面的一部分是procN的领空,而这个领空的内容是这样的(从下往上):返回procN-1(调用procN的proc)的地址(call是epi应有的内容);刚进入procN的ebp,即procN-1的基址;大小为SRC2*4的一段指针列表(指向前面各个用了enter的proc的基址),这段类表的原理就是递推的方法,其中SRC2*4-4的内容是从procN-1的领空中复制的,为了能让procN+1也能用enter创建一断指针列表,列表最上面也要压入指向procN自己基址的指针,虽然在procN中,这最后一个没什么用处;然后是依据SRC1开辟的本地局部变量区域。其实这个结构有个名字,堆栈桢,貌似《编译原理》里会学到这个概念。利用堆栈桢,就可以方便的访问之前嵌套了自己的各辈函数的本地局部变量。总之,要使用这个功能,必须保证一路上都用了enter,否则即使procN用了enter,在复制procN-1的指针段的时候复制的东西根本不是指针,而是,比如procN-1的本地局部变量什么的(因为procN-1根本没有这么一段指针表啊)。

下面给段代码,可以动态调试一下便于理解

    .486                                ; create 32 bit code
    .model flat, stdcall                ; 32 bit memory model
    option casemap :none                ; case sensitive

    .data

    .code
proc5 proc
 enter 8,5
 mov DWORD PTR [ebp-4-4*5],54
 mov eax,DWORD PTR [ebp-4-4*0]
 push DWORD PTR [eax-4-4*1]
 pop DWORD PTR [ebp-4-4*5-4] 
 ;这里把第二个本地变量设为proc1的值 
 leave
 ret 
proc5 endp
proc4 proc 
 enter 4,4
 mov DWORD PTR [ebp-4-4*4],53
 call proc5
 leave
 ret 
proc4 endp
proc3 proc
 enter 8,3
 mov DWORD PTR [ebp-4-4*3],52
 mov eax,DWORD PTR [ebp-4-4*1]
 push DWORD PTR [eax-4-4*2]
 pop DWORD PTR [ebp-4-4*3-4] 
 ;这里把第二个本地变量设为proc2的值 
 call proc4
 leave
 ret 
proc3 endp
proc2 proc 
 enter 4,2
 mov DWORD PTR [ebp-4-4*2],51
 call proc3
 leave
 ret 
proc2 endp
proc1 proc 
 enter 4,1
 mov DWORD PTR [ebp-4-4*1],50
 call proc2
 leave
 ret 
proc1 endp
start:
 call proc1  
 ret
end start

 

通常在函数调用中使用堆栈来传递参数,保存函数返回地址和为自动变量分配内存。

通常在进入函数中时有两条命令,如下:

push ebp  ; 保存上一个函数的栈帧基地址

mov ebp,esp ; 设置新的函数栈帧基地址

在返回函数前通常有如下两条指令:

mov esp,ebp ; 将当前函数栈帧基地址保存到esp中

pop ebp ; 恢复上一个函数的栈帧基地址

这是前奏。。之后Intel又设计了两条指令来简化上面的两个步骤,那就是ENTER和LEAVE指令。

我先说LEAVE指令吧。。这条指令就相当于 mov esp,ebp 和 pop ebp 两条指令的执行效果。

而ENTER指令要麻烦一点。我先将它的指令格式列出来:

ENTER A,B 

A表示的是在栈上分配的临时变量的空间大小,B表示词法嵌套级(我英文不好,原英文是:lexical nesting level)

A很容易理解,就是sub esp,N之类的指令,用来给当前函数的栈帧分配局部变量空间。

B我也不太明白啥意思。不过没关系,一般这个值为0。

举个例子吧,例如:ENTER 4,0 相当于如下三条指令:

push ebp

mov ebp,esp

sub esp,4

就这么简单!但是这条enter指令也有一个先天上的不足,那就是速度慢。所以一般编译器生成代码时很少使用enter指令。还是用以前的老方法,倒是LEAVE指令经常被用到。



推荐阅读
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • 本文介绍了Windows操作系统的版本及其特点,包括Windows 7系统的6个版本:Starter、Home Basic、Home Premium、Professional、Enterprise、Ultimate。Windows操作系统是微软公司研发的一套操作系统,具有人机操作性优异、支持的应用软件较多、对硬件支持良好等优点。Windows 7 Starter是功能最少的版本,缺乏Aero特效功能,没有64位支持,最初设计不能同时运行三个以上应用程序。 ... [详细]
  • 本文介绍了如何使用PHP向系统日历中添加事件的方法,通过使用PHP技术可以实现自动添加事件的功能,从而实现全局通知系统和迅速记录工具的自动化。同时还提到了系统exchange自带的日历具有同步感的特点,以及使用web技术实现自动添加事件的优势。 ... [详细]
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • 本文介绍了RPC框架Thrift的安装环境变量配置与第一个实例,讲解了RPC的概念以及如何解决跨语言、c++客户端、web服务端、远程调用等需求。Thrift开发方便上手快,性能和稳定性也不错,适合初学者学习和使用。 ... [详细]
  • 加密世界下一个主流叙事领域:L2、跨链桥、GameFi等
    本文介绍了加密世界下一个主流叙事的七个潜力领域,包括L2、跨链桥、GameFi等。L2作为以太坊的二层解决方案,在过去一年取得了巨大成功,跨链桥和互操作性是多链Web3中最重要的因素。去中心化的数据存储领域也具有巨大潜力,未来云存储市场有望达到1500亿美元。DAO和社交代币将成为购买和控制现实世界资产的重要方式,而GameFi作为数字资产在高收入游戏中的应用有望推动数字资产走向主流。衍生品市场也在不断发展壮大。 ... [详细]
  • 本文介绍了在Windows环境下如何配置php+apache环境,包括下载php7和apache2.4、安装vc2015运行时环境、启动php7和apache2.4等步骤。希望对需要搭建php7环境的读者有一定的参考价值。摘要长度为169字。 ... [详细]
  • 本文介绍了在多平台下进行条件编译的必要性,以及具体的实现方法。通过示例代码展示了如何使用条件编译来实现不同平台的功能。最后总结了只要接口相同,不同平台下的编译运行结果也会相同。 ... [详细]
  • Redis底层数据结构之压缩列表的介绍及实现原理
    本文介绍了Redis底层数据结构之压缩列表的概念、实现原理以及使用场景。压缩列表是Redis为了节约内存而开发的一种顺序数据结构,由特殊编码的连续内存块组成。文章详细解释了压缩列表的构成和各个属性的含义,以及如何通过指针来计算表尾节点的地址。压缩列表适用于列表键和哈希键中只包含少量小整数值和短字符串的情况。通过使用压缩列表,可以有效减少内存占用,提升Redis的性能。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • 本文介绍了在CentOS上安装Python2.7.2的详细步骤,包括下载、解压、编译和安装等操作。同时提供了一些注意事项,以及测试安装是否成功的方法。 ... [详细]
author-avatar
专业STB
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有