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