欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[原创]谈谈IA-32的指令格式

[原创]谈谈IA-32的指令格式

整理资料,发现一篇老文。如果版主觉得过于基础,可以移到初学者那边。现在不知道基础的东西应该发到哪个版块了。
            谈谈IA-32的指令格式 
    废话不多说,作为CISC的代表,x86指令集的解码工作较为复杂。这种工作主要是为了照顾兼容性,实际上现在把一堆东西挤在几个比特里面完全没有必要,看看MIPS的汇编器,除了伪指令外,其他就是一一对应好嘛。
    这部分的参考资料主要是intel手册2的第2章和第三章的3.1,当然还有手册2的附录。intel手册分为三册,手册1是给汇编程序员用的,主要指导你怎么写汇编程序;手册2是给编译器设计者用的,主要指导你怎么给你的编译器写一个x86后端--这也是我从事工作的一部分;手册3是给系统程序员用的,主要是指导操作系统编写者如何利用CPU。
   先看看x86指令(IA-32)基本编码格式:
 
这部分没有明确的一致的术语,中文翻译确实有点不伦不类。
一、前缀:(prefix)
   前缀是可选字段,也就是说它可以不出现。它分为四类(组),intel的术语叫四个group。在一条指令里,每一类前缀最多只能出现一次。这四类是:
  1.锁总线和重复前缀
  F0H:LOCK 锁总线前缀
    F2H:REPNE/REPNZ 
  F3H:REP REPE/REPZ
    注意这三小类占用的是一个group,也就是说,前缀中出现了LOCK就不能再出现REP之类的重复前缀。
   其实应该发现一个奇怪的问题,F1H不见了,它是否是一种前缀呢?事实上,inte和amd手册在这里存在着差异。在intel手册中,F1H的指令槽是空出来的,但是AMD手册里面把F1H给了INT1。这实际上是一个undocumented opcode。
  2.段超越和分支提示前缀
  CS/SS/DS/ES/FS/GS 六个段超越前缀 对应的编码是
    2E/36/3E/26/64/65
    分支不被接受:2E
    分支被接受:3E
    注意到,2E和3E是被复用的。如果我们写一个反汇编引擎的时候,仅仅得到2E和3E是不清楚到底是CS/DS的超越前缀还是分支预测。需要往后看opcode,这是解析前缀指令需要注意的一点。请注意段超越前缀不能用于分支跳转类指令,而分支预测前缀只能用于Jcc(条件跳转指令),它们是不存在交集的。
   3.操作数超越前缀
    实话说,此处“超越”翻译的不咋样。实际上,是可以改变操作数的宽度。
    66H
   4.地址超越前缀
    67H
这些前缀有些需要说明:
  1)不少前缀需要等待opcode才能确定。所以编程时候需要往后看opcode才能决定prefix解释成什么。
  2)第一类的REP和REPE有什么区别?REPE和REPZ有什么区别?
   REP和REPE的区别是opcode不同。opcode如果修改标志位,则F3H是REPE,否则就是REP。这也是需要看标志位的prefix之一,实际上是两类指令前缀复用了这个指令槽。
   REPE和REPZ没什么区别,助记符的两种写法。
3)什么叫地址超越?什么叫操作数超越?
   超越是对英文override的翻译,这个翻译很奇特,也不符合override的真实意义。但是国内大部分介绍8086的书籍都采用这种翻译。实际上override在此处意思是“重写”。
   重写了什么呢?操作数默认的size(宽度)可能是16位或者32位。而操作数超越前缀就是允许此指令使用非默认的宽度。
   同样地址超越作用类似,允许指令使用非默认的地址宽度(16位或者32位)。
   那么默认的操作数宽度、默认的地址宽度在哪儿呢?代码段选择子有一个位(第22位 CS.D)指明了默认值。被置位,上面两个默认宽度都是32位;被清零,上面两个默认宽度都是16位。
   请注意,给指令施加这两类超越前缀不会改变默认宽度,仅仅是改变该指令的执行使用的宽度。
二、opcode
   实际上opcode分为两个部分,一部分就是opcode本身,另一部分是个别指令会占用modr/m的reg/opcode位域。
   学MIPS或者其他RISC指令集的见到intel的opcode编码可能会吓一跳。相比较MIPS那种I J R三类指令,x86的指令opcode编码实在是太复杂了。x86的opcode最短是1个字节,最长是3个字节。这是其一。
   和MIPS解决方案不同,x86对于源操作数和目的操作数是暗含在opcode里面的。对于念MIPS教科书过来的朋友,几乎难以理解。这话意思是:
   比如说mov指令:
   
我框出来的这两条指令没有任何的本质区别,就是源操作数和目的操作数反过来了。也就是说谁是源操作数谁是目的操作数就隐藏在opcode中。这和MIPS的想法不同。
比方说:
   88 D9 
对应  MOV CL,BL
而 8A D9
对应  MOV BL,CL
D9还是那个D9,但是谁是源谁是目的不一样了(也就是说由于opcode不同,对于D9的解释不一样)。这一点对于modr/m字节理解有帮助。
单字节opcode比较好理解。
二字节和三字节opcode比较特殊:
二字节通用opcode是0fh+一字节的编码;但是二字节的SIMD opcode是一个强制前缀+0fh+一字节的操作码。
不知道大家看懂了没。intel的二字节的SIMD opcode实际上是三个字节,有一个字节(“强制前缀”)被计入了前缀部分。对于二字节的SIMD opcode,这个前缀不是可选部分。
同样的,三字节的通用opcode,是0fh+二字节的编码。SIMD opcode格式是强制前缀+0FH+二字节编码。
问:
  1)此处强制前缀,是否计入“前缀”的四个字节限制?
    不计入。此处强制前缀,是为了引导出来此处是SIMD指令,类似某种转义码。实际上,此处强制前缀,应该纳入opcode的范畴,所以此处SIMD所谓二字节、三字节名不副实,应该是三字节、四字节。
    个别的SIMD指令不需要强制前缀来引导,比如addps(0FH+58H)
 2)0FH是什么?
    x86常用的转义码。此处用来提示是多字节opcode。
3)强制前缀有哪些?
    和前面的prefix编码有重叠。是66h f3h F2h。
4 ) 二字节和三字节都用0FH作为转义码,那么解码时候如何知道是二字节还是三字节呢?
    问的好!事实上三字节指令的第二个字节(它和0FH一起构成三字节的转义码)是在二字节指令槽里面的。就像0FH在一字节指令槽里面一样。
三、modr/m字节
   modr/m是一个字节。被切分成三个位域(23),它用来确定寻址方式,并可能对opcode做一定补充。
    
  mod:提供寻址模式,11=寄存器寻址 其余都是内存寻址
  reg/opcode:   
    两种作用,第一种是提供寄存器寻址;另一种为某些opcode提供补充说明。
  R/M:
    结合MOD位域,提供内存/寄存器寻址。
 1.这里先研究reg/opcode位域:
   1)请问此处提供的是源操作数还是目的操作数的寄存器寻址?换而言之,比如我要编码mov eax,ebx ,此处reg/opcode负责编码eax还是ebx?
    都可以。取决于你使用什么opcode。这个问题我们在opcode那里已经解释了一部分,这里着重解释一下。
    
请注意画红框的两条opcode,都可以用来编码mov eax,ebx。如果采用89编码,则eax为r/m位域,编码是:
  89H       11         011   000
  opcode   reg寻址    ebx   eax
请注意89编码时候,目的地址是在r/m字段,也就是说,此处的eax是目的地址。这样编码就是89 D8。
而采用8B编码,则eax为reg/opcode位域:
  8BH       11        000    011
 opcode   reg寻址    eax     ebx
最终编码是8B C3。
这也就是我上面说的,x86把源和目的操作数隐藏在opcode里面了。
   2)什么类型的opcode需要这个位域来补充?为何不干脆再用一个字节编码opcode,要占用这个位域?
   这个问题你得问设计这些指令槽的设计师。实际上再设计一个字节编码也毫无问题。这部分编码类似元素周期表中镧系元素和锕系元素。
   3)这会给反汇编引擎带来什么问题?
   导致有些指令(被称为组编码指令--在原始的opcode表里面一组占用一个格子,用阴影区分)需要读到ModR/m才能确定其作用。
2.下面谈mod 位域和R/M位域
   这个位域配合r/m一起来确定寻址方式,mod有4种,每一种对应一种寻址方式。请注意,16位下的mod r/m编码对应寻址方式和32位下的完全不同--也就是说,它们没有任何可比性。
   16位下面是没有SIB字节的,那么16位怎么实现基址变址寻址呢?只好通过modr/m来实现。为此,16位牺牲了通用的寄存器间接寻址(只提供BP BX SI DI作为间接寻址的寄存器,并且BP实现稍微特殊)。也就是说16位下面没有mov [al],bl这种东西。
   
我们稍微来研究一下这张32位的modr/m编码表。mod=11时候,是寄存器寻址,这很简单。请注意红杠标注的那些个行。我们注意到:
 1)[ESP]这种寻址方式在modR/M里面没有。它被用来引导SIB(可以理解R/M=100作为SIB的转义码)。表示[ESP]的编码方法要在SIB里面。
 2)[EBP]这种寻址方式类似16位里面[BP]的编码方式。在mod=00时候,反倒没有[EBP]。[EBP]是采用[EBP]+disp8或者[EBP]+disp32来编码的。
  稍后我们会看到,在SIB里面,也有类似的特殊设置。
四、SIB字节
   
同modr/m类似,SIB字节也是采用23切分成三个位域,名字分别叫Scale、Index、Base。SIB的名字也来自这三个位域名字的首字母缩写。
 SIB字节由R/M=100 MOD≠11引导出来。
SIB确定的寻址方式是[base+Index* Scale +disp]
disp意思是后面尾随的若干个displacement字节。

这个图,可以补充几点:
1)base中的ebp哪去了?
   SIB.base=ebp的时候,要看mod决定是何种寻址方式。上图的附注里面已经说明了。这是解析SIB字节,需要反过来查modr/m的情况。
2)index里面的esp哪去了?
   esp作为index时候,index自动被忽略--此时scale因子视为0.(请注意scale编码为00时候,因子视为1)上表里面none真是混淆是非的典范。也就是说esp作为index,计算方法是[base+disp],不管你的scale是多少。
   这种表示有一个重要的作用,就是我们可以表示[ESP]这种寻址方式。如果你还记得modr/m那边把本该属于[esp]的编码当做转义码来引导SIB字节了,导致modr/m无法表示[ESP]。只好用sib来表示。
  前面说过sib表示的是[base+ Index*Scale+disp],要想表示[esp],只好把base设置成esp,scale因子设置成0,disp设置成0.但是我们发现,scale没有0这个编码(00、01、10、11分别代表1 2 4 8 没有0)。只好用替代的方法,如果index是100(esp),则scale*index=0。
  当然这个规则是一致的,就算你的base不是esp,而是eax等寄存器,只要把index设置成100,scale*index一样视为0。
五、displacement和immediate
   问:
  1)这两者有什么区别?
    要说核心区别,那就是displacement大部分用在寻址上,而immediate用于操作数的编码上。
   举个例子:
   mov ecx,1
可以编码成
   OPCODE IMME
   B9      01 00 00 00H
请注意目的操作的地址ecx(一个寄存器)已经蕴含在操作码B9里面了
而mov ecx,[0x1]
编码成:
  opcode   mod   reg   r/m  displacement
  8B       00    001   101  01 00 00 00H
                 ecx   
即
  8B 0D 01 00 00 00H
 其中mod和r/m在一起,表示disp32的寻址方式。
当然了,你也可以额外使用一个SIB字节来编码mov ecx,[0x1]
  opcode   mod   reg   r/m  scale    index  base   displacement
  8B       00   001  100    01     100    101  01 00 00 00H
                     引导   scale随便写
即:8B 0C 65 01 00 00 00
其中scale是可以随便写的,00 01 10 11都行,因为index=100,不管你写什么,都视为index*scale=0.
2)嵌入到指令编码里面所有的立即数不是disp就是imme吗?
  个别指令提供了moffs*的立即数寻址,比如mov指令:
 
此处moffs32等就是moffs*的立即数,可以直接编码在opcode后面,你可以把他们归为一类disp,但是这类disp不需要modr/m字节来引导出来。
比如mov eax,[0x1]就可以编码为以下形式:
a)采用moffs32编码
   opcode moffs32
   A1    01 00 00 00
A1 01 00 00 00H
b)采用MODR/M引导的disp
  opcode   mod   reg   r/m  displacement
  8B       00    000   101  01 00 00 00H
8B 05 01 00 00 00H
c)采用SIB引导的disp
  opcode   mod   reg   r/m  scale    index  base   displacement
  8B       00   000  100    01     100    101  01 00 00 00H
                     引导   scale随便写
8B 04 65 01 00 00 00H

上传的附件 image001.png
image003.png
image005.png
image007.png
image009.png
image011.png
image013.png
image015.png

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途。
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功