0

我正在编写一个 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

所以第一个是正确的,就像手册说的那样,但是汇编器使用像 REX 前缀这样的 ADD 参考表更下方的指令,为什么使用那些而不是我之前列出的那些?

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

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

4

2 回答 2

4

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

操作码 操作说明 描述
REX.W + 05 id ADD RAX, imm32 将 imm32 符号扩展为 64 位添加到 RAX。
REX.W + 81 /0 id ADD r/m64, imm32 将 imm32 符号扩展为 64 位添加到 r/m64。
REX.W + 83 /0 ib ADD r/m64, imm8 将符号扩展的 imm8 添加到 r/m64。

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

现在,如果您仔细查看表格,您可能会看到一些“奇怪”的东西

操作码 操作说明 描述
05 iw ADD AX, imm16 将 imm16 添加到 AX。
05 id ADD EAX, imm32 将 imm32 添加到 EAX。
81 /0 iw ADD r/m16, imm16 将 imm16 添加到 r/m16。
81 /0 id ADD r/m32, imm32 将 imm32 添加到 r/m32。
01 /r ADD r/m16, r16 将 r16 添加到 r/m16。
01 /r ADD r/m32, r32 将 r32 添加到 r/m32。
03 /r ADD r16, r/m16 将 r/m16 添加到 r16。
03 /r ADD r32, r/m32 将 r/m32 添加到 r32。

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

答案是当前模式将定义指令类型。如果您在 16 位模式下运行,则默认使用 16 位寄存器,如果您在 32 或 64 位模式下,则默认大小将为 32 位。如果要使用其他大小,则必须使用66h (操作数大小覆盖)前缀。这意味着在 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可以有一个单字节立即数。例如,在线反汇编程序给了我以下输出:

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

你可能想读这个

于 2018-10-13T09:38:26.683 回答
3

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

    05 00 00 00 01

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

于 2018-10-13T03:58:38.373 回答