Intel硬编码(二):不定长指令、ModR/M与SIB详解(基于P6微架构)

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/Apollon_krj/article/details/77524601

Intel硬编码(一):Opcode Map、定长指令与指令前缀
我们在Opcode Map中提到定长指令的索引方式,也分析了比较常见的一些定长指令,接着我们就要进行不定长指令的分析了。所谓不定长指得是SIB部分、Displcement、Immediate三部分存在与否以及各自长短,在Opcode与ModR/M确定之前都是不确定的。而ModR/M存在与否也是根据Opcode来确定的,一旦Opcode确定,我们就能知道它是一个定长指令还是不定长指令,而其不定长的细节还得根据ModR/M和SIB来进行一一分析:
这里写图片描述

1、ModR/M引出:

(1)、首先来看几条指令的通用公式:

88<–>mov Eb,Gb
89 <–>mov Ev,Gv
8A<–> mov Gb,Eb
8B <–>mov Gv,Ev

这几条指令也是根据Opcode Map查出来的(下图圈出来不定长指令的一部分):
这里写图片描述

解释:

G:通用寄存器
E:寄存器/内存
b:字节
v:word\double word\quadword(16/32/64位,取决于CPU模式)

由于88、89、8A、8B中都有E,而E表示是“寄存器/内存”这里便存在是寄存器还是内存的不定性,这是不定长指令的一个体现,但是这还不是具体体现,只是一个形式不同的表象。但是这也是从Opcode Map中查表时能确定是定长指令还是不定长指令的方式。

(2)、指令常规情况分析:
如果确定是不定长指令,则其后必定存在一个字节的ModR/M,而ModR/M的bit信息指出了通用形式的不定长指令的具体形式,ModR/M的格式如下所示:

这里写图片描述

其中第3、4、5位三位即Reg/Opcode来确定是哪一个通用寄存器G,(暂时仅考虑Reg/Opcode中reg的情况);
其它两部分来确定E是什么(R/M)以及具体细节。
(Mod值有03四种情况、Reg/Opcode和R/M有07八种情况;Mod的00~10是内存,11是寄存器;R/M与Reg/Opcode的值即为寄存器的编号:eax/ax/al编号0、ecx/cx/cl编号为1…)

我们拿一条指令来具体分析:

测试一:"88 01 02 03 04 05 06 …"
分析:"88"我们知道其同通式是“mov Eb,Gb”,因此88是不定长指令,所以其后的一个字节**"01"即为ModR/M;
②我们将
“01”按照ModR/M的格式拆分成三部分:
01== 0000 0001 ==> 00 000 001三部分 ==> Mod=000=0,Eb即为byte ptr的内存;Reg/Opcode=000=0,即为eax/ax/al寄存器(Eb即byte则为al);R/M=001=1,即为ecx
③确定出“8801”的汇编指令为:mov byte ptr [ecx],al ==>mov byte ptr ds:[ecx],al(没有指令前缀则DS是默认的)
④而
02 03 04 05 06…**就是下一条指令的编码了。

测试二:89 01 …(以32位CPU为准)
①由89可以确定是mov Ev,Gv格式(v在32位CPU下是dword);
②01 == 00 000 001三部分 ==>Mod=00(DS:[]);Reg/Opcode=000(EAX);R/M=001(ECX)
③所以汇编指令为:mov dword ptr ds:[ecx],eax

以上计算步骤归结为一张表:
这里写图片描述
该表分为五大块:寄存器编号部分的最上面一块,以及以Mod分界的下面的四块。用ModR/M解析出来的Reg/Opcode去第一块中查具体寄存器;以Mod和R/M去查Mod块中具体的某一行,最后再合并查到的各部分得到汇编指令。

测试三:8A 82 12 34 56 78
①8A确定是mov Gb,Eb格式;
②82 ==> 1000 0010 ==> 10 000 010三部分
③查表得Reg=al,Mod与R/M确定内存格式:disp32[edx](disp32即32位偏移地址,在硬编码中高地址在低字节存放
④汇编指令为:mov al,byte ptr ds:[edx+78563412]

2、ModR/M中的特殊情况与SIB引出:

我们在以上分析的三条指令都属于常规的,仅根据分析与**“Table2-2”**就能确定的。但是在“Table2-2”表中有几种特殊情况(即用绿框和红框标记的4种情况)。

(1)、绿框框起来的是Mod=00且R/M=101时的情况(对应ModR/M的值由05、0D、15、1D、25、2D、35、3D八种具体情况),这些情况只需要将原本的ebp换成一个disp32即可(该数即机器指令中紧接着ModR/M后面的四个字节)。这其实也是不需要其它辅助性工作就能解析出来的,测试如下:
这里写图片描述

(2)、除了绿框之外的三种情况,仅仅依靠Table2-2一张表是无法解析出来的。还需要SIB和另外一张表(SIB的解析步骤归结的一张表)才能够解析的,Table2-2的Notes部分也提到了这张表Table2-3:
这里写图片描述
该表中也存在特殊情况,我们先来分析该表的一般情况:该表是根据SIB的bit信息来索引查看的,SIB是紧接着ModR/M的一个字节。不定长指令后必有ModR/M,而ModR/M的Mod不为"11"且R/M值为"100"(ESP)时则ModR/M后就有SIB。
我们先来看SIB的格式与解析方式:
这里写图片描述
该三部分均存在于[]的括号中,格式为:Base + Index*2^(Scale),Base为寄存器编号索引的寄存器,Index也是寄存器编号索引的寄存器,Scale为00~11,因此格式又为:Base + Index * 1/2/4/8所以格式形如:ds:[eax+ecx*4]

我们举例来具体分析:

解析"88 84 48 12 34 56 78":
Opcode = 88 --> 指令格式:mov Eb,Gb
ModR/M = 84 --> 10 000 100 -->[reg+disp32](普通格式), al,esp
③由于Mod为10,且R/M为ESP,则属于特殊情况,不遵循普通格式,所以下一个字节为SIB(可确定汇编指令为:mov byte ptr [–][–][disp32],al
④[–][–]解析:SIB = 48H --> 01 001 000;Scale=1,Index=1(ECX),Base=0(EAX)
⑤得到汇编指令为:mov byte ptr [eax][ecx2][78563412],al ==> **mov byte ptr [eax+ecx2+78563412],al**
测试如下:
这里写图片描述

3、SIB中的特殊情况:

SIB中的特殊情况我们已经框了出来,接下来让我们一一来看看这些特殊的情况是如何处理的,Table2-3的Notes中说明的这些特殊情况的处理方式:

The [*] nomenclature means a disp32 with no base if MOD is 00, [EBP] otherwise. This provides the following addressing modes:
disp32[index] (MOD=00).
disp8[EBP][index] (MOD=01).
disp32[EBP][index] (MOD=10)

在官方文档解决方式中分类进行描述(或许是OD实现时与官方文档有差异),但是经测试发现该描述似乎有点繁琐也不准确(MOD无论是00~11里面的那个SIB=20、60、A0、E0,disp都是disp32):
这里写图片描述
其实就只有一种情况需要特别对待,我们知道[–][–]两部分分别为:[Base]和[Index * 2^(Scale) ]。
若**index == 100(ESP)[Index * 2^(Scale)]**部分不存在。其他情况完全照常,和Base等于101与否完全没有关系,不会影响Base的结果,比如:

①index 不等于100(SIB = 54/55),base等于101与(55)否(54),结果都一样(Base和index都存在):
88 9C 54 12 34 56 78
88 9C 55 12 34 56 78
②index 等于100(SIB = 64/65),base等于101与(65)否(64),结果都一样(index都不存在):
88 9C 64 12 34 56 78
88 9C 65 12 34 56 78
这里写图片描述

总结:在上面我们遇到了88 0189 01这些2字节长度的指令,但是在对于同一个Opcode还有不同长度的指令如:88 84 48 12 34 56 78的长度为7字节长,对于88来说这不定的指令长度便是由ModR/M与SIB引起的,而这对于种情况,也是设计时为了解决对于过多形式的汇编指令,仅需要采用极少的硬编码(指令前缀、Opcode、ModR/M、SIB组合)就能表示的的一种设计方式。

参考资料: Intel Architecture Software Developer’s Manual Volume 2: Instruction Set Reference(Intel白皮书卷2:指令集参考)

没有更多推荐了,返回首页