【问题标题】:NASM x87 FILD instructionNASM x87 FILD 指令
【发布时间】:2015-02-11 01:52:22
【问题描述】:

我正在阅读this,我注意到他们的示例显示 fld 将值加载到不同的位置(st0、st1,然后返回到 st0),而没有指定加载到的位置。我假设 fild 以类似的方式工作,因为它只是加载整数的方法(或者这是我的理解),但我可能是错的。所以我的问题是:fld,更具体地说,将值加载到哪里?是否有一个参数来指定要使用哪个 fpu 寄存器,或者它只是循环通过 8,还是我缺少一些完全不同的方式?

我专门使用的代码是尝试将 3 个数字相乘。我假设一种方法是加载到 st0,然后加载到 st1,然后加载到 st2,然后是 fmul st0 和 st1(导致 st0),然后是 fmul st0 和 st2。代码如下:

mov dword [ebp-8], 4
mov dword [ebp-12], ecx
fild dword [ebp-4]
fild dword [ebp-8]
fild dword [ebp-12]
fmul st0, st1
fmul st0, st2
fistp dword [ebp-8]
mov eax, dword [ebp-8]

ecx = 5 和 [ebp-4] = 5

此代码崩溃,使用 OllyDbg 我看到在 00000069 处存在访问冲突,但当前未包含在任何寄存器中。

所以是的,有没有办法指定 fild 加载值的位置,有没有一种很好的方法来确定它们应该去哪里,如果我在循环中运行它会改变什么吗?

--编辑 3-- 大部分是固定的,有点。一件大事是fmul 不会将值推送到st0,它只是覆盖st0 中的任何内容。新代码:

mov dword [ebp-8], ecx
fild dword [ebp-4]
fild dword [four]
fild dword [ebp-8]
fmul st1
fmul st2
fistp dword [ebp-12]
mov eax, dword [ebp-12]

这个循环和递减直到ecx == 2,然后尝试fild 2 和 1 给出与之前相同的bad -NAN FFFF C0000000 00000000。我不确定 3 与 2 或 1 有何不同(除了更小),但那是它开始给出错误值的时候。我应该注意到 ERROR_MOD_NOT_FOUND 被抛出了,虽然我不太确定这意味着什么,因为所有的 cpu 和 fpu 寄存器都应该可以访问。

--编辑 2-- 修复了 Parham Alvani 与文档内容一起展示的流行内容:

mov dword [ebp-8], ecx      ; moves eax (starts as 5) into local var (fild can't take a cpu register)
fild dword [ebp-4]          ; starts as 5, moves down with outer loop
fild dword [four]           ; the integer 4
fild dword [ebp-8]          ; starts as 5, moves down with inner loop
fmul st0, st1               ; 0 := 0 * i
fmul st0, st3
fistp dword [ebp-12]        ; move st0 to local var
mov eax, dword [ebp-12]     ; move local var to eax

这将推 5,然后推 bad -NAN FFFF C0000000 00000000 两次。 fmul 似乎没有做任何事情(可能是因为价值观不好)。有没有更好的方法来加载值?似乎我对fild 做错了什么,但根据第一个链接中提供的示例,以及here 的定义,fild 只是将你给它的任何东西推到 st0 上。

--编辑 1-- 正如 Jester 建议的那样,我现在每个循环都弹出 fpu 堆栈:

mov dword [ebp-8], 4
mov dword [ebp-12], ecx
fild dword [ebp-4]
fild dword [ebp-8]
fild dword [ebp-12]
fmul st0, st1
fmul st0, st2
fstp st2
fstp st1
fistp dword [ebp-8]
mov eax, dword [ebp-8]

此代码仍然崩溃。 00000009 处的访问冲突,st0-4 为 0,st5 = 100,st6 = 4,st7 = 4

【问题讨论】:

  • x87 FPU 是基于栈的,FILD 将一个新值推送到栈顶,通常称为st0。因此之前的st0 变为st1,以此类推。在循环中也要小心,因为你只有 8 个寄存器。我不知道那本维基书,但我可以推荐simply FPU tutorial
  • 那我怎么弹出?这是fistp的作用吗?
  • 是的,大多数指令中的P 表示弹出。也有非爆裂变体。
  • 我可以直接弹出值而不存储在某个地方吗?
  • 是的。你可以fstp st0。另请参阅 this question 和指令集参考。

标签: assembly nasm x87


【解决方案1】:

您的原始代码是正确的。 fldfild 将加载的值推送到 x87 堆栈上。此推送始终将值放入 st0,将 st0 的旧值移动到 st1,将旧 st1 移动到 st2,依此类推。

fild dword [ebp-4]   ; st0 = x
fild dword [ebp-8]   ; st1 = x, st2 = y
fild dword [ebp-12]  ; st2 = x, st1 = y, st0 = z
fmul st0, st1        ; st2 = x, st1 = y, st0 = z * y
fmul st0, st2        ; st2 = x, st1 = y, st0 = z * y * x
fistp dword [ebp-8]  ; st1 = x, st0 = y

您的代码可能会崩溃,因为ebp 指向一个错误的地方,或者因为您的代码的另一部分(而不是您发布的部分)有错误。你没有说哪个指令崩溃了。在崩溃的那一刻,程序计数器(pc)指向崩溃指令。

我将你的代码放在一个小程序中,并在我的 OpenBSD/amd64 机器上的 gdb 中成功运行。

section .data
    dd 0
    dd 0
    dd 5
space:

section .text
global main
main:
    mov ebp, space
    mov ecx, 5
    mov dword [ebp-8], 4
    mov dword [ebp-12], ecx
    fild dword [ebp-4]
    fild dword [ebp-8]
    fild dword [ebp-12]
    fmul st0, st1
    fmul st0, st2
    fistp dword [ebp-8]
    mov eax, dword [ebp-8]
    int 3

组装和运行:

$ nasm -felf64 fmul3.s && gcc -nopie -o fmul3 fmul3.o 
$ gdb fmul3
...
(gdb) run
...
Program received signal SIGTRAP, Trace/breakpoint trap.
...
(gdb) x/3wd (char *)&space - 12
0x601000 <__data_start>:        5       100     5
(gdb) print (int)$rax
$1 = 100

程序成功乘以 5 * 4 * 5 = 100。

【讨论】:

  • 提一下通常的调用约定要求 x87 堆栈在调用/返回时为空(或返回值在 st0 中)可能是个好主意。这段代码在 x87 堆栈中留下了一些值,所以重复运行会溢出它(如果我没记错的话,会导致 NaN)。鉴于有关使用 fstp st0 在循环中弹出 FP 堆栈的更新,我猜 OP 已经弄清楚了。
猜你喜欢
  • 2012-11-23
  • 2013-12-08
  • 2021-12-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-23
  • 2011-12-29
相关资源
最近更新 更多