为什么我的汇编器为什么不使用手册手册中的05操作码(添加eax,imm32)缩写来添加AAD EAX,1,却使用04 ADD AL,1呢?

时间:2018-10-13 01:01:35

标签: assembly x86 opcode machine-code instruction-set

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

在ADD参考表(第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  

组装:

;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参考表的下方使用了指令,例如REX前缀,为什么使用那些而不是我之前列出的指令?

现在第二个ADD ax, 1;搜索之后,我发现66是操作数大小的覆盖前缀,但未在ADD参考表中列出,因此,当我选择添加此前缀时,似乎找不到更多的信息。英特尔手册中的旧前缀?

我试图按照手册中的说明拆卸05 01,但是它不能将其识别为只是数字的操作码。英特尔手册是一个很好的资源,我认为它只是缺少一些额外的说明和结构,仍然试图把我的头都围绕在ModRM上。

2 个答案:

答案 0 :(得分:2)

在列出的说明中注意立即数的大小。立即数与寄存器的大小相同。无论寄存器的大小如何,您测试的汇编程序使用的指令均使用一字节立即数。这使指令更短。您可以通过提供适当大小的立即数来使用列出的说明,例如:         05 01 00 00 00

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

答案 1 :(得分:1)

有多种操作码可将立即数添加到64位寄存器中

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.

https://www.felixcloutier.com/x86/ADD.html

因为01可以容纳一个字节,所以汇编程序使用操作码83来保存指令长度。如果尝试使用add rax, 100000000或类似的方法,您将获得操作码05

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

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

Why NASM on Linux changes registers in x86_64 assembly

现在,如果您仔细查看桌子,您可能会看到一些奇怪的东西

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
  

我试图按照手册中的说明拆卸05 01,但是它不能将其识别为只是数字的操作码

因为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

您可能想阅读