【问题标题】:Segment prefix when using pointers as function parameters使用指针作为函数参数时的段前缀
【发布时间】:2011-06-19 11:37:41
【问题描述】:

我有一个汇编程序/c 问题。我刚刚阅读了段前缀,例如 ds:varX 等等。前缀对于逻辑地址的计算很重要。我也读过,默认值是“ds”,一旦您使用 ebp 寄存器计算地址,就会使用“ss”。对于代码“cs”是默认值。这一切都说得通。 现在我在 c 中有以下内容:

int x; // some static var in ds

void test(int *p){
...
*p =5;

}

... main(){

test(&x);
//now x is 5
}

如果你现在考虑一下 test-function 的实现......你会在堆栈上获得指向 x 的指针。如果要取消引用指针,首先从堆栈中获取指针值(x 的地址)并将其保存在 eax 中。然后你可以取消引用 eax 来改变 x 的值。但是 c 编译器如何知道给定的指针(地址)是否引用堆栈上的内存(例如,如果我从另一个函数调用 test 并将局部变量的地址作为测试参数推送)或数据段?如何计算完整的逻辑地址?该函数无法知道给定地址偏移量与哪个段相关..?!

【问题讨论】:

  • 下次你可能想读一本 1990 年以后出版的书... ;-)

标签: c assembly x86 segment


【解决方案1】:

一般情况下,在分段平台上,您不能按照您的建议将指针值“读入eax”。在分段平台上,指针通常会同时保存段值和偏移值,这意味着读取这样的指针意味着初始化至少 两个 寄存器 - 段和偏移 - 而不仅仅是一个 eax

但在特定情况下,它取决于所谓的内存模型。分段平台上的编译器支持多种内存模型。

首先,出于显而易见的原因,只要段寄存器保存正确的值,您使用哪个段寄存器并不重要。例如,如果DSES 寄存器内部保存相同的值,那么DS:<offset> 将指向与ES:<offset> 相同的内存位置。

在所谓的“微小”内存模型中,例如,所有段寄存器都保存相同的值,即所有内容 - 代码、数据、堆栈 - 都适合一个段(这就是为什么它被称为“小”)。在这个内存模型中,每个指针只是这个段中的一个偏移量,当然,使用哪个段寄存器来处理这个偏移量并不重要。

在“更大”的内存模型中,您可以为代码 (CS)、堆栈 (SS) 和数据 (DS) 设置单独的段。但是在这种内存模型上,指针对象通常会在其中保存 both 地址的偏移量和段部分。在您的示例中,指针 p 实际上是一个由两部分组成的对象,同时包含段值和偏移值。为了取消引用此类指针,编译器将生成从p 读取段和偏移值的代码并使用它们。例如,段值将被读入ES 寄存器,而偏移值将被读入si 寄存器。然后代码将访问ES:[di] 以读取*p 值。

还有“中间”内存模型,代码将存储在一个段 (CS) 中,而数据和堆栈都将存储在另一个段中,因此 DSSS 将保持相同的值。在那个平台上,显然没有必要区分DSSS

在最大的内存模型中,您可以有多个数据段。在这种情况下,很明显,在分段模式下正确的数据寻址实际上并不是选择正确的段寄存器(您似乎相信),而是采取几乎 any 段的问题在执行访问之前注册并使用正确的值初始化它。

【讨论】:

  • 我明白,但如果我在汇编程序中编写“测试”函数,通常你必须推送这样一个“复杂”指针对象吗?有替代品吗?
  • @Martin:同样,它只取决于您的test 函数期望p。如果它需要一个复杂的对象,那么您必须推送一个复杂的对象。如果它只需要一个偏移量,那么你只需要推动一个偏移量。
  • 我猜你在这里解释的都是关于旧的 16 位寄存器(你在这里提到 di)而 OP 可能对 32 位寄存器感兴趣(提到 ebp)。当使用 32 位架构时,指针可能永远不会包含段寄存器(这将使它们成为 48 位),因此 DS 必须等于 SS 才能使指针工作。
  • @anatolyg:32 位代码可以使用远指针。只是使用平面内存模型更容易,所以大多数现代操作系统都使用它,或者非常接近。
  • @anatolyg:嗯,我的想法不同。由于 OP 甚至提到了段寄存器,这意味着他使用平面内存模型。如果他正在使用平面内存模型,那么它只是我在回答中描述的“微型”内存模型的现代 64 位版本。此外,x64 架构完全能够使用“非小型”内存模型。就是编译器认为“64 位微型”应该对每个人都足够了。
【解决方案2】:

AndreyT 描述的是 DOS 时代发生的事情。如今,现代操作系统使用所谓的flat memory model(或者更确切地说是非常相似的东西),其中设置了所有(保护模式)段,以便它们都可以访问整个地址空间(即:它们的基数为 0和一个限制 = 整个地址空间)。

【讨论】:

  • 整个虚拟地址空间。 :-)
  • @R.. 在这种情况下,我认为这是整个线性地址空间;P
  • 具有扁平内存模型的现代操作系统不提供对物理内存的扁平无限制访问。它提供了对其自己的虚拟地址空间的进程平面访问。
  • @R.. 我挑剔的太多了。在 x86 上,分段之后但分页之前的地址称为线性地址。
【解决方案3】:

在具有分段内存模型的机器上,C 实现必须执行以下操作之一才能符合要求:

  • 将完整地址(带段)存储在每个指针中,或者
  • 确保所有用于获取地址的变量的堆栈地址都可以通过数据段访问,可以是相同的相对地址,也可以是编译器在获取局部变量的地址时可以应用的某个魔术偏移量,或者
  • 不要将堆栈用于获取地址的局部变量,并在每个函数进入/返回时执行隐藏的 malloc/free(对 longjmp! 进行特殊处理)。

也许还有其他方法可以做到,但我能想到的只有这些。分段内存模型在 C 语言中确实非常不受欢迎,因此被抛弃是有充分理由的。

【讨论】:

    【解决方案4】:

    分段是 Intel 16 位 8086 处理器的遗留产物。实际上,您可能在virtual memory 中操作,这里的一切都只是一个线性地址。使用-S 标志编译并查看生成的程序集。

    【讨论】:

    • 实际上,在 x86 上,一切都是虚拟地址,其偏移量,由于段基数设置为 0,将 1:1 映射到线性地址,然后通过分页映射到物理地址地址。
    • 那和我说的有什么不同?
    • 它有一个额外的间接层。请参阅 RFC1925。
    【解决方案5】:

    由于在取消引用之前将地址移动到 eax,因此它默认为 ds 段。然而,正如 Nikolai 所提到的,在用户级代码中,这些段可能都指向同一个地址。

    【讨论】:

      【解决方案6】:

      在x86下,直接使用堆栈会使用堆栈段,而间接使用则将其视为数据段。如果您反汇编指针​​取消引用并写入堆栈部分指针,您可以看到这一点。在 x86 cs 下,由于线性寻址,ss 和 ds 的处理方式几乎相同(至少在非内核模式下)。 intel 参考手册也应该有一个关于段寻址的部分

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-11-29
        • 2011-02-04
        • 2012-03-07
        • 1970-01-01
        相关资源
        最近更新 更多