第三章 指令编码核心
  这部分内容较比,花了多点时间去写,所以比较仔细。
  本章节讲解的核心是Opcode、ModRM以及SIB,这三者的已经是紧密结合,无论分开谁单独来讲都不能透切的了解x64的指令编码体系。



第一节 学会看 Opcode 表

  怎么去看指令的opcode,获得指令opcode编码,是很有学问的。

一、从指令参考页里看opcode 码

  可能许多人喜欢从指令参考页里查看opcode,下面的图是AMD文档中对于指令参考页的描述:
 
从指令参考页里可以得出以下信息:
(1)  指令助记符mnemonic
(2)  指令的Operand属性
(3)  指令的Opcode码
(4)  指令的描述。

  这确实可以得到想要的Opcode码,还是Operand数以其属性。下面摘录了mov指令一部分的参考页:


 

  从这里看出,这个Opcode 8B 有几种操作数形式,reg <- reg、reg <- mem,operand-size 可以是16/32/64。

  不过这并不是了解Opcode码的好地方,指令参考页主要是对指令的操作进行相应的描述。对掌握Opcode码不是那么直观和透彻。



二、掌握Opcode 表

  只有学会看Opcode表才是正道,Opcode表是一个全面的透彻的总结表,又可以说十分细致。Intel和AMD的文档中均提供了Opcode表,Opcode表有One-byte Opcode表、Two-byte Opcode表和X87 Opcode表等。

1、Opcode表上的基本元素

  Opcode表上描述的范围是00 ~ FF,即1个字节共256个值,每1值描述不同的属性,包括:
●  绝大部分代表1个Opcode码。
●  26、2E、36、3E、64、65、66、67、F0、F2、F3则是prefix。
●  OF指示2个字节的Opcode。
●  还有一部分是Group指令,由ModRM中的reg来决定。这部分还包括了x87 float指令的Opcode码。
  每个Opcode码还附有相应的Operand属性,Operand属性是用来描述Operand的,包括Operand寻址类型及Size。

看看mov指令8B Opcode 表是怎样的:
 


  上图圆圈所示是Opcode 8B,它对应的是mov的mnemonic,表格中的Gv, Ev是描述这个Opcode码所对应的指令的Operand属性。表示:
(1)  两个Operands分别是:目标操作数Gv,源操作数Ev
(2)  Gv表示:G是寄存器操作数,v是表示操作数大小依赖于当前代码的Default Operand-Size,也就是CS.D,可以是16位,32位以及64位。
(3)  Ev 表示:E 是寄存器或者内存操作数,具体要依赖于ModRM,操作数大小和Gv一致。

  4个字符便可以很直观的表示出:操作数的个数以及寻址方式,更重要的信息是这个Opcode的操作数需要ModRM进行解析。
  要看懂Opcode表必须学会分析和理解Operand属性字符,Intel和AMD的Opcode表前面都有对Operand属性字符很仔细清晰的定义和说明。

记住以下两点:
(1)  Operand属性都有两组字符来定义,前面的一组大写字母是Operand类型,后面一组小定字母是Operand 大小。
如:Gv 这里G是Operand类型,表示是General-Purpose Register(GPR)通用寄存器,也就是rax~r15共16个。这是有别与Segment Register、XMM寄存器等。v是Operand大小,这个Size是依赖于当前的Default Operand-Size。

(2)  操作数是直接编码在Opcode中,这些操作数是寄存器。这些寄存器的ID值已在Opcode中,而无需指出。Operand Size是依赖于当前Default Operand-Size。

以下举两个例子。
(1)  以典型的Jmp Jz为例,它的Opcode是E9,Operand属性是Jz,J是代表基于EIP的相对寻址,也就是说,操作数寻址是偏移量(Offset)加上EIP得出。z则表示Operand-Size是当前Default Operand-Size,这个Operand-Size是不能被override的,也就是说不能被加66h Prefix来调整Operand-Size。

(2)  另一个典型的例子是call Ev,它的Opcode是FF,这个Opcode是个典型的Group Opcode,为什么会定义为Group,下面的将会有阐述。操作数的寻址是典型的ModRM寻址,更严格地讲是ModRM中的r/m寻址。E既可是GPR也可以是Mem。它同样是v属性的Operand-Size。


下面列举一些常见的Operand属性字符,详述请参考Intel或AMD手册。
(1)、Operand类型字符
E:GPR或Mem,依赖于ModRM中的r/m域
G:GPR具体ID依赖于ModRM中的reg域
I:Immediate直接在指令encode中
J:EIP相对寻址操作数,EIP+offset
O:绝对寻址和Immediate一样,直接在指令encode中


(2)、Operand大小字符
b:One-byte
d:four-byte(doubledword)
q:eight-byte(quadword)
v:16位、32位、64位依赖于Default Operand-Size,可被66h Prefix改写
z:16、32、64位依赖于Default Operand-Size,不可被66h改写

通过查Opcode表能迅速得出该指令的Opcode及Operand详情。


2、透析Opcode的编码规则
  prefix与Opcode共享00~FF的空间,由于Prefix部分是可选的,当CPU取指单元从ITLB加载指令L1-Icache和prefetch buffer,预解码单元通过prefix自已的ID值来解析prefix。如读入66h时是解析为prefix而不是Opcode。同样,读入0F时被解析为是2个字节的Opcode中的第1个字节。
  Opcode的operand寻址一部分是在Opcode码直接中指定,一部分是依赖ModRM给定,还有一部分不依赖ModRM给定。直接中Opcode指定的operand寻址的是GPR寻址,如:inc eax指令(Opcode是40h),还有串指令,如loads等。
  有2个Operands数的Opcode,有三种情况,一种是:一个Operand由Opcode指令指定,另一个operand却不依赖于ModRM。这种情况下,一个operand必定是GPR,另一个不依赖于ModRM的operand必定是Immediate和Displacement。看看以下两个Opcode:
  指令mov rax, Iv 它的Opcode是B8,目标操作数是由Opcode中指定的GPR(rax),源操作数不依赖于ModRM的Immediate,在这种情况下,这个Immediate可以不受4字节限制,它可以是64位的值,即:mov rax, 0x1122334455667788是完全正确的。
  又如指令mov rax,Ov 它的Opcode是A1,目标操作数由Opcode指定的rax寄存器,源操作数是不依赖于ModRM的displacement值,同样在这种情况下,这个displacement可以不受4字节限制,它可以是64位值,即:mov rax, qword ptr [0x1122334455667788] 是完全正确的。

  第二种是:两个操数都由ModRM提供寻址,这种情况下,这两个操作数要么两个都是寄存器寻址。要么是一个是寄存器,一个是内存寻址。那么,ModRM中reg提供具体的寄存器寻址,r/m提供另一个寄存器寻址或内存寻址。这种指令很常见,如:mov eax,ecx 它的操作数由ModRM提供寻址。大多数编译器会将ecx由reg提供寻址,即:reg = 001。而eax则由r/m提供寻址,即:r/m = 000,这样,ModRM的值就是:11-001-000 也就是c8。所以:mov eax, ecx指令的机器编码是:89 c8

  第三种是:一个操作数由ModRM提供寻址,另一个操作数则不由ModRM提供寻址,那么这个Opcode必定是Group属性Opcode。因为ModRM的reg将会用来协同Opcode来确定最终Opcode。这种指令也很常见,如以下这条指令很常见:mov dword ptr [eax-0xc], 1 它是operand属性字符是:Ev,Iz 这是很典型的Group属性Opcode,目标操作数由ModRM的r/m提供寻址,源操作数则是立即数,这种情况的Immediate则要受到4字节的限制,若是:mov qdword ptr [rax- 0x0c], 0x1122334455667788 则是错误的。最终它的机器编码是:
C7 40 f4 01 00 00 00



  要深入掌握Opcode规则,必要记住上述2点:一是Opcode表的基本元素,学会分析Operand属性字符,二是理解上面所讲的operand寻址模式。


三、x87指令、3Dnow指令、64-media and 128-media指令介绍

1、x87指令介绍
  Opcode范围从D8 ~ DF是x87 float指令Opcode,实际上x87指令的Opcode是2个字节的,D8~DF是主字节,ModRM是补充字节,协助主字节。也可以说它是Group属性的Opcode,但x87的ModRM比Group属性Opcode中的ModRM是更进一步协助关系。
  x87指令绝大部分是float寄存器(st0~st7)寻址的,实际是与mmx0~mmx7寄存器共用物理寄存器。
  在ModRM的mod=11模式下,r/m能提供8个Opcode,它们全是寄存器寻址,而在mod != 11 模式下,x87指令可以提供内存操作数寻址。

  像以下这条常见的x87 float指令:
(1)fstp st(1)   ;将st(0)值复制到st(1)到,并置stack顶为st(1)
它是寄存器寻址,ModRM中的mod = 11 提供寄存器寻址模式,reg = 011 提供fstp opcode,r/m = 001 提供寄存器ID,所以,这条指令的编码是:dd d9

(2)fstp dword ptr [eax]     ;将st(0)值复制到 [eax],并置stack顶为st(1)
它是内存寻址,ModRM的mod = 00 提供无disp内存寻址,reg = 011 提供fstp mem32 opcode,r/m = 00 提供 [eax] 内存寻址。所以,这条指令的编码是:d9 18


2、3DNow 指令
  AMD 设计的3Dnow别开生面,实际上3个Opcode的指令集,前2个Opcode是0F 0F,这两个Opcode是起引导为3Dnow的作用,第3个Opcode是主Opcode 但是这个Opcode却又不跟在第二个Opcode后面。实际,第3个Opcode改由Immediate来充当,这个Immediate值是固定为1个字节的。
  它是编码序列是:0F 0F  ModRM  SIB  displacement  Immediate
  AMD在最新的SSE5指令也沿用了这种模式,只是更进一步增加编码序列单元,这将在以后章节讲述。
  下面举1个例子来示范:指令pfcmpge mmx1, qword ptr [eax]
  这条指令的operand助记符是:Pq,Qq  实际上与 Gv, Ev 情况一样,只不过这里寄存器由GPR变为mmx寄存器,操作数却固定为64位。
  它的Opcode码是:90  也就是imme为90,由ModRM的reg提供P的寻址,r/m提供Q的寻址。故 mod = 00,reg = 001, r/m = 000
  所以,这条指令的编码是:0F 0F 08 90


3、64-media与128-media指令
  一部分指令是3个Opcode一部分是2个Opcode,而3个Opcode则是有一个prefix来充当。这是Intel设计的编码架构,若是AMD设计的编码则极大可能不同,AMD设计的风格是使用后面的Imme来充当第3个Opcode,而Intel则是使用prefix,这里可以看出AMD与Intel风格的不同。
这些指令使用66h、F2以及F3 prefix 作为第1个Opcode,0F作为第2个Opcode。
举1个例子:movntdq xmmword ptr [rax], xmm0
   这是一条复128位的指令,从xmm0复制到 [rax] 内存上,它采用66 0F前导,第3个Opcode是E7,ModRM提供寻址,mod = 00,reg = 000,r/m = 000
  所以,这条指令的编码是 66 0F E7 00 



四、  强悍的AMD SSE5指令集
  AMD设计3Dnow与SSE5指令集的目的很明显,想摆Intel的制约,反过来想引导Intel向他自已靠拢。特别是SSE5指令集。SSE5指令集的数量不少,有八九十个之多。实事上AMD推出的x86_64指令集就成功的制约了Intel,Intel不得不跟随AMD脚步。SSE5能不能再引导Intel向他靠还得拭目以待。

1、  AMD SSE5指令集编码的特点:
(1)  SSE5指令操作数可以增加到4个。这个4个操作数可以全是寄存器操作数,这是通过增加DREX字节来实现的。
(2)  SSE5指令编码有3个Opcode,其中2个是引导Opcode和1个主导Opcode。
(3)  SSE5指令编码中有3个字节来定位寻址操作数。在原有的ModRM和SIB的基础上,增加了Drex字节来寻址。
(4)  无需prefix以及REX prefix进行修饰。
(5)  目标操作数固定为寄存器


2、  SSE5指令编码序列,如下:

0F 24(或0F 25)+Opcode3+ModRM+SIB+DREX+Displacment+Immediate 

●  0F 24 和 0F 25 是引导Opcode,这两个Opcode在原来是无效。
●  Opcode3是主导Opcode,定性SSE5的操作
●  ModRM与SIB意义和原来一致
●  DREX意即:Dest+REX,DREX.dest是定义目标操作数,REX的含义和REX prefix一致。
●  Displacement含义和以前一致。
●  Immediate含义有些改动,在SSE5指令里只有1个字节大小。


3、Opcode3的结构
位       含义
7 ~ 3            Opcode码
2               Oc1,它与DREX的Oc0组合起来控制操作数
1 ~ 0            OPS,它定义操作数的大小


4、DREX的结构
位       含义
7 ~ 4            Dest域,即目标操作数的ID值
3               Oc0,与Opcode3的Oc1组合起来控制操作数
2               即REX.R
1               即REX.X
0               即 REX.B


5、控制操作数
  Opcode3.Oc1+DREX.Oc0 组合共2位值控制操作数分配
  对于4个操作数的指令来说,例:fmaddps dest, src1,src2,src3 其含义如下:

值             含义
00             dest = DREX.dest,src1 = DREX.dest,src2 = ModRM.reg
               src3 = ModRM.r/m

01             dest = DREX.dest, src1 = DREX.dest, src2 = ModRM.r/m 
               src3 = ModRM.reg

10             dest = DREX.dest,src1 = ModRM.reg,src2 = ModRM.r/m
               src3 = DREX.dest

11             dest = DREX.dest,src1 = ModRM.r/m,src2 = ModRM.reg
               src3 = DREX.dest

像:fmaddps xmm1, xmm2, xmmword ptr [rax], xmm1 这条指令的机器编码是:
0F 24 04 10 10

若:fmaddps xmm1,xmm2,xmmword ptr [rax+0x11223344], xmm1 
则是:0F 24 04 90 10 44 33 22 11







第二节 ModRM寻址



  本节将讲解另一个重点:ModRM寻址。这个ModRM寻址非常重要。是理解x86和x64平台上指令Operand的关键。
  记住,理解透彻是最关键。若真的不能理解,能背出也不错了。


一、ModRM的含义
  ModRM字节的组成部分为:mod-reg-r/m 三个部分,mod为2位,reg与r/m是3,组成2-3-3的比例。

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值。

这里有2个设计上的问题:
(1) 如果像这条指令:mov eax, [eax+ecx*2+0x0c] 在这条指令里eax是base寄存器,ecx是Index寄存器,2是scale,还有一个displacement 
  这种内存寻址是base+index*scale+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 中给出。

所以,在最终的mod =00上,r/m 寻址设计上,如下表格:
r/m                 内存寻址
000                 [rax]
001                 [rcx]
010                 [rdx]
011                 [rbx]
100                 [SIB]   ---- 原本对应 [rsp]
101                 [disp]   ---- 原本对应 [rbp]
110                 [rsi]
111                 [rdi]

而在mod = 01及02上,r/m 的 101域上,设计为 [rbp + disp8] 及 [rbp + disp32],提供了对 [rbp+偏移量] 的支持,这样也符合原本设计的语义。


4、16位寻址下的ModRM
  在16位寻址下是不支持base+index*scale这种寻址模式的,这样在编码序列里就无需提供SIB字节了。但还是有限地支持基址+变址寻址模式,基址寄存器只有2个,就是bx 和bp。变址寄存器也只有2个,就是si和di。
  基于上述设计,基址+变址寻址的组合只有4个,也就是:[bx+si]、[bx+di]、[bp+si] 以及 [bp+di]。它们对应r/m域就是000 ~ 011。
那么这4个寄存器的间接寻址就是:[si]、[di]、[bp] 以及 [bx],对应于r/m的100 ~ 111,同样如上述,bp 是stack frame 指针,一般使用需加 disp(offset),所以 [bp] 让位给 [disp] 解决直接寻址的问题。

所以,16位的mod = 00 下,ModRM最终设计方案是:
r/m                 内存寻址
   000                 [bx+si]
001                 [bx+di]
010                 [bp+si]
011                 [bp+di]
100                 [si]
101                 [di]
110                 [disp]     ------ [bp] 让位给 [disp]
111                 [bx]

mod = 01、10 以及 11 的情形下如前如述。


5、64位寻址下的ModRM
  在64位下,ModRM的含义与32位一致,改进的只是在原来基础上增加了8个GPRs,通过REX prefix进行对新增的寄存器进行访问。
  寄存器的ID取值为:0000 ~ 1111。由REX.R以及REX.B位进行扩展访问。




二、结合Opcode来看寻址模式及Opcode的定位
  Opcode定义指令的执行码,用于执行什么操作,对于操作数寻址上,Opcode结合ModRM来定义操作数,这是一个经过反复琢磨推敲的过程,而最终又影响到Opcode的定位。下面讲讲怎么影响到Opcode最终定位。

3、  一个操作数的Opcode定位
  操作数要么就是registers,要么就是memory,要么就是Immediate值。如果指令只有一个操作数。
(1)  如它是register的话,Opcode是无需ModRM配合确定寻址方式的,ModRM的寻址方式的定位是定位2个操作数寻址方式的这种模式。在只有1个寄存器操作数的情况下,ModRM无用武之处。所以,在这种情冲下,寄存器操作数绝大部分是嵌在Opcode里面,它由Opcode的寄存器域指出。如常见的inc ecx、dec ecx、push eax等。那么另一部分肯定是Group属性。若是Group属性,则ModRM有用武之地了。
(2)  如它是Immediate的话,它绝对是无ModRM。直接将Immediate值嵌入指令编码里。
(3)  如它是memory的话,它绝对是Group属性,需要ModRM的reg来配合定位。那为什么不能是直接offset呢,直接offset寻址留给最常用的,最有用的Opcode,以免Opcode占位,浪费资源。

4、  两个操作数的Opcode定位
  两个操作数大部分都需 ModRM 配合定位寻址。ModRM提供的2个操作数寻址大有用武之地。

(1)2个操作数中,其中1个是寄存器的这种情况最直接简单,由ModRM的reg及r/m提供寻址。若其中1个是GPRs的情形下更简单,GPRs则直接由Opcode提供寻址。
(2)2个操作数中,没有寄存器的情形下,也就是要么是memory,要么是Immediate,它必然是个Group属性,reg域提供Opcode的定位,r/m提供内存寻址。Immediate 直接嵌入指令编码中。

5、  三个操作数的Opcode定位
  三个操作数中有一个必定是Immediate,在AMD的SSE5 指令集推出之前,x86平台是无法提供第3个非Immediate操作数的定位。直至AMD 的SSE5通过增加另一个描述操作数的字节来寻址第3个操作数。






第三节 SIB 寻址
  

   在ModRM 无法提供更多内存寻址方式时,使用SIB进行协助寻址。对内存操作数寻址提供补充定义。使用SIB进行寻址的完整模式如:[rax + ecx * 8 + 0x11223344]。
  此时,SIB字节是:scale = 11,index = 001,base = 000,组合起来是 c8


一、SIB的含义及结构
  SIB意即:Scale  Index  Base,用来定义base+index*scale+disp这种寻址模式。同样按2-3-3比例组合。
scale 索引因子的含义:
值             含义
00             无 scale,或者:scale * 1
01       按 index * 2 比例
10             按 index * 4 比例
11       按 index * 8 比例

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


二、对ModRM的补充定义
  前面提到ModRM中,ModRM.r/m = 100时,[esp] 让位给 [SIB] 提供引导 SIB 字节。这种情况下,指令使用了base+index的寻址方式。
   SIB提供了从 [base+index] 到 [base+index*8] 寻址范围,那么这里延续上一节提到的ModRM设计上的问题:[esp] 这种寻址方式将怎么解决呢?在ModRM中抛弃了 [esp] 的寻址模式,在 SIB中将得到补救。
在index = 100 时,按规则,应该是 [esp + base] 这种寻址模式吧?由于esp的特殊性,esp寄存器只能做base寄存器,以esp为基址。而不能作为index寄存器。所以:[esp+base] 这种寻址模式中 esp 被去除,那么将剩余 [base]。
故当 index = 100时,[esp+base]具体形式将取决于 base。

1、对 [esp] 寻址的补救方式
  由于ModRM中没提供 [esp] 寻址,而又确实需要 [esp] 寻址的话,在 SIB 中通过 index = 100时提供,[esp+base] 中去除掉esp,留下 [base] 寻址,所以在 base = 100时就提供了对 [esp] 的寻址。

2、重复编码
  在index = 100,去除esp后,变为 [base] 寻址,那么此时又提供了[base]的寻址方式,这样就与ModRM.mod = 00 时提供了完全相同的寻址方式,所不同的是一种由ModRM.mod = 00直接给出,另一种通过ModRM./rm = 100然后转接到SIB,再由SIB.index = 100时SIB.base提供。
  Intel设计的时候已不能顾及这么多了,反正重码也没什么多大的害处,只是麻烦了processor的解码单元而已,但这样就显不得够严谨,话说回来,本来x86平台指令集就不是十分严谨。

3、那么当 index = 100,也就是 [base] 寻址,而 base = 101时,按规则此时应该为 [ebp] 寻址。Intel 又变变花样,在 [ebp] 时而又不是 [ebp]。
  前面提到,ModRM,当r/m = 100,也就是让位给 [SIB] 的原来属于 [esp]的地方,r/m = 101提供的是 [ebp] 寻址,当r/m = 100,而SIB.base = 101时,若还是 [ebp],就重复了。
  选择在重复 [ebp] 时改变,可能还是基于 [ebp+disp] 这个stack frame指针+偏移量用法的考虑吧。
此时 [ebp] 而不是 [ebp] 随着ModRM.mod而改变。ModRM.mod = 00时,index = 100,base = 101,此时会改变 [disp32] 寻址,这和ModRM.mod = 00,ModRM.r/m = 101又完全一样了,提供的是 [disp32] 的寻址。
当ModRM.mod = 01和10时,index = 100,base = 101,提供的是 [ebp+disp8] 和 [ebp+disp32] 寻址。而这又和ModRM.mod = 01和10时,ModRM.r/m = 101 提供了完全相同的编码。
  换句话来说:Intel完全没有提供 [ebp] 这种寻址方式,这又验证了Intel的设计构思,就是:ebp是stack frame 指针,因此使用ebp作为基址的话,则要加上一个偏移量。[ebp+disp]这种寻址正好符合Intel的这种理念。
那么,要使用 [ebp] 这种寻址,只好变通一点:采用 [ebp + 0x0] 这种形式了。


三、总结一下SIB寻址

1、重码问题
x86指令的内存寻址有三个地方的重码现象:

(1)、[disp32] 寻址方式的重码现象
●  ModRM.mod = 00,ModRM.r/m = 101 提供了 [disp32] 寻址。
●  ModRM.mod = 00,ModRM.r/m = 100 
然后 SIB.index = 100,SIB.base = 101 提供了 [disp32] 重码寻址。

(2)、[base] 寻址方式的重码现象
● ModRM.mod = 00 提供了 [base] 寻址,也就是 [eax] 之类。
● ModRM.mod != 11 ModRM.r/m = 100 
然后 SIB.index = 100提供了 [base] 寻址,导致重码。

(3)、[ebp+disp8] 与 [ebp+disp32] 的重码现象
● ModRM.mod = 01和10,ModRM.r/m = 101,提供了相应的 [ebp+disp8] 和 [ebp+disp32] 寻址。

●  ModRM.mod 01和10,ModRM.r/m = 100,
SIB.index = 100,SIB.base = 101,提供了相应的 [ebp+disp8] 和 [ebp+disp32]

2、不支持esp做为index寄存器寻址的问题
  因esp设计为stack top指针,而不支持 [esp+base] 这种寻址方式。 esp只能作为base 寻址,即:[esp]。 故 [esp+base] 被去除esp,只剩下 [base],而引发重码问题。

3、  不支持 [ebp] 寻址方式,须作为stack frame pointer 加偏移量的寻址方式。这符合设计语义。程序中使用 [ebp] 则要变为:[ebp+disp8] 或者 [ebp + disp32] 这种方式。



  x86 和 x64 平台的指令编码核心部分:Opcode、ModRM以及SIB 讲解到此为止。
  后续章节将探讨displacement和immediate话题。