为什么 .COM 程序中只有一个段,既用于程序代码又用于堆栈?

Why is there only one segment in .COM programs, both for the program code and for the stack?

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

示例:

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

堆栈容量为1024 Byte(400h),引用堆栈段3996h,与代码段3995h不同。所以我确定数据不会混淆。

我不明白的是,当我必须处理 .COM 程序时;因为我很清楚他们只使用一个段,我发现自己遇到了类似这样的情况:

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

我有与代码段匹配的堆栈段。所以如果我一直把值放在栈上,它们迟早会出现在我的代码中吗?

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

使用 CS:IP 为 114A:0100 和 SS:SP 为 114A:FFFE 的第二个示例,您的 COM 程序可以将 SS:SP 更改为 210A:03FE .两个 SS:SP 值将指向相同的线性地址 (2149E) 和相同的 return 值 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,但是溢出时堆栈会覆盖自身,因此并不能从根本上消除问题。最后,作为开发人员,您有责任确保您的程序已经为自己分配了足够的堆栈 space 并且您的代码永远不会超过该限制。


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

*.com 文件可以使用函数 4ah 将 ram 的大小调整为可执行文件的最小值,并最终从 DOS 中使用函数 48h 获得空闲 ram。