x86 机器指令编码依次由一下部分组成:
- 指令前缀(prefix,非必需)
- 操作码(opcode,必需)
- 寻址方式 R/M(ModR/M,非必需)
- 比例因子-变址-基址(SIB,非必需)
- 地址偏移量(displacement,非必需)
- 立即数(immediate,非必需)
指令前缀 | 操作码 | 寻址方式 R/M | 比例因子-变址-基址 | 地址偏移量 | 立即数 |
一、指令前缀
指令前缀(prefix)可有可无,可以有多个,每一个前缀都用一字节表示。指令前缀的名称和编码如下表所示。
种类 | 名称 | 二进制编码 | 说明 |
Lock | Lock | F0H | 让指令在执行时先禁用数据线的复用特性,用在多核的处理器上,一般很少需要手动指定 |
rep | repne/repnz | F2H | 用cx(16 位下)或ecx(32位下)或rcx(64位下)作为指令是否重复执行的依据 |
rep/repe/repz | F3H | 同上 | |
Segment Override | cs | 2EH | 段重载(默认数据使用 DS 段) |
ss | 36H | 同上(强制指令使用该段寄存器) | |
ds | 3EH | 同上 | |
es | 26H | 同上 | |
fs | 64H | 同上 | |
gs | 65H | 同上 | |
rex | 64 位 | 40H~4FH | x86-64 位的指令前缀 |
Operand size Override | Operand size Override | 66H | 用该前缀来区分访问 32 位或 16 位操作数;也用来区分 128 位和 64 位操作数 |
Address Override | Address Override | 67H | 64 位下指定用 64 位还是 32 位寄存器作为索引 |
rep 是串操作指令前缀,它又可细分为 rep、repz、repnz 等,重复执行操作指令,直到满足或者不满足某些条件。这三个前缀不会同时使用,最多只使用其中一个。
Segment Override 是跨段前缀。在分段内存管理模式下,使用跨段前缀来强制改变当前指令要访问的段,而不使用该机器指令默认访问的段。在扁平内存管理模式下,依然能在语句中使用跨段前缀,但是指令的功能等同无前缀。例如:
mov ebx,offset x ; x 是 data 段中定义的一个变量
mov eax,cs:[ebx] ; 机器码是 2E 8B 03
mov eax,[ebx] ; 机器码是 8B 03
后两条语句执行的结果是相同的。
Operand size Override 为操作数类型重载。在 .model flat 下,默认的操作数 32 位的(如使用 32 位的寄存器来访问操作数),此时不需要前缀。但是该环境下能够使用 16 位的操作数,在机器指令编码中增加前缀 66H,以区分指令是 16 位的操作数还是 32 位的操作数。例如:
mov ax,[ebx] ; 机器码是 66 8B 03
mov eax,[ebx] ; 机器码是 8B 03
由于 eax 和 ax 的编码是相同的(寄存器编码规则,在下面),从 "8B 03" 中区分不出使用的是 eax 还是 ax,故在使用 16 位操作数时,增加了类型重载前缀 66H。
al 的编码也与 eax、ax 的编码相同。"mov al,[ebx]" 的机器码是 8A 03,没有前缀。操作码(8AH)的最后一个二进制位为 0,表示是对字节进行操作;操作码(8BH)的最后一个二进制位为 1,表示是对字进行操作(32 位指令中默认的是 32 位的操作数)。
二、操作码