通过查看指令的英特尔量,我发现了这个:
1)88 /r MOV r/m8,r8
2)8A /r MOV r8,r/m8
当我在NASM中写这样的一行时,用列表选项组装它:
mov al, bl
我在列表中得到了这个:
88D8 mov al, bl
很明显NASM选择了上面两个的第一条指令,但不是第二条指令选项二吗?如果是这样,NASM在什么基础上选择了第一个呢?
答案 0 :(得分:6)
使用不同编码的有趣副作用:来自A86手册。
- 醇>
A86利用了可以为同一指令生成多个操作码集的情况。 (例如, MOV AX,BX可以使用89或8B操作码生成 在以下有效地址字节中反转字段。都 表单在功能和执行方面完全相同 在这种情况下,A86采用了不寻常的混合选择。 这创建了代码生成“足迹”,不占用空间 在您的程序文件中,但将使我能够告诉,并且 如果一个非平凡的目标文件具有,则在法庭上证明 由A86生产。这个“足迹”的规范是 足够模糊和复杂,以至于不可能 意外复制。我要求特定的专有权 我选择了“足迹”,禁止任何人复制它。 这至少有两个具体含义:
一个。任何复制“足迹”的汇编程序都是我的。如果 它不是我的,并根据这些条款发布, 那些出售或分发汇编程序的人将是 受到起诉。
湾任何标有“足迹”的程序都已生成 由我的汇编。它符合上述条件5。
答案 1 :(得分:3)
这两种编码的存在是因为modr / m字节只能编码一个内存操作数。因此,要同时允许mov r8,m8
和mov 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)。
请注意,寄存器A
(B
)和xmm0
(xmm0
)使用相同的数字进行编码。
然而,弄清楚为什么有两种编码仍然很有趣。
作为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