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 和目标寄存器都是硬编码的。
我不明白为什么我会收到一条非法指令,其中有一段代码在 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 和目标寄存器都是硬编码的。