为什么在.COM程序中,对于程序代码和堆栈,只有一个段?

问题描述

我正在研究.EXE程序和.COM程序之间的区别。 .EXE对我而言是合乎逻辑的,即相对于程序代码而言,堆栈位于另一段中(实际上是强制堆栈并使用.STACK指示此限制),因此,当我在其中插入值时堆栈(在.EXE文件中),使用的段与程序不同,因此我不会混淆这两个段。

示例:

SP = 0400
SS = 3996 CS = 3995 IP = 0000

堆栈的容量为1024字节(400h),并引用堆栈段3996h,这与代码段3995h不同。因此,我确定数据不会混淆。

我不了解的事情是当我不得不处理.COM程序时;因为我很清楚他们只使用了一个细分,而我发现自己的处境与此类似:

SP = FFEE
SS = 114A CS = 114A IP = 0100

我有代码段匹配的堆栈段。因此,如果我继续将值放在堆栈上,它们早晚会出现在我的代码中吗?

解决方法

虽然在加载COM文件后MS-DOS确实将所有段寄存器设置为程序入口上的同一段,但是没有什么阻止您更改寄存器以使用不同的段。但是,您所设想的段之间的硬分隔实际上并不存在。即使有一个单独的堆栈段,如果堆栈溢出,您仍将在不应该写入的地方写入内存,结果将不可预测。

使用第二个CS:IP为114A:0100,而SS:SP为114A:FFFE的示例,您的COM程序可以将SS:SP更改为210A:03FE。两个SS:SP值都将指向相同的线性地址(2149E)和MS-DOS在条目上推入堆栈的相同的返回值。现在,堆栈只有1024个字节,就像在EXE示例中一样,但是堆栈段仍然是64k。在实模式下,段始终为64k。

现在猜测当SS:SP为210A:0000并尝试将一个单词压入堆栈时会发生什么? CPU不会生成异常,它不会以某种方式拒绝执行该操作,它只会执行其始终执行的操作。它从SP中减去2,然后将单词存储在新的SS:SP地址中。在这种情况下,这意味着SP将绕回FFFE,并且您推入的值将存储在210A:FFFE或3109E的线性地址中。

现在的问题是,线性地址3109E分配了什么? MS-DOS可能会将此内存分配给您的程序,并在其中写入值是无害的,因为您的程序没有将其用于任何事情。但是,某些TSR或驱动程序可能会在那里分配内存并覆盖它,这将导致崩溃或其他无法预测的行为。

如果在堆栈溢出时重写自己的代码,实际上会更好,因为这意味着您更有可能注意到该错误并进行修复。

EXE也存在相同的环绕问题。如果您想要一个真正隔离的堆栈,则需要制作64K的大小,但是在溢出时发生的事情是该堆栈会覆盖自身,因此从根本上不能解决问题。最终,作为开发人员,您有责任确保程序为自己分配了足够的堆栈空间,并且您的代码永远不会超过该限制。


请注意,COM文件需要担心一个与堆栈相关的陷阱,并且不能保证MS-DOS能够为您的COM文件分配完整的64K。 MS-DOS将分配COM文件将适合的最大的空闲内存块。如果该内存块小于64K,则SP不会设置为FFFE,而是将SP设置为指向分配给程序的最后一个字。在最坏的情况下,这意味着堆栈将立即开始覆盖您的COM文件中的代码和数据。因此,最好在COM文件末尾分配空间以为堆栈保留空间。

,

*。com文件可以将功能为4ah的ram大小调整为可执行文件的最小值,并最终从功能为48h的DOS中获取免费的ram。