龙空技术网

2.14 堆栈平衡

爱达人编程达人 368

前言:

当前朋友们对“使用堆栈操作指令时要注意什么问题”可能比较注意,同学们都想要分析一些“使用堆栈操作指令时要注意什么问题”的相关文章。那么小编也在网上收集了一些对于“使用堆栈操作指令时要注意什么问题””的相关文章,希望你们能喜欢,看官们快快来学习一下吧!

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种方式实现堆栈平衡。

标签: #使用堆栈操作指令时要注意什么问题