MOV r/m8,r8和MOV r8之差,r/m8

Bit*_*tes 4 x86 assembly nasm

通过查看英特尔指令量,我发现了这个:

1)88 /r MOV r/m8,r8
2)8A /r MOV r8,r/m8

当我在NASM中写这样的一行时,用list选项组装它:

mov al, bl

我在列表中得到了这个:

88D8 mov al, bl

所以很明显NASM选择了上面两个的第一条指令,但不是第二条指令选项二吗?如果是这样,NASM在什么基础上选择了第一个?

old*_*mer 6

有不同编码的有趣副作用:来自A86手册.

  1. A86利用了可以为同一指令生成多个操作码集的情况.(例如,MOV AX,BX可以使用89或8B操作码生成,通过反转以下有效地址字节中的字段.两种形式在功能和执行速度上完全相同.)A86采用了不同的选择组合的情况.这会创建一个代码生成"足迹",在程序文件中不占用任何空间,但是如果A86生成了一个非平凡的目标文件,我将告诉并在法庭上进行演示.这种"足迹"的规范足够模糊和复杂,以至于不可能偶然复制.我声称对我选择的特定"足迹"拥有专有权,并禁止任何人复制它.这至少有两个具体含义:

    一个.任何复制"足迹"的汇编程序都是我的.如果未被确定为我的并且根据这些条款签发,那么出售或分发汇编程序的人将受到起诉.

    湾 任何标有"足迹"的程序都是由我的汇编程序生成的.它符合上述条件5.


fuz*_*fuz 5

这两种编码存在是因为一个 modr/m 字节只能编码一个内存操作数。所以要同时允许mov r8,m8mov m8,r8,需要两种编码。当然,通过这种方式,我们可以mov使用任何一种编码对两个操作数都是寄存器的使用进行编码,并nasm随机选择一个。选择没有特殊原因,我见过汇编程序做出不同的选择。

我还听说过一个汇编器,它通过以特定方式选择指令编码来对它组装的二进制文件加水印。通过这种方式,汇编程序的作者可以追查并起诉使用他的汇编程序而无需付费的人。

  • 我认为它是 as86 但到目前为止无法在网上找到正确的命中,如果我能找到的话,我在家里有一本(点阵)印刷手册...... (2认同)
  • 显然是A86 (2认同)

Mar*_*oom 5

正如哈罗德指出的那样:没有理由。
也许作者发现使用向前移动比反向移动更吸引人,或者他们只是拿起了第一个操作码。

我查看了 NASM 源代码,发现编码基本上是用一个大查找表完成的,所以这真的是一个品味问题。使用其他操作码(8AC3)将简化了代码(我猜的)如果解析未使用的查找表:指令喜欢addps是不对称的,并使用8A /rmov al, bl该代码可以重复使用来计算的MODR / M字节addps和类似的说明也。addps xmm0, xmm3使用相同的MODR / M字节(C3)作为mov al, bl所述时8A /r被使用。
请注意,寄存器A( B) 和xmm0( xmm0) 使用相同的数字进行编码。

然而,弄清楚为什么有两种编码仍然很有趣。


正如马克霍普金斯(重新)发现的那样,早期的 x86 指令编码在八进制中更有意义
八进制中的一个字节包含三个数字,我将其称为 GPF(组、操作、标志)。

G 是八进制组,同一组中的指令往往执行相似的任务(例如算术与移动)。
然而,这并不是一个严格的划分。
P是操作;例如,在算术组中,一个运算是减法,另一个是加法。
F 是用于控制操作行为的位集。每个组和操作随意使用数字 F,它甚至可能没有设置(例如 G=2,P=7 是mov r16, imm16,F 用于选择r16)。

对于mov从内存/寄存器移动到寄存器或相反的指令,G 为 2,P 为 1。
F 是一个 3 位字段,具有语义:

  2   1   0    bit
+---+---+---+
| s | d | b |
+---+---+---+

s = 1 if moving to/from a segment register
    0 if moving to/from a gp register

d = 1 if moving mem -> reg
    0 if moving mem <- reg

b = 1 if moving a WORD
    0 if moving a BYTE
Run Code Online (Sandbox Code Playgroud)

我们可以开始形成操作码,但我们仍然错过了选择操作数的方法。

G=2, P=1, F={s=0, d=0, b=0} 210 (88) mov r/m8, r8
G=2, P=1, F={s=0, d=0, b=1} 211 (89) mov r/m16, r16
G=2, P=1, F={s=0, d=1, b=0} 212 (8A) mov r8, r/m8
G=2, P=1, F={s=0, d=1, b=1} 213 (8B) mov r16, r/m16
G=2, P=1, F={s=1, d=0, b=0} 214 (8C) mov r/m16, Sreg
G=2, P=1, F={s=1, d=0, b=1} 215 (8D) Not a move, segment registers are 16-bit
G=2, P=1, F={s=1, d=1, b=0} 216 (8E) mov Sreg, r/m16
G=2, P=1, F={s=1, d=1, b=1} 217 (8F) Not a move, segment registers are 16-bit
Run Code Online (Sandbox Code Playgroud)

在操作码之后必须是 ModR/M 字节,用于选择寻址模式和寄存器。

ModR/M 字节可以被视为八进制的三个字段:XR M。

X 和 M 组合在一起形成寻址模式。
R 选择寄存器(例如 0 = A,3 = B)。

其中一种寻址模式 (X=3, M=any) 允许我们寻址寄存器(通过 M)而不是内存。
例如,X=3, R=0, M=3 (C3) 将寄存器 B 设置为“内存”操作数,将寄存器 A 设置为寄存器操作数。
而 X=3, R=3, M=0 (D8) 将寄存器 A 设置为“内存”操作数,将寄存器 B 设置为寄存器操作数。

在这里我们可以看到歧义所在:ModR/M 字节让我们可以对源寄存器和目标寄存器进行编码。同时,操作码让我们对从源到目标或从目标到源的移动进行编码——这使我们可以自由选择哪个寄存器是什么。

例如,假设我们要将 B 移到 A。

如果我们将 A 作为寄存器操作数(源),B 作为内存操作数(目标),那么 ModR/M 字节是 X=3, R=0, M=3 (C3)。
要从 B 移动到 A,如在您的示例中,仅使用低 8 位,我们将移动编码为 G=2, P=1, F={s=0,d=1,b=0} (8A)因为我们移动了 mem->reg (B->A)。因此最终指令是8AC3。

如果我们选择 A 作为内存操作数(目标)和 B 作为寄存器操作数(源),则 ModR/M 字节为 X=3, R=3, M=0 (D8)。
移动是 G=2, P=1, F={s=0,d=0,b=0} (88) 因为我们移动 reg->mem (B->A)。
最后一条指令是88D8。

如果我们想移动整个 16 位寄存器(我们在这里忽略操作数大小前缀),我们只需设置 F 的 b 位:

对于第一种情况,G=2, P=1, F={s=0,d=1,b=1},导致 8BC3。
对于第二种情况,G=2,P=1,F={s=0,d=0,b=1},导致 89D8。

你可以看看这个 ndisasm

00000000  8AC3              mov al,bl
00000002  88D8              mov al,bl
00000004  8BC3              mov ax,bx
00000006  89D8              mov ax,bx
Run Code Online (Sandbox Code Playgroud)