为什么我的汇编程序不使用 ADD EAX,1 的手册文档中的 05 操作码 (add eax,imm32) 缩写形式,而是使用 04 ADD AL, 1?

Why doesn't my assembler use the 05 opcode (add eax,imm32) short form the manual documents for ADD EAX,1 but it does for 04 ADD AL, 1?

我正在编写一个 x86-64 汇编程序。我正在浏览 Intel x86 手册第 2 卷,试图了解如何从程序集中生成正确的指令。我基本上了解它是如何工作的,但一直在组装和拆卸说明以检查我是否正确。

在 ADD 参考文献 table(第 2A 卷,3.31)中:

opcode        | Instruction  
04 ib         | ADD AL, imm8  
05 iw         | ADD AX, imm16  
05 id         | ADD EAX, imm32  
REX.W + 05 id | ADD RAX, imm32  

Assemble:

;add.s   
add al, 1
add ax, 1
add eax, 1
add rax, 1

反汇编:

.text:
   0:   04 01           add al, 1
   2:   66 83 c0 01     add ax, 1
   6:   83 c0 01        add eax, 1
   9:   48 83 c0 01     add rax, 1

所以第一个是正确的,就像手册说的那样,但是汇编器使用 ADD 参考 table 后面的指令,比如 REX 前缀,为什么要使用那些而不是我之前列出的那些?

现在是第二个ADD ax, 1;搜索后我发现 66 是操作数大小覆盖前缀,但它没有在 ADD 参考 table 中列出,所以我什么时候选择添加这个前缀我似乎找不到太多信息它或英特尔手册中的其他遗留前缀?

我尝试按照手册中的说明反汇编 05 01,但它没有将其识别为只是数字的操作码。 Intel 手册是一个很好的资源,我认为它只是缺少一些额外的解释和结构,我仍然试图围绕 ModRM 的东西进行思考。

注意您列出的指令中立即数的大小。立即数与寄存器的大小相同。您测试的汇编程序使用的指令使用单字节立即数,而不管寄存器的大小。这使得指令更短。您可以通过提供适当大小的立即数来使用您列出的指令,例如 add eax, 1000000h:

    05 00 00 00 01

有关前缀的说明,请参阅第 2.1.1 节。 操作数大小覆盖前缀允许程序在 16 位和 32 位操作数大小之间切换。大小都可以 成为默认值;使用前缀选择非默认大小。 在 64 位模式下,32 位始终是默认值,因此 66h 前缀选择 16 位操作数大小。

有多个opcodes for adding an immediate to a 64-bit register

Opcode Instruction Description
REX.W + 05 id ADD RAX, imm32 Add imm32 sign-extended to 64-bits to RAX.
REX.W + 81 /0 id ADD r/m64, imm32 Add imm32 sign-extended to 64-bits to r/m64.
REX.W + 83 /0 ib ADD r/m64, imm8 Add sign-extended imm8 to r/m64.

因为 01 适合一个字节,您的汇编程序使用操作码 83 来节省指令长度。如果您尝试 add rax, 100000000 或类似的东西,您将获得操作码 05

现在要强制进行另一种解码而不是更有效的解码,您需要在汇编器中定义一些语法。例如 nasm 使用 strict keyword

mov    eax, 1                ; 5 bytes to encode (B8 imm32)
mov    rax, strict dword 1   ; 7 bytes: REX mov r/m64, sign-extended-imm32.    NASM optimizes mov rax,1 to the 5B version, but dword or strict dword stops it for some reason
mov    rax, strict qword 1   ; 10 bytes

现在,如果您仔细观察 table,您可能会发现一些“奇怪”的东西

Opcode Instruction Description
05 iw ADD AX, imm16 Add imm16 to AX.
05 id ADD EAX, imm32 Add imm32 to EAX.
81 /0 iw ADD r/m16, imm16 Add imm16 to r/m16.
81 /0 id ADD r/m32, imm32 Add imm32 to r/m32.
01 /r ADD r/m16, r16 Add r16 to r/m16.
01 /r ADD r/m32, r32 Add r32 to r/m32.
03 /r ADD r16, r/m16 Add r/m16 to r16.
03 /r ADD r32, r/m32 Add r/m32 to r32.

为什么同一指令的所有 16 位和 32 位版本都具有相同的操作码?

答案是当前模式将定义指令类型。如果您 运行 在 16 位模式下,则默认使用 16 位寄存器,如果您在 32 或 64 位模式下,则默认大小将为 32 位。如果您想使用其他尺寸,则必须使用 66h (Operand-size override) prefix。这意味着在 16 位模式下你会得到下面的输出而不是你上面看到的

83 c0 01           add ax, 1
66 83 c0 01        add eax, 1

I tried to disassemble 05 01 as shown in the manual but it didn't recognise it as an opcode just numbers

因为05后面必须跟一个4字节立即数(id/imm32如手册中所述)或2字节立即数(iw/imm16)取决于默认操作数尺寸。只有带 imm8/ib 的指令才能有单字节立即数。例如 online disassembler 给我以下输出:

0:  05 01 02 03 04          add    eax,0x4030201
5:  66 05 01 02             add    ax,0x201

出于与上述相同的原因,选择操作码 83h 是因为 0x01 适合一个字节,使两者长度相同,汇编程序可以选择任何它喜欢的

0:  66 83 c0 01             add    ax,0x1
4:  66 05 01 00             add    ax,0x1

您可能想阅读这篇文章