1. 程式人生 > >X86指令編碼內幕 --- ModRM 定址模式

X86指令編碼內幕 --- ModRM 定址模式

ModRM 定址模式

在 x86/x64 指令集的世界裡:

Opcode 對指令提供操作碼,ModRM 最主要作用是對指令的 operands 提供定址,另一個作用是對 Opcode 進行補充,而 SIB 則是對 ModRM 進行補充定址

有兩種情況下是無需用 ModRM 提供定址的:

(1)一部分運算元是暫存器的,它直接嵌入 Opcode 中。

(2)一部分運算元是立即數的,它直接嵌入指令編碼中。


回到前面舉的這個例子:

REX prefix Opcode ModRM SIB displacement immediate
4 W R X B
mod reg r/m scale index base
0100 1 0 0 0 C7 10 000 100 11 001 000 44 33 22 11 78 56 34 12

(1)C7 這個 Opcode 是 Group 屬性,需要 ModRM.reg 來配合定位。

(2)ModRM.mod = 10 以提供 [SIB + disp32] 的定址

(3)ModRM.reg = 000 表明 Opcode 是個 mov Ev, Iz

(4)SIB 由 ModRM.r/m = 100 來引匯出

(5)REX.W = 1 使用 64 位的 operand


1、ModRM 的結構

表1: ModRM 結構表

-- 描述
ModRM.mod [7:6]  提供定址模式: 11 = register  !11 = memory
ModRM.reg [5:3]  提供 register 或者對 Opcode 進行補充
ModRM.r/m [2:0]  提供 register 或者 memory 依賴於 ModRM.mod


  上表所示 ModRM 位元組的組成部分為:mod-reg-r/m 三個部分,按 2-3-3 的比例組成,在這整篇文件中的寫法是:ModRM.mod、ModRM.reg 以及 ModRM.r/m。

★ ModRM.mod 提供定址的模式,這個模式以 displacement 值作區別的。當 ModRM.mod = 11 時,它提供 register 定址。

★ ModRM.reg 提供暫存器定址,reg 表示暫存器 ID 值,或者對 Group 屬性的 Opcode 進行補充。

★ ModRm.r/m 提供 register 定址或 memory 定址。

1.1、 ModRM.mod 定址模式。

  2 位組成 4 種定址模式,總的來說,只有兩種定址模式,就是:記憶體定址模式和暫存器定址模式。 如下表所示:


表2:mod 定址模式表

  ModRM.mod  定址模式  描述
00  [base]  提供 [base] 形式的 memory 定址
01  [base + disp8]  提供 [base + disp8] 形式的 memory 定址
10  [base + disp32]  提供 [base + disp32] 形式的 memory 定址
11  register  提供 register 定址。





1.2、 ModRM.reg 定址 register

  3 位組成 8 個暫存器 ID 值,從 000 ~ 111,對應於 RAX、RCX、RDX、RBX、RSP、RBP、RSI 以及 RDI。

  這個 ID 值可以被 REX.R 擴充套件為 4 位: 0000 ~ 1111  


1.3、ModRM.r/m 定址 register 或 memory

r/m 意即:registers/memory,提供對 register 或 memory 的定址,它與 ModRM.mod 是密切相關的。如下表:

表2: ModRM.r/m 定址

ModRM.mod ModRM.r/m ModRM.r/m 定址
REX.B = 0 REX.B = 1
00 000 [rax] [r8]
001 [rcx] [r9]
010 [rdx] [r10]
011 [rbx] [r11]
100 [SIB] [SIB]
101 [disp32] 或 [rip + disp32]  * [disp32] 或 [rip + disp32]
110 [rsi] [r14]
111 [rdi] [r15]
01 000 [rax + disp8] [r8 + disp8]
001 [rcx + disp8] [r9 + disp8]
010 [rdx + disp8] [r10 + disp8]
011 [rbx + disp8] [r11 + disp8]
100 [SIB + disp8] [SIB + disp8]
101 [rbp + disp8] [r13 + disp8]
110 [rsi + disp8] [r14 + disp8]
111 [rdi + disp8] [r15 + disp8]
10 000 [rax + disp32] [r8 + disp32]
001 [rcx + disp32] [r9 + disp32]
010 [rdx + disp32] [r10 + disp32]
011 [rbx + disp32] [r11 + disp32]
100 [SIB + disp32] [SIB + disp32]
101 [rbp + disp32] [r13 + disp32]
110 [rsi + disp32] [r14 + disp32]
111 [rdi + disp32] [r15 + disp32]
11 000 rax r8
001 rcx r9
010 rdx r10
011 rbx r11
100 rsp r12
101 rbp r13
110 rsi r14
111 rdi r15

上表所示:

★ ModRM.r/m = 0100 或 1100 時,它定址的是 SIB 位元組,引匯出 SIB 位元組,交由 SIB 進行補充定址。

★ ModRM.mod = 00 & ModRM.r/m = 0101 或 1101 時,在 32 位下它是 32 位的 displacment 值,在 64 位下它是 [rip + disp32] 定址。

★ REX.B 可以擴充套件 ModRM.r/m 為 4 位: 0000 ~ 1111


2、 [rsp] 定址的疑惑(ModRM.r/m = 100) 

  按照 ModRM 編碼規則,當 ModRM.r/m = 100,ModRM.r/m 應該是對應於 [rsp] 定址。

  事實上並非如此,ModRM.r/m 對應的是 SIB 定址,由此而引匯出 SIB 位元組。指令系統設計者選擇在 [rsp] 位置上用 SIB 進行替代。

這是因為:

★ 必須要有編碼對應於 SIB 位元組。

★ 用 SIB 代替 [rsp] 或許因為 [rsp] 相比其它的定址並不那麼常用。

2.1、那麼怎樣實現 [rsp] 定址的編碼?

  既然在 ModRM 上無法滿足 [rsp] 的定址,那麼只好在 SIB 裡實現 [rsp] 的定址。

  在 SIB 裡實現 [rsp] 定址,是根據一個重要的原則: rsp 暫存器不能做為 index 暫存器,只能做 base 暫存器。  

3、 [rbp] 定址的疑惑(ModRM.r/m = 101)

  同樣情況下,按照 ModRM 編碼規則,ModRM.r/m = 101 時,應該對應於 [rbp] 定址。

  事實上並非如此:當 ModRM.mod = 00 時,ModRM.r/m = 101 它對應的是 [disp32] 的定址,在 64 位下,它是 [rip + disp32]。

  指令系統的設計者們在 ModRM.r/m = 101 這個地方又進行了變通。

3.1、 那麼怎樣實現 [rbp] 定址的編碼呢?

  在 [rbp] 定址的實現上,又遵循另一個重要原則:rbp 作為 base 暫存器時,它必須以 [base + disp] 這種形式存在。

  這個原則是建立在:rbp 意為 stack base pointer(基址指標),從語義上講 base pointer 應該要加一個偏移量(displacement)

  因此:[rbp] 這種形式在 encode 上是不存在的。

  但是在 assembly(彙編語句)層面上是正確的,即:mov qword ptr [rbp], rax 是正確的語句。 

4、 16 位下的 ModRM 定址模式


   16 位定址與 32 位定址有較大的差異,16 位下是不支援 [base + index * scale + disp] 這種定址模式的,這樣在編碼序列裡就無需提供 SIB 位元組了。

  即:在 16 位下是無 SIB 位元組的。

注:
  16 位的 ModRM 定址支援的是基址和變址暫存器間接定址和 基址+變址定址模式。基址暫存器 2 個:就是 bx 和 bp。變址暫存器也是 2 個:就是 si 和 di。

  基於上述設計,基址+變址定址的組合只有 4 個,也就是:[bx+si]、[bx+di]、[bp+si] 以及 [bp+di]。

表3: 16 位的 ModRM 定址

ModRM.mod ModRM.r/m ModRM.r/m 定址
00 000 [bx+si]
001 [bx+di]
010 [bp+si]
011 [bp+di]
100 [si]
101 [di]
110 [disp16]
111 [bx]
01 000 [bx+si+disp8]
001 [bx+di+disp8]
010 [bp+si+disp8]
011 [bp+di+disp8]
100 [si+disp8]
101 [di+disp8]
110 [bp+disp8]
111 [bx+disp8]
10 000 [bx+si+disp16]
001 [bx+di+disp16]
010 [bp+si+disp16]
011 [bp+di+disp16]
100 [si+disp16]
101 [di+disp16]
110 [bp+disp16]
111 [bx+disp16]
11 000 ax
001 cx
010 dx
011 bx
100 sp
101 bp
110 si
111 di

5、 64 位定址下的 ModRM

  在 64 位下,ModRM 的含義與 32 位一致,改進的只是在原來基礎上增加了 8 個 GPRs,通過 REX prefix 進行對新增的暫存器進行訪問。

  暫存器的 ID 取值為:0000 ~ 1111。由 REX.R 以及 REX.B 位進行擴充套件訪問。




6、結合 Opcode 來看定址模式及 Opcode 的定位

  Opcode 定義指令的執行碼,用於執行什麼操作,對於運算元定址上,Opcode 結合 ModRM 來定義運算元,這是一個經過反覆琢磨推敲的過程,而最終又影響到 Opcode 的定位。



6.1、 一個運算元的 Opcode 定位

運算元要麼就是 registers,要麼就是 memory,要麼就是 Immediate 值。如果指令只有一個運算元。

(1)operand 是 register 的情況下

   在只有 1 個暫存器運算元的情況下,非 Group 屬性的 Opcode,則 ModRM 無用武之處。所以,在這種情衝下,暫存器運算元絕大部分是嵌在 Opcode 裡面,它由 Opcode 的暫存器域指出。如常見的 inc ecx、dec ecx、push eax 等。

除了上述嵌入 Opcode 情況外,如果 ModRM 用於定址 1 個運算元,這條指令的 Opcode 必定是:Group 屬性


   若是 Group 屬性,則 ModRM 有用武之地了。


(2)如它是 Immediate 的話,它絕對是無 ModRM。直接將 Immediate 值嵌入指令編碼裡

(3)如它是 memory 的話,它絕對是 Group 屬性,需要 ModRM.reg 來配合定位。那為什麼不能是直接 offset 呢,直接 offset 定址留給最常用的,最有用的 Opcode,以免 Opcode 佔位,浪費資源。




8.5.2、 兩個運算元的 Opcode 定位

   兩個運算元大部分都需 ModRM 配合定位定址。ModRM 提供的 2 個運算元定址大有用武之地。

(1)2 個運算元中,其中 1 個是暫存器,另1個不是立即數的這種情況最直接簡單,由 ModRM 的 reg 及 r/m 提供定址。
   若其中 1 個是 GPRs 另 1 個是 immediate 或 displacement 的情形下更簡單,GPRs 則直接由 Opcode 提供定址。


(2)2 個運算元中,沒有暫存器的情形下,也就是要麼是 memory,要麼是 Immediate,它必然是個 Group 屬性,reg 域提供 Opcode 的定位,r/m 提供記憶體定址。Immediate 直接嵌入指令編碼中。



8.5.3、 三個運算元的 Opcode 定位

   三個運算元中有一個必定是 Immediate,在 AMD 的 SSE5 指令集推出之前,x86 平臺是無法提供第 3 個非 Immediate 運算元的定位。
   直至 AMD 的 SSE5 通過增加另一個描述運算元的位元組來定址第 3 個運算元。


   如上所述,Opcode 的設計要考慮到與 ModRM 的配合。主要表現在什麼時候是 Group 屬性。

前面已提過:
   Opcode 的分組是按 Operands 屬性進行的。即:這一組 Opcode 擁有相同型別的 Operands。
   這個型別的 Operands 主要是單個記憶體運算元或單個暫存器運算元而非嵌入 Opcode 中。