x86 將龐大且不規則的指令集歸納為資料移動、算術運算、位元操作、控制流程與字串處理等通用模式。運算元大小、位址大小、執行模式、前置詞與 ISA 擴充都可能改變指令的編碼方式或可用性。Intel 與 AMD 的架構手冊仍然是權威資料;組譯成功只能證明某個工具能夠將原始碼編碼。
資料移動與位址產生 #
MOV 將立即值或暫存器值複製到暫存器或記憶體,或將記憶體複製到暫存器。一般 MOV 最多包含一個明確的記憶體運算元。MOVZX 對較窄的整數進行零延伸,而 MOVSX 與 MOVSXD 進行符號延伸。在 64 位元模式下,以 32 位元通用暫存器為目的地會隱含清除高 32 位元。
CMOVcc 根據狀態旗標,有條件地複製暫存器或記憶體來源,而不改變控制流程。以記憶體為來源的條件移動,仍可能在選擇結果前讀取來源,因此不得用來保護無效位址。SETcc 使用相同的條件家族寫入一個位元組結果。
XCHG 交換兩個運算元。其記憶體形式即使沒有明確的 LOCK 前置詞,也具有鎖定語意,因此相對於其他處理器,對所定址的記憶體具有原子性。原子性並不能使複合演算法自動正確;順序、快取行配置與重試行為仍然重要。
LEA 計算有效位址並寫入算術結果,但不存取記憶體:
lea rax, [rdi + rdi*4] ; rax = rdi * 5編譯器使用 LEA 進行指標計算,以及執行選定的加法或常數乘法。由於沒有解參照記憶體運算元,因此不會套用區段基底與分頁。
堆疊操作 #
PUSH 遞減堆疊指標並儲存值;POP 載入值並遞增堆疊指標。確切的運算元大小行為取決於模式。對齊、紅區、陰影空間、保存的暫存器,以及哪些記憶體可作為有效的堆疊儲存空間,都是由平台 ABI 而非指令決定。
CALL 儲存返回位址並轉移控制,而近返回 RET 從堆疊載入返回位址。因此,堆疊指標不匹配或返回位址損毀都會改變控制流程。遠呼叫、遠返回、呼叫閘門與 IRET 還會處理區段狀態或特權狀態,不應將它們視為一般函式呼叫指令。
整數算術、比較與旗標 #
ADD、ADC、SUB 與 SBB 實作一般算術與多精度算術。ADC 使用進位旗標 CF,而 SBB 將其作為借位使用,因此由低位元到高位元的 limb 序列會傳播進位或借位。INC 與 DEC 更新多個算術旗標,但保留 CF。
CMP 只執行減法以設定旗標。TEST 只執行位元 AND 以設定旗標。條件分支、條件移動與位元組設定指令接著解讀這些旗標:
- 相等使用
ZF。 - 無號排序使用
CF與ZF。 - 有號排序結合使用
SF、OF與ZF。
CF 記錄無號進位或借位,而 OF 記錄有號溢位。因此,同一個位元模式需要使用與程式資料解讀相符的有號或無號條件。
單一運算元的 MUL 與 IMUL 將累加器乘以明確運算元,並在隱含的高位與低位暫存器中產生雙倍寬度結果。雙運算元與三運算元形式的 IMUL 將截斷後的結果寫入明確目的地。DIV 與 IDIV 對隱含的雙倍寬度被除數執行除法。除數為零,或商無法容納於目的地時,兩者都會引發除法錯誤;軟體必須在除法前正確準備高半部。
邏輯、位元測試、移位與旋轉操作 #
AND、OR、XOR 與 NOT 以位元為單位操作。使用 xor eax, eax 清除暫存器,是目前亂序處理器上常見的解除相依慣用法,但其架構結果單純就是零。
BT 將選取的位元複製到 CF;BTS、BTR 與 BTC 還會分別設定、重設或補數該位元。記憶體位元索引可以定址超出編碼記憶體運算元的範圍,因此邊界推理十分重要。明確的 LOCK 前置詞可以使受支援的記憶體讀取-修改-寫入形式具備原子性。
BSF 與 BSR 定位最低位或最高位的設定位元。來源為零時,目的地未定義。若存在相關擴充,TZCNT 與 LZCNT 會為零輸入提供定義結果;但在不支援該擴充的處理器上,其編碼可能會作為 BSF 或 BSR 執行。因此必須進行功能偵測。
SHL 與 SAL 是等價的左移。SHR 移入零位元,而 SAR 複製符號位元。ROL 與 ROR 在運算元內旋轉;RCL 與 RCR 透過 CF 旋轉。移位計數會依運算元大小進行遮罩,而大計數或零計數的旗標結果需要查閱指令定義。
直接與間接控制流程 #
近形式的 JMP、CALL 與條件式 Jcc 指令可以使用相對位移,位移以指令結尾為測量基準。間接 JMP 與 CALL 從暫存器或記憶體讀取目的地。RET 從堆疊取得目的地。間接目標會與控制流程完整性、返回預測與推測執行緩解措施互動,因此外觀等價的形式可能具有不同的安全性或效能特性。
短條件分支會編碼 8 位元有號位移。近形式在 32 位元與 64 位元模式下使用較寬的位移。x86 沒有能夠有條件地呼叫或返回的一般指令;程式碼透過條件分支組合出這些操作。
例外、軟體中斷與外部中斷會透過中斷描述表轉移。INT、IRET 與以閘門為基礎的轉移具有超出一般分支的特權語意。SYSCALL/SYSRET 與 SYSENTER/SYSEXIT 使用特定模型的設定與作業系統 ABI。
字串指令與重複前置詞 #
MOVS、CMPS、SCAS、LODS 與 STOS 使用隱含的來源、目的地、累加器與計數暫存器。位元組、字組、雙字組以及選定的四字組形式會明確指定元素大小。方向旗標(DF)選擇索引暫存器遞增或遞減。
REP 在計數不為零時重複執行操作。REPE/REPZ 與 REPNE/REPNZ 也會在比較或掃描指令中使用 ZF。目前的處理器可以最佳化大型 REP MOVSB 與 REP STOSB 操作,但依功能與大小進行函式庫分派仍是實作決策。
當目的地從來源範圍內部且高於來源起始位置開始時,向前執行 rep movsb 並不安全,因為較早的儲存會覆寫尚未讀取的位元組。向後複製可以處理這種重疊:
std ; 遞減索引
rep movsb
cld ; 還原 ABI 要求的方向呼叫方必須將指標初始化為最後一個元素,以進行向後複製。大多數 x86 ABI 要求函式進入與返回時 DF=0,因此執行 STD 的程式碼即使在失敗路徑上也必須將其還原。正式環境中的 memmove 實作也會根據重疊情況、大小與處理器功能選擇方向及最佳化的指令序列。
CLI、STI、LGDT、LIDT、控制暫存器存取、連接埠 I/O 與 HLT 等特權操作受到目前模式與特權的限制。組譯器接受某條指令並不授予執行權限。
評論
在 GitHub 上閱讀或評論(發表評論需要登入)