前言:
要進行指令模擬,我們先需要了解X86架構下的指令是長什么樣子的。根據intel的編程手冊我們找到了如下信息。
Intel CPU的機器指令格式如下圖所示:
e.g.:圖片位於intel開發手冊第二卷第二章的2.1
根據開發手冊,一條指令由 指令前綴(Instruction Prefixes) + 操作碼(Opcode) + ModR/M + SIB + 偏移(displacement) + 立即數(Immediate data)幾部分組成,一條指令至少需要有Opcode,其它幾部分,在不同指令中可能存在可能不存在。今天我們主要來看Opcode、Instruction Prefixes、Immediate data三部分在不同指令中的使用。
一:x86指令解析
1.1 指令前綴
指令前綴分為四組,每組都有一組允許的前綴代碼。對於每條指令,它僅在包含來自四個組(組 1、2、3、4)中的每一組的最多一個前綴代碼時有用。第 1 組至第 4 組可以以任何相對於彼此的順序放置。
第一組:封鎖和重復執行前綴
F0H: LOCK前綴,封鎖總線。在有數的指令(如ADD,ADC)前方時,使指令變為原子操作,並與被修飾的指令一起提供內存屏障效果。
F2H:REPNE/REPNZ前綴(只位於字符串指令前)
F3H:REP前綴(只位於字符串指令前)
F3H:(與REP前綴同碼)
第二組:段前綴
在32位匯編中,有8個段寄存器:ES、CS、SS、DS、FS、GS、LDTR、TR(順序固定),不再用段寄存器尋址而只做權限控制。前綴和寄存器對應如下:
2E - CS 36 - SS 3E - DS 26 - ES 64 - FS 65 - GS
使用前綴修飾后,指令(opcode)的默認段寄存器會被修改,如默認的MOV操作從DS段拿數據,加上36前綴后會基於SS段地址取數據。
第三組:修改操作數默認長度
66H,用來“反轉”默認的16位或32位操作數寬度。例如,當默認的操作數寬度是32位時,可以用這個前綴選擇16位寬度的操作數,或者反之。如下指令碼和匯編對照:
50: PUSH EAX 6650: PUSH AX
第四組:修改默認地址長度
67H,用來“反轉”默認的16位或32位地址寬度。例如,當默認的地址寬度是32位時,可以用這個前綴選擇16位寬度的地址,或者反之。如下指令碼和匯編對照:
8801: MOV DS:[ECX],AL 678801: MOV DS:[BX+DI], AL
2. 指令碼(opcode)
主操作碼的長度可以是 1、2 或 3 個字節。如果主操作碼不能確定指令則會有額外的字段編輯倒ModR/M中。如果主操作碼是0x0f開頭則需要取第二字節,如果主操作碼開頭是0x0f38,0x0f3a開頭則再取第三字節。
從intel編程手冊中截取一字節指令碼格式如下圖(附錄A,第三章):
部分釋義:
G:通用寄存器
E:寄存器/內存
b:字節
v:word\double word\quadword(16/32/64位,取決於CPU模式)
opcode查看方法:
如指令碼0x48,查看第4行第0列,屬於dec eax(REX是為64位指令集使用的,可自行搜索研究),此時指令為定長指令。
再比如0x28(表示sub Eb, Gb),這里Gb表示通用寄存器,Eb表示還需要其他字段來確定這個參數,這就是下面要講的ModR/M字段。此時指令為不定長指令。
3. ModR/m
字段名稱解析:
Mod: 指明操作碼中的E表示寄存器還是內存,11表示內存,其余表示寄存器
Reg/Opcode:標記通用寄存器
R/M:配合Mod字段標明取址方式,若為內存尋址則需要SIB字段輔助
ModR/m格式表格:
指令解析示例:88 01
①"88"我們知道其同通式是“mov Eb,Gb”,因此88是不定長指令,所以其后的一個字節**"01"即為ModR/M;
②我們將“01”按照ModR/M的格式拆分成三部分:01== 00 000 001三部分 ==> Mod=00=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是默認的)
3. SIB
SIB字段 引出:
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為100,則屬於特殊情況,不遵循普通格式,所以下一個字節為SIB(可確定匯編指令為:mov byte ptr [–][–][disp32],al)
④[–][–]解析:SIB = 48H --> 01 001 000;Scale=1,Index=1(ECX),Base=0(EAX)
⑤得到匯編指令為:mov byte ptr [eax][ecx*2][78563412],al ==> **mov byte ptr [eax+ecx*2+78563412],al**
二:指令模擬
本文不對虛擬化的基礎知識再進行展開,如需理解下面的內容須先 了解硬件輔助虛擬化的基礎知識,如 KVM/GVM/HAXM的實現流程,網上講基礎的帖子很多,此處就不再展開。
如下內容基於HAXM進行流程分析,假設大家都知道了一些基礎知識(如VM_EXIT,VM_ENTRY,VMX)
2.1 功能定位
指令模擬在硬件輔助虛擬化環境下,所處的執行流程如下圖:
2.2 功能描述
功能描述部分屬於上圖藍色部分的展開,便於后續對代碼的理解
2.3 代碼流程