前言:
要進行指令模拟,我們先需要了解X86架構下的指令是長什麼樣子的。根據intel的程式設計手冊我們找到了如下資訊。
Intel CPU的機器指令格式如下圖所示:
e.g.:圖檔位于intel開發手冊第二卷第二章的2.1
根據開發手冊,一條指令由 指令字首(Instruction Prefixes) + 操作碼(Opcode) + ModR/M + SIB + 偏移(displacement) + 立即數(Immediate data)幾部分組成,一條指令至少需要有Opcode,其它幾部分,在不同指令中可能存在可能不存在。今天我們主要來看Opcode、Instruction Prefixes、Immediate data三部分在不同指令中的使用。
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程式設計手冊中截取一位元組指令碼格式如下圖: