装配x86 FPU - 堆栈混乱

时间:2018-04-15 23:48:37

标签: assembly x86 nasm cpu-registers x87

我想了解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“腾出空间”,否则程序会崩溃。但是通过这样做,我改变了“常规堆栈”的指针,这对于我对两个独立堆栈的想法没有意义。

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

2 个答案:

答案 0 :(得分:5)

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中,然后在尝试获取代码时出现段错误从那个坏地址,或那里的字节解码到segfault的指令。

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

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

请注意,add esp,4 / sub esp, 8有点傻。 add esp, +4 - 8(即add esp, -4)将通过一条指令自我记录。

答案 1 :(得分:1)

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

本质;有8个寄存器(让我们称之为FPU_R0FPU_R1,...,FPU_R7)和8个名称(假设它们是st0,{{1} },...,st1),并且有一个“FPU堆栈顶部”值,用于确定哪个名称用于哪个寄存器。

您可以将新值推送到FPU寄存器堆栈。例如:

st7

您可以从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