我在一个程序中有这个指令:
FSTENV (28-BYTE) PTR SS:[ESP-1C]
它有什么作用?
它使用和更新哪些寄存器?
谢谢!
它存储浮点环境。其中包括:当前控制字、状态字、标签字、指令指针和操作数指针。这些存储在内存中的结构中。在 16 位模式下,该结构为 14 个字节。在 32 位模式下,它是 28 个字节。我完全不确定它在 64 位模式下是否可用(64 位模式主要使用 SSE 代替)[编辑:显然在 32 位和 64 位模式下操作相同。]
我不相信它会改变协处理器的任何当前状态[编辑:哎呀——确实如此,它掩盖了 FP 异常,但大多数人从不从一开始就取消掩盖它们,所以...]——但是当你使用fldenv
,这会将状态恢复到您用于fstenv
存储它时的状态。
Jerry Coffins 答案是正确的。
如果您想知道(28-BYTE) PTR SS:[ESP-1C]
:
这是存储 FP 环境的有效地址,它指定命令的 28 字节版本,并指向堆栈段中堆栈指针下方的 28 (0x1c) 字节。
我只是添加了英特尔的官方描述,这是我使用搜索引擎找到的。
描述
将当前 FPU 操作环境保存在目标操作数指定的内存位置,然后屏蔽所有浮点异常。FPU 操作环境由 FPU 控制字、状态字、标签字、指令指针、数据指针和最后操作码组成。IA-32 英特尔® 架构软件开发人员手册第 1 卷中的图 7-13 到 7-16 显示了存储环境在内存中的布局,具体取决于处理器的操作模式(受保护的或真实的)和当前操作数-size 属性(16 位或 32 位)。在虚拟 8086 模式下,使用实模式布局。
FSTENV 指令在存储 FPU 环境之前检查并处理任何未决的未屏蔽浮点异常;FNSTENV 指令没有。保存的图像反映了指令流中FSTENV/FNSTENV指令之前的所有浮点指令都已执行后FPU的状态。
这些指令经常被异常处理程序使用,因为它们提供对 FPU 指令和数据指针的访问。环境通常保存在堆栈中。保存环境后屏蔽所有异常可防止浮点异常中断异常处理程序。英特尔® 架构兼容性
在 MS-DOS* 操作系统兼容模式下运行 Pentium® 或 Intel486™ 处理器时,可能(在不寻常的情况下)在执行 FNSTENV 指令之前中断以处理未决的 FPU 异常。有关这些情况的描述,请参阅 IA-32 英特尔® 架构软件开发人员手册第 1 卷附录 D 中标题为“No-Wait FPU 指令可以在窗口中获取 FPU 中断”的部分。在 Pentium Pro 处理器上不能以这种方式中断 FNSTENV 指令。
手术
DEST[FPUControlWord) <- FPUControlWord;
DEST[FPUStatusWord) <- FPUStatusWord;
DEST[FPUTagWord) <- FPUTagWord;
DEST[FPUDataPointer) <- FPUDataPointer;
DEST[FPUInstructionPointer) <- FPUInstructionPointer;
DEST[FPULastInstructionOpcode) <- FPULastInstructionOpcode;
受影响的 FPU 标志
C0、C1、C2 和 C3 未定义。
浮点异常
没有任何。
保护模式异常
GP(0) - 如果目标位于不可写段中。如果内存操作数有效地址超出 CS、DS、ES、FS 或 GS 段限制。如果 DS、ES、FS 或 GS 寄存器用于访问内存并且它包含一个空段选择器。
SS(0) - 如果内存操作数有效地址超出 SS 段限制。
NM - CR0 中的 EM 或 TS 已设置。
PF(fault-code) - 如果发生页面错误。
AC(0) - 如果启用对齐检查并在当前特权级别为 3 时进行未对齐的内存引用。实地址模式异常
GP - 如果内存操作数有效地址超出 CS、DS、ES、FS 或 GS 段限制。
SS - 如果内存操作数有效地址超出 SS 段限制。
NM - CR0 中的 EM 或 TS 已设置。虚拟 8086 模式异常
GP(0) - 如果内存操作数有效地址超出 CS、DS、ES、FS 或 GS 段限制。
SS(0) - 如果内存操作数有效地址超出 SS 段限制。
NM - CR0 中的 EM 或 TS 已设置。
PF(fault-code) - 如果发生页面错误。
AC(0) - 如果启用对齐检查并进行未对齐的内存引用。
只是为了完整起见,这里是FSTENV
/FNSTENV
指令产生的内存布局。它在 x86-64"Long 64-bit Mode"
和 x86中是相同的"32 bit compatibility mode"
(除非您在 x86-64 中添加前缀66h
。)
引用这个笨重的英特尔文档,这是布局:
(顺便说一句,上面的图像也应该被命名为“长模式”。)
因此,如果我们以长 64 位模式运行实际测试:
FNSTENV
并在具有以下上下文状态的指令后立即中断:
它返回的内存布局如下:
我不会隐瞒,这很奇怪。(从截断的 RIP 寄存器到看起来很奇怪的操作码。)但我猜出于遗留原因,它仍然受支持。
好消息是,在编写现代 x64 代码时,这些古老的 x87 FPU 指令如今已没有多少用途。在这种情况下,您绝对应该坚持使用XMM0
直通XMM15
寄存器及其相关指令来满足您的浮点需求。
我想用不同的方法来回答这个问题。
FSTENV 不是“真正的”指令。
搜索“FNSTENV”操作码时可能会更幸运。
仔细查看编码(来自Intel SDM):
FSTENV 9B D9 /6
FNSTENV D9 /6
看到前面的“9B”了吗?
它实际上是“FNSTENV”前面的“FWAIT”。
因此,“FSTENV”与许多其他指令一样,只是大多数汇编程序和反汇编程序都可以理解的约定。
英特尔手册确实提到了这个特性,但你不应该期望它在 100% 的情况下是准确的,有时它可能会忽略这些细节:
FSTENV/FNSTENV — 存储 x87 FPU 环境(第 2A 卷 3-393)
汇编器为 FSTENV 指令发出两条指令(一条FWAIT指令后跟一条 FNSTENV 指令),处理器分别执行这些指令中的每一条。如果这些指令中的任何一条产生了异常,则保存 EIP 会指向导致异常的指令。
有很多这样的“特殊”指令。
例如,您可能会对x86 中有多少 NOP感到惊讶,它们通常是其他指令的别名。
英特尔 XED可以在您的斗争中派上用场,原因如下:
Go asm有一个名为x86.csv的东西,它以英特尔 SDM 方式列出 x86 指令,但有时还包含附加信息。
如果您将其 grep 为“FSTENV”,您将看到与之关联的“伪”标签。
请注意,x86.v0.2.csv 可能会遗漏一些说明,尤其是来自较新扩展的说明(不过,这将在 v0.3 中修复)。