在 FPU 中访问 Tag Word 的正确方法是什么?

What is the correct way to access Tag Word in FPU?

汇编是我学习的第一门编程语言 我已经阅读了 Intel IA-32 手册和 http://www.ray.masmcode.com/tutorial/fpuchap3.htm#fstenv sections related to the FPU. I found the memory layout for 32-bit:

根据上面的图表,标记字在地址 8 上占用 16 位。 不幸的是,Intel 指南和 ray 指南都使用了语法示例。

我的问题是,当我在第二次循环中将 fld $Tvalueinc[ecx] 加载到 st(0) 时,我 WAS 得到 1#IND。

为了找出我得到#1INF 的原因,我四处寻找,发现您可以使用 TAG WORD 寄存器查看 FPU 状态(http://www.ray.masmcode.com/tutorial/fpuchap1.htm)。

使用这段代码:

fstenv [ebx-16]
fwait
fldenv [ebx-16]
mov    eax, [ebx-16]

如果代码有效,数据应该在每个循环中改变,但它保持不变。

我在尝试访问 FPU 时做错了什么? 您不必担心我的代码的重点,只是为什么我为什么无法访问 TAG WORD。

    .386
.model flat, stdcall
option casemap :none  

includelib \masm32\lib\msvcrt.lib
sprintf proto C :vararg
includelib \masm32\lib\user32.lib 
MessageBoxA proto :ptr,:ptr,:ptr,:DWORD
includelib \masm32\lib\kernel32.lib
ExitProcess proto :dword 

.data
   _title db "Result",13,10,0
   $interm db "%0.4f","+","%0.5f",13,10,0
   Aval REAL8 1.000
   Bval REAL8 -2.000
   Cval REAL8 19.000
   _fourval REAL8 4.000
   $Tvalueinc REAL4 1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0
   $sampleval real10 4478784.0
   squareroot dq ?
   $prevCW dw ?
   $Tagword dd ?




.code
main PROC
LOCAL szBuf[9]:byte
fstcw $prevCW
fwait
fld Bval ;  [loads first instance of b]]
fmul Bval ; [b*b = b^2]
fld Aval ;[Load a (a*c)]
fmul Cval ;(a*c)
fmul _fourval ;[4*a*c]
fsubp;[b^2-4*a*c]
ffree st(7)
ftst ;compare ST(0) with 0.0
fstsw ax ;[store camparison results in ax]
fwait;wait
sahf ;transfer flags from AH register
mov ecx, 04h
fld $sampleval

jb _negative ;jump if <0
fsqrt ;sqrt(b^2-4*a*c)
jmp Finished



_negative:


$repeat:
mov ax, $prevCW


push eax
fld $Tvalueinc[ecx]
fstenv [ebx-16]
fwait
fldenv [ebx-16]
mov eax,[ebx-16]
fdiv
fldcw [esp]

fstsw ax

FRNDINT
fldcw $prevCW
pop eax


jne $repeat






Finished:  

   fstp squareroot
   mov eax, dword ptr squareroot
   mov edx, dword ptr squareroot[4h]
   invoke sprintf, addr szBuf, offset $interm, eax, edx
   invoke MessageBoxA, 0, addr szBuf, offset _title, 0
   invoke ExitProcess, 0



main ENDP
END main

注意:我使用 Visual Studio 2017 并且调试器出于某种原因保留显示的弹出值。

根据您链接的图表,控制字和状态字存储在标记字之前。 (但请注意,这是实模式布局。您 运行 处于 32 位保护模式。(如果您 运行 32 位代码处于 64 位 OS,但它本质上与 32 位 OS 下的 32 位保护模式相同。mov eax, [ebx-16] 将加载控制字,加上高 16 位中的任何内容。(IDK if它保证为零或不为零。)

尝试movzx eax, word ptr [ebx - 8]加载从ebx-16开始计数的第3个四字节双字的低半部分。

fstenv    [ebx-16]
;fwait                  ; you don't need this
;fldenv    [ebx-16]     ; you don't need to do this.
mov eax,  [ebx-16]      ; loads the x87 control word into eax, plus padding

试试这个:

fnstenv     [ebx-16]
movzx       eax, word ptr [ebx-16 + 8]  ; should be the tag word if that's the right diagram

FP 异常(如异常或无效结果)在进程启动时默认全部被屏蔽,因此它们仅在 x87 状态字中设置位,而不是在您执行诸如除以零之类的操作时实际跳转到异常处理程序。由于 fstenv 仅通过屏蔽所有异常来修改 FP 环境,因此您无需使用 fldenv.

恢复它

Assembly is the first programming language I am learning

我强烈建议您学习一些 C,这样您就可以轻松查看 C 编译器 asm 输出,了解编译器如何执行您在 C 中理解的操作。(例如 http://gcc.godbolt.org/

另外,我建议不要在 x87 FP 上花太多时间,因为它很奇怪而且过时了。 x86 中没有其他任何东西使用带有标签的寄存器堆栈来说明哪些是有效的。现代 x86 代码使用 SSE2 指令进行 FP 数学运算。

请参阅 标签 wiki 以获得许多好的链接。

I use Visual Studio 2017 and the debugger keeps popped values shown for some reason.

它应该向您显示寄存器是否被标记 "free" 或正在使用中的某种指示。

将寄存器标记为空闲与更改其中的值是分开的。 x87 堆栈使用标签来跟踪哪个底层寄存器实际上是当前堆栈的顶部,以及哪些是空闲的。修改这些标签不会清除数据,我想 fstenv 仍然会将值存储到调试器可以获取它的内存中。

My issue is that I WAS getting 1#IND when I load fld $Tvalueinc[ecx] into st(0) during the 2nd loop.

IIRC,当 x87 堆栈已满时会发生这种情况。