通过查看英特尔的指令量,我发现:
1) 88 /r MOV r/m8,r8
2) 8A /r MOV r8,r/m8
当我在NASM中写这样的一行时,用列表选项组装它:
mov al, bl
我在清单上看到了这个:
88D8 mov al, bl
很明显,NASM选择了上述两种指令中的第一种,但第二种指令不是第二种吗?如果是这样,那么NASM选择第一个的依据是什么?
发布于 2017-06-02 19:27:33
这两种编码的存在是因为modr/m字节只能编码一个内存操作数。因此,为了同时允许mov r8,m8和mov m8,r8,需要两个编码。当然,这样我们可以对mov的使用进行编码,两个操作数都是使用编码的寄存器,而nasm只是随机选择一个操作数。没有特殊的选择理由,我也见过汇编程序做出了不同的选择。
我还听说过一个汇编程序,它通过选择一种特定的指令编码方式来组装带有水印的二进制代码。这样,“汇编程序”的作者就可以追踪并起诉使用他的汇编程序的人,而不用付钱。
发布于 2017-06-03 00:05:43
有不同编码的有趣的副作用:来自A86手册。
发布于 2017-06-02 21:22:58
正如哈罗德所指出的:没有理由。
也许作者发现向前移动的使用比反向移动更有吸引力,或者他们只是拿起了第一个操作码。
我看了一下NASM源代码,发现编码基本上是用一个大的查找表来完成的,所以这确实是一个品味问题。使用其他操作码(8AC3)可以简化代码(我猜),如果解析没有使用查找表:像addps这样的指令是不对称的,通过使用8A /r for mov al, bl,代码可以被重用来计算addps的ModR/M字节和类似的指令。addps xmm0, xmm3在使用mov al, bl时使用与mov al, bl相同的ModR/M字节(C3)。
注意,寄存器A (B)和xmm0 (xmm0)是用相同的数字编码的。
然而,找出为什么会有两种编码仍然很有趣。
作为Mark (重新)发现,早期的x86指令编码在八进制中更有意义。。
八进制中的字节有三个数字,我称之为G (Group,oPeration,Flags)。
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我们可以开始形成操作码,但是我们仍然错过了选择操作数的方法。
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、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,bxhttps://stackoverflow.com/questions/44335265
复制相似问题