机器码的格式结构:
一般的 x86 机器指令格式,包含了一个指令前缀字节、操作码、Mod R/M 字节、伸缩索引字节(SIB)、地址位移和立即数。指令按小端顺序存放,因此前缀字节位于指令的起始地址(基本遇不到)。
每条指令都必须要有一个操作码,而其他字段则是可选的。少数指令包含了全部字段,平均来看,绝大多数指令都有 2 个或 3 个字节。
指令字段解析:
1):指令前缀覆盖默认操作数大小。(基本遇不到)
2):操作码指定指令的特定变体,比如按照使用的参数(寄存器|操作数)类型,指令 ADD 有 9 种不同的操作码。
(具体操作码看参考手册)
3):Mod R/M 字段指定寻址模式和操作数,符号 “R/M” (register/memory) 代表的是寄存器和模式。
4):伸缩索引字节(scale index byte, SIB)用于计算数组索引偏移量。
5):地址位移字段保存了操作数的偏移量,在 “基址-偏移量” 或 “基址-变址-偏移量” 寻址模式中,该字段还可以与 “基址或变址寄存器” 相加。
6):立即数字段保存了常量操作数。
操作码的组成:在多数操作码中,常使用某些位来指示某些信息,下面列举出部分位及其含义:
(1:在参考手册 opcodes.html 的 Opcode 列中已经不需要考虑 d 和 s 了(定死了),但还是理解一下)
(2:上面操作码组成中只列出了 d、s、w,还有很多其它的如 rrr、sss 等,具体看操作码手册附录)
|_____________OP_______________|__d__|__w__|
|_____________OP_______________|__s__|__w__| <--此格式用于立即寻址方式
w (word) 值表示紧跟的操作数的大小类型:
w=1 时 对字来操作(如果是寄存器的话就是 16 位寄存器)
w=0 时 对字节来操作(如果是寄存器的话就是 8 位寄存器)
d 值在双操作数指令中才有效:(两个都是寄存器时以目的寄存器为准,不包括立即数的双操作数下)
d=1 时 有且只有一个寄存器用于目的操作数
d=0 时 有且只有一个寄存器用于源操作数
s 值只要有立即数存在就使用:
s=1 时 立即数为 8 位,但要求扩展成 16 位数
s=0 时 当指令作字节操作/有 16 位立即数
Mod R/M 字节标识操作数寻址方式:(不包括立即数的双操作数指令的情况下)
| mod | reg | r/m |
|_____|_____|_____|_____|_____|_____|_____|_____|
reg 指定寄存器编号,在不包括立即数的双操作数指令的情况下,规定必须有一个操作数在寄存器中。如果只有一个寄存器,则该寄存器由 reg 字段指定编号。如果两个操作数都是寄存器,则以目的寄存器为准。并与操作码字节中的 w 位相组合进一步确定寄存器大小类型。(reg 的 rrr 确定 8 个通用寄存器编号,操作码的末尾 w 位确定寄存器的大小)
Mod 值指定操作数的寻址模式:
Mod(oo) | 位移 |
00 | 如果 mmm = 110,则操作码后面跟 16 位偏移 |
01 | 操作码后面是一个 8 位有符号的偏移 |
10 | 操作码后面是一个 16 位有符号的偏移 |
11 | R/M (mmm)字段包含的是寄存器编号 |
rrr W=0 W=1 reg32
- 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
mmm : Function
- 000 : DS:[BX+SI]
- 001 : DS:[BX+DI]
- 010 : SS:[BP+SI]
- 011 : SS:[BP+DI]
- 100 : DS:[SI]
- 101 : DS:[DI]
- 110 : SS:[BP]
- 111 : DS:[BX]
sss : Segment Register
- 000 : ES
- 001 : CS
- 010 : SS
- 011 : DS
- 100 : FS (Only 386+)
- 101 : GS (Only 386+)
Mod R/M 字节与内存寻址模式探究:
如果 Mod R/M 字节只用于标识寄存器操作数(mod=11),那么 Intel 指令编码就会相对简单。实际上 Intel 汇编语言有着各种各样的内存寻址模式,这就使得 Mod R/M 字节编码相当复杂。
Mod R/M 字节可以指定 256 个不同组合的操作数,下表只列岀了 mod 字段 为 00 时的 Mod R/M 字节的值:
(Mod 在橙色块,Reg 在上面绿色块,R/M 在紫色块,黄色块是总的加起来的 Mod R/M 字节值,蓝色块是要操作的 R/M 字段对应的源操作数。根据 Mod R/M 字节内部结构,阅读顺序是 mod—>reg—>r/m)
字节 | AL | CL | DL | BL | AH | CH | DH | BH | |
字 | AX | CX | DX | BX | SP | BP | SI | DI |
寄存器ID | 000 | 001 | 010 | 011 | 100 | 101 | 110 | 111 |
Mod | R/M | Mod R/M 值 | 有效地址 |
00 | 000 | 00 | 08 | 10 | 18 | 20 | 28 | 30 | 38 | [BX+SI] |
| 001 | 01 | 09 | 11 | 19 | 21 | 29 | 31 | 39 | [BX+DI] |
| 010 | 02 | 0A | 12 | 1A | 22 | 2A | 32 | 3A | [BP+SI] |
| 011 | 03 | 0B | 13 | 1B | 23 | 2B | 33 | 3B | [BP+DI] |
| 100 | 04 | 0C | 14 | 1C | 24 | 2C | 34 | 3C | [SI] |
| 101 | 05 | 0D | 15 | 1D | 25 | 2D | 35 | 3D | [DI] |
| 110 | 06 | 0E | 16 | 1E | 26 | 2E | 36 | 3E | 16 位偏移量 |
| 111 | 07 | 0F | 17 | 1F | 27 | 2F | 37 | 3F | [BX] |
Mod R/M 编码解析:
mod 字段指定寻址模式的集合,比如 mod=00 时有 8 种可能的 R/M 数值(000b〜111b),上表的 “有效地址栏” 指定了对应源操作数类型。
举例编码 MOV AX, [Si]:
根据上表 “有效地址栏” 可得 Mod 位为 00,R/M 位为 100,AX 的寄存器编号为 000,因此完整的 Mod R/M 字节为 00 000 100。(即 04h)
附录一:32 位寻址模式
oo | mmm | rrr | Description |
00 | 000 | | DS:[EAX] |
00 | 001 | | DS:[ECX] |
00 | 010 | | DS:[EDX] |
00 | 011 | | DS:[EBX] |
00 | 100 | 000 | DS:[EAX+scaled_index] |
00 | 100 | 001 | DS:[ECX+scaled_index] |
00 | 100 | 010 | DS:[EDX+scaled_index] |
00 | 100 | 011 | DS:[EBX+scaled_index] |
00 | 100 | 100 | SS:[ESP+scaled_index] |
00 | 100 | 101 | DS:[disp32+scaled_index] |
00 | 100 | 110 | DS:[ESI+scaled_index] |
00 | 100 | 111 | DS:[EDI+scaled_index] |
00 | 101 | | DS:disp32 |
00 | 110 | | DS:[ESI] |
00 | 111 | | DS:[EDI] |
01 | 000 | | DS:[EAX+disp8] |
01 | 001 | | DS:[ECX+disp8] |
01 | 010 | | DS:[EDX+disp8] |
01 | 011 | | DS:[EBX+disp8] |
01 | 100 | 000 | DS:[EAX+scaled_index+disp8] |
01 | 100 | 001 | DS:[ECX+scaled_index+disp8] |
01 | 100 | 010 | DS:[EDX+scaled_index+disp8] |
01 | 100 | 011 | DS:[EBX+scaled_index+disp8] |
01 | 100 | 100 | SS:[ESP+scaled_index+disp8] |
01 | 100 | 101 | SS:[EBP+scaled_index+disp8] |
01 | 100 | 110 | DS:[ESI+scaled_index+disp8] |
01 | 100 | 111 | DS:[EDI+scaled_index+disp8] |
01 | 101 | | SS:[EBP+disp8] |
01 | 110 | | DS:[ESI+disp8] |
01 | 111 | | DS:[EDI+disp8] |
10 | 000 | | DS:[EAX+disp32] |
10 | 001 | | DS:[ECX+disp32] |
10 | 010 | | DS:[EDX+disp32] |
10 | 011 | | DS:[EBX+disp32] |
10 | 100 | 000 | DS:[EAX+scaled_index+disp32] |
10 | 100 | 001 | DS:[ECX+scaled_index+disp32] |
10 | 100 | 010 | DS:[EDX+scaled_index+disp32] |
10 | 100 | 011 | DS:[EBX+scaled_index+disp32] |
10 | 100 | 100 | SS:[ESP+scaled_index+disp32] |
10 | 100 | 101 | SS:[EBP+scaled_index+disp32] |
10 | 100 | 110 | DS:[ESI+scaled_index+disp32] |
10 | 100 | 111 | DS:[EDI+scaled_index+disp32] |
10 | 101 | | SS:[EBP+disp32] |
10 | 110 | | DS:[ESI+disp32] |
10 | 111 | | DS:[EDI+disp32] |
附录二:寻址模式与机器码(假设 myWord 的起始地址偏移量为 0102h)
指令 | 机器码 | 寻址模式 |
mov ax, my Word | A1 02 01 | 直接 |
mov my Word,bx | 89 IE 02 01 | 直接 |
mov [di],bx | 89 ID | 变址 |
mov [bx+2],ax | 89 47 02 | 基址 - 偏移量 |
mov [bx+si],ax | 89 00 | 基址 - 变址 |
mov word prt [bx+di+2], 1234h | C7 41 02 34 12 | 基址 - 变址 - 偏移量 |
附录三:8 位和 16 位 MOV 指令所有的指令格式和操作码:(根本还是对照着参考附录来)
操作码 | 指令 | 说明 | 操作码 | 指令 | 说明 |
88/r | MOV eb, rb | 字节寄存器送 EA 字节操作数 | 8E/2 | MOV SS, rw | 字寄存器送 SS |
89/r | MOV ew, rw | 字寄存器送 EA 字操作数 | 8E/3 | MOV DS, mw | 内存字送 DS |
8A/r | MOV rb, eb | EA 字节操作数送字节寄存器 | 8E/3 | MOV DS, rw | 字寄存器送 DS |
8B/r | MOV rw, ew | EA 字操作数送字寄存器 | A0 dw | MOV AL, xb | 字节变量(偏移量为 dw)送 AL |
8C/0 | MOV ew, ES | ES 送 EA 字操作数 | A1 dw | MOV AX, xw | 字变量(偏移量为 dw)送 AX |
8C/1 | MOV ew, CS | CS 送 EA 字操作数 | A2 dw | MOV xb, AL | AL 送字节变量(偏移量为 dw) |
8C/2 | MOV ew, SS | SS 送 EA 字操作数 | A3 dw | MOV xw, AX | AX 送字寄存器(偏移量为 dw) |
8C/3 | MOV ew, DS | DS 送 EA 字操作数 | B0+rb db | MOV rb, db | 字节立即数送字节寄存器 |
8E/0 | MOV ES, mw | 内存字送 ES | B8+rw dw | MOV rw, dw | 字立即数送字寄存器 |
8E/0 | MOV ES, rw | 字寄存器送 ES | C6 /0 db | MOV eb, db | 字节立即数送 EA 字节操作数 |
8E/2 | MOV SS, mw | 内存字送 SS | C7 /0 dw | MOV ew, dw | 字立即数送 EA 字操作数 |
下表是上面中缩写符号的补充信息:
/n: | 操作码后面跟一个 Mod R/M 字节,该字节后面可能再跟立即数和偏移量字段。数字 n( 0〜7 )为 Mod R/ M 字节中 reg 字段的值 |
/r: | 操作码后面跟一个 Mod R/M 字节,该字节后面可能再跟立即数和偏移量字段 |
db: | 操作码和 Mod R/M 字节后面跟一个字节立即操作数 |
dw: | 操作码和 Mod R/M 字节后面跟一个字立即操作数 |
+rb: | 8 位寄存器的编号(0〜7 ),与前面的十六进制字节一起构成 8 位操作码 |
+rw: | 16 位寄存器的编号(0〜7 ),与前面的十六进制字节一起构成 8 位操作码 |
实战部分机器指令类型:
单字节指令:(空操作数或隐含操作数)
没有操作数或只有一个隐含操作数的指令是最简单的指令,这种指令只需要操作码,其值由处理器的指令集直接确定,没有任何变化可言。
下表列出了几个常见的单字节指令:
指令 | 操作码 | 指令 | 操作码 |
AAA | 37 | LODSB | AC |
AAS | 3F | XLAT | D7 |
CBW | 98 | INC DX | 42 |
在这些指令中,INC DX 指令好像是不应该岀现的,它出现的原因是:指令集的设计者决定为某些常用指令提供独特的操作码。其结果是,为了代码量和执行速度要对寄存器增量操作进行优化(就是对常用指令给特权,提高执行速度)。
立即数送寄存器指令:(MOV Reg Imm 1011wrrr)
举例由指令手册可知,将一个立即数送寄存器的 MOV 指令格式为 MOV Reg Imm 1011wrrr ,其中操作码可以拆分成 B8 + wrrr,B8 是 MOV Reg Imm 的基指令 (10110000),+wrrr 表示变体类型。
w 表示对字操作(16位寄存器),rrr 指明寄存器编号(0〜7)。Imm 立即操作数(常数)按照小端顺序(起始地址为最低字节)添加到指令
【示例 1】MOV AX, 1 机器指令为 B8 01 00(十六进制),编码过程如下:
1) 立即数送 16 位寄存器的操作码为 B8
2) AX 的寄存器编号为 0,将 0 加上 B8(参见上表所示)
3) 立即操作数(0001)按小端顺序添加到指令(01, 00)(因为操作数是单独的类型,字大小,所以在最小 byte 划分中逆序存放)。
【示例 2】MOV BX, 1234h 机器指令为 BB 34 12,编码过程如下:
1) 立即数送 16 位寄存器的操作码为 B8。
2) BX 的寄存器编号为 3,将 3 加上 B8 得到操作码 BB。
3) 立即操作数字节为 34 12。
寄存器送寄存器指令:(MOV Reg Reg 1000101woorrrmmm)
举例由指令手册可知,寄存器送寄存器指令指令格式为 MOV Reg Reg 1000101woorrrmmm ,其中操作码可以拆分成 BA + w + Mod R/M 。
BA 是 MOV Reg Reg 的基指令 (10001010),+w 表示操作数类型。Mod R/M 字节标识操作数寻址方式,这里由于两个都是寄存器寻址,所以 Mod(oo)字段值为 11,reg 和 r/m 字段分别指定目的寄存器和源寄存器编号。
比如,MOV AX, BX 的机器码为 8B C3,解析如下:
寄存器送其他操作数的 16 位 MOV 指令的 Intel 编码为 8B/r,其中 /r 表示操作码后面带一个 Mod R/M 字节。(基指令 8A (10001010) 加 w=1 (16 位寄存器),所以是 8B (10001011))
Mod R/M 的值为 C3,它包含如下字段:
- 位 6〜7 是 mod 字段,指定寻址模式。mod 字段为 11 表示 r/m 字段包含的是一个寄存器编号(r/m 字段指定源操作数)。
- 位 3〜5 是 reg 字段,指定目的操作数。在本例中,AX 就是编号为 000 的寄存器。
- 位 0〜2 是 r/m 字段,指定源操作数。本例中,BX 是编号为 011 的寄存器。
下表列出了更多使用 8 位和 16 位寄存器操作数的例子:
(al 和 ax 分别是 8 位和 16 位寄存器,w 分别 为 0 和 1,所以才一个是 8A 一个是 8B)
指令 | 机器码 | mod | reg | r/m |
mov ax, dx | 8B C2 | 11 | 000 | 010 |
mov al, dl | 8A C2 | 11 | 000 | 010 |
mov ex, dx | 8B CA | 11 | 001 | 010 |
mov cl, dl | 8A CA | 11 | 001 | 010 |