x86 汇编/机器语言转换
x86 汇编指令与底层的机器指令之间存在一一对应关系。这意味着我们可以使用查找表将汇编指令转换为机器指令。本页将讨论一些从汇编语言到机器语言的转换。
x86 架构是一种复杂指令集计算机 (CISC) 架构。其中一个含义是 x86 架构的指令长度各不相同。这可能会使汇编、反汇编和指令解码的过程变得更加复杂,因为需要计算每个指令的指令长度。
x86 指令的长度可以是 1 到 15 个字节。根据指令的可用操作模式、所需操作数的数量等,每个指令的长度都单独定义。
这是 8086 在主内存中顺序排列的通用指令格式
前缀 (可选) |
操作码 (第一个字节) | D | W |
操作码 2 (偶尔的第二个字节) |
MOD | Reg | R/M |
位移量或数据 (偶尔: 1、2 或 4 个字节) |
- 前缀
- 可选的前缀,它们会改变指令的操作
- D
- (1 位) 方向。1 = 寄存器是目标,0 = 寄存器是源。
- W
- (1 位) 操作大小。1 = 字,0 = 字节。
- 操作码
- 操作码是一个 6 位的量,它决定代码属于哪个指令族
- MOD (Mod)
- (2 位) 寄存器模式。
- Reg
- (3 位) 寄存器。每个寄存器都有一个标识符。
- R/M (r/m)
- (3 位) 寄存器/内存操作数
并非所有指令都有 W 或 D 位;在某些情况下,操作的宽度无关紧要或隐式,对于其他操作,数据方向无关紧要。
请注意,英特尔指令格式是小端格式,这意味着最低有效字节最靠近绝对地址 0。因此,字的存储顺序是低字节在前;值 1234H 在内存中存储为 34H 12H。按照惯例,最高有效位始终显示在字节内的左侧,因此 34H 为 00110100B。
在最初的 2 个字节之后,每个指令可以有许多额外的寻址/立即数数据字节。
Mod | 位移量 |
00 | 如果 r/m 为 110,则位移量 (16 位) 是地址;否则,没有位移量 |
01 | 8 位位移量,符号扩展为 16 位 |
10 | 16 位位移量 (示例:MOV [BX + SI]+ 位移量,al) |
11 | r/m 被视为第二个“reg”字段 |
Reg | W = 0 | W = 1 | 双字 | |
000 | AL | AX | EAX | |
001 | CL | CX | ECX | |
010 | DL | DX | EDX | |
011 | BL | BX | EBX | |
100 | AH | SP | ESP | |
101 | CH | BP | EBP | |
110 | DH | SI | ESI | |
111 | BH | DI | EDI |
r/m | 操作数地址 |
000 | (BX) + (SI) + 位移量 (0、1 或 2 个字节长) |
001 | (BX) + (DI) + 位移量 (0、1 或 2 个字节长) |
010 | (BP) + (SI) + 位移量 (0、1 或 2 个字节长) |
011 | (BP) + (DI) + 位移量 (0、1 或 2 个字节长) |
100 | (SI) + 位移量 (0、1 或 2 个字节长) |
101 | (DI) + 位移量 (0、1 或 2 个字节长) |
110 | (BP) + 位移量,除非 mod = 00 (参见 mod 表) |
111 | (BX) + 位移量 (0、1 或 2 个字节长) |
请注意 MOD 00、r/m 110 的特殊含义。通常,这应该被认为是操作数 [BP]。但是,位移量被视为绝对地址。要编码值 [BP],可以使用 mod = 01、r/m = 110、8 位位移量 = 0。
让我们将以下指令转换为机器代码
XOR CL, [12H]
请注意,这是将 CL 与地址 12H 的内容进行异或运算 - 方括号是常见的间接寻址指示符。XOR 的操作码是“001100dw”。D 为 1,因为 CL 寄存器是目标。W 为 0,因为我们有一个字节的数据。因此,我们的第一个字节是“00110010”。
现在,我们知道 CL 的代码是 001。因此,Reg 的值为 001。地址被指定为一个简单的位移量,因此 MOD 值为 00,R/M 为 110。因此,字节 2 是 (00 001 110b)。
字节 3 和 4 包含有效地址,低位字节在前,0012H 作为 12H 00H,或 (00010010b) (00000000b)
总的来说,
XOR CL, [12H] = 00110010 00001110 00010010 00000000 = 32H 0EH 12H 00H
现在,如果我们要使用立即数操作数,如下所示
XOR CL, 12H
在本例中,由于没有方括号,因此 12H 是立即数:它是要与之进行异或运算的数字。立即数 XOR 的操作码是 1000000w;在本例中,我们使用了一个字节,因此 w 为 0。因此,我们的第一个字节是 (10000000b)。
第二个字节,对于立即数操作,采用“mod 110 r/m”的形式。由于目标是一个寄存器,因此 mod 为 11,使 r/m 字段成为一个寄存器值。我们已经知道 CL 的寄存器值为 001,因此我们的第二个字节是 (11 110 001b)。
第三个字节 (如果这是一个字操作,那么第四个字节也是) 是立即数数据。由于它是一个字节,因此只有一个字节的数据,12H = (00010010b)。
总的来说,
XOR CL, 12H = 10000000 11110001 00010010 = 80 F1 12
32 位指令的编码方式与 16 位指令非常相似,只是 (默认情况下) 它们作用于双字量,而不是字。此外,它们支持更加灵活的内存寻址格式,这得益于添加了一个 SIB “比例-索引-基址”字节,该字节位于 ModR/M 字节之后。
继续前面的绝对寻址示例,我们采用以下输入
XOR CL, [12H]
...然后我们得到如下 32 位机器代码
首先是操作码字节,它保持不变,为 32H。查阅英特尔 IA-32 手册,第 2C 卷,第 5 章,“XOR” - 我们看到这个操作码定义了:a) 它需要 2 个操作数,b) 操作数有方向,第一个操作数是目标,c) 第一个操作数是 8 位宽度的寄存器,d) 第二个操作数也是 8 位,但可以是寄存器或内存地址,以及 e) 目标寄存器 CL 将被覆盖以包含操作的结果。这符合我们上面的情况,因为第一个操作数是 CL(“L”表示“C”寄存器的低 8 位),而第二个操作数是对内存中地址 12H 处存储的值的引用 (一个直接/绝对指针或地址引用)。看起来我们不需要任何前缀字节来获得我们想要的操作数大小。
现在我们知道我们需要一个 ModR/M 字节,因为操作码需要它;a) 它需要多个操作数,b) 它们没有在操作码或任何前缀中定义,c) 没有立即数操作数。因此,我们再次查阅英特尔手册,第二卷 A,第二章,2.1.5 节“ModR/M 和 SIB 字节的寻址模式编码”,表 2-2“使用 ModR/M 字节的 32 位寻址形式”。我们知道第一个操作数将是我们的目标寄存器 CL,因此我们看到它映射到 REG=001b。接下来,我们寻找与第二个操作数匹配的有效地址公式,该操作数是一个没有寄存器的位移(因此没有段、基址、比例因子或索引)。最接近的匹配将是 disp32,但由于脚注,阅读表格很棘手。基本上,我们的公式不在该表格中,我们想要的公式需要一个 SIB 字节,记为 [--][--],它告诉我们需要指定 Mod=00b、R/M=100b 来启用 SIB 字节。因此,我们的第二个字节是 00001100b 或 0CH。
我们知道 SIB 字节(如果使用)始终紧随 ModR/M 字节,因此我们继续到英特尔手册中的下一张表 2-3“使用 SIB 字节的 32 位寻址形式”,并寻找比例因子、索引和基址值的组合,这些组合将为我们提供所需的 disp32 公式。请注意有一个脚注 [*],它基本上告诉我们指定 Scale=00b、Index=100b、Base=101b,这意味着 disp32 没有索引、没有比例因子和没有基址。因此,我们的第三个字节现在是 25H。
我们知道位移字节(如果使用)始终紧随 ModR/M 和 SIB 字节,因此我们在这里只需以小端序指定我们的 32 位无符号整数,这意味着我们的下一个四个字节是 12000000H。
最后,我们得到了机器代码。
XOR CL, [12H] = 00110010 00001100 00100101 00010010 00000000 00000000 00000000 = 32 0C 25 12 00 00 00
此指令在 32 位保护模式和 64 位长模式下都有效。