X86指令编码笔记

By xia0

由于重装系统同步导致原文章消失,只能再写一遍,方便以后查阅。

#x86指令编码格式

很多时候我们都是查阅指令参考页去了解一个指令的含义,但这并不能理解指令的编码格式。而只有学会看懂opcode表的时候才是王道。

x86是一种CISC指令集,CISC的全称是“Complex Instruction Set Computer”,表示的是一种复杂的指令集,其中一个最重要的复杂性在于在这个指令集中,指令是不定长的,要使得CPU在这种不定长的指令集里面确定每一条指令的含义,就需要一种特定的指令格式,下图显示了Intel 64IA-32架构下的指令格式,当然,这两种架构所采用的都是x86指令编码:

指令编码格式

从上面可以看出,x86指令格式中,由6部分组成,但在这之中只有opcode是必须的,其余都是可选。我们稍后就介绍这些组成部分。

#指令prefix

prefix简单的说来就是调整内存操作数属性,增强指令的作用等。

  • lockrepeat

    其中,LOCK prefix保证该条指令对共享内存的访问是独占的;而repeat prefixes 表示这条指令会重复执行多次,直到某个条件满足位置。其中第二种repeat prefix只能用在对string的操作,或者对I/O的操作上。

  • operand-size override66H — 改变默认操作数大小)

    这个prefix主要是在解析指令的操作数的时候,可以在十六位或者三十二位的操作数大小间进行切换。

    operand_size_override

  • address-size override67H — 改变默认操作数地址大小)

    这个prefix主要是在进行指令寻址的时候,可以在十六位或者三十二位的地址大小中进行切换。

    address_override

  • segment overridebranch hintsbound

    其中,segment override prefix会在执行这条指令的时候将默认的段寄存器给换掉;branch hints prefix主要应用在条件跳转指令(Jcc)中,可以协助CPU进行指令的prefetch;而bound prefix主要是用intel MPX硬件特性上。

    | 2E | 3E | 26 | 64 | 65 | 36 |
    | ———– | ———– | ———– | ———– | ———– | ———– |
    | CS register | DS register | ES register | FS register | GS register | SS register |

#REX prefix—开启 64 位计算的基石

REX prefix 是不定值,它的取值范围是:40 - 4F (共 16 个)

7 6 5 4 3 2 1 0
0 1 0 0 W R X B
  • W: operand width 标志位,当 W = 0 时使用 default operand size,当 W = 1 时使用 64 位 operand size
  • R: 用来扩展 ModRM.reg 域
  • X: 用来扩展 SIB.index 域
  • B: 用来扩展 SIB.base, ModRM.r/m 以及 Opcode.reg
#寄存器编码表

register_table

下面来分析这条指令:

mov eax,ebx

正常编码为:89 d8 下面看看在不同的REX.W66H prefix下的不同:

  • 48 89 d8——————> mov rax,rbx

  • 66 48 89 d8—————> mov rax,rbx

  • 66 40 89 d8—————> mov ax, bx

第 1 条指令编码使用 REX prefix 扩展访问 64 位寄存器,REX.W = 1

第 2 条指令编码加上了 66H prefix 同时还有 REX prefix(REX.W = 1),此时一般会认为产生了冲突:是使用 64 位还是 16 位 operand size 呢?
实际上,很简单!48H 位于 66H 后面,66H 将被覆盖!也就是说:66H prefix 将会被忽略,REX prefix 产生了作用!因此:指令的 operand size 是 64 位的。

第 3 条指令编码也同样使用了 66H prefix REX prefix,但是 REX.W = 0 意味着不改变原来的 operand size!
在这种情况下,REX prefix 不会与 66H prefix 产生冲突,最终的作用于 66H prefix,因此 operand size 是 16 位的。

#opcode

整个编码中opcode是必须的,因为代表着这条指令的含义,整个指令的核心,且prefix与Opcode共享00~FF的空间。

一个opcode可以由一个byte组成,我们称之为1-byte opcode,当然,与之对应的,就有2-bytes opcode3-bytes opcode。其中,后两者一般会有一个被称为escape opcode的byte进行引导,该byte的数值是0FH。所以,一般情况下,2-bytes opcode就是0FH后面再加一个byte,而3-bytes opcode就是0FH后面再加两个bytes。

#ModR/M

ModRM在指令中非常重要,是理解x86平台opcode的关键,决定着寻址模式。

由一个byte组成,其格式如下:

7 6 5 4 3 2 1 0
mod reg R/M

1、mod:寻址模式。
  2位组成4种寻址模式,总的来说,只有两种寻址模式,就是:内存寻址模式和寄存器寻址模式。

  • mod = 11指出寄存器寻址模式,mod = 00 ~ 10 时指出内存寻址模式:
  • mod = 00,定义 [register] 间接寻址,无displacement值。
  • mod = 01,定义 [register + disp8],有8位displacemnet 偏移值。
  • mod = 10,定义 [register + disp32],有32位displacement偏移值。

2、reg:寄存器ID值
  3位组成8个寄存器ID值,从 000 ~ 111,对应于 RAX、RCX、RDX、RBX、RSP、RBP、RSI以及RDI。这个ID值可以被REX prefix扩充为4位,范围从 0000 ~ 1111可表示16个寄存器。

reg域的另一含义是对Opcode的补充,对分为一组Opcode的进行选择(Group属性)。

3、r/m:意即register / memory。

​ 提供对registers或memory的寻址,也用来表示寄存器ID,当是registers时是寄存器ID值。当是memory时是寄存器间接寻址中的寄存器ID值。当mod != 11 时,r/m 表示 [rax] ~ [rdi],REX prefix用来扩充寄存器ID值。

注意:

(1) 如果像这条指令:mov eax, [eax+ecx2+0x0c] 在这条指令里eax是base寄存器,ecx是Index寄存器,2是scale,还有一个displacement ,这种内存寻址是base+indexscale+disp。这需要SIB字节来进行确定,那么ModRM必须要有一个手段来引出后续的SIB字节。在 [rax] ~ [rdi] 的范围里,Intel选择了原来应属于 [rsp] 的值用来引出SIB,一是因为 [rsp] 并不常用吧。二是因为 rsp 设计为 stack top指针,专用于stack top指针。
  原来属于 [rsp] 的领域对应的,r/m是100,这个领域被 [SIB] 替代了,事实上在16位机器原本是没有SIB字节的,base+index*scale+disp这种寻址是后来才增加的。16位的ModRM上是没有SIB引导域。

(2)如果内存寻址中没有base和index,只有disp的话,如:mov ebx, [0x11223344],这种直接寻址方式,在设计上ModRM还必须为提供这个模式。
  Intel又作出修改,选择了原来属于 [rbp] 模式的领域提供给 [disp],选择 [rbp] 让给 [disp],是因为 rbp 原本意图就是设计为 stack基址指针。[rbp] 寻址一般都要加上一个偏移量,也就是基于stack frame指针的偏移量,即 [ebp + disp] 这种寻址模式在 mod = 01 或 mod = 10 中给出。

#SIB

SIB意即:Scale – Index – Base,用来定义base+index*scale+disp这种寻址模式。同样按2-3-3比例组合。

7-6 5-4-3 2-1-0
scale index base

index 域指出index寄存器的ID值,范围从 000 ~ 111。base 域指出base寄存器的ID 值,从 000 ~ 111。Index与base经过 REX prefix可以扩展为0000 ~ 1111。

#Displacement 和 Immediate

某些指令会在最后要求有一个用于计算内存地址的值,或者一个立即数。很明显,不多说。

#综合起来,看下面的例子:

  • 49 8b 7d 00

    49=0100 1001对应REX prefix,默认64为size模式

    8b= mov opcode

    7d=01 111 101—>ModR/M mod = 01,定义 [register + disp8],有8位displacemnet 偏移值。rdi ,r13

    00=Displacement = 0

    所以指令= mov rdi,QWORD PTR [r13+0]—>mov rdi,[r13]

#参考及索引

http://ref.x86asm.net/coder32.html —-> 查阅opcode

http://pnx.tf/files/x86_opcode_structure_and_instruction_overview.png —>quick look

Intel x86 Assembler Instruction Set Opcode Table

http://www.mouseos.com/x64/index.html

https://bbs.pediy.com/thread-78121.htm

X86指令编码的那些事儿

http://subler.github.io/note/compiler/x86_instruction_encode.html

onlineDisassembler