【问题标题】:Different Segments may collapse with each other不同的Segment可能会相互折叠
【发布时间】:2019-06-17 13:01:20
【问题描述】:

当我们编码时,那么我们给不同的段寄存器赋予相同的值,这样两个不同的段可能会相互混淆,并且可能会出现问题。 例如在以下给定条件下。

MOV AX, CS
MOV SS, AX
ASSUME SS: DOSGROUP 

如上所述,在第 1 行中,我们将 CS 移动到 AX。紧接着我们AX到SS。这意味着此时 CS 和 SS 寄存器包含相同的值或相同的内存位置,这不是真的,当我们进一步编码时,CS 的内容将与内容 SS 一起崩溃。

【问题讨论】:

  • “崩溃”是什么意思?
  • CPU 会允许的。您需要确保代码和堆栈保持不同,并且堆栈写入命令(PUSH、CALL)不会覆盖代码。段中有 64 KB 的空间 - 如果堆栈停留在低偏移量并且代码加载到高偏移量(例如 CS:8000),它可能会工作。
  • 我们如何使用相同的内存位置执行两个操作(例如,一个用于 CS,另一个用于 SS)。在这种情况下,我提到了崩溃。
  • 没有任何问题。每当它需要访问内存时,cpu 将使用指令中指定的任何段执行地址映射。多个段寄存器可能指向同一个段这一事实无关紧要。管理内存使用是您的工作,以免意外覆盖内容。

标签: assembly x86-16


【解决方案1】:

设置段寄存器不会改变内存的内容(实际上在实模式下根本不会访问内存),它只会改变CPU对内存的看法(逻辑到线性平移)。

拥有两个具有相同值的段寄存器仅意味着可以使用相同的偏移量X来访问相同的数据。
如果我们不想指示汇编器如何计算不同段的偏移量,这会很方便。
如果我们天真地编写汇编程序,我们将缺少汇编程序的段信息,汇编程序会将我们的程序视为单个代码段。因此,汇编器将为 数据和代码使用相同的偏移计数器,但在运行时这两个区域是具有不同段的访问(即 DSCS),除非您在每次加载时都覆盖它/商店。
如果上面的属性不正确,那么汇编器生成的数据偏移量将与加载程序加载程序时不匹配。

这在获取函数指针或通过指针处理堆栈上的数据时也很有用(即没有与bp 的固定偏移量),它实质上将每个指针转换为近指针。
所以一个变量的偏移量实际上变成了一个变量的地址,而不需要进一步考虑段。这使得地址/偏移量更容易推理,因为现在如果变量具有偏移量X,这是我们必须关注的唯一偏移量,X 与我们使用CSDS 或@987654328 时相同@.
所以X 是 1-1 映射到它的 var,如果我们必须考虑分段,每个 var 可能有多达 64Ki/16 个偏移量,并且这个集合与另一个变量(即偏移量X 可以表示两个不同的变量,当与两个不同的段一起使用时),因此指针必须很远。
由于我们只有少量有效的空闲段寄存器,远指针(除了指针大小加倍)给寄存器分配策略带来了很大压力。

当然,将所有代码、数据和堆栈放入一个段中可能具有挑战性,因为段的大小相对较小 (64KiB)。
代码和数据通常不是由程序员设置的(一旦构建了二进制文件,它们的布局就固定了),所以只设置了堆栈,必须注意不要将堆栈指针设置得太靠近数据的末尾和代码。
堆栈的正确处理通常可以通过计算最大调用链深度的餐巾纸背面来忽略,但是如果我们不能绑定我们的调用链或者我们有太多的代码/数据,堆栈大小是数百字节我们可能需要重构。
由于缺乏功能,在实模式下正确管理堆栈很难,我想不出比在每个函数入口处检查堆栈指针更好的方法。

除非我们对每种数据类型(数据/堆栈、代码)有多个段,否则使用单独的段会免费增加可寻址内存,但在初始化时会增加几行(假设我们不使用函数指针)。
将指针传递到堆栈上的结构时,堆栈和数据具有相同的段非常有用。

DOS .com 可执行程序是其中CSDSSS 都共享相同值的程序示例。 DOS .exe 可执行文件通常针对小内存模型进行组装/编译,其中我们只有代码 (CS) 和数据 (DS, SS) 段。
然而,它们可以处理其他内存模型,包括我们可以为数据类型(代码、堆栈、数据)拥有多个段的巨大内存模型。

多段编程容易出错,这就是为什么 32 位操作系统停止使用它而 64 位操作系统不能使用它(但以有限的方式)。
然而,段也很有用,它们可以在内存中创建方便的视图,尤其是在访问 MMIO 和 ISA 区域时。

如果您没有足够的(痛苦的?)片段经验,这堵文字墙很难解析,我的建议是从一个工作程序开始,通过将数据移动到(不必要的)分离的片段中来故意伤害自己。
请记住逻辑地址(段:偏移)如何转换为线性地址(段 * 16 + 偏移)。

【讨论】:

    【解决方案2】:

    我想把我的答案分成两部分。

    第 1 部分

    这意味着此时 CS 和 SS 寄存器包含相同的值或相同的内存位置...

    在 16 位 x86 程序中,段不描述内存位置,而是描述内存位置的范围

    “理想化”的 16 位 CPU 只能使用 64 KiB 的内存。为了能够使用超过 64 KiB 的内存,兼容 8086 的 CPU 具有段:

    段寄存器的值描述了 CPU 应该工作的 64 KiB 大小的内存范围。因此,一个 16 位程序可以(仅)访问 64 KiB 的内存,但是通过更改段寄存器的值,它可以选择 1 MiB 内存中的哪一个 64 KiB 可以访问。这意味着程序可以通过更改段寄存器的值来有效地访问 1 MiB 的内存(而不仅仅是 64 KiB)。

    当然,代码和不同类型的数据可能位于同一个 64 KiB 内存范围内。 (对于只有 64 KiB 内存的计算机(例如 1982 年的 IBM PCjr),这一点更为明显。)

    在这种情况下,CSSS 具有相同的值,因为代码(CS 寄存器)和堆栈(SS 寄存器)位于相同的 64 KiB 内存范围内.

    但是“相同的内存范围”(包含很多不同的内存位置)和“相同的内存位置”是有区别的。

    第 2 部分

    我们如何执行两个操作...使用相同的内存位置。

    让我们更改您的示例并使用寄存器对SS:SPES:DI 而不是寄存器CSSS。这些对实际上描述的是内存位置,而不是内存范围。

    如果SS:SPES:DI 具有相同的值会怎样?

    答案:没有。

    CPU 使用SS:SP 计算堆栈操作的内存位置(例如pushpop)和ES:DI 用于字符串写入操作(例如stosmovs)。

    因为 CPU 不会同时执行两个操作,所以如果为两种操作指定相同的内存位置是没有问题的。

    但是,您必须注意不要发生任何愚蠢的事情。示例:

    push bx
    ; Let's say SS:SP and ES:DI contain the same value here
    stosw
    pop bx
    

    使用stosw 指令,您将使用push 指令破坏您拥有push 的数据,因为SS:SPES:DI 指向相同的内存位置。

    CPU 不关心这个。如果指令 CPU 覆盖堆栈,CPU 将覆盖堆栈。

    作为程序员,避免这种情况是你的任务。

    但实际上,每种编程语言都有完全相同的问题。在 C、C++、C# 或 Java 中,您可能会犯以下错误:

    for(i = 0; i < 5; i++)
    {
        for(i = 0; i < 10; i++)
        {
            ...
        }
    }
    

    ...不明白为什么内部循环执行了 10 次而不是 50 次。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-11-20
      • 2018-06-28
      • 1970-01-01
      • 2016-08-02
      • 2015-05-10
      • 1970-01-01
      相关资源
      最近更新 更多