ModRM:不能使用直接地址,汇编 x86

ModRM: Can't use direct address, assembly x86

我不明白为什么我会收到一条非法指令,其中有一段代码在 C 程序中实现为程序集。我解释一下。

我在特定地址有一个值,我想将该值移动到 EAX x86 寄存器中,例如

MOV EAX, [address]

在 C 代码中,我有指向该地址的指针。这个指令的二进制代码我是这样准备的:

MOV OpCode:

 7 6 5 4 3 2 1 0
|_|_|_|_|_|_|D|S|

D = 1 --> I want REG as destination
S = 1 --> 32 bit operand

                 D S
--> |1|0|0|0|1|0|1|1| --> 0x89


Mod R/M:

 7 6 5 4 3 2 1 0
|_|_|_|_|_|_|_|_|

--> MOD = |7|6|    bits
--> REG = |5|4|3|  bits
--> R/M = |2|1|0|  bits

MOD = Indirect  --> |0|0|
REG = EAX = 0   --> |0|0|0|

地址不在寄存器中,我想将内存数据移动到 EAX 寄存器。我有内存位置的值,所以:

R/M = |1|1|0| --> direct

--> |0|0|0|0|0|1|1|0| --> 0x06

Address = [byte0] [byte1] [byte2] [byte3]

--> 0x8b 0x06 [byte3] [byte2] [byte1] [byte0] 0xc3

这样我得到了一条非法指令,但我不明白为什么。 如果我使用 R/M = 101 我得到 0x8b 0x05 [byte3] [byte2] [byte1] [byte0] 0xc3 并且一切正常。

那么,问题出在哪里呢?有人可以解释 MOD=00, R/M=110 这个 table 的用法吗? 直接???

MOD=00,R/M=101 有效,但在 table 我看到从 DI 计算的内存位置,但我有一个直接内存位置...

注意:代码在 Ubuntu 20.04(64 位)上使用 -m32 gcc 开关编译

好的,我用错了 table。我从英特尔网站上找到了正确的 table。感谢您的快速回答

关键问题是您正在查看 16 位 modr/m table 而汇编 32 位 mode。每个操作 mode 都有自己的寻址 mode 并且 modr/m 和 SIB 字节在内存操作数的编码方式上有所不同。

至于32位mode,直接寻址选择mod = 00,r/m = 101。所以正确的modr/m字节是:

 00 000 101 XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX
mod reg r/m ---------- displacement -----------

其中reg=000(编码eax)和mod=00,r/m=101编码直接地址。这是您已经观察到的正在工作的 05 modr/m 字节。

在 16 位 mode 中,直接寻址改为 mod = 00,r/m = 110,位移 2 字节,给出 modr/m 字节06.

请注意,在 16 位和 32 位操作 modes 中,您可以通过提供 67 地址大小覆盖前缀在 16 位和 32 位 modr/m 字节之间切换。

作为您特定用例的替代选项,您还可以将 A1 操作码用于 mov eax, off32。此操作码直接采用 32 位地址加载,没有 modr/m 字节,但寻址 mode 和目标寄存器都是硬编码的。