欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
Linux兼容內核論壇 ? 查看主題 - 【x64 指令系統】之指令編碼內幕

目  錄
-------------------------------------------------------------------------------
1、序言     -------------- 1 樓
2、指令格式     -------------- 2 樓
3、深入了解 prefix       -------------- 3、4 樓
4、64 位計算 ------------- 5 樓
5、指令編碼核心之 Opcode   ------------- 6 樓
6、x87 指令、3DNow 指令、SSEx 指令 -------------- 7 樓
7、強悍的 AMD SSE5 指令 --------------- 8 樓
8、指令編碼核心之 ModRM 尋址 --------------- 9 樓
9、指令編碼核心之 SIB 尋址 ---------------- 10 樓
10、Displacement 與 Immediate ---------------- 11樓
11、解析指令 (完結) ---------------- 12 樓
--------------------------------------------------------------------------------



1、序言




在講解 x64 指令編碼之前,先給 2 個(gè)例子看看,相當于學(xué)習 C 語(yǔ)言經(jīng)典的第一節課。

main()
{
  printf(“hello,world!”);
}





1、匯編代碼譯為機器碼


例子1:在當前 32 位機器,32 位系統下,有如下匯編指令:

mov word ptr es:[eax + ecx * 8 + 0x11223344], 0x12345678

分析這條匯編碼:
  這是一條 mov 指令,目標操作數是 mem, 源操作數是 imme, 

注意:我特地將操作數的大小定為是word(2個(gè)字節),而不是 dword
源操作數故意定為 0x12345678,這個(gè) dword 大小的立即數。


對應的機器編碼是:26 66 c7 84 c8 44 33 22 11 78 56

現在,我對這個(gè)機器碼略為解釋一下:

26:在指令序列里是:prefix 部分,作用是調整內存操作數的段選擇子
66:在指令序列里是:prefix 部分,作用是調整操作數的缺省大小
C7:在指令序列里是:Opcode 部分,是 mov 指令是操作碼
84:在指令序列里是:ModRM 值,定義操作數的屬性
C8:在指令序列里:SIB 值定義內存操作數的屬性
44332211:在指令序列里是: displacement 值
7856:在指令序列里是:immediate 值
-----------------------------------------------------------------
對于多數編譯器,立即數 0x12345678 會(huì )被截斷,只取低 16 位值。要么就是編譯器拒絕支持。



至于為什么會(huì )譯為這個(gè)機器編碼,在以后的章節里再學(xué)習




2、將機器碼譯為匯編碼


例2:隨便找一個(gè)機器碼如:FF 15 D4 81 DF 00

粗略分析一下:

FF:這個(gè)字節是個(gè)具有 Group 屬性的 Opcode 碼,它進(jìn)行什么操作需要依賴(lài)于 ModRM 字節的 Reg 域.。換句話(huà)來(lái)說(shuō),FF 并不是完整獨立的 Opcode 碼,它要聯(lián)合 ModRM 才能確定具體的操作。

15:這個(gè)是 ModRM 字節,Mod 域為 00 Reg 域為 010 RM 域為 101。 其中 Reg 域被 FF 作為確定具體操作碼的參考。

FF / 010 :最終確定為:Call 指令,
Mod 域以及RM域確定操作數的屬性,這是一個(gè)內存操作數是且是個(gè) offset 值或者說(shuō)是 displacement 值。


所以,這個(gè)機器碼最終被解碼為: call dword ptr [00DF81D4]



這 2 個(gè)例子,作為對學(xué)習 x86 指令編碼的一個(gè)感性認識。下面逐一剖析 x86 指令編碼的來(lái)龍去脈。
2、指令格式



在序言里的例子里:

mov dword ptr es:[eax + ecx * 8 + 0x11223344], 0x12345678

  這里稍作修改:將 word ptr 這個(gè)內存操作數指示字改回 dword ptr,這是個(gè)具有典型指令編碼意義的指令。

它的encode(機器編碼)是:26 c7 84 c8 44 33 22 11 78 56 34 12 (共12個(gè)字節)。

go ahead~




1、編碼序列




如上圖所示:
  這個(gè)x86_x64 體系的 General-Pupose Instruction(通用體系指令)的編碼格式,記住這個(gè)編碼序列很重要,這是解析指令編碼的基石。

這個(gè)編碼序列分為 Legacy Prefix、REX prefix、Opcode、ModRM、SIB、Displacement 以及 Immediate 7個(gè)部分。

按功能組別,我將這個(gè)指令序列分為 4 個(gè)部分:Prefix、Opcode、ModRM/SIB、Disp/Imme



● Prefix(前綴):
  AMD推出 x86 擴展 64 位技術(shù)時(shí),增加了一個(gè)用于擴展訪(fǎng)問(wèn) 64 位數據的 REX prefix,而 x86 的 prefix 是 Legacy prefix。
  在 x86 模式下,REX prefix 是無(wú)效的。但是在 x64 的 64 位下 Legacy prefix 是有效的。

● Opcode(操作碼):
  大多數通用指令 Opcode 是單字節,最多是 2 字節,但是對有些 Float 指令和 SSEx midea 指令來(lái)說(shuō)是 3 個(gè)字節的。

● ModRM/SIB:
  ModRM 字節實(shí)際意義為:mod-reg-rm,按 2-3-3 比例劃分字節,SIB 意即:Sacle-Index-Base 也是按 2-3-3 比例劃分字節。 這兩個(gè)字節用來(lái)修飾指令操作數。

● Disp/Imme:
  Displacement 最大可為 8 個(gè)字節 64 位,當然 8 個(gè)字節的 displacment 只有在 x64 平臺下的某些情況才會(huì )有,displacement 可理解為 offset。同樣 immediate 大可為8個(gè)字節,同樣在 x64 下的某些情況才會(huì )有的。
  需要注意的一點(diǎn)是:displacement 和 immediate 都是符號數(single),在 32 位下,小于 32 位被符號擴展至 32 位,在 64 位下,小于 64 位會(huì )被符號擴展 64 位。


對照上面的 encode 來(lái)看:
26 c7 84 c8 44 33 22 11 78 56 34 12

(1) 26 是 prefix,這是 segment-override prefix,指明是 ES 段選擇子
(2) c7 是 Opcode,表明這個(gè)指令是 mov reg/mem, imme
(3) 84 是 ModRm,即:10-000-100。
(4) c8 是 SIB,即:11-001-000
(5) 44332211 是 disp,是 32 位 displacement 值
(6) 78563412 是 imme,是 32 位 immediate 值



2、指令長(cháng)度

圖中顯示,指令長(cháng)度最長(cháng)是 15 個(gè)字節,在什么時(shí)候達到飽和的 15 個(gè)字節呢?

答案是像這條指令:lock mov dword ptr es:[eax+ecx*8+0x11223344], 0x12345678
當在 16 位下,這條指令將達到飽和的 15 個(gè)字節長(cháng)度。
注意,僅在 16 位下,這條指令的編碼是:26 66 67 F0 C7 84 C8 44 33 22 11 78 56 34 12 (正好 15 個(gè)字節)

2.1、這個(gè)編碼的具體含義
26 66 67 F0: 這 4 個(gè)字節是 prefix,這 4 個(gè)字節達到了飽和的 prefix 狀態(tài)。
26 是 ES segment register
66 是 operand-size override
67 是 address-size override
F0 是 Lock prefix
C7:Opcode
84:ModRM
C8:SIB
44 33 22 11:displacement
78 56 34 12:immediate

有沒(méi)有超過(guò) 15 個(gè)字節的指令編碼,答案是:沒(méi)有! 那么在 64 位下呢? 答案同樣是沒(méi)有!


勘誤:
  以前,由于作為演示如何達到 15 個(gè)字節長(cháng)度飽和狀態(tài),而忽視了 lock 用在 mov 指令上是無(wú)效的。
  經(jīng)網(wǎng)友指出 dxcnjupt 實(shí)驗指出,謝謝。

更正:
  mov 指令是屬于 load - store 類(lèi)指令。lock 用在 mov 指令上會(huì )引發(fā) #UD 異常。
  lock 應用于 read-modify-write 類(lèi)指令上。意即:指令執行會(huì )產(chǎn)生中間結果,運算后再 write 內存。



那么,將上面的例子改為:lock add dword ptr es:[eax+ecx*8+0x11223344], 0x12345678

對應編碼為:26 66 67 F0 81 84 C8 44 33 22 11 78 56 34 12 (共 15 個(gè)字節)



既然這樣,順便提一提:

2.1、 為什么指令長(cháng)度最長(cháng)是 15 字節?

 ?。?)4 個(gè)字節的 prefix 已經(jīng)達到飽和度了:1 個(gè)字節用來(lái)調整 segment,1 個(gè)字節用來(lái)調整 Operand-Size,1 個(gè)字節用來(lái)調整 Address-Size,還有 1 個(gè)字節用來(lái) lock 總線(xiàn)。已經(jīng)無(wú)法再增加 prefix 了。

 ?。?)若是采用 2 個(gè)字節的 Opcode 碼,則尋址模式上不會(huì )有 mem32, imm32 這種操作法。所以還是采用 1 個(gè)字節的 Opcode,而得到 imm32 4 個(gè)字節的立即數。

 ?。?)ModRM + SIB:2 個(gè)字節。
 ?。?)4 個(gè)字節的 displacement 值。
 ?。?)4 個(gè)字節的 immediate 值。

這樣每個(gè)組成部分都呈飽和狀態(tài),加起來(lái)總共 15 個(gè)字節,而只有采用 mem32, imme32 這種尋址模式可能會(huì )達到飽和狀態(tài)。

  在 64 位下,若采用 mem, imme 的尋址模式,這和 32 位是一致的,所以不會(huì )超越 15 個(gè)字節,
3、深入了解 Prefix



  在 GPI(General-Purpose Instruction)指令里,Legacy Prefix 在整個(gè)編碼序列里起了對內存操作數進(jìn)行修飾補充作用,在這里我稱(chēng)呼它為 x86 prefix,這樣比較直觀(guān)。
  x86 prefix 主要起了三個(gè)作用:調整、加強、附加。REX prefix 只是起將操作擴展 64 位的作用。
  要徹底了解 x86 prefix,必須清楚了解 3 個(gè)很重要的上下文環(huán)境:缺省 operand-size 和缺省 addess-size 環(huán)境、編譯器上下文環(huán)境以及當前執行上下文環(huán)境。


1、調整改變操作數

  x86 指令編碼會(huì )根據上面提到的 3 個(gè)上下文環(huán)境而對操作數的位置、大小以及地址進(jìn)行調整改變。這里操作數特指是內存操作數。出現調整的情形,這是因為:
(1)指令的操作數大小可以為:8位、16位、32 位以及 64 位
(2)操作數的位置因段選擇子而不同。
(3)操作數的地址大小可以為:16 位、32 位以及 64 位


1.1、調整操作數的大?。?6H prefix ------ Default Operand-Size Override)

  66h 這個(gè) prefix 作用是改變操作數的大小,那么:

1.1.1 為什么需要改變操作數大???
  原因是:16 位下代碼需要訪(fǎng)問(wèn) 32 位數據或者 32 位代碼需要訪(fǎng)問(wèn) 16 位數據。

看看這兩個(gè)例子:
例 1 :在 16 位代碼下,指令 mov eax, ebx
  由于在 16 位下,操作數的大小缺省是 16 位的,如上指令要訪(fǎng)問(wèn) 32 位的寄存器,那么需要進(jìn)行調整。
變?yōu)椋?6 89 d8

例 2:在 32 位代碼下,指令 mov ax, bx
  由于在 32 位下,操作數的大小缺省是 32 位的,如上指令要訪(fǎng)問(wèn) 16 位寄存器,那么需要進(jìn)行調整。
變?yōu)椋?6 89 d8
---------------------------------------------------------
  這里有些人會(huì )覺(jué)得奇怪,為什么例1 與 例2 編譯器生成的結果是一樣的。這就是在不同的環(huán)境里,processor 解碼單元會(huì )譯為不同的操作結果。



1.1.2、 根據什么來(lái)改變?
  根據什么來(lái)改變。這就是根據上面提到過(guò)的 3 個(gè)很重要的上下文環(huán)境:
● 缺省操作數大小
● 編譯器編譯環(huán)境
● 當前執行環(huán)境

這 3 個(gè)環(huán)境是有機結合起來(lái)的,是個(gè)整體。


1.1.3、缺省操作數大?。―efault Operand-Size)
   對于實(shí)模式環(huán)境下,操作數的 Default Operand-Size 是16位,在 32 位保護模式下,操作數的 Default Operand-Size 是 32位,在 64 位 Long 模式下 Default Operand-Size 也是 32 位大小。
  當在保護模式下,讀取 16 位值時(shí),則須作出調整,同樣在實(shí)模式下,讀取 32 位值時(shí),測須作出調整。

實(shí)際上:
  Default Operand-Size 在實(shí)模式下是 16 位這沒(méi)錯! 但是,在 32 位保護模式下,Default Operand-Size 并非一定就是 32 位!
  保護模式下,Default Operand-Size 依賴(lài)于當前 code segment-descriptor 的 D 位,也就是 CS.D(code segment register's D)。當 CS.D = 1 時(shí),Default Operand-Size 是 32 位的,CS.D = 0 時(shí),Default Operand-Size 是 16 位的。
  這也就是,為什么在 64 位 long 模式下,Default Operand-Size 還是 32 位而不是 64 位的根本原因,CS 中只有 0 與 1 來(lái)進(jìn)行 16 位與 32 位的選擇,而沒(méi)有 64 位的選擇!那為什么不設為 CS.D = 1 時(shí)是 64 位呢? 原因很簡(jiǎn)單:為了平滑無(wú)縫地運行 32 位代碼。這是 x86_64 設計的根本原因!
  為什么在實(shí)模式下,Default Operand-Size 不能進(jìn)行選擇呢? 因為,實(shí)模式下根本沒(méi)有進(jìn)行設置的途徑。原因是沒(méi)有 segment descriptor 進(jìn)行設定。
 
更深入一點(diǎn):
  其實(shí),在實(shí)模式下,還是有手段進(jìn)行設置 Default Operand-Size 為 32 位的?!£P(guān)于這點(diǎn),以后有機會(huì )再講解。



以下舉2個(gè)例子加以說(shuō)明:

例1:在 32 位保護模式下,指令:mov ax, [11223344h]

  在 Microsoft 的語(yǔ)法里,在內存操作數前一般要加指示字 word ptr,指明操作數的大?。簃ov ax, word ptr [11223344h] 實(shí)際上,在這條指令里,這個(gè)指示字不是必須的,加指示字只是比較直觀(guān)。但有些情況是必須要加的,如:mov dword ptr [11223344], 1
例 1 這條指令里,絕大多數編譯器會(huì )編譯為以下機器編碼encode:
66 a1 44 33 22 11   
  在個(gè) encode 里,66 是prefix,a1 是 opcode,44332211 是 displacement 或者說(shuō) mem-offset

66 改變了缺省的操作數大小,將 32 位調整為 16 位。


例2:在16位實(shí)模下,同樣一條指令:mov eax, [11223344]

  同樣一樣指令,只是目的操作數大小不同,在16 位實(shí)模式下,這條指令將被編譯器編譯為:66 67 a1 44 33 22 11
  在這個(gè) encode 里,66 prefix 將16 位缺省操作數調整為 32 位大小,67 這也是 prefix,但它是調整 Addess-Size preifx 將 16 位地址調整為 32 位地址。其余的字節和 例1 的完全一樣。





1.1.4、編譯器編譯上下文環(huán)境
  所謂“編譯器上下文”,是指編譯器編譯目標平臺上下文環(huán)境。說(shuō)明白點(diǎn)就是:編譯器為什么機器編譯代碼,是編譯為 16 位代碼,還是編譯為 32 位代碼或者是編譯為 64 位代碼?
  例如操作系統的引導初始化代碼部分是 16 位的,現在絕大多數 OS 是 32 位的,因此,在當前系統下寫(xiě)引導代碼,則需要求編譯器編譯為 16 位實(shí)模式代碼。
  因此,你不得不寫(xiě) 16 位代碼,編譯器根據情況將 32 位操作和地址調整至 16 操作數和地址。但在大部分情況下,不需要作調整,直接生成 16 位代碼即可。
  這其實(shí)也和編程人員相關(guān)的。



1.1.5、當前執行環(huán)境
  Processor 處理什么模式下,這是程序員需要考慮的問(wèn)題,從而通過(guò)代碼體現出來(lái),編譯器根據代碼生成相應的代碼。
  一個(gè)很典型的例子就是:當 16 位初始化代碼完以及保護模式系統數據結構初始化完成后開(kāi)啟保護模式,然后需要從 16 位代碼跳轉至 32 位代碼。
  由于在一個(gè)匯編程序里同時(shí)存在 16 位和 32 位代碼,所以,程序員在匯編級代碼里應指出 16 位與 32 位分界線(xiàn)。編譯器正確同樣生成 16 位和 32 位代碼。這里當然是通過(guò) Operand-Size 的調整和 Address-Size 的調整,即:66H 和 67H prefix。
  此時(shí),程序的腦海里,應存在這樣一個(gè)概念,在運行 16 位代碼時(shí),processor 當前處于實(shí)模式狀態(tài),跳轉至 32 位保護模式代碼時(shí),processor 當前處于 32 位保護模式狀態(tài)。

  這就是 processor 當前執行上下文環(huán)境。



1.2.1、操作數變?yōu)槭裁矗?或者說(shuō)可以調整什么?
  這是需要探討的另一個(gè)重要話(huà)題。
  前面已經(jīng)提到:為什么需要改變? 后根據什么來(lái)改變? 現在是:可以改變?yōu)槭裁矗?br>
  在 16 位實(shí)模式下:操作數的大小可以是 16 位或 32 位,也就是說(shuō):可以從 16 位變?yōu)?32 位。
  在 32 位保護模式下:同樣,操作數大小可以是 32 或 16 位,也就是說(shuō),可以從 32 變?yōu)?16 位。
  ---- 僅此而已。

  但是,在 64 位 long 模式下,操作數大小有 3 個(gè)選擇:64 位、32 位以及 16 位。 可以從 32 位變?yōu)?64 位,也可以從 32 位變?yōu)?16 位!

  以上所述,這是關(guān)于 Operand-Size 的有效 Size 的話(huà)題。






2、調整地址大?。?7H prefix ----- Address-Size Override)

  Address-Size 和 Operand-Size 一樣,也有缺省地址大?。―efault Address-Size),當需要改變地址大小的時(shí)候,也需要使用67H prefix 來(lái)進(jìn)行調整,所不同的是,Default Address-Size 不需要從 Descriptor 里獲取。直接定義:
  在16實(shí)模式下 Default Address-Size 為 16 位,32 位保護模式下 Default Address-Size 為 32 位,64 位 Long 模式下 Default Address-Size 為 64位。
  這是必定的。


  和 Operand-Size 一樣,Address 也有“有效 Address-Size 模式”的語(yǔ)義
  16 位下:可以調整為 32 位地址。
  32 位下:可以調整為 16 位地址。
  64 位下:可以調整為 32 位地址,但不能調整為 16 位地址。

更深入一點(diǎn):
  64 位下,雖然能調整為 32 位址,但實(shí)際上還是 64 位地址,32 位地址會(huì )被擴展為 64 位地址,所以:調整為 32 位址,實(shí)際上是限制在前 4G 范圍內活動(dòng)而已。



以下,也舉幾個(gè)例子來(lái)說(shuō)明

例1:16 位實(shí)模式下,序言里的指令:mov dword ptr [eax+ecx*8+0x11223344], 0x12345678

  由于在 16 位下,但該指令是 32 位 operand-size 以及 32 位 address-size,也就是說(shuō)既要調整 default operand-size 也要調整 default address-size。所以,應加上 66 調整 operand-size,再加上 67 調整 address-size,最終的 encode 為:
66 67 c7 84 c8 44 33 22 11 78 56 34 12


例2:在 32 位模式下,指令:mov eax, [11223344]

  對該指令,編譯器不會(huì )產(chǎn)生 16 位代碼,所以,我們手工編譯該指令,得出 encode:
67 a1 44 33 22 11
  這條指令是不對的,用 67 調整為 16 位地址,那么在匯編碼來(lái)看,它將是:
mov eax, [3344]
  它的地址將被截斷為 16 位?!〖?,地址:0x3344,多出 22 11 兩個(gè)字節屬下條指令邊界了,同時(shí),目標操作數被改變?yōu)?ax
除非,這樣編碼 66 67 a1 44 33 那么,結果是 mov ax, [3344]






3、調整段選擇子(段寄存器)

  對于大多數內存操數據來(lái)說(shuō),缺省以 DS 為段基址的。常見(jiàn)的是:DS 段基址,SS 段基址。


來(lái)看看下面的代碼片段:

Foo:
push ebp
mov ebp, esp
lea eax, [ebp-0xc] ; int *p = &i;
mov dword ptr [eax], 0 ; *p = 0
….
mov esp,ebp
pop ebp
------------------------------------------
[ebp-0xc]:這個(gè)內存操作數缺省是基于 SS 段的
[eax]: 這個(gè)內存操作數缺省是基于 DS 段的。



因此,正確的語(yǔ)義應該要這樣才對

lea eax, [ebp-0xc]
mov dword ptr ss:[eax], 0 ; 將 DS 改變?yōu)?SS,這才是正確的邏輯



  為什么一般程序都不會(huì )這么寫(xiě)呢? 那是因為,現代的操作系統都是采用平坦的內存模式,即:CS=SS=DS=ES,所以對 [eax] 這個(gè)操作數不需調整其結果是正確的。

  那么,我們真要對 [eax] 內存操作數進(jìn)行調整為:mov dword ptr ss:[eax], 0

  這樣的話(huà),會(huì )產(chǎn)生下面的encode:
36 c7 00 00 00 00 00

  其中,36 也就是 prefix,是 SS segment-override 的 prefix,將 DS 段調整為 SS 段

好啦,每個(gè)段寄存都有它對應的 prefix,下面列出每個(gè)段寄存器的 prefix:
CS: 2E
DS: 3E
ES: 26
FS: 64
GS: 65
SS: 36


  當需要進(jìn)行調整段寄存器時(shí),就使用以上的 segment-override prefix。但有些指令的缺省段寄存是 ES,典型的如 movsb 這些串操作指令
movsb 指令的實(shí)際意義是: movs byte ptr es:[edi], byte ptr ds:[esi],此時(shí)是不需要調整缺省段寄存器2、增強指令功能

  一些 prefix 對 Opcode 進(jìn)行補充,增強指令的功能,優(yōu)化指令執行,看下面這段 c 代碼:

char *move_char(char *d, char *s, unsigned count)
{
char *p = d;
while (count--)
*d++ = *s++;

return p;
}


這是典型的、經(jīng)典的字符串復制c代碼,對應以下類(lèi)似的匯編代碼:

move_char:
push ebp
mov ebp, esp
sub esp, 0xc
mov eax, [ebp+8]
mov edi, eax
mov esi, [ebp+0xc]
mov ecx, dword ptr [ebp+0x10]

move_loop:
mov bl, byte ptr [esi]
mov byte ptr [edi], bl
inc esi
inc edi
dec ecx
test ecx,ecx
jnz move_loop

mov esp, ebp
pop ebp
ret



  上面的代碼性能低下,是很死板的實(shí)現,優(yōu)化的空間巨大。

  x86 為串提供了相應的串操作指令(ins,outs,lods,stos,scas,cmps),對這些串指令提供 prefix 來(lái)增強優(yōu)化這些指令:


● F3: rep prefix 或 repe prefix
● F2: repne prefix


2.1、 rep prefix

  重復進(jìn)行串操作,僅應用于 ins,outs,movs,lods,stos 這些不改變標志位的串指令,結束條件是 ECX 為 0。
使用串操作及 rep prefix 上面的匯編代碼可簡(jiǎn)單如下:

move_char:
… …
mov eax, [ebp+8]
mov edi, eax
mov esi, [ebp+0xc]
mov ecx, [ebp+0x10]
rep movsb
… …

rep movsb 的操作原理和上面的 C 代碼一致,下面是偽碼:

while (ecx != 0) {
c = ds:[esi];
es:[edi] = c;
esi = esi + 1
edi = edi + 1
ecx = ecx – 1;
}




2.2、 repe prefix

  F3 的另個(gè) prefix 意思是 repe/repz,用于改變標志位的串操作:scas,cmps 意思是當相等(ZF=1)并且循環(huán)次數(ecx)不為 0 時(shí)進(jìn)行重復操作。
  結束條件是:ecx 為 0 或者 ZF 標志位為 0, 或者說(shuō)是循環(huán)條件是(ecx 不為 0,并且 ZF 標志位為 1)



常見(jiàn)運用一些跳過(guò)字符的邏輯上,如下面 C 代碼,用于截除串前面空格

char *trim(char *s)
{
while (*s && *s == ‘ ‘)
s++;

return s;
}



而用偽碼表示為:

While (ecx != 0 and ZF = 1) {
Ecx = ecx – 1;
cmpsb
}



rep 與 repe/repz 是相同的 prefix,作用不用體現在對串指操作上:

movsb 的 opcode 是 A4,scasb 的opcode 是 AE,對于下面的 encode:

F3 A4:這時(shí) F3 prefix 是 REP, movsb 不改變標志位
F3 AE:這時(shí) F3 prefix 是 REPZ, scasb 改變標志位




2.3、 repne/repnz prefix

  F2:這個(gè) prefix 是 repne/repnz,意思是:循環(huán)次數(ecx)不為 0 并且 ZF=0 時(shí)重復操作。結束條件是:ecx=0 或者 ZF=1

  同樣也是用于改變標志位的串操作 scas 和 cmps。


常見(jiàn)一些查找字符的邏輯上,如下面 C 代碼:

char *get_c(char *s, char c)
{
while (*s && *s != c) s++;

return s;
}



而用偽碼表示為:

While (ecx !=0 and ZF != 1) {
Ecx = ecx – 1;
cmpsb
}








3、 附加功能(LOCK)

  對于寫(xiě)內存的一些指令增加了鎖地址總線(xiàn)的功能,這些寫(xiě)內存的指令如常見(jiàn)的 sub,add 等指令,通過(guò) Lock prefix 來(lái)實(shí)現這功能,使用 Lock prefix 將會(huì )使 processor 產(chǎn)生 LOCK# 信號鎖地址總線(xiàn)

注意:
  Lock prefix 僅使用在一些對內存進(jìn)行 read-modify-write 操作的指令上,如:add, sub, and 等指令?!》駝t,將會(huì )產(chǎn)生 #UD (無(wú)效操作碼) 異常


4、64 位計算



  這里講的只是 x64 提供的 64 位計算方案,而非關(guān)于 64 位方面的編程知識。關(guān)于 64 位編程方面以后有機會(huì )再講解。
AMD 在 x86 體系的 32 位計算擴展為 64 位計算,這是通過(guò)什么來(lái)實(shí)現的? 它是怎樣設計的? 具體細節是什么?


4.1、 x64 的硬件編程資源
  了解現在 processor 提供編程資源是很重要的,下面介紹 x86 和 x64 提供的用戶(hù)編程資源。


4.1.1、 x86 原來(lái)的 32 位編程資源

● 8 個(gè) 32 位通用寄存器(GPRs):EAX、ECX、EDX、EBX、ESP、EBP、ESI、EDI
  這些寄存器還可分解為 8 個(gè) 8 位寄存器:AL、CL、DL、BL、AH、CH、DH、BH
  和 8 個(gè) 16 位寄存器:AX、CX、DX、BX、SP、BP、SI、DI
● 6 個(gè)段寄存器:ES、CS、SS、DS、FS、GS
● 32 位的 EFLAGS 標志位寄存器
● 32 位的指令指針寄存器 EIP
● 8 個(gè) 64 位 MMX 寄存器(mmx0~ mmx7, 同 ST0 ~ ST7)
● 8 個(gè) 128 位 XMM 寄存器(xmm0 ~ xmm7)
● 32 位的尋址空間(Virtual Address Space):00000000 ~ FFFFFFFF


4.1.2、 x64 的 64 位編程資源
  x64 通過(guò)擴展和新增了編程資源:

● 32 位通用寄存器被擴展至 64 位,除了原有的 8 個(gè)寄存器,又新增 8 個(gè)寄存器,共 16 個(gè)通用寄存器:RAX、RCX、RDX、RBX、RSP、RBP、RSI、RDI、R8、R9、R10、R11、R12、R13、R14、R15
● 保留了原有的 6 個(gè)段寄存器,但是作用被限制
● 32 位的標志寄存器被擴展為 64 位的標志寄存器 RELAGS
● 8 個(gè) 64 位 MMX 寄存器不變 (mmx0 ~ mmx7)
● 新增 8 個(gè) XMM 寄存器,共 16 個(gè) XMM 寄存器(xmm0 ~ xmm15)
● 64 位的尋址空間(Virtaul Address Space):00000000_00000000 ~ FFFFFFFF_FFFFFFFF

  x64 雖然新增了 8 個(gè)通用寄存器,對于一般 RISC 實(shí)現的 32 個(gè)通用寄存器來(lái)說(shuō),還是少了點(diǎn)。AMD 在 64 位尋址空間實(shí)則只實(shí)現了 48 位 virtual address 尋址空間,高 16 位被保留起來(lái)。





4.2、 寄存器編碼(或者說(shuō) ID 值)

● 16 個(gè) 64 位通用寄存器是: 0000 ~ 1111, 其值是:0 ~ 15
  x86 原有的 8 個(gè) 32 位通用寄存器是:000 ~ 111  其值是:0 ~ 7
● 6 個(gè)段寄存器的編碼是:000 ~ 101 其值是:0 ~ 5
● 8 個(gè)MMX 寄存器編碼是: 000 ~ 111 其值是:0 ~ 7
● 16 個(gè) XMM 寄存器編碼是: 0000 ~ 1111 其值是:0 ~ 15

寄存器編碼是寄存器對應的二進(jìn)制編碼,按順序來(lái)定義,看下面的表格:

RAX/ES/MMX0/XMM0 -> 0000
RCX/CS/MMX1/XMM1 -> 0001
RDX/SS/MMX2/XMM2 -> 0010
RBX/DS/MMX3/XMM3 -> 0011
RSP/FS/MMX4/XMM4 -> 0100
RBP/GS/MMX5/XMM5 -> 0101
RSI/MMX6/XMM6 -> 0110
RDI/MMX7/XMM7 -> 0111
R8/XMM8   -> 1000
R9/XMM9   -> 1001
R10/XMM10 -> 1010
R11/XMM11 -> 1011
R12/XMM12 -> 1100
R13/XMM13 -> 1101
R14/XMM14 -> 1110
R15/XMM15 -> 1111


  x64 的 16 個(gè)通用寄存器的編碼是 0000 ~ 1111 是在 x86 原有的通用寄存器 3 位編碼上通過(guò) REX prefix 對相應的寄存器進(jìn)行擴充 1 位,從而變成了 4 位編碼,共能表達 16 個(gè)值。





4.3、 開(kāi)啟 64 位計算的基石(REX prefix)

  AMD64 體系的物理環(huán)境:操作數的 Default Operand-Size 是 32 位,而 Address-Size 是固定為 64 位的。因此,在怎么設計 64 位的計算方案時(shí),有 3 個(gè)問(wèn)題要解決的:

● 問(wèn)題1:當要訪(fǎng)問(wèn)是 64 位的寄存器時(shí),那么必須要有一種機制去開(kāi)啟或者說(shuō)確認訪(fǎng)問(wèn)的寄存器是 64 位的。
● 問(wèn)題2:當要尋址內存操作數時(shí),那么也必須要去開(kāi)啟 64 位地址。
● 問(wèn)題3:如何去訪(fǎng)問(wèn)新增加的幾個(gè)寄存器呢? 那么也必須要有方法去訪(fǎng)問(wèn)增加的寄存器?


  那么在 64 位 Long 模式下,為什么不將操作數的 Default Operand-Size 設計為 64 位呢? 原因前面已提到:由于體系限制,x86 體系初計的初衷是為了實(shí)現平滑無(wú)縫地運行原有的 32 位代碼,達到完美的兼容性。
  x86 體系當初設計時(shí)就沒(méi)思考到會(huì )被擴展到 64 位。所以在 Segment-Descriptor(段描述符)里就沒(méi)有可以擴展為 64 位的標志位。CS.D 位只有置 1 時(shí)是 32 位,清 0 時(shí)為 16 位,這兩種情況。
  AMD在保持兼容的大提前下,只好令謀計策。AMD的解決方案是:增加一個(gè) 64 位模式下特有 Prefix,以起到開(kāi)啟 64 位計算功能以及訪(fǎng)問(wèn)新增寄存器的能力。 這就是 REX prefix。



4.3.1、 REX prefix 的具體格式及含義

  REX prefix 的取值范圍是:40 ~ 4F(0100 0000 ~ 0100 1111),這樣一來(lái)原有相應的 Opcode 被占用了,這些原來(lái)的 Opcode 在 64 位 Long 模式下是無(wú)效的。變成了 REX prefix。
  來(lái)看下原來(lái) opcode 取值范圍的 40 ~ 4F 的是什么指令:Opcode 為 40 ~ 47 在 x86 下是 inc eax ~ inc edi 指令,48 ~ 4F 在 x86 下是 dec eax ~ dec edi 指令。


REX prefix字節的組成部分如下:

0 1 0 0 0 0 0 0
- - - -
W R X B


● bit0:REX.B
● bit1:REX.X
● bit2:REX.R
● bit3:REX.W
● bit4 ~ bit7:此域固定為 0100,也就是高半字節為 4。

--------------------------------------------------------------------------------
REX.W 用來(lái)打開(kāi) 64 位訪(fǎng)問(wèn)能力,REX.W = 1 時(shí),操作數是 64 位, REX.W = 0 時(shí),操作數是 Default Operand-Size
REX.R 用來(lái)擴展 ModRM 尋址中的 ModRM.reg 使用 ModRM.reg 為 4 位編碼。
REX.X 用來(lái)擴展 SIB 尋址中的 SIB.index,使得 SIB.index 域為 4 位編碼。
REX.B 用來(lái)擴展 SIB 尋址中的 SIB.base, 使用 SIB.base 域為 4 位編碼及對直接嵌在 Opcode 中的 reg 進(jìn)行擴展。




4.3.2、 解決之道

(1)設計 REX.W 來(lái)解決訪(fǎng)問(wèn) 64 位操作數的能力。在 REX.W = 1 就開(kāi)啟了 64 位計算能力,包括 64 位操作數和 64 位尋址。
(2)設計 REX.R 來(lái)解決訪(fǎng)問(wèn)新增的 8 個(gè)寄存器的能力。 ModRM.reg = 000 而 REX.R = 1,組合的寄存器 ID 為 1000,這是寄存器 r8。
(3)設計 REX.X 及 REX.B 來(lái)解決 64 位尋址的問(wèn)題。



下面使用幾個(gè)例子來(lái)說(shuō)明解決之道:

例1:指令 mov eax, 1  
  這條指令的 Default Operand-Size 是 32 位,在 32 位下它的機器編碼是:b8 01 00 00 00(其5個(gè)字節)

  64 位下使用 64 位寄存器,它的語(yǔ)法元素變成: mov rax, 1
  此時(shí),它的機器編碼是 48 b8 01 00 00 00 00 00 00 00 (共10個(gè)字節)

  注意這里的 48 就是 REX prefix字節,即:0100 1000 它的各個(gè)域值是:REX.W = 1,定義操作數是 64 位的,REX.R = 0、REX.X = 0、 REX.B = 0 這條指令不需要 ModRM 和 SIB 字節進(jìn)行尋址,所以 RXB 域都為 0。


  這里有個(gè)值得思考的地方,若 REX.W 域為 0 時(shí),這條指令的操作數是 32 位的,機器編碼:40 b8 01 00 00 00(其 6 個(gè)字節)是與 b8 01 00 00 00 結果一樣的,都是 mov eax, 1
  40 即:0100 0000 REX.WRXB 都為 0,沒(méi)有什么實(shí)際意義。





例2:指令:mov rax, r14

  這是一條常見(jiàn) 64 位指令,它是需要 ModRM 來(lái)進(jìn)行尋址的。源寄存器是 r14,目標寄存器是 rax 它的機器編碼是:
  4c 89 f0(共3個(gè)字節)
  在這個(gè)編碼里 4c 是 REX prefix,89 是 opcode,F0 是 ModRM。
  4c (0100 1100),其中 REX.W = 1,REX.R = 1,XB 都為 0。
  ModRM 的值是 F0(11-110-000),Mod=11,Reg=110, R/M = 000,在這里先不講 ModRM 的含義,在后面的章節再詳述。在這條指令里,Reg 表示源操作數 r14 的 ID 值。
  r14 是新增加寄存器,所以需要 REX.R 進(jìn)行擴展,得出最終寄存器的 ID 值:1110,這是 r14 寄存器的 ID 值,從而得出正確的編碼。 REX.R 擴展了 ModRM.reg 從而變成 4 位 ID 值。




例3:回到序言里的例子:mov word ptr es:[eax + ecx * 8 + 0x11223344], 0x12345678

  作為例子,我將它改為 64 位指令,如下:mov qword ptr [rax + rcx * 8 + 0x11223344], 0x12345678
  操作數大小變?yōu)?64 位,而 base 寄存器和 index 寄存器都改為 64 位,disp(offset)和 imme(值不變),為啥不變?在以后的章節會(huì )有詳述。


好,現在來(lái)看看指令怎么譯:

(1) REX.W: 要置為 1 以使用 64 位大小。
(2) REX.B: 由于 base 不是新增的寄存器,所以置為 0
(3) REX.X: 由于 index 也不是新增的寄存器,所以置為 0
(4) REX.R: 源操作數和目標作數不是寄存器,所以置為 0

所以,REX prefix 就等于 48(0100 1000)

故,整條指令編碼是:48 c7 84 c8 44 33 22 11 78 56 34 12(共12個(gè)字節)





例4:我將上面的例子再改一改,變?yōu)椋簃ov qword ptr [r8 + r9 * 8 + 0x11223344], 0x12345678

那么,看看這指令怎么譯:

(1)REX.W:置 1,使用 64 位大小
(2)REX.B:base 寄存器是 r8,是新增寄存器,所以置為 1
(3)REX.X:index 寄存器是 r9,是新增寄存器,所以置為 1
(4)REX.R:操作數中沒(méi)有寄存器,所在置為 0

所以,REX prefix就等于(0100 1011)4b

故,整條指令編碼是:4b c7 84 c8 44 33 22 11 78 56 34 12(共12個(gè)字節)



例5:看看這條指令 mov r8, 1
(1)REX.W:置1
(2)REX.B:訪(fǎng)問(wèn) Opcode 中的寄存器 ID 值,它是新增寄存器,所為置 1
(3)REX.X:置 0
(4)REX.R:置 0

所以,REX是 49(0100 1001)
故整條指令編碼是:49 b8 01 00 00 00 00 00 00 00





4.3.3、 解開(kāi) REX prefix 迷惑

(1)關(guān)于順序:REX 一定是在 x86 prefix 之后,而在 Opcode 之前。

(2)關(guān)于沖突:當 x86 prefix 和 REX prefix 同時(shí)出現,而又出現沖突時(shí),REX 的優(yōu)先權要優(yōu)于 x86 prefix,

舉個(gè)例子:指令 mov r8, 1

  若解碼器遇到以下編碼怎么辦? 
66 49 b8 01 00 00 00 00 00 00 00 既有 66 又有 49,那么結果 66 會(huì )被忽略,也就等于:49 b8 01 00 00 00 00 00 00 00。

  而對于 66 b8 01 00 00 00 00 00 00 00 這個(gè)編碼來(lái)說(shuō):會(huì )被解析為:mov ax, 1
  去掉了 49 這個(gè) REX prefix 后操作數被調整為 16 位。


(3) 關(guān)于原來(lái) Opcode 碼,由于 40 ~ 4F 被作為 REX prefix,那么原指令 inc reg/dec reg,只能使用 FF/0 和 FF/1 這兩個(gè) Opcode 了。


(4)缺省操作數大?。―efault Operand-Size)

  64 位絕大部分缺省操作數是 32 位的,但有一部分是 64 位的,依賴(lài)于 rsp 的尋址和短跳轉(near jmp/near call)是 64 位的。

如下指令:push r8
  REX 值是 41(0100 0001),即 REX.W 為 0,使用 default opearnd-size,它的編碼是 41 ff f0


  有兩大類(lèi)指令,它的 Default Operand-Size 是固定 64 位。 它不依賴(lài)于 CS.D,且不能被 66h prefix 改變 Size。

  第一類(lèi)是:轉移指令,包括 call、jmp(任何形式)以及 loop(任何形式)。這類(lèi)指令的操作數是依賴(lài)于 rip (Instruction Pointer 寄存器),64 位下 rip 是 64 位且不能改變。 如:call [rax] 這條指令,它的編碼是:FF 10。 它是無(wú)需給出 REX prefix 的。
  
  第二類(lèi)是:棧操作指令,包括 push、pop 以及 enter、leave 等。 同樣,它不依賴(lài)于 SS.B 且不能被 66h prefix 改變 Size。這類(lèi)指令依賴(lài)于 rsp(stack pointer 寄存器)。 64 位下 rsp 是固定 64 位且不能改變的?!∪纾簆ush [rax] 這條指令的編碼是:FF 30,同樣它無(wú)需給出 REX prefix。


更深入一點(diǎn):
  對于第一類(lèi)指令:call [rax],它深入的含義是: rip = [rax]、 goto rip。 rip 的獲取是由 [rax] 給出。rip 的 Size 是固定為 64 位的,rip 的值不可能會(huì )被改變 32 位。所以,依賴(lài)于 rip 的這類(lèi)轉移指令是 rip 的長(cháng)度,也就是 64 位。

  對于第二類(lèi)指令:push [rax],它深入的含義是:rsp = rsp - 8、[rsp] = [rax]。 同樣在 64 位,rsp 的值是不能改變的。

  但是,rsp 與 rip 含義不同。 在 32 位下,esp 所指向的棧結構 的值是可以被改變的。那是因為:stack pointer 的值要依賴(lài)于 SS.B(SS segment 的 B 位),SS.B = 1 時(shí),esp = esp +- 4 ,stack pointer 為 32 位。 SS.B = 0 時(shí),esp = esp +- 2,stack pointer 為 16 位?!?br>  但是在 64 位 Long 模式下,SS segment 是無(wú)效的。也就是說(shuō):SS.B 是無(wú)效的。此時(shí):rsp 的值固定為 64 位。

  在 64 位下,call [eax] 這類(lèi)指令,編譯器要么就提示錯誤。要么就忽略錯誤,自動(dòng)調正。




  最后,一句話(huà)來(lái)對 x64 的 64 位擴展作出評價(jià):我認為它的設計架構是完美的,也很成功的。對 AMD 的設計能力是很贊同。我不會(huì )認為比 Intel 差,甚至要好。因為在對 Opcode 的設計上,Intel 就不那么完美,這是后一節的話(huà)題了。

5、 指令編碼核心之 Opcode



  x86 指令編碼的核心是:Opcode、ModRM 以及 SIB,Opcode 提供指令的操作碼,ModRM 及 SIB 提供操作數的尋址模式。指令編碼設計模式是:Opcode 的設計要考慮兼顧 ModRM。ModRM 要服務(wù)于 Opcode,SIB 是對 ModRM 的補充輔助。



5.1、 初窺 Opcode   
  一條指令對應一個(gè)唯一的 Opcode 碼。 在 1 個(gè)字節的空間里:00 ~ FF,Prefix 與 Opcode 共同占用這個(gè)空間。由于 x86 是 CISC 架構,指令不定長(cháng)。解碼器解碼的唯一途徑就是按指令編碼的序列進(jìn)行解碼,關(guān)鍵是第 1 字節是什么? 遇到 66h,它就是 prefix,遇到 89h,它就是 Opcode。

記?。?br>  Prefix 與 Opcode 共享空間的原因是:Prefix 是可選的。在編碼序列里,只有 Opcode 是不可缺少的,其它都是可選。這就決定了指令編碼中的第 1 個(gè)字節對解碼工作的重要性。


  除了 1 個(gè)字節的 Opcode 外,還有 2 個(gè)字節的 Opcode 以及 3 個(gè)字節的 Opcode,第 2 個(gè) Opcode 碼是由 0F 字節進(jìn)行引導。即:2 個(gè)字節的 Opcode 碼,其第 1 個(gè) Opcode 必定是 0F 字節。

  在 AMD 的 SSE5 指令集推出之前,x86 平臺沒(méi)有真正意義上的 3 個(gè)字節的 Opcode 碼??梢岳斫鉃椋簜?3 個(gè)字節 Opcode。3 個(gè)字節的 Opcode 是通過(guò) Prefix 修飾而來(lái)。用于修飾 Opcode 的 prefix 是:66h、F2h 以及 F3h。
  使用 66h (Default Operand-Size Override),F2h(REPNZ)及 F3(REP/REPZ)來(lái)修飾 Opcode 而達到 3 個(gè)字節 Opcode 碼,是其 Opcode 碼有這 3 個(gè) prefix 功能的隱含語(yǔ)義。





5.2、 學(xué)會(huì )看 Opcode 表

  怎么去看指令的 opcode,獲得指令 opcode 編碼,還是很有學(xué)問(wèn)的。



5.2.1、 從指令參考頁(yè)里看opcode 碼

  可能許多人喜歡從指令參考頁(yè)里查看 Opcode,下面的圖是 AMD 文檔中對于指令參考頁(yè)的描述:




從指令參考頁(yè)里可以得出以下信息:

(1)指令助記符 mnemonic
(2)指令的 Operand 屬性
(3)指令的 Opcode 碼
(4)指令的描述。


  這確實(shí)可以得到想要的 Opcode 碼,還是 Operand 數以其屬性。下面摘錄了 mov 指令一部分的參考頁(yè):



  從這里看出,這個(gè) Opcode 8B 有幾種操作數形式,reg <- reg、reg <- mem,Operand-size 可以是 16/32/64。

  不過(guò)這并不是了解 Opcode 碼的好地方,指令參考頁(yè)主要是對指令的操作進(jìn)行相應的描述。對掌握 Opcode 碼不是那么直觀(guān)和透徹。下面要看全局的 Opcode 表格。




5.2.2、 怎么看 Opcode 表

  學(xué)會(huì )看 Opcode 表才能清晰地進(jìn)行分析,Opcode 表是一個(gè)全面的透徹的總結表,又可以說(shuō)十分細致。Intel 和 AMD 的文檔中均提供了 Opcode 表,Opcode 表有 One-byte Opcode 表、Two-byte Opcode 表和 X87 Opcode 表等。


5.2.2.1、 Opcode表上的基本元素

  Opcode 表上描述的范圍是 00 ~ FF,即 1 個(gè)字節共 256 個(gè)值,每 1 個(gè)值描述不同的屬性,包括:

● 絕大部分代表 1 個(gè) Opcode 碼。
● 26、2E、36、3E、64、65、66、67、F0、F2、F3 則是 prefix。
● 0F 指示 2 個(gè)字節的 Opcode,引導性 Opcode。
● 還有一部分是 Group 屬性指令,由 ModRM 中的 reg 來(lái)決定。這部分還包括了 x87 float 指令的 Opcode 碼。


  每個(gè) Opcode 碼還附有相應的 Operands 屬性,Operands 屬性是用來(lái)描述 Operands 的,包括 Operands 個(gè)數、尋址類(lèi)型及 Size。

注意:
  所謂 Group(組)屬性指令是指:Intel 將一些 Opcode 碼抽出來(lái),不具體實(shí)際的操作。具體的功能是由 ModRM 的 reg 來(lái)決定。ModRM.reg 就起決定性作用,它反過(guò)影來(lái) Opcode 碼,這主要原因是原因:這種 Opcode 的操作數無(wú)法與 ModRM 得到良好的配合。從而決定了 Opcode 受制于 ModRM.reg。
  很典型的 FFh,這就是一個(gè) Group 屬性的 Opcode 碼。 FFh 是一組指令的代表,FFh 要由 ModRM.reg 才能決定它的指令功能。當 ModRM.reg = 010 時(shí),FFh 是 CALL 指令的 Opcode 碼。 當 ModRM.reg = 000 時(shí),它是 INC 指令的 Opcode 碼。


更透徹一點(diǎn):
  Opcode 的組是按照 Operands 的屬性進(jìn)行分組的。
  說(shuō)白了就是:這些 Opcode 提供的 Operands 尋址是不含寄存器。還就是說(shuō): 內存尋址和立即數尋址或是 disp 值。






看看 mov 指令 8B Opcode 表是怎樣的:



  上圖圓圈所示是 Opcode 8B,它對應的是 mov 的 mnemonic,表格中的 Gv, Ev 是描述這個(gè) Opcode 碼所對應的指令的 Operand 屬性。表示:

(1)兩個(gè) Operands 分別是:目標操作數 Gv,源操作數 Ev

(2)Gv 表示:G 是寄存器操作數,v 是表示操作數大小依賴(lài)于當前代碼的 Default Operand-Size,也就是CS.D??梢允?16 位,32位以及 64 位。

(3)Ev 表示:E 是寄存器或者內存操作數,具體要依賴(lài)于 ModRM.r/m,操作數大小和 Gv 一致。

  4 個(gè)字符便可以很直觀(guān)的表示出:操作數的個(gè)數以及尋址方式,更重要的信息是這個(gè) Opcode 的操作數需要 ModRM 進(jìn)行尋址。

  要看懂 Opcode 表必須學(xué)會(huì )分析和理解 Operand 屬性字符,Intel 和 AMD 的 Opcode 表前面都有對 Operands 屬性字符很仔細清晰的定義和說(shuō)明。



記住以下兩點(diǎn):

(1)Operands 屬性都有兩組字符來(lái)定義,前面的一組大寫(xiě)字母是 Operand 類(lèi)型,后面一組小定字母是 Operand Size。
  如:Gv 這里 G 是 Operand 類(lèi)型,表示是 General-Purpose Register(GPR)通用寄存器,也就是 rax~r15 共 16 個(gè)。這是有別與 Segment Register、XMM 寄存器等。v 是 Operand 大小,這個(gè) Size 是依賴(lài)于當前的 Default Operand-Size。

(2)操作數是直接編碼在 Opcode 中,這些操作數是寄存器。這些寄存器的 ID 值已在 Opcode 中,而無(wú)需指出。Operand Size 是依賴(lài)于當前 Default Operand-Size。




以下舉兩個(gè)例子。

(1)以典型的 Jmp Jz 為例,它的 Opcode 是 E9,Operand 屬性是 Jz,J 是代表基于 EIP 的相對尋址,也就是說(shuō),操作數尋址是偏移量(Offset)加上 EIP 得出。z 則表示 Operand-Size 是當前 Default Operand-Size,這個(gè) Operand-Size 是不能被 override 的,不能被加 66h Prefix 來(lái)調整 Operand-Size?!∵@與 v 明顯不同:v 是可以調整,z 是固定的。

(2)另一個(gè)典型的例子是 call Ev,它的 Opcode 是FF,這個(gè) Opcode 是個(gè)典型的 Group Opcode,為什么會(huì )定義為 Group,下面的將會(huì )有闡述。操作數的尋址是典型的 ModRM 尋址,準確地講是 ModRM.r/m 尋址。E 既可是 GPRs 也可以是 Mem。它同樣是 v 屬性的 Operand-Size。



下面列舉一些常見(jiàn)的 Operands 屬性字符,詳述請參考 Intel 或 AMD 手冊。

(1)、Operand類(lèi)型字符

E:GPR 或 Mem,依賴(lài)于 ModRM.r/m
G:GPRs 具體 ID 依賴(lài)于 ModRM.reg
I:Immediate 直接在指令 encode 中
J:EIP 相對尋址操作數,即:EIP+offset
O:絕對尋址和 Immediate 一樣,直接在指令 encode 中


(2)、Operand大小字符

b:One-byte
d:four-byte(doubledword)
q:eight-byte(quadword)
v:16 位、32 位、64 位依賴(lài)于 Default Operand-Size,可被 66h Prefix 改寫(xiě)
z:16、32、64 位依賴(lài)于 Default Operand-Size,不可被 66h 改寫(xiě)


以上所述通過(guò)查 Opcode 表能迅速得出該指令的 Opcode 及 Operands 詳情。




5.3、 透析 Opcode 的編碼規則

  如上所述:prefix 與 Opcode 共享 00~FF 的空間,由于 Prefix 部分是 可選的,當 CPU 取指單元從 ITLB 加載指令 L1-Icache和 prefetch buffer,預解碼單元通過(guò) prefix 自已的 ID 值來(lái)解析 prefix。如讀入 66h 時(shí)是解析為 prefix 而不是 Opcode。同樣,讀入 0Fh 時(shí)被解析為是 2 個(gè) 字節的 Opcode 中的第 1 個(gè)字節。

  Opcode 的 operands 尋址一部分是在 Opcode 碼直接中指定,一部分是依賴(lài) ModRM 給定,還有一部分不依賴(lài) ModRM 給定。直接中 Opcode 指定的 operand 尋址的是 GPRs 尋址,如:inc eax 指令(Opcode 是 40h),還有串指令,如 loads 等。



5.3.1、 1 個(gè) Operand 的 Opcode 碼編碼規則

(1)直接嵌入 Opcode 中
  一部分 Opcode 的操作數是直接嵌入 Opcode 中的,如:inc eax、push eax、pop eax 等。 這些指令編碼是 1 個(gè)字節。不依賴(lài)于 ModRM 尋址。是常見(jiàn)的指令。

(2)依賴(lài)于 ModRM 尋址,Group 屬性 Opcode
  對于單 Operand 的指令而又依賴(lài)于 ModRM 尋址。這種指令必定是 Group 屬性的指令。這種 Opcode 操作數的定義是 Ev 字符。ModRM.reg 決定 Opcode 操作碼,ModRM.r/m 決定尋址模式。如前面提到的典型 Call Ev 這種指令,操作數既可是寄存器,也可以是內存操作數,由 ModRM.r/m 來(lái)決定到底是 registers 還是 memory。

(3)不依賴(lài)于 ModRM 尋址,不是 Group 屬性 Opcode
  這種情況下的 Operand 既不嵌入 Opcode 中,也不依賴(lài)于 ModRM 進(jìn)行尋址,那么它必定是 immediate 或者 displacement 值。它的 Operand 屬性字符是 Iv、Ib 或者 Jz。 這種指令很常見(jiàn),如:push Iv、push Ib、Jmp Jz、Jmp Jb 等。
  push 0x12345678 這就是常見(jiàn)的這種指令,還非常常見(jiàn)的短跳轉 jmp $+0x0c。




5.3.2、 2 個(gè) Operands 的 Opcode 碼編碼規則。

(1)1 個(gè) Operand 嵌入 Opcode,另一個(gè) Operand 不依賴(lài)于 ModRM(非 Group 屬性)
  這種情況下,一個(gè) Operand 必定是 GPRs,另一個(gè)不依賴(lài)于 ModRM 的 Operand 必定是 Immediate 或 Displacement。所以它不是 Group 屬性的??纯匆韵聝蓚€(gè) Opcode:

  指令 mov rax, Iv 它的 Opcode 是 B8,目標操作數是由 Opcode 中指定的 GPRs(rax),源操作數不依賴(lài)于 ModRM 的 Immediate。是一個(gè)寄存器與立即數的尋址指令。

  在這種情況下,這個(gè) Opcode 不依賴(lài)于 ModRM 進(jìn)行尋址。
  所以:這個(gè) Immediate 可以不受 4 字節限制,它可以是 64 位的值,即:mov rax, 0x1122334455667788 是完全正確的。


思考另一個(gè)問(wèn)題,在 64 位下:
  mov qword ptr [rax],0x1122334455667788,這指令是的錯誤的。原因是它的尋址是依賴(lài)于 ModRM 的。此時(shí),它要受限于 Immediate 最大為 4 個(gè)字節的限制。

  mov rax, qword ptr [0x1122334455667788],這條指令是完全正確的。它不依賴(lài)于 ModRM 尋址。此時(shí),它不受于 displacement 最大為 4 個(gè)字節的限制。它的 displacement 值是直接嵌入指令編碼中。




(2)依賴(lài)于 ModRM 尋址,非 Group 屬性
  這種依賴(lài)于 ModRM 尋址而又非 Group 屬性的 2 個(gè) Operands,絕大部分是:寄存器與內存操作數之間或 2 個(gè)寄存器之間。它的 Operands 屬性字符是 Gv, Ev 或 Ev, Gv。
  典型的如: mov eax, ebx


(3)依賴(lài)于 ModRM 尋址,是 Group 屬性
  在這種 Opcode 編碼下,另一個(gè)操作數必定是 Immediate 或 Displacement。典型的如:mov ecx, 0x10 ,它的 Operands 屬性字符是 Ev, Iv 等。



5.3.3、 3 個(gè) Operands 的 Opcode 編碼
  在 AMD 的 SSE5 指令集推出之前,是沒(méi)有第 3 個(gè) Operand 是非寄存器或內存操作數的情形。所以,第 3 個(gè)操作必定是 Immediate 值。這種指令很少。
  imul eax, ebx, 3 這是其中的一種形式。


  關(guān)于 AMD SSE5 指令集在后續中有稍為詳細的介紹。
 



  要深入掌握 Opcode 表,必要記住 2 點(diǎn):一是 Opcode 表的基本元素,學(xué)會(huì )分析 Operands 屬性字符,二是理解上面所講的 Operand 尋址模式。
6、 x87 指令、3DNow 指令、SSEx 指令



  對于 GPIs 來(lái)說(shuō),這些指令的尋址模式會(huì )簡(jiǎn)單些,但是 Opcode 碼的數量卻在增加,到現在的 SSE4 指令,3 個(gè)實(shí) Opcode 加上 1 個(gè) prefix Opcode,達到了 4 個(gè) Opcode 數量。



6.1、 x87 float 指令集

  Opcode 范圍從 D8 ~ DF 是 x87 float 指令的 Opcode,實(shí)際上 x87 指令的 Opcode 是 2 個(gè)字節的,D8 ~ DF 是主字節,ModRM 是補充字節,協(xié)助主字節。
  因此,它是 Group 屬性的 Opcode,比起 GPIs 的 Group 屬性指令,x87 的 ModRM 進(jìn)一步增強協(xié)助關(guān)系。

  x87 指令絕大部分是 float 寄存器(st0~st7)尋址的,實(shí)際是與 mmx0~mmx7 寄存器共用物理寄存器。



6.1.1、 x87 float 指令格式

Opcode + XXX = x87's Opcode
------ ---
| |
| |

D8 ~ DF ModRM.reg


x87 float 指令的編碼序列里,沒(méi)有 prefix 和 immediate 部分。x87 的 Group 屬性 Opcode 由上表格式所示:

D8 ~ DF: 共 8 個(gè) Opcode 被安排為 x87 的 Opcode 碼。
ModRM.reg : 這部分是附加的 Opcode 碼,輔助 Opcode。


● 在 ModRM.mod = 11 模式下,ModRM.r/m 能提供 8 個(gè)不同組合的寄存器尋址(st0 ~ st7),它們全是寄存器尋址。
● 在 ModRM.mod != 11 模式下,ModRM.reg 能提供 8 個(gè)不同的 Opcode 碼。 此時(shí),x87 指令可以提供內存操作數尋址。
● D8 ~ DF 共 8 個(gè) Opcode 碼,理論上可提供 8 * 8 = 64 條 x87 指令。 在 ModRM.mod = 11 和 ModRM.mod != 11 之間提供兩組 Opcode 值,所以,x87 的數量理論上可達到 64 ~ 128 條。實(shí)際上,x87 float 指令約為 70 條左右。



  
6.1.2、 看看兩條常見(jiàn)的 x87 float 指令編碼。

(1)fstp st(1)    ; 將 st(0) 值復制到 st(1) 到,并置 stack 頂為 st(1)

  它是寄存器尋址,ModRM.mod = 11 提供寄存器尋址模式,ModRM.reg = 011 提供 fstp opcode,ModRM.r/m = 001 提供寄存器 ID 值。ModRM 的值是:11-011-001 = D9

  所以,這條指令的編碼是:dd d9


(2)fstp dword ptr [eax] ; 將 st(0) 值復制到 [eax],并置 stack 頂為 st(1)

  它是內存尋址,ModRM.mod = 00 提供無(wú) disp 內存尋址,Mod.reg = 011 提供 fstp mem32 指令的 opcode,Mod.r/m = 000 提供 [eax] 內存尋址。 ModRM 值是:00-011-000

  所以,這條指令的編碼是:d9 18




6.2、 AMD 3DNow 指令

  AMD 設計的 3Dnow 別開(kāi)生面,在編碼上,實(shí)際 3 個(gè) Opcode 的指令集,前 2 個(gè) Opcode 是 0F 0F,這兩個(gè) Opcode 是起引導為 3Dnow 的作用,第 3 個(gè) Opcode 是主 Opcode。
  這個(gè) Opcode 卻又不跟在第二個(gè) Opcode 后面。 實(shí)際,第 3 個(gè) Opcode 改由 Immediate 來(lái)充當,這個(gè) Immediate 值是固定 為 1 個(gè)字節。


它是編碼序列是:

0F 0F ModRM SIB displacement Immediate




舉1個(gè)例子來(lái)示范:
  指令 pfcmpge mmx1, qword ptr [eax]

  這條指令的 Operand 助記符是:Pq,Qq 實(shí)際上與 Gv, Ev 情況一樣,只不過(guò)這里寄存器由 GPRs 變?yōu)?mmx 寄存器,操作數卻固定為 64 位。
  它的 Opcode 碼是:90 也就是 imme 為 90,由 ModRM 的 reg 提供 P 的尋址,r/m 提供 Q 的尋址。故 mod = 00,reg = 001, r/m = 000

  所以,這條指令的編碼是:0F 0F 08 90



6.3、 SSEx 指令

  一部分指令是 3 個(gè) Opcode,一部分是 2 個(gè) Opcode,而 3 個(gè) Opcode 則是有一個(gè) prefix 來(lái)充當。這是 Intel 設計的編碼架構,若是 AMD 設計的編碼則極大可能不同,AMD 設計的風(fēng)格是使用后面的 Imme 來(lái)充當第 3 個(gè) Opcode,而 Inte l則是使用 prefix,這里可以看出 AMD 與 Intel 風(fēng)格的不同。
  Intel 的 SSE4 指令集達到了 4 個(gè) Opcode 的規模。真 3 個(gè) Opcode 和 1 個(gè) 偽 Opcode(prefix)。
  這些指令使用 66h、F2 以及 F3 prefix 作為第 1 個(gè) Opcode,0F 作為第 2 個(gè) Opcode。

  SSEx 系列指令發(fā)展到 SSE4 數量龐大,編碼混亂。具體指令參考 Intel 文檔

舉 1 個(gè)例子:
  movntdq xmmword ptr [rax], xmm0

  這是一條復制 128 位的指令,從 xmm0 復制到 [rax] 內存上,它采用 66 0F 前導,第 3 個(gè) Opcode 是E7,ModRM 提供尋址,mod = 00,reg = 000,r/m = 000,ModRM 的值是:00

所以,這條指令的編碼是 66 0F E7 00

7、 強悍的 AMD SSE5 指令集



  AMD 設計 3Dnow 與 SSE5 指令集的目的很明顯,想擺 Intel 的制約,在 x86 平臺上有屬于自已的東西,在 x86 平臺上能站穩腳步。特別是 SSE5 指令集的推出,目的想讓 Intel 向自己靠。
  SSE5 指令集的數量不少,有八九十個(gè)之多。實(shí)事上AMD推出的 x86_64 的 64 位平滑擴展技術(shù)就成功的制約了 Intel,Intel 不得不跟隨 AMD 腳步。SSE5 能不能再創(chuàng )輝煌,引導 Intel 向他靠還得拭目以待。



7.1、 AMD SSE5 指令集編碼的特點(diǎn):

(1)SSE5 指令 Operands 可以增加到 4 個(gè)。這 4 個(gè)操作數可以全是寄存器操作數,這是通過(guò)增加 DREX 字節來(lái)實(shí)現的。
(2)SSE5 指令編碼有 3 個(gè) Opcode,其中 2 個(gè)是引導 Opcode 和 1 個(gè)主導 Opcode。
(3)SSE5 指令編碼中有 3 個(gè)字節來(lái)定位尋址操作數。在原有的 ModRM 和 SIB 的基礎上,增加了 Drex 字節來(lái)尋址。
(4)無(wú)需 prefix 以及 REX prefix 進(jìn)行修飾。
(5)目標操作數固定為寄存器


  對比 Intel 的 SSE4 指令集,個(gè)人會(huì ) AMD 的設計更為優(yōu)越。SSE5 有 4 個(gè)操作數,SSE4 是 2 個(gè)操作數。SSE5 取消掉 prefix 的修飾,SSE4 的 Opcode 達到了 4 個(gè)之多,雖然 1 個(gè)是偽 Opcode(prefix)。
  縱觀(guān) SSEx 系列指令集,Opcode 布局混亂、指令繁多。沒(méi)有一個(gè)編碼統一的機制。若有新的指令集產(chǎn)生,恐怕還得修改編碼序列。
  編碼不統一是由于 CISC 的緣故,從編碼的角度來(lái)講,使得任一個(gè) x86 平臺生產(chǎn)者都可以肆意添加新的指令集。


關(guān)于編碼不統一的話(huà)題:
  對當今的處理器來(lái)說(shuō),是 RISC 指令集,還是 CISC 指令集? 這已經(jīng)不重要了。對 CISC 架構的 x86 處理器來(lái)說(shuō),這些 x86 指令集已經(jīng)不是原生執行的指令集了。
  x86 指令在處理器內部會(huì )被解碼為微指令行式執行,這些微指令執行單一功能。 AMD 可以在 x86 處理器上添加新的指令集。只要在 GPIs 指令集架構(ISA)層面上保證兼容就行了。
  對于新增的指令集,這是只給了解碼器更多的解析工作而已。



7.2、 SSE5 指令結構



  上圖是 SSE5 指令編碼序列

● 0F 24 和 0F 25 是引導 Opcode,這兩個(gè) Opcode 在原來(lái)是無(wú)效。
● Opcode3 是主導 Opcode,定性 SSE5 的操作
● ModRM 與 SIB 意義和原來(lái)一致
● DREX 意即:Dest+REX,DREX.dest 是定義目標操作數,REX 的含義和 REX prefix 一致。
● Displacement 含義和以前一致。
● Immediate 含義有些改動(dòng),在 SSE5 指令里只有 1 個(gè)字節大小。




7.3、 Opcode3 的結構

位       含義
------------------------------------------------------------------
7 ~ 3 Opcode 碼
2 Oc1,它與 DREX 的 Oc0 組合起來(lái)控制操作數
1 ~ 0 OPS,它定義操縱數據的大小


Opcode: 定義 SSE5 指令的 Opcode 碼
OC1 ?。?Opcode3.oc1 與 DREX.oc0 組合起來(lái)是 00 ~ 11
OPS : 指令執行時(shí)對操縱的數據的大小。




7.4、 DREX 的結構

位       含義
------------------------------------------------------------------
7 ~ 4 Dest 域,即目標操作數的 ID 值
3 Oc0,與 Opcode3 的 Oc1 組合起來(lái)控制操作數
2 即 REX.R
1 即 REX.X
0 即 REX.B


DREX.dest 是目標操作數的 ID,這個(gè)操作數必定是 xmm 寄存器。
DREX.RXB 的含義與 REX prefix 的含義是一致的,用來(lái)擴展 xmm8 ~ xmm15 寄存器




7.5、 控制操作數

  Opcode3.Oc1+DREX.Oc0 組合共 2 位值控制操作數分配,從 00 ~ 11

  對于 4 個(gè)操作數的指令來(lái)說(shuō),例:fmaddps dest, src1,src2,src3 


其控制的操作數分配如下:

值 含義
---------------------------------------------------------------------------------------
00 dest = DREX.dest,src1 = DREX.dest,src2 = ModRM.reg
src3 = ModRM.r/m

01 dest = DREX.dest, src1 = DREX.dest, src2 = ModRM.r/m
src3 = ModRM.reg

10 dest = DREX.dest,src1 = ModRM.reg,src2 = ModRM.r/m
src3 = DREX.dest

11 dest = DREX.dest,src1 = ModRM.r/m,src2 = ModRM.reg
src3 = DREX.dest

----------------------------------------------------------------------------------------------



像:fmaddps xmm1, xmm2, xmmword ptr [rax], xmm1 

這條指令的機器編碼是:0F 24 04 10 10


若:fmaddps xmm1,xmm2,xmmword ptr [rax+0x11223344], xmm1 

則是:0F 24 04 90 10 44 33 22 11
8、 指令編碼核心之:ModRM 尋址




前面多次提到:

  Opcode 對指令提供操作碼,ModRM 則對指令提供操作數尋址,SIB 對 ModRM 進(jìn)行補充尋址。

有兩種情況下是無(wú)需用 ModRM 提供尋址的:

  第一是:操作數是寄存器,它直接嵌入 Opcode 中。

  第二是:操作數是立即數,它直接嵌入指令編碼中。




  本節講解指令核心另一個(gè)重點(diǎn):ModRM 尋址。這個(gè) ModRM 尋址非常重要。是理解 x64 平臺上指令 Operands 的關(guān)鍵。



8.1、 ModRM 的含義

  ModRM 字節的組成部分為:mod-reg-r/m 三個(gè)部分,mod 為 2 位,reg 與 r/m 是 3,組成 2-3-3 的比例,在這整篇文檔中的寫(xiě)法是:ModRM.mod、ModRM.reg 以及 ModRM.r/m。
  mod 是提供尋址的模式,這個(gè)模式以 displacement 值作區別的。reg 是提供寄存器尋址,reg 表示寄存器 ID 值。r/m 提供對內存的尋址中的寄存器 ID 值或寄存器尋址中的 ID 值。



8.1.1、 mod 尋址模式。

  2 位組成 4 種尋址模式,總的來(lái)說(shuō),只有兩種尋址模式,就是:內存尋址模式和寄存器尋址模式。mod = 11 時(shí)指出寄存器尋址模式,mod = 00 ~ 10 時(shí)指出內存尋址模式:


值 含義
-------------------------------------------------------------------------------------
00 [register] 間接尋址,無(wú) displacement 值。
01 [register + disp8],有 8 位 displacemnet 偏移值。
10 [register + disp32],有 32 位 displacement 偏移值。
11 registers 尋址





8.1.2、 reg 尋址寄存器

  3 位組成 8 個(gè)寄存器 ID 值,從 000 ~ 111,對應于 RAX、RCX、RDX、RBX、RSP、RBP、RSI 以及 RDI。這個(gè) ID 值可以被REX prefix 擴充為 4 位,范圍從 0000 ~ 1111 可表示 16 個(gè)寄存器。
  ModRM.reg 的另一含義是對 Opcode 的補充,對分為一組 Opcode 的進(jìn)行選擇(Group 屬性)。

  ModRM.reg 是提供 registers 尋址。



8.1.3、r/m 尋址 register / memory

  r/m 意即:registers/memory,提供對 registers 或 memory 的尋址。
  000 ~ 111 也是用來(lái)表示寄存器 ID 值。當尋址 registers 時(shí)是寄存器 ID 值。當尋址 memory 時(shí)是寄存器間接尋址中的寄存器 ID 值。當 mod != 11 時(shí),r/m 表示 [rax] ~ [rdi]。mod = 11 時(shí),r/m 表示 rax ~ rdi
  REX prefix 用來(lái)擴充寄存器ID值。





8.2、 探討 2 個(gè)設計上的問(wèn)題以及解決之道


8.2.1、如果像這條指令:mov eax, [eax+ecx*2+0x0c]   

  在這條指令里 eax 是 base 寄存器,ecx 是 Index 寄存器,2 是 scale,還有一個(gè) displacement 這種內存尋址是 base+index*scale+disp。
  這需要 SIB 字節來(lái)進(jìn)行補充尋址,那么 ModRM 必須要有一個(gè)手段來(lái)引出后續的 SIB 字節。


  在 [rax] ~ [rdi] 的范圍里,Intel 選擇了原來(lái)應屬于 [rsp] 的值用來(lái)引出 SIB 字節,一是因為 [rsp] 并不常用吧。二是因為 rsp 設計為 stack top 指針,專(zhuān)用于指示 stack top 指針,設計的語(yǔ)義上是用來(lái)指示 stack top,而不是尋址。

  經(jīng)過(guò)衡量和考慮,把原來(lái)屬于 [rsp] 的領(lǐng)域騰空,用于 [SIB] 來(lái)實(shí)現引導 SIB 字節。
  Mod.r/m = 100,這個(gè)領(lǐng)域被 [SIB] 替代了。
  事實(shí)上在 16 位機器原本是沒(méi)有 SIB 字節的,base+index*scale+disp 這種尋址是后來(lái)才增加的。16 位的 ModRM 上是沒(méi)有 SIB 引導域。



8.2.2、 另一種情況是 [disp32]

  如果內存尋址中沒(méi)有 base 和 index,只有 disp 的話(huà),如:mov ebx, [0x11223344]。對于這種直接尋址方式,在設計上 ModRM 還必須為它提供這種尋址模式。

  Intel 又作出修改,選擇了原來(lái)屬于 [rbp] 模式的領(lǐng)域提供給 [disp32]。

  選擇 [rbp] 讓給 [disp32],是因為 rbp 原本意圖就是設計為 stack frame 基址指針。[rbp] 尋址一般都要加上一個(gè)偏移量,也就是基于stack frame 指針的偏移量。
  所以,[rbp] 尋址顯得不合邏輯。


  [rbp + disp] 這種尋址模式是符合邏輯語(yǔ)義。在 ModRM.mod = 01 或 ModRM.mod = 10 中給出了這種尋址模式。那么,最終 ModRM.mod = 00 時(shí),[rbp] 讓位給了 [disp32]。


在最終的 ModRM.mod = 00 上, r/m 尋址設計上,如下表格:

r/m 內存尋址
--------------------------------
000 [rax]
001 [rcx]
010 [rdx]
011 [rbx]
100 [SIB] ---- 原本對應 [rsp]
101 [disp] ---- 原本對應 [rbp]
110 [rsi]
111 [rdi]



  而在 ModRM.mod = 01 及 ModRM = 10 上,ModRm.r/m = 101,設計為 [rbp + disp8] 及 [rbp + disp32],提供了對 [rbp+偏移量] 的支持,這樣也符合原本設計的語(yǔ)義。




8.3、16 位尋址下的 ModRM

  在 16 位尋址下是不支持 base+index*scale 這種尋址模式的,這樣在編碼序列里就無(wú)需提供 SIB 字節了。

  16 位的 ModRM 尋址支持的是基址和變址寄存器間接尋址和 基址+變址尋址模式?;芳拇嫫?2 個(gè):就是 bx 和 bp。變址寄存器也是 2 個(gè):就是 si 和 di。


  基于上述設計,基址+變址尋址的組合只有 4 個(gè),也就是:[bx+si]、[bx+di]、[bp+si] 以及 [bp+di]。它們對應 ModRm.r/m 域就是 000 ~ 011。
  那么這 4 個(gè)寄存器的間接尋址就是:[si]、[di]、[bp] 以及 [bx],對應于 Mod.r/m 的 100 ~ 111。
  同樣如上述,bp 是 stack frame 指針,一般使用需加 disp(offset),所以 [bp] 讓位給 [disp] 解決直接尋址的問(wèn)題。


所以,16 位的 ModRM.mod = 00 下,ModRM 最終設計方案是:

r/m 內存尋址
-------------------------------------------------
000 [bx+si]
001 [bx+di]
010 [bp+si]
011 [bp+di]
100 [si]
101 [di]
110 [disp] ------ [bp] 讓位給 [disp16]
111 [bx]



ModRM.mod = 01、ModRM.mod = 10 和 11 的情形下如前如述。





8.4、 64 位尋址下的 ModRM

  在 64 位下,ModRM 的含義與 32 位一致,改進(jìn)的只是在原來(lái)基礎上增加了 8 個(gè) GPRs,通過(guò) REX prefix 進(jìn)行對新增的寄存器進(jìn)行訪(fǎng)問(wèn)。
  寄存器的 ID 取值為:0000 ~ 1111。由 REX.R 以及 REX.B 位進(jìn)行擴展訪(fǎng)問(wèn)。






8.5、 結合Opcode來(lái)看尋址模式及Opcode的定位

  Opcode 定義指令的執行碼,用于執行什么操作,對于操作數尋址上,Opcode 結合 ModRM 來(lái)定義操作數,這是一個(gè)經(jīng)過(guò)反復琢磨推敲的過(guò)程,而最終又影響到 Opcode 的定位。
  下面講講怎么影響到 Opcode 最終定位。


8.5.1、 一個(gè)操作數的 Opcode 定位

  操作數要么就是 registers,要么就是 memory,要么就是 Immediate 值。如果指令只有一個(gè)操作數。

(1)如它是 register 的話(huà),Opcode 是無(wú)需 ModRM 配合確定尋址方式的。

記住 1:
  ModRM 是用于尋址 2 個(gè)操作數,這兩個(gè)操作是必定是寄存器之間,以及寄存器與內存操作數之間的尋址方式。


  在只有 1 個(gè)寄存器操作數的情況下,ModRM 無(wú)用武之處。所以,在這種情沖下,寄存器操作數絕大部分是嵌在 Opcode 里面,它由 Opcode 的寄存器域指出。如常見(jiàn)的 inc ecx、dec ecx、push eax 等。

記住 2:
  除了上述嵌入 Opcode 情況外,如果 ModRM 用于尋址 1 個(gè)操作數,這條指令的 Opcode 必定是:Group 屬性。


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


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

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




8.5.2、 兩個(gè)操作數的 Opcode 定位

  兩個(gè)操作數大部分都需 ModRM 配合定位尋址。ModRM 提供的 2 個(gè)操作數尋址大有用武之地。

(1)2 個(gè)操作數中,其中 1 個(gè)是寄存器,另1個(gè)不是立即數的這種情況最直接簡(jiǎn)單,由 ModRM 的 reg 及 r/m 提供尋址。
  若其中 1 個(gè)是 GPRs 另 1 個(gè)是 immediate 或 displacement 的情形下更簡(jiǎn)單,GPRs 則直接由 Opcode 提供尋址。


(2)2 個(gè)操作數中,沒(méi)有寄存器的情形下,也就是要么是 memory,要么是 Immediate,它必然是個(gè) Group 屬性,reg 域提供 Opcode 的定位,r/m 提供內存尋址。Immediate 直接嵌入指令編碼中。



8.5.3、 三個(gè)操作數的 Opcode 定位

  三個(gè)操作數中有一個(gè)必定是 Immediate,在 AMD 的 SSE5 指令集推出之前,x86 平臺是無(wú)法提供第 3 個(gè)非 Immediate 操作數的定位。
  直至 AMD 的 SSE5 通過(guò)增加另一個(gè)描述操作數的字節來(lái)尋址第 3 個(gè)操作數。


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


前面已提過(guò):
  Opcode 的分組是按 Operands 屬性進(jìn)行的。即:這一組 Opcode 擁有相同類(lèi)型的 Operands。
  這個(gè)類(lèi)型的 Operands 主要是單個(gè)內存操作數或單個(gè)寄存器操作數而非嵌入 Opcode 中。


9、指令編碼核心之 SIB 尋址




  在 ModRM 無(wú)法提供更多內存尋址方式時(shí),使用 SIB 進(jìn)行協(xié)助尋址。對內存操作數尋址提供補充定義。SIB 提供的補充內存尋址支持 base + index * scale + displacement 尋址,如:[rax + rcx * 8 + 0x11223344]。
  此時(shí),SIB 字節是:scale = 11,index = 001,base = 000,組合起來(lái)是 c8



9.1、 SIB 的含義及結構

  SIB 意即:Scale – Index – Base,用來(lái)定義 base+index*scale+disp 這種尋址模式。同樣按 2-3-3 比例組合。


Scale 索引因子的含義:

值 含義
----------------------------------------------------------------------
00     無(wú) scale, 或者:index * 1
01     按 index * 2 比例
10     按 index * 4 比例
11     index * 8 比例



  Index 域指出 Index 寄存器的 ID 值,范圍從 000 ~ 111。Base 域指出 Base 寄存器的 ID 值,從 000 ~ 111。

  同樣,Index 與 Base 經(jīng)過(guò) REX prefix 可以擴展為 0000 ~ 1111,可尋址 16 個(gè)寄存器。




9.2、 對 ModRM 尋址的補充

  前面提到 ModRM 中,ModRM.r/m = 100 時(shí),[esp] 讓位給 [SIB] 提供引導 SIB 字節。這種情況下,指令可使用 base+index 的尋址方式。


ModRM.r/m 含義 __ SIB.index 含義
------------------ /  ---------------------
... ... / 000 [rax + base]
100 [SIB] ---> SIB.scale = 00 / 001 [rcx + base]
... ... = 01 \ 010 [rdx + base]
= 10 \ 011 [rbx + base]
= 11 \ 100 [base]
--- 101 [rbp + base]
110 [rsi + base]
111 [rdi + base]






  SIB 提供了從 [base+index] 到 [base+index*8] 尋址范圍,那么這里延續上一節提到的 ModRM 設計上的問(wèn)題:[rsp] 這種尋址方式將怎么解決呢? 在 ModRM 中拋棄了 [rsp] 的尋址模式,在 SIB 中將得到補救。

  在 index = 100 時(shí),按規則應該是 [rsp + base] 這種尋址模式吧?

  由于 rsp 的特殊性,rsp 寄存器只能做 base 寄存器,以 rsp 為基址。而不能作為 index 寄存器。所以:[rsp+base] 這種尋址模式中 rsp 被去除,那么將剩余 [base]。



  故當 index = 100 時(shí),[rsp+base]具體形式將取決于 base。




9.2、 對 [rsp] 尋址的補救方式

  由于 ModRM 中沒(méi)提供 [rsp] 尋址,而又確實(shí)需要 [rsp] 尋址的話(huà),在 SIB 中通過(guò) index = 100 時(shí)提供,[rsp+base] 中去除掉 rsp,留下 [base] 尋址,所以在 base = 100 時(shí)就提供了對 [rsp] 的尋址。





9.3、 重復編碼

  在 index = 100,去除 rsp 后,變?yōu)?[base] 尋址,那么此時(shí)又提供了 [base] 的尋址方式,這樣就與 ModRM.mod = 00 時(shí) ModRM.r/m 提供了完全相同的尋址方式。
  所不同的是一種由 ModRM.mod = 00 直接給出,另一種通過(guò) ModRM./rm = 100 然后轉接到 SIB,再由 SIB.index = 100 時(shí) SIB.base = 100 提供。

  Intel 設計的時(shí)候已不能顧及這么多了,反正重碼也沒(méi)什么多大的害處,只是麻煩了 processor 的解碼單元而已,但這樣就顯不得夠嚴謹,話(huà)說(shuō)回來(lái),本來(lái) x86 平臺指令集就不是十分嚴謹。?




9.4、 對于 [rbp] 尋址模式的裁減

  那么當 index = 100,也就是 [base] 尋址,而 base = 101(rbp)時(shí),按規則此時(shí)應該為 [rbp] 尋址是吧?

  Intel 又變變花樣,應是 [rbp] 時(shí)而又不是 [rbp],原因前面已經(jīng)提過(guò),rbp 的語(yǔ)義應該是 stack frame pointer,以 rbp 為基址加上 offset (displacement)進(jìn)行尋址,這才符合語(yǔ)義。

  前面提到:當 ModRM.r/m = 100,[rsp] 讓位給 [SIB],而鏈接到 SIB 尋址。
  而 SIB.index = 100 ([base] 尋址)且 SIB.base = 101(rbp),Intel 對它作出了裁減,去掉 [rbp] 尋址模式。

Intel 為了保證 [rbp + disp] 尋址,兩處地方對 [rbp] 作出了裁減:
  (1) ModRM.mod = 00 & ModRM.r/m = 101,原來(lái)對應 [rbp] 變成了 [displacement32] 這種直接尋址模式。

  (2) ModRM.r/m = 100(引發(fā)[SIB]),SIB.index = 100(變?yōu)?[base]),SIB.base = 101(對應 [rbp]),作出了裁減:ModRM.mod = 00 時(shí),SIB = xx-100-101 下,變成了 [displacement32] 尋址。ModRM.mod = 01,SIB(XX-100-101)變成了 [rbp + disp8]。ModRM.mod =10,SIB(XX-100-101),變成了 [rbp + disp32] 尋址。



  這樣,使用 ModRM 尋址和使用 ModRM + SIB 尋址下,保證了 [rbp+disp8] 及 [rbp+disp32] 這兩種尋址,去掉了 [rbp] 這種尋址模式。
  那么,在匯編語(yǔ)法中 [rbp] 這種尋址,只好變通一點(diǎn):采用 [rbp + 0x00] 或 [rbp + 0x00000000] 這種形式了,編譯器會(huì )自動(dòng)產(chǎn)生這種編碼模式。





9.5、 解開(kāi) SIB 尋址的迷惑


1、重碼問(wèn)題

  x86 指令的內存尋址有三個(gè)地方的重碼現象:

(1)[disp32] 尋址方式的重碼現象
● ModRM.mod = 00,ModRM.r/m = 101 提供了 [disp32] 尋址。
● ModRM.mod = 00,ModRM.r/m = 100,SIB.index = 100,SIB.base = 101 提供了 [disp32] 重碼尋址。
-----------------------------------------------------------------
即 ModRM = 00-XXX-101 與 ModRM = 00-XXX-100 + SIB = XX-100-101 下重碼

(2)[base] 尋址方式的重碼現象
● ModRM.mod = 00 提供了 [base] 尋址,也就是 [rax] 之類(lèi)。
● ModRM.mod != 11 & ModRM.r/m = 100 ,SIB.index = 100 提供了 [base] 尋址,導致重碼。
-----------------------------------------------------------------------------------
即:ModRM = 00-XXX-XXX 與 ModRM = 00-XXX-100 + SIB = XX-100-XXX(不含101)下重碼


(3)[ebp+disp8] 與 [ebp+disp32] 的重碼現象
● ModRM.mod = 01 和 10,ModRM.r/m = 101,提供了相應的 [ebp+disp8] 和 [ebp+disp32] 尋址。
● ModRM.mod 01 和 10,ModRM.r/m = 100。
  SIB.index = 100,SIB.base = 101,提供了相應的 [ebp+disp8] 和 [ebp+disp32]
---------------------------------------------------------------------------------------------------------------
即:ModRM = 01-XXX-101 與 ModRM = 01-XXX-100 + SIB = XX-100-101 都提供了 [rbp+disp8]
ModRM = 10-XXX-101 與 ModRM = 10-XXX-100 + SIB = XX-100-101 都提供了 [rbp+disp32]




2、不支持 rsp 作為 index 寄存器

  因 rsp 設計為 stack top 指針,而不支持 [rsp+base] 這種尋址方式。 rsp 只能作為 base 尋址,即:[rsp]。 故 [rsp+base] 被去除 rsp,只剩下 [base],而引發(fā)重碼問(wèn)題。




3、 SIB 同樣不支持 [rbp] 尋址方式

  須作為 stack frame pointer 加偏移量的尋址方式。這符合設計語(yǔ)義。程序中使用 [rbp] 則要變?yōu)椋篬rbp+disp8] 或者 [rbp + disp32] 這種方式。
  SIB 和 ModRM 回繞在一起,保證不提供 [rbp] 尋址。


本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
AMD 的 XOP 指令集編碼內幕
深入系統底層
ftofficer|張聰的blog ? CALL指令有多少種寫(xiě)法
什么是匯編語(yǔ)言一匯編底層原理指令字節碼匯編語(yǔ)言原理OldJohn86的博客(例如操作碼OF A2對應IA32的CPUID指令當處理器執行這條指令時(shí)它會(huì )在微處理器的不同寄存器中存放一些特定的信息)
STC89C52數據手冊詳解.5(尋址相關(guān)概念)
單片機試題及答案
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久