先看 2 个例子:
fadd st(0), st(1) |
与
fadd dword ptr [eax] |
这 2 条指令只是 operand 不同,指令的 opcode 都是相同的。
第 1 条指令的编码为: d8 c1
d8 |
11 |
000 |
001 |
opcode |
mod |
reg |
r/m |
ModRM = c1 |
第 2 指令的编码为:d8 00
d8 |
00 |
000 |
000 |
opcode |
mod |
reg |
r/m |
ModRM = 00 |
和通用指令的寻址模式一样。
ModRM.mod = 11 提供 registers 寻址,此时, ModRM.r/m = 001 则提供的是 st(1) 浮点寄存器。
ModRm.mod = 00 提供的是 [base] 这种 memory 寻址模式,ModRM.r/m = 000 是 [eax] 操作数。
在 x87 浮点指令编码中,ModRM 字节同时提供 2 个作用:
可以看出:这和通用指令编码中的 Group 属性 opcode 的指令没什么两样。
下面这个表格列出 ModRM.reg 的定位作用
opcode |
ModRM.reg |
指令 |
D8 |
000 |
fadd |
001 |
fmul |
|
010 |
fcom |
|
011 |
fcomp |
|
100 |
fsub |
|
101 |
fsubr |
|
110 |
fdiv |
|
111 |
fdivr |
而 Opcode 码大部分只是起分门别类作用,实际由 ModRM.reg 进行确定指令,如上面的表格显示,opcode 为 D8 ,而 ModRM.reg 用来确定最终的指令。
ModRM.mod |
ModRM.r/m |
registers |
11 |
000 |
st(0) |
001 |
st(1) |
|
010 |
st(2) |
|
011 |
st(3) |
|
100 |
st(4) |
|
101 |
st(5) |
|
110 |
st(6) |
|
111 |
st(7) |
可见 x87 指令中的 ModRM 与通用指令中的 ModRM 作用是一样的。只是寄存器不同。
在这种情况下,x87 指令的 operand 是 memory 操作数,和 通用指令中的内存寻址是完全一样的。
有关 ModRM 寻址,详见:ModRM 寻址模式
x87 指令的 operands 寻址分三种情况:
只有当 ModRM.mod = 11 时,才会有无 operand 时的情况,而当 ModRM.mod ≠ 11 时,需要为指令提供 memory 操作数。
无 operand 时,指令 opcode 由 ModRM.reg 与 ModRM.r/m 联合确定。
fldz |
上面这条指令是典型的无 operand 指令,它的 opcode 组成部分如下:
d9 |
11 |
101 |
110 |
opcode |
mod |
reg |
r/m |
ModRM = ee |
其中 ModRM.reg = 101 配合 ModRM.r/m = 110 来确定 opcode
x87 指令中单 operand 的情况下,这个 operand 是 memory 操作数。
当 ModRM.mod ≠ 11 时,ModRM.r/m 提供 memory 寻址,前面已经提到:这个 memory 操作数寻址与通用指令中的 memory 操作数寻址是完全一样的。
fld dword ptr [rax] |
上面这条指令是典型的单 operand 指令,它的 opcode 组成部分如下:
d9 |
00 |
000 |
000 |
opcode |
mod |
reg |
r/m |
ModRM = 00 |
这条指令的最终编码是: d9 00
对于 memory 操作数,legacy prefix 与 REX prefix 都可以使用。
fld dword ptr gs:[r10] |
这条指令使用了 segment override prefix 和 REX prefix,它的编码组成部分如下:
65 |
0100 |
0 |
0 |
0 |
1 |
d9 |
00 |
000 |
010 |
legacy prefix |
4 |
W |
R |
X |
B |
opcode |
mod |
reg |
r/m |
REX.[4WRXB] = 41 |
ModRM = 02 |
这条指令最终的编码是:65 41 d9 02
这 2 个 operands 都是浮点寄存器,且其中 1 个 operand 必定是 st(0) 寄存器,另 1 个 operand 则由 ModRM.r/m 提供寻址。
fadd st(0), st(1) |
上面这条指令两个 operand 都是 register,其中 destination 操作数是 st(0), source 操作数是 st(1)
它的 opcode 组成部分如下:
d8 |
11 |
000 |
001 |
opcode |
mod |
reg |
r/m |
ModRM = c1 |
这条指令的最终编码是:d8 c1
x87 浮点寄存器在 16 位、32位以及 64 位模式下宽度都是一样的。
但是对于 memory 操作数来说,情形和通用指令一样,分为 16 位地址模式、32 位地址模式以及 64 位地址模式。取决于指令的 effective address-size
以指令编码 d9 00 为例,在不同模式下的表现形式:
指令编码 |
模式 |
指令形式 |
描述 |
d9 00 |
16 位 |
fld dword ptr [bx+si] |
16 位地址模式 |
32 位 |
fld dword ptr [eax] |
32 位地址模式 |
|
64 位 |
fld dword ptr [rax] |
64 位地址模式 |
和通用指令一样,对于 memory 寻址上可以进行 address-size override 操作。
指令 fld dword ptr [eax] 在 16 位模式下,它的编码是:67 d9 00 (使用 67 进行 address size override)
以指令 fadd st(0),st(1) 为例,在不同模式下的编码是:
指令 |
模式 |
指令编码 |
描述 |
fadd st(0),st(1) |
16 位 |
d8 c1 |
寄存器宽度不变 |
32 位 |
d8 c1 |
||
64 位 |
d8 c1 |
浮点寄存器没有宽度之分,在哪个模式下都是一样的。
如同 mmx 与 xmm 寄存器一般。
x87 指令编码方式与通用指令完全一致的。但是 x87 指令没有 immediate 域, 是因为 x87 指令不支持立即数寻址。
从上一篇的 x87 指令格式中了解到:opcode 与 ModRM 是必须的。这一点和通用指令有些区别:在通用指令中 ModRM 字节是可选的。
mik 写于 2009-05-17 00:38