跳转到内容

x86 汇编/机器语言转换

来自维基教科书,自由的教科书

与机器码的关系

[编辑 | 编辑源代码]

x86 汇编指令与底层的机器指令之间存在一一对应关系。这意味着我们可以使用查找表将汇编指令转换为机器指令。本页将讨论一些从汇编语言到机器语言的转换。

CISC 和 RISC

[编辑 | 编辑源代码]

x86 架构是一种复杂指令集计算机 (CISC) 架构。其中一个含义是 x86 架构的指令长度各不相同。这可能会使汇编、反汇编和指令解码的过程变得更加复杂,因为需要计算每个指令的指令长度。

x86 指令的长度可以是 1 到 15 个字节。根据指令的可用操作模式、所需操作数的数量等,每个指令的长度都单独定义。

8086 指令格式 (16 位)

[编辑 | 编辑源代码]

这是 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 / Reg / R/M 表格

[编辑 | 编辑源代码]
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

x86 指令 (32/64 位)

[编辑 | 编辑源代码]

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 位长模式下都有效。

华夏公益教科书