80386汇编-浮点处理器
9unk Lv5

简介

浮点处理器也称x87处理器,在 486 之前是和 386 处理器分开的。到了 486 之后就把浮点处理器集成到了 x86 中。浮点处理器的功能就是处理浮点数的运算。浮点数运算主要是了解一些浮点数指令,在逆向过程中这块内容基本不会关注,只要知道浮点数结果是多少就行。

浮点数二进制表示

浮点数由三部分构成:符号、尾数和指数。以数字 -123.154 为例,该校书分解成 -1.23154 x 10^2为例,其中 “-“是符号,表示该浮点数是负数,1.23154是尾数,2是指数。

IEEE 二进制浮点数的表示

1

符号

二进制浮点数的符号由一个符号位表示,如果该位为 1 表示附属,为 0 表示正数。浮点数 0 是正数。

尾数

2
3

尾数的精度

4

指数

5

二进制浮点数的正规化

6

IEEE 表示法

7
8
9
10

把十进制分数转换为二进制实数

11
12

浮点单元

13

浮点寄存器栈

14
15

浮点数据寄存器

16
17

特殊寄存器的用途

18

近似

FPU 在进行浮点计算时试图产生准确的结果,步过在许多情况下这是不可能的,因为目的操作数根本就不能准确表示计算的结果。例如:假设某种存储格式只允许3个小数位,这种格式就只能存储 1.011 或 1.101,但不能存储1.0101。如果计算产生的精确结果是 +1.0111(十进制数1.4275),我们就必须通过加 .0001 或减去 .0001 向上或向下近似:
(a) 1.0111 —> 1.100
(b) 1.0111 —> 1.011

如果精确结果是负数,那么加 -.0001 会使近似值趋向 -∞,减去 -.0001 会使近似值趋向 0 和 +∞

19

浮点异常

20
21

浮点指令集

22

初始化(FINIT)

FINIT指令初始化浮点单元,把 FPU 的控制字设为 037Fh,掩盖所有的浮点异常,把近似方法设置为最接近的偶数,并把计算精度设为 64 位。一般程序的开始会调用 FINIT,使 FPU 处于一个固定的初始状态。

浮点数据类型

在定义 FPU 指令使用的数据类型时,会用到这些类型的数据。例如:在加载一个浮点变量至 FPU 堆栈时,变量可定义为 REAL4,REAL8,REAL10。
23

加载浮点值(FLD)

FLD(加载浮点值)指令复制一个浮点数至 FPU 的栈顶 [即ST(0)],操作数可以是 32 位、64 位、或 80 位的内存操作数(REAL4、REAL8、REAL10)或另外浮点寄存器:

1
2
3
4
FLD m32fp
FLD m64fp
FLD m80fp
FLD ST(i)

内存操作数类型:FLD 支持的内存操作数的类型和 MOV 是一样的。下面的一些例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
;---------------------------
;程序名:fld.asm
;功能:演示 FLD 加载浮点值指令
;作者:9unk
;编写时间:2023-4-25
;----------------------------
INCLUDE Irvine32.inc

.data
array REAL8 10 DUP(?)
dblOne REAL8 234.56
dblTwo REAL8 10.1

.code
main PROC
fld array
fld [array+16]
;fld REAL8 PTR[esi]
;fld array[esi]
;fld array[esi*8]
;fld array[esi*TYPE array]
;fld REAL8 PTR[ebx+esi]
;fld array[ebx+esi]
;fld array[ebx+esi*TYPE array]

;加载两个直接操作数至FPU堆栈
fld dblOne
fld dblTwo
main ENDP
END main

24

FILD 指令

FILD 指令把 16 位、32位、64位的整数源操作数转换成双精度浮点数并把其加载到 ST(0),源操作数的符号位保留。FILD支持的内存操作数类型(间接操作数、变址操作数、基址变址操作数等)同 MOV 指令。

加载常量: 下面的指令在堆栈上加载特定的常量,这些指令无操作数:

  • FLD1 指令在寄存器堆栈上压入 1.0
  • FLDL2T 指令在寄存器堆栈上压入 1b 10(即log2 10)
  • FLDL2E 指令在寄存器堆栈上压入 1b e(即log2 e)
  • FLDPI 指令在寄存器堆栈上压入 Π(圆周率)
  • FLDLG2 指令在寄存器堆栈上压入 lg 2(即log10 2)
  • FLDLN2 指令在寄存器堆栈上压入 ln 2(即log2 e)
  • FLDZ 指令在寄存器堆栈上压入 0.0(把 +0.0 压入压入堆栈中)

存储浮点值(FST、FSTP)

FST 指令(存储浮点值)复制FPU的栈顶的操作数至内存中,操作数可以是 32 位、64 位或 80 位的内存操作数(REAL4、REAL8、REAL10)或另外一个浮点寄存器:

1
2
3
FST m32fp
FST m64fp
FST ST(i)

FST 不会弹出栈顶元素,下面的指令把 ST(0) 存储到内存中,如下面的例子 ST(0) 等于 10.1 并且 ST(1) 等于 234.56:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
;---------------------------
;程序名:fst.asm
;功能:演示 FST 把 ST(0) 存储到内存中
;作者:9unk
;编写时间:2023-4-26
;----------------------------
INCLUDE Irvine32.inc

.data
dblOne REAL8 234.56
dblTwo REAL8 10.1
dblThree REAL8 0.0
dblFOur REAL8 0.0

.code
main PROC
FINIT ;浮点单元初始化
;加载两个直接操作数至FPU堆栈
fld dblOne
fld dblTwo

;FST存储浮点值
fst dblThree
fst dblFOur
main ENDP
END main

FSTP: 复制 ST(0) 至内存并弹出 ST(0),如下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
;---------------------------
;程序名:fst.asm
;功能:演示 FSTP 把 ST(0) 存储到内存中,并弹出ST(0)
;作者:9unk
;编写时间:2023-4-26
;----------------------------
INCLUDE Irvine32.inc

.data
dblOne REAL8 234.56
dblTwo REAL8 10.1
dblThree REAL8 0.0
dblFOur REAL8 0.0

.code
main PROC
FINIT ;浮点单元初始化
;加载两个直接操作数至FPU堆栈
fld dblOne
fld dblTwo

;FST存储浮点值
fst dblThree
fst dblFOur

;FSTP 存储浮点值
fstp dblThree
fstp dblFOur
main ENDP
END main

执行后,从逻辑上来讲两个值已经从堆栈上移除了。从物理上来讲,每次 FSTP 指令执行后,TOP指针增1,改变了 ST(0) 的位置。

FIST:把 ST(0) 中的值转换成有符号数整数并把结果存储到目的操作数中,值可以存储在字或双字中。FIST 支持的内存操作数格式同 FST

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
;----------------------------------------------------
;程序名:fst.asm
;功能:演示 FIST 把 ST(0) 中的值转换成有符号整数并把结果存储到目的操作数中,值可以存储在字或双字中。
;作者:9unk
;编写时间:2023-4-26
;----------------------------------------------------
INCLUDE Irvine32.inc

.data
dblOne REAL8 234.56
dblTwo REAL8 10.1
dblThree REAL8 0.0
dblFOur REAL8 0.0
num dw 0

.code
main PROC
FINIT ;浮点单元初始化
;加载两个直接操作数至FPU堆栈
fld dblOne
fld dblTwo

;FST存储浮点值
fst dblThree
fst dblFOur

;FIST 存储浮点值
fist num
fstp dblFOur
fist num
main ENDP
END main

算数运算指令

基本的算数运算指令如下表所示:
25

算数运算指令支持的内存操作数类型同 FLD(加载)和 FST(存储),因此操作数类型可以是间接操作数、变址操作数、基址变址操作数等。

FCHS 和 FABS

26

FADD,FADDP,FIADD

27
28

FSUB,FSUBP,FISUB

FSUB 指令从目的操作数中减去源操作数,把差存储到目的操作数中。目的操作数是一个 FPU 寄存器,源可以是 FPU 寄存器或内存操作数,其操作数格式同FADD:

1
2
3
4
5
FSUB
FSUB m32fp
FSUB m64fp
FSUB ST(0),ST(i)
FSUB ST(i),ST(0)

29

FMUL,FMULP,FIMUL

30
31

FDIV,FDIVP,FIDIV

32

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
;----------------------------------------------------
;程序名:fdiv.asm
;功能:演示 FDIV 指令
;作者:9unk
;编写时间:2023-4-25
;----------------------------------------------------
INCLUDE Irvine32.inc

.data
dblOne REAL8 234.56
dblTwo REAL8 10.1
dblQuot REAL8 ?

.code
main PROC
FINIT ;浮点单元初始化
;加载两个直接操作数至FPU堆栈
fld dblOne
fdiv dblTwo
fstp dblQuot
main ENDP
END main

33

浮点值比较

浮点值比较不能使用 cmp 指令,应使用 FCOM 指令。
在执行完 FCOM 指令之后,使用条件跳转指令之前还要执行一些必须的指令。
FCOM,FCOMP,FCOMPP:指令比较 ST(0) 和 源操作数,源草组数可以是内存操作数或 FPU 寄存器,其格式如下:
34

条件码: C3,C2,C0 这三个 FPU 条件码标志说明了浮点值比较的结果,下表中的标题栏列出了各个浮点标志对应的 CPU 状态标志,这是因为 C3,C2,C0 分别与零标志、奇偶表示和进位标志在功能上类似。
35

例子:假如下 C++ 代码:

1
2
3
4
5
6
7
double X = 1.2;
double Y = 3.0;
int N = 0;
if(X < Y>)
{
N = 1;
}

反汇编:

36

在 C/C++ 中用的是 CMOISD 指令,COMISD 指令可以接受内存操作数,且比较结果会直接传到 EFLAGS 寄存器中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
;---------------------------
;程序名:fcmop.asm
;功能:演示浮点比较指令 fcmop
;作者:9unk
;编写时间:2023-5-4
;----------------------------
INCLUDE Irvine32.inc
.data
X REAL8 1.2
Y REAL8 3.0
N DWORD 0

.code
main PROC
fld X
fcomp Y
fnstsw ax ;把 FPU 状态字传送到 AX 寄存器中
sahf ;把 AH 复制到 EFLAGS 寄存器中
jnb L1
mov N,1
L1:
exit
main ENDP
END main

P6的改进: 对于前面的例子,需要注意的是:浮点数比较比整数比较运行时间开销更大,因此 Intel 的 P6 处理器引入了 FCOMI 指令,该指令比较两个浮点值并直接设置零标志、奇偶标志和进位标志。FOMI 的 格式如下:

FCOMI ST(0),ST(i)

下面使用 FCOMI 指令重写前面例子的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
;---------------------------
;程序名:fcmoi.asm
;功能:演示浮点比较指令 fcmoi
;作者:9unk
;编写时间:2023-5-4
;----------------------------
INCLUDE Irvine32.inc
.data
X REAL8 1.2
Y REAL8 3.0
N DWORD 0

.code
main PROC
;if(X<Y),注意 X 要放在 ST(0) 的位置
fld Y
fld X
fcomi ST(0),ST(1)
jnb L1
mov N,1
L1:
exit
main ENDP
END main

FCOMI 指令代替了前面例子中的三条指令,不过需要一套额外的 FLD 指令。FCOMI 指令不接受内存操作数。

比较是否相等

37

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
;---------------------------
;程序名:fequal.asm
;功能:演示比较两个浮点数是否相等
;作者:9unk
;编写时间:2023-5-4
;----------------------------
INCLUDE Irvine32.inc
INCLUDE macros.inc
.data
epsilon REAL8 1.0E-12
val2 REAL8 0.0
;val3 REAL8 1.001E-13
val3 REAL8 1.001E-12 ;如果使 val3 大于临界值,则val3和val2将不再相等

.code
main PROC
;if(X<Y),注意 X 要放在 ST(0) 的位置
fld epsilon
fld val2
fsub val3
fabs
fcomi ST(0),ST(1)
ja skip
mWrite <"Values are equal",0dh,0ah>
skip:
exit
main ENDP
END main

38

读写浮点值

39

例子程序:下面的例子程序在 FPU 堆栈上压入两个浮点值,然后显示,接下来读入两个用户输入的值,相乘并显示其乘积:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
;---------------------------
;程序名:floatTest32.asm
;功能:下面的例子程序在 FPU 堆栈上压入两个浮点值,然后显示,接下来读入两个用户输入的值,相乘并显示其乘积
;作者:9unk
;编写时间:2023-5-4
;----------------------------
INCLUDE Irvine32.inc
INCLUDE macros.inc

.data
first REAL8 123.456
second REAL8 10.0
third REAL8 ?

.code
main PROC
finit ;初始化 FPU
;压入两个浮点数到 FPU 栈中,并显示 FPU 栈
fld first
fld second
call ShowFPUStack
;输入两个浮点是并显示其乘积
mWrite "Please enter a real number: "
call ReadFloat

mWrite "Please enter a real number: "
call ReadFloat

fmul ST(0),ST(1) ;相乘
mWrite "Their product is: "
call WriteFloat
call Crlf

exit
main ENDP
END main

40

异常的同步

41
42

代码示例

43
44
45

混合模式运算

46
47

屏蔽和未屏蔽异常

浮点异常默认是屏蔽的,因此在浮点异常发生时,处理器给结果赋一个默认值,并继续安静地执行。

因此我在异常的程序这一块,试验的两个例子都没出现报错,我一开始还以为是系统比较新的原因。

如果在 FPU 控制字中未屏蔽异常,处理器将自动执行合适的异常处理程序。关闭异常屏蔽是通过清除 FPU 控制字中向合适的位完成的。想要关闭对除 0 异常的屏蔽,需要执行下面的步骤:

  1. 存储 CPU 控制字到一个 16 位变量中
  2. 清除位 2(除零标志位)
  3. 加载变量到控制字中

49
50

案例:屏蔽除零异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
;---------------------------
;程序名:floaterr.asm
;功能:浮点异常默认是屏蔽的,演示关闭浮点异常
;作者:9unk
;编写时间:2023-2-13
;----------------------------
INCLUDE Irvine32.inc
.data
ctrlWord WORD ?
val1 DWORD 1
val2 REAL8 0.0

.code
main PROC
fstcw ctrlWord ;获取控制字
and ctrlWord,1111111111111011b ;关闭对除零异常的屏蔽
fldcw ctrlWord ;加载会 FPU 中

fild val1
fdiv val2
fst val2
exit
main ENDP
END main

48

  • 本文标题:80386汇编-浮点处理器
  • 本文作者:9unk
  • 创建时间:2023-04-23 14:06:00
  • 本文链接:https://9unkk.github.io/2023/04/23/80386-hui-bian-fu-dian-chu-li-qi/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!