【问题标题】:Assembly x86 FPU - Stack Confusion组装 x86 FPU - 堆栈混乱
【发布时间】:2018-04-15 23:48:36
【问题描述】:

我正在尝试理解 FPU,但我很困惑。问题是我从here 了解到,FPU 有自己的堆栈。但例如在这段代码(NASM)中:

global _main

extern _printf

section .data
    hellomessage db `Hello World!\n`, 10, 0
    numone dd 1.2
    digitsign db '%f', 0xA, 0

section .text
_main:
    ;Greet the user
    push hellomessage
    call _printf
    add esp,4

    sub esp, 8

    fld dword[numone]
    fstp qword[esp]

    push digitsign
    call _printf
    add esp, 12
    ret

我必须让sub esp, 8 行为double“腾出空间”,否则程序会崩溃。但是通过这样做,我改变了“常规堆栈”的指针,这对我的两个独立堆栈的想法没有意义。

我确定我不明白某事,但我不知道这是什么。

【问题讨论】:

  • 因为您使用了fstp qword [esp],它从 FPU 堆栈传输到 CPU 堆栈。因此,您需要 CPU 堆栈上的空间。

标签: assembly x86 nasm cpu-registers x87


【解决方案1】:

x87 加载/存储使用与其他所有操作相同的内存地址。 x87 堆栈是寄存器 st0..st7,根本不是内存。

有关 x87 寄存器堆栈的详细信息,请参阅 SIMPLY FPU: Chap. 1 Description of FPU Internals

fstp qword[esp] 将 8 个字节存储到常规调用堆栈,就像 mov [esp], eax / mov [esp+4], edx 会做的那样。 寻址模式在与 x87 加载/存储指令一起使用时不会改变含义!即您的进程只有一个地址空间。


因此,如果您删除sub esp, 8,您的fstp 将覆盖您的返回地址。

然后在函数结束时,add esp, 12 将留下 esp 指向上方 8 个字节,因此ret 会将一些垃圾弹出到 EIP 中,然后在尝试从该错误地址获取代码时出现段错误,或者那里的字节解码为段错误的指令。

main 的返回地址上方,您会找到argc,然后是char **argv。它是指向指针数组的指针,因此将其用作返回地址意味着您将指针 values 作为代码执行。 (如果我猜对了。)

使用调试器查看单步时寄存器和内存会发生什么变化。


请注意,add esp,4 / sub esp, 8 有点傻。 add esp, +4 - 8(即add esp, -4)将是一种自我记录的方式,只需一条指令即可。

【讨论】:

    【解决方案2】:

    FPU 有一个“寄存器堆栈”(而不是 RAM 中的堆栈)。

    基本上;有 8 个寄存器(我们称它们为 FPU_R0FPU_R1、...、FPU_R7)和 8 个名称(假设它们是 st0st1、...、st7) ,并且有一个“FPU 堆栈顶部”值来确定哪个名称用于哪个寄存器。

    您可以将新值压入 FPU 寄存器堆栈。例如:

        fld qword [A]     ;st0 = FPU_R7 = A
        fld qword [B]     ;st0 = FPU_R6 = B, st1 = FPU_R7 = A
        fld qword [C]     ;st0 = FPU_R5 = C, st1 = FPU_R6 = B, st2 = FPU_R7 = A
    

    您可以从 FPU 寄存器堆栈中弹出值。例如:

                          ;st0 = FPU_R5 = C, st1 = FPU_R6 = B, st2 = FPU_R7 = A
        fstp qword [C]    ;st0 = FPU_R6 = B, st1 = FPU_R7 = A
        fstp qword [B]    ;st0 = FPU_R7 = A
        fstp qword [A]
    

    【讨论】:

    • 我认为这个答案不值得反对。第一句话是对 OP 的困惑的答案,其余的是演示 x87 堆栈是如何工作的(并且它是一个寄存器堆栈,与内存中的调用堆栈无关。)这不是一个很好的答案,这就是我写的原因我自己的;它几乎没有回答这个问题。但它所说的一切实际上都是正确的。 (错误,除了 st0 在几个 cmets 中重复。我认为你有一个复制/粘贴 + 编辑错误并且忘记增加你的 var 名称)。最好显示fmul st3 或其他内容。
    • 这个答案是完整答案的重要部分,谁反对这个,为什么?它没有解释任何关于 OP 问题的常规堆栈,但正确解释了 FPU“堆栈”部分,它本身在技术上应该足以消除 OP 的困惑并解决他们的问题。 (关于这个答案的质量,甚至可能显示fstfstp 之间的差异是有道理的)(edit2:我也不会对 OP 投反对票,这有点费力,因为在阅读了 FPU 第一次,但这个问题相当清楚地表明了混淆是什么,以及尝试了什么)
    • 这个答案肯定它值得反对。因为虽然它不像来自 PeterCordes 的那样完整,但我会对其进行投票。尽管如此,我还是非常感谢@Brendan 在这方面付出的时间和精力:)
    • 问题是为什么需要sub esp,而不是 FPU 堆栈的工作原理。这个答案没有提供任何洞察力。但这本身并没有错。
    猜你喜欢
    • 1970-01-01
    • 2013-11-22
    • 2013-06-18
    • 1970-01-01
    • 1970-01-01
    • 2012-10-12
    • 1970-01-01
    • 2015-11-20
    相关资源
    最近更新 更多