组装 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_R0
、FPU_R1
、...、FPU_R7
)和 8 个名称(假设它们是 st0
、st1
, ..., 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
)将是一种通过一条指令完成此操作的自我记录方式。
我想了解 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_R0
、FPU_R1
、...、FPU_R7
)和 8 个名称(假设它们是 st0
、st1
, ..., 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
)将是一种通过一条指令完成此操作的自我记录方式。