5.1 指令格式与机器码编码
第 4 章介绍了 8086 指令可以使用的状态:寄存器、FLAGS、段和存储器。处理器不能直接阅读 MOV AX,1234H 这样的汇编文本,而是读取按顺序排列的机器码(machine code)字节。
指令编码(instruction encoding)是把操作及其操作数转换为这些字节的规则。反向解释机器码叫作译码(decoding);若输出汇编文本,也称为反汇编(disassembly)。
一条指令是有结构的字节包
8086 指令采用变长格式。根据具体指令,字节流可能包含:
| 部分 | 用途 | 每条指令都有吗? |
|---|---|---|
| 前缀 | 修改段选择、重复或总线锁定行为 | 否 |
| 操作码 | 标识操作,有时也包含宽度、方向或寄存器 | 是 |
| ModR/M 字节 | 描述寄存器与“寄存器或存储器”操作数 | 否 |
| 位移量 | 地址计算中加入的常量,或直接偏移量 | 否 |
| 立即数 | 作为操作数使用的字面数据 | 否 |
操作码必须位于由它定义的后续字段之前。译码器只有先知道指令家族,才能判断后面的字节究竟是 ModR/M、位移还是立即数。
并非每条指令都使用每个框。例如:
MOV AL,7FH使用操作码B0H,后接一个立即数字节7FH。
MOV AX,1234H使用操作码B8H,后接立即数字节34H 12H。
第二个例子按小端顺序保存 16 位立即数:先低字节,后高字节。这与存储器中保存字的字节序相同,只是现在应用于指令流内嵌的数据。
某些操作码直接包含寄存器编号
对于 MOV r16,imm16,B8H 到 BFH 八个操作码分别选择一个 16 位寄存器:
| 编码 | 寄存器 | 操作码 |
|---|---|---|
| 000 | AX | B8H |
| 001 | CX | B9H |
| 010 | DX | BAH |
| 011 | BX | BBH |
| 100 | SP | BCH |
| 101 | BP | BDH |
| 110 | SI | BEH |
| 111 | DI | BFH |
操作类型与寄存器选择被压进同一个字节。因此 MOV DX,BEEFH 以 BAH 开始,后接立即数低字节 EFH 和高字节 BEH。
实验 1——锻造字节流,让译码器判断结果
直接编辑操作码与数据字节。实时译码器会响应每一次变化,因此颠倒立即数字节、选错寄存器编号或写错操作码都会产生另一条具体指令,而不只是显示笼统的“错误”。
检查理解
ModR/M 字节描述两个操作数
许多 8086 指令家族在第二个字节中保存操作数信息,这个字节称为 ModR/M:
- mod 区分寄存器形式与存储器形式,并决定位移长度。
- reg 通常选择一个寄存器操作数。在部分指令家族中,它也可能扩展操作码。
- r/m 选择一个寄存器,或一种合法的存储器地址表达式。
mod 字段有四种模式:
| mod | 含义 |
|---|---|
00 | 存储器,通常无位移 |
01 | 存储器加有符号 8 位位移 |
10 | 存储器加 16 位位移 |
11 | 寄存器到寄存器;r/m 表示寄存器 |
有一个必须掌握的特例:mod=00 且 r/m=110 表示后面跟随一个 16 位直接地址,并不表示 [BP]。编码 [BP] 时,处理器要使用 mod=01 加 8 位零位移,或使用 mod=10 加 16 位位移。
对于字操作数,reg 和寄存器形式的 r/m 都使用上表所示的三位寄存器顺序。例如,MOV AX,BX 可以使用:
- 操作码
8BH,含义是reg16 ← r/m16;
- ModR/M
11 000 011,表示寄存器形式、reg=AX、r/m=BX;
- 完整字节
8B C3。
实验 2——逐位组装 ModR/M
选择 MOV 数据方向,切换 ModR/M 的八个位,并且只在所选 mod 形式需要时补充位移字节。任务覆盖寄存器到寄存器、基址/变址存储器、有符号 8 位位移和直接地址特例。
检查理解
可靠的编码流程
1. 确认操作类型、操作数宽度和数据方向。
2. 选择指令家族与操作码。
3. 如果需要,填写 mod、reg 和 r/m。
4. 判断后面是否需要位移字节。
5. 追加立即数;对于 16 位值,先放低字节。
不要在不知道位置和指令家族的情况下孤立解释一个字节。例如,C3H 可以是 8B C3 中的 ModR/M 字节,但同一个位模式若出现在操作码边界,就会表示完全不同的指令。
下一节将关注译码完成后操作数的含义:字面数据、寄存器数据,以及通过直接存储器地址取得的数据。