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

时间:2017-06-02 18:23:41

标签: assembly x86 nasm

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

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

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

mov al, bl

我在列表中得到了这个:

88D8 mov al, bl

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

3 个答案:

答案 0 :(得分:6)

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

  
      
  1. A86利用了可以为同一指令生成多个操作码集的情况。 (例如,   MOV AX,BX可以使用89或8B操作码生成   在以下有效地址字节中反转字段。都   表单在功能和执行方面完全相同   在这种情况下,A86采用了不寻常的混合选择。   这创建了代码生成“足迹”,不占用空间   在您的程序文件中,但将使我能够告诉,并且   如果一个非平凡的目标文件具有,则在法庭上证明   由A86生产。这个“足迹”的规范是   足够模糊和复杂,以至于不可能   意外复制。我要求特定的专有权   我选择了“足迹”,禁止任何人复制它。   这至少有两个具体含义:

         

    一个。任何复制“足迹”的汇编程序都是我的。如果     它不是我的,并根据这些条款发布,     那些出售或分发汇编程序的人将是     受到起诉。

         

    湾任何标有“足迹”的程序都已生成     由我的汇编。它符合上述条件5。

  2.   

答案 1 :(得分:3)

这两种编码的存在是因为modr / m字节只能编码一个内存操作数。因此,要同时允许mov r8,m8mov m8,r8,需要两种编码。当然,通过这种方式,我们可以编码mov的使用,两个操作数都是使用编码的寄存器,而nasm只是随机选择一个。选择没有特别的原因,我看到汇编者做出了不同的选择。

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

答案 2 :(得分:3)

正如Harold所指出的那样:没有理由 也许作者发现前向移动的使用比反向移动更有吸引力,或者他们可能只是选择了第一个操作码。

我看了一下NASM源代码,发现编码基本上是用一个大的查找表完成的,所以这真的是一个品味问题。 如果解析没有使用查找表,使用其他操作码(8AC3)会简化代码(我猜):addps之类的指令是非对称的,并且8A /r用于mov al, bl代码可以重用来计算addps和类似指令的ModR / M字节。使用addps xmm0, xmm3时,mov al, bl使用与8A /r相同的ModR / M字节(C3)。
请注意,寄存器AB)和xmm0xmm0)使用相同的数字进行编码。

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

作为Mark Hopkins (re)discovered, the earlier x86 instructions encoding make a lot more sense in octal
八进制中的一个字节有三个数字,我称之为G P F(Group,oPeration,Flags)。

G是八进制组,同一组中的指令往往执行类似的任务(例如算术与移动) 但是,这不是一个严格的划分 P是操作;例如,在算术组中,操作是减法,另一个是加法 F是用于控制操作行为的bitset。每个组和操作都使用数字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

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

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

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

ModR / M字节在八进制中可视为三个字段:X R M。

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

其中一种寻址模式(X = 3,M =任意)让我们寻址寄存器(通过M)而不是存储器。
例如,X = 3,R = 0,M = 3(C3)将寄存器B设置为&#34;存储器&#34;操作数和寄存器A作为寄存器操作数 当X = 3,R = 3时,M = 0(D8)将寄存器A设置为&#34;存储器&#34;操作数和寄存器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