前言:
当前朋友们对“使用堆栈操作指令时要注意什么问题”可能比较注意,同学们都想要分析一些“使用堆栈操作指令时要注意什么问题”的相关文章。那么小编也在网上收集了一些对于“使用堆栈操作指令时要注意什么问题””的相关文章,希望你们能喜欢,看官们快快来学习一下吧!2.14 堆栈平衡
为什么学习本节内容?因为如果不考虑堆栈平衡,会造成堆栈溢出,程序崩溃,这是我们不愿意看到的。
本节必须掌握的知识点:
堆栈是什么
堆栈的特点
堆栈平衡
在上节中我们有一个疑问没有解答,在解答这个疑问之前,我们先整明白堆栈是什么?
这节相当于总结堆栈相关知识了,如果大家从汇编一开始一步一步跟着做实验应该对堆栈有些了解。这里大概总结一下,希望大家在看下面的内容时,自己能总结出对堆栈的认识。
2.14.1【堆栈是什么?】
1、初始堆栈空间是操作系统给应用程序分配的内存空间;
2、程序运行时用来存储临时数据的地方,比如参数、返回值;
3、寄存器ESP是栈顶指针,ESP指向哪个内存地址,哪个内存地址就是堆栈栈顶,ESP保存的数据,正是堆栈已使用地址的栈顶。
我们论证一下总结的三点是否正确。
论证第一点:初始堆栈空间是操作系统给应用程序分配的内存空间。
借用DTDebug.exe打开飞鸽软件时,我们就能看到堆栈窗口已经有内存地址,我们并没有对调试和被调试的软件做什么操作,且堆栈中有的内存已经被用了,如图2-14-1所示。
图2-14-1堆栈窗口中,内存地址0x0019FFFC到内存地址0x0019FFF0之间的内存都已经被占用了,那操作系统给我们分配了多大的内存空间范围哪?我们通过查看FS位,FS位存储的数据是0x00365000,在命令行输入 DD 0x00365000,如图2-14-2所示。
看图2-14-2内存窗口中,操作系统给我们分配的内存空间范围0x0019D000~0x001A0000。
论证第二点:程序运行时用来存储临时数据的地方,比如参数。
我们往堆栈中存储数据,输入以下指令,例如:
PUSH 1
PUSH 2
PUSH 3
……
写入后按F8执行,看到堆栈中已经有了我们保存的临时数据。如图2-14-3所示。
当前已经把数据保存到堆栈中了,只能反映出堆栈是用来存储数据的,并没有体现存储的是临时数据。别着急,先记录当前ESP寄存器存储的数据,ESP存储的数据为0x0019FFE4,重点来了,把以下代码输入堆栈窗口中,如图2-14-4所示。
POP EAX
PUSH 4
按F8执行POP EAX,如图2-14-5所示。
看图2-14-5中,ESP存储的数据发生了变化,由0x0019FFE4变为了0x0019FFE8,把之前存储的数据放入了EAX中。但是堆栈中内存地址0x0019FFE4现在存储的数据还是3,并没有发生变化,我们接着按F8执行PUSH 4,如图2-14-6所示。
看图2-14-6中,ESP存储的数据发生了变化,由0x0019FFE8变为了0x0019FFE4,且堆栈中内存地址0x0019FFE4存储的数据为4。
我们把没执行
POP EAX
PUSH 4
之前的堆栈进行对比,可以说明堆栈中保存的是临时数据,应用程序在运行时会出现大量向堆栈中读取数据的操作,若全部保存在操作系统分配的堆栈是远远不够的,所以堆栈中保存的是临时数据。
论证第三点:寄存器ESP是栈顶指针,ESP指向哪个内存地址,哪个内存地址就是堆栈栈顶,ESP保存的数据,正是堆栈已使用地址的栈顶。
我们看图2-14-6所示,当前ESP存储的数据为0x0019FFE4,而堆栈中黑色定位光标中内存地址为0x0019FFE4,内存地址0x0019FFE4存储的数据为4。我们可以根据ESP寻址的方式去提取堆栈中存储的数据,把存入堆栈中的 4、2、1依次存储到EAX、ECX、EDX中。
输入以下指令:
MOV EAX,DWORD PTR SS:[ESP]
MOV ECX,DWORD PTR SS:[ESP+4]
MOV EDX,DWORD PTR SS:[ESP+8]
如图2-14-7所示:
按F8执行并观察EAX、ECX、EDX存储数据的变化。
图2-14-8中,已经成功将堆栈中存储的数据提取出来。所以ESP指向哪个内存地址,哪个内存地址就是栈顶。
以上是对堆栈是什么我们做了总结,也做了论证,希望大家能够自己动手做实验,自己能够总结出对堆栈的理解。
2.14.2【堆栈的特点】
通过操作我们可以大概总结出堆栈的特点:
1、初始堆栈空间有限;
2、可读可写;
3、频繁修改;
4、方便查找、方便读写(方便使用);
5、地址连续;
6、使用时,是从高地址到低地址(处理器(CPU)规定的)。
大家看到总结堆栈的特点有没有这样的疑问,为什么堆栈使用时,是从高地址到低地址(CPU规定的)?
答:因为对堆栈的操作方式,由两个指令PUSH和POP直接操作堆栈,而PUSH指令和POP指令是属于处理器(CPU)的,操作系统为了迎合处理器只能是从高低址到低地址,所以堆栈使用时,是从高低址到低地址由处理器决定。
我们在DTDebug.exe软件汇编窗口中往下拉,看图2-14-9所示,可以看到有很多CALL指令,我们知道执行CALL指令就是调用一个函数,那么问题来了,函数之间能不能使用同一块内存?答案是可以的。
这些函数执行到RETN指令的时候,返回值是从哪取出来的?都是从[ESP]中取的,但是这些函数返回时都不是同一个地址,因为每一个函数都在堆栈中有一块属于自己的内存空间,用来存放临时数据,由于堆栈大小空间是有限的,当一个函数执行完,我们要释放它用过的内存空间,如果不释放会导致堆栈溢出。
如何解决堆栈使用过程中不断存储数据导致堆栈溢出?
解决方案:函数调用时为临时数据分配堆栈空间,函数执行完毕后,释放这块空间。
2.14.3【堆栈平衡】
介绍到这里,我们现在来解决上一节留下来的疑问“我们的函数执行完了,可是我们的数据还保存在堆栈中,该怎么解决呢”如图2-14-10所示。
这里就需要使用堆栈平衡的知识了。为什么需要使用堆栈平衡的知识哪?我们首先搞清楚什么是堆栈平衡?堆栈平衡就是函数调用时为临时数据分配堆栈空间,函数执行完毕后,释放这块空间。
在图2-14-10中函数执行过程中的临时数据有:参数,返回值。当函数执行完时要释放掉这些存在堆栈中的数据,一般函数运行中返回值是通过RETN指令释放掉的空间,而参数我们该怎么释放哪?
有两种解决方案:
1、外平栈
2、内平栈
先介绍外平栈,我们看图2-14-10中,我们向堆栈窗口中压入了5个参数,思考一下我们该怎么用指令实现使ESP存储的数据变为0x0019FFF0。
可以在函数执行完返回到CALL指令的下一行地址里, 输入ADD ESP,0x14
如图2-14-11所示:
按F8执行并观察堆栈变化,如图2-14-12所示。
看图2-14-12中,按F8执行后看到了ESP存储的数据为0x0019FFF0。恢复到参数没有压入堆栈时的内存地址,使堆栈平衡。
介绍内平栈,所谓的内平栈就是在函数没有执行完,完成堆栈平衡,思考一下该怎么实现堆栈平衡。看图2-14-13所示。函数执行到RETN指令时,RETN指令是将CALL指令压入堆栈的数据弹出堆栈。我们就可以利用RETN指令将堆栈平衡,记录当前ESP存储的数据为0x0019FFD8由于图2-14-13堆栈中压入了5个数据,输入以下指令
RETN 0x14
按F8执行RETN指令观察ESP存储的数据变化,如图2-14-14所示。
看图2-14-14中,按F8执行完,ESP存储的数据变成0x0019FFF0。
以上是内平栈的操作,总结:在函数没有执行完,使用RETN指令操作的平衡堆栈叫内平栈。
那么问题来了什么情况下需要考虑堆栈平衡呢?
首先考虑一下,怎么才能使堆栈不平衡哪?无非就是向堆栈中传递参数。
参数传递的方式有:寄存器、堆栈、寄存器加内存这三种,首先排除寄存器传参,因为它没有使堆栈产生变化;而用堆栈传参的时候,我们要注意向堆栈里传递了多少参数,使用完这些参数后,我们要考虑堆栈平衡;寄存器加内存设计到操作系统内核的知识了,在这里就不介绍了。
下节介绍ESP寻址。
练习:
1、用汇编编写一个函数,功能是实现对任意10个整数的加法运算(不考虑溢出),
要求:
1、前2个参数使用寄存器进程参数传递;
2、后8个参数使用堆栈进行传递。
3、用2种方式实现堆栈平衡。
标签: #使用堆栈操作指令时要注意什么问题