x86 有 2 种冗余方式来编码任何基本 ALU 指令(可追溯到 8086 年)的 2 寄存器实例,使用 r/m 源和 r/m 目标形式。
reg,reg 编码的这种冗余是 x86 机器代码如何允许大多数指令的内存目标或内存源的结果:而不是花费 ModR/M 字节中的位来对两个操作数进行灵活编码,大多数指令只有两个单独的操作码。
(这就是为什么任何指令都不允许使用两个显式内存操作数,例如 xor [eax], [ecx]。Only a few instructions 其中一个或两个内存操作数是隐式的,例如 rep movs 或 push [mem] 允许两个内存操作数,绝不是一条指令具有两种独立的 ModR/M 编码寻址模式。)
编码有模式
请注意,31 与 33 for word/dword/qword-sized xor 仅在第 1 位不同。其他指令如 29 与 2B sub 遵循相同的模式。 位 #1 有时称为操作码的“方向”位。 (不要与 EFLAGS 中的 DF 混淆,方向 flag)。
还请注意,这些指令的字节与字/双字/qword 操作数大小版本仅在低位上有所不同,例如30 XOR r/m8, r8 与31 XOR r/m16, r16。同样,这种模式出现在可追溯到 8086 的 ALU 指令编码中。这些操作码的位 #0 有时称为“大小”位。
这些具有每个方向和大小组合编码的“基本 ALU”指令可追溯到原始 8086;许多后来的指令(如386 bsf r, r/m 或186 imul r, r/m, imm)没有允许内存目标的形式。或者对于bt* r/m, r 只有,目的地可以是 reg/mem。
这也是为什么后来的指令(或它们的新形式,如 imul)通常没有用于字节操作数大小的单独操作码,只允许通过正常前缀机制的 word/dword/qword。 8086 占用了大部分编码空间,后来的扩展想为更多的未来扩展留出空间。所以这就是为什么没有imul r, r/m8。
(dword 和 qword 操作数大小本身是扩展;8086 没有操作数大小或 REX 前缀。因此,原始 8086 在使用其操作码编码空间方面是相当明智的,并且具有使解码不会一团糟的模式.)
表单之间没有执行差异
对于 reg,reg 指令,它们在我所知道的任何 CPU 上的解码和执行方式都没有区别;唯一需要关心汇编器使用哪种编码的情况是,当您希望机器代码满足其他一些要求时,例如仅使用表示可打印 ASCII 字符的字节。 (例如,用于漏洞利用负载)。
指定你希望汇编器使用哪种形式
一些汇编器具有覆盖其默认编码选择的语法,例如GAS had a .s suffix to get the non-default encoding。现在已弃用,您应该在助记符 (see the docs) 之前使用 {load} 或 {store} 前缀,如下所示:
{load} xor %eax, %ecx
{store} xor %eax, %ecx
{vex3} vpaddd %xmm0, %xmm1, %xmm1
vpaddd %xmm0, %xmm1, %xmm1 # default is to use 2-byte VEX when possible
gcc -c foo.S && objdump -drwC foo.o
0: 31 c1 xor %eax,%ecx
2: 33 c8 xor %eax,%ecx
4: c4 e1 71 fe c8 vpaddd %xmm0,%xmm1,%xmm1
9: c5 f1 fe c8 vpaddd %xmm0,%xmm1,%xmm1
(相关:What methods can be used to efficiently extend instruction length on modern x86? 用于 {vex3}、{evex} 和 {disp32} 的用例。)
NASM 也有 {vex2}、{vex3} 和 {evex} 前缀,其语法与 GAS 相同,例如{vex3} vpaddd xmm1, xmm1, xmm0。但我看不到覆盖op r/m, r 与op r, r/m 操作码选择的方法。
相关问答,有些基本重复