1. 程式人生 > >Intel硬編碼(二):不定長指令、ModR/M與SIB詳解(基於P6微架構)

Intel硬編碼(二):不定長指令、ModR/M與SIB詳解(基於P6微架構)

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:指令集參考)