由于重装系统同步导致原文章消失,只能再写一遍,方便以后查阅。
#x86指令编码格式
很多时候我们都是查阅指令参考页去了解一个指令的含义,但这并不能理解指令的编码格式。而只有学会看懂opcode表的时候才是王道。
x86是一种CISC指令集,CISC的全称是“Complex Instruction Set Computer”,表示的是一种复杂的指令集,其中一个最重要的复杂性在于在这个指令集中,指令是不定长的,要使得CPU在这种不定长的指令集里面确定每一条指令的含义,就需要一种特定的指令格式,下图显示了Intel 64
和IA-32
架构下的指令格式,当然,这两种架构所采用的都是x86指令编码:
从上面可以看出,x86指令格式中,由6部分组成,但在这之中只有opcode是必须的,其余都是可选。我们稍后就介绍这些组成部分。
#指令prefix
prefix简单的说来就是调整内存操作数属性,增强指令的作用等。
lock
和repeat
其中,LOCK prefix保证该条指令对共享内存的访问是独占的;而repeat prefixes 表示这条指令会重复执行多次,直到某个条件满足位置。其中第二种repeat prefix只能用在对string的操作,或者对I/O的操作上。
operand-size override
(66H — 改变默认操作数大小)这个prefix主要是在解析指令的操作数的时候,可以在十六位或者三十二位的操作数大小间进行切换。
address-size override
(67H — 改变默认操作数地址大小)这个prefix主要是在进行指令寻址的时候,可以在十六位或者三十二位的地址大小中进行切换。
segment override
,branch hints
和bound
其中,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
#寄存器编码表
下面来分析这条指令:
mov eax,ebx
正常编码为:89 d8
下面看看在不同的REX.W
和66H 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 opcode
和3-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
http://subler.github.io/note/compiler/x86_instruction_encode.html