组装 x86 FPU - 堆栈混乱

Assembly x86 FPU - Stack Confusion

我想了解 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

对于 double,我必须将 sub esp, 8 行连接到 "make space",否则程序会崩溃。但是通过这样做,我改变了 "regular stack" 的指针,这与我的两个独立堆栈的想法没有意义。

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

FPU 有一个 "register stack"(而不是 RAM 中的堆栈)。

本质上;有 8 个寄存器(假设它们是 FPU_R0FPU_R1、...、FPU_R7)和 8 个名称(假设它们是 st0st1, ..., st7),还有一个 "top of FPU stack" 值决定哪个名称用于哪个寄存器。

您可以将新值压入 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]

x87 loads/stores 使用与其他一切相同的内存地址。 x87 堆栈是寄存器 st0..st7,根本不是内存。

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

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


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

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

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

使用调试器查看单步执行时寄存器和内存发生了什么。


请注意 add esp,4 / sub esp, 8 有点傻。 add esp, +4 - 8(即 add esp, -4)将是一种通过一条指令完成此操作的自我记录方式。