x86 彙編/X86 架構
X86 架構有 8 個通用暫存器 (GPR)、6 個段暫存器、1 個標誌暫存器和一個指令指標。64 位 X86 有額外的暫存器。
8 個 GPR 如下[1]
- 累加器暫存器 (AX)。用於算術運算。將常量合併到累加器的操作碼為 1 位元組。
- 基址暫存器 (BX)。用作指向資料的指標(位於段暫存器 DS 中,當處於分段模式時)。
- 計數器暫存器 (CX)。用於移位/旋轉指令和迴圈。
- 堆疊指標暫存器 (SP)。指向堆疊頂部的指標。
- 堆疊基址指標暫存器 (BP)。用於指向堆疊的底部。
- 目標索引暫存器 (DI)。用作指向流操作中目標的指標。
- 源索引暫存器 (SI)。用作指向流操作中源的指標。
- 資料暫存器 (DX)。用於算術運算和 I/O 操作。
它們在這裡列出的順序是有原因的:它與在推入堆疊操作中使用的順序相同,這將在後面討論。
所有暫存器都可以在 16 位和 32 位模式下訪問。在 16 位模式下,暫存器由上面列表中的兩位字母縮寫標識。在 32 位模式下,這個兩位字母縮寫以 'E'(擴充套件)為字首。例如,'EAX' 是作為 32 位值的累加器暫存器。
類似地,在 64 位版本中,'E' 被替換為 'R'(暫存器),因此 'EAX' 的 64 位版本稱為 'RAX'。
還可以以 16 位的大小訪問前四個暫存器 (AX、CX、DX 和 BX) 的兩個 8 位部分。最低有效位元組 (LSB) 或低位部分,透過將 'X' 替換為 'L' 來標識。最高有效位元組 (MSB) 或高位部分,使用 'H' 代替。例如,CL 是計數器暫存器的 LSB,而 CH 是它的 MSB。
總的來說,這給了我們五種方法來訪問累加器、計數器、資料和基址暫存器:64 位、32 位、16 位、8 位 LSB 和 8 位 MSB。另外四個只可以用四種方法訪問:64 位、32 位、16 位和 8 位。下表總結了這一點
| 暫存器 | 累加器 | 基址 | 計數器 | 堆疊指標 | 堆疊基址指標 | 目標 | 源 | 資料 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 64 位 | RAX | RBX | RCX | RSP | RBP | RDI | RSI | RDX | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 32 位 | EAX | EBX | ECX | ESP | EBP | EDI | ESI | EDX | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 16 位 | AX | BX | CX | SP | BP | DI | SI | DX | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 8 位 | AH | AL | BH | BL | CH | CL | SPL | BPL | DIL | SIL | DH | DL | ||||||||||||||||||||||||||||||||||||||||||||||||||||
6 個段暫存器是
- 堆疊段 (SS)。指向堆疊('S' 代表 '堆疊')。
- 程式碼段 (CS)。指向程式碼('C' 代表 '程式碼')。
- 資料段 (DS)。指向資料('D' 代表 '資料')。
- 附加段 (ES)。指向額外資料('E' 代表 '額外';'E' 在 'D' 之後)。
- F 段 (FS)。指向更多額外資料('F' 在 'E' 之後)。
- G 段 (GS)。指向更多更多額外資料('G' 在 'F' 之後)。
大多數現代作業系統(如 FreeBSD、Linux 或 Microsoft Windows)上的大多數應用程式使用一種記憶體模型,該模型將幾乎所有段暫存器都指向同一個位置(並使用分頁來代替),從而有效地停用了它們的使用。通常 FS 或 GS 的使用是對此規則的例外,而是用於指向執行緒特定的資料。
EFLAGS 是一個 32 位暫存器,用作表示布林值的位的集合,以儲存操作的結果和處理器的狀態。
這些位的名稱是
| 31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ID | VIP | VIF | AC | VM | RF |
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| 0 | NT | IOPL | OF | DF | IF | TF | SF | ZF | 0 | AF | 0 | PF | 1 | CF | |
名為 0 和 1 的位是保留位,不應該修改。
| 這些標誌的不同用途是 | |
|---|---|
| 0. | CF : 進位標誌。如果上次算術運算在暫存器大小之外進位(加法)或借位(減法),則設定。然後在操作後緊隨帶有進位的加法或帶有借位的減法進行檢查,以處理超過單個暫存器可以容納的值。 |
| 2. | PF : 奇偶校驗標誌。如果最低有效位元組中設定位的數量是 2 的倍數,則設定。 |
| 4. | AF : 調整標誌。二進位制編碼十進位制 (BCD) 數算術運算的進位。 |
| 6. | ZF : 零標誌。如果操作的結果為零 (0),則設定。 |
| 7. | SF : 符號標誌。如果操作的結果為負,則設定。 |
| 8. | TF : 陷阱標誌。如果設定則一步一步除錯。 |
| 9. | IF : 中斷標誌。如果中斷已啟用,則設定。 |
| 10. | DF : 方向標誌。流方向。如果設定,字串操作將遞減其指標而不是遞增其指標,反向讀取記憶體。 |
| 11. | OF : 溢位標誌。如果帶符號算術運算導致的值太大,以至於暫存器無法容納,則設定。 |
| 12-13. | IOPL : I/O 許可權級別欄位 (2 位)。當前程序的 I/O 許可權級別。 |
| 14. | NT : 巢狀任務標誌。控制中斷的連結。如果當前程序連結到下一個程序,則設定。 |
| 16. | RF : 恢復標誌。對除錯異常的響應。 |
| 17. | VM : 虛擬-8086 模式。如果處於 8086 相容模式,則設定。 |
| 18. | AC : 對齊檢查。如果對記憶體引用的對齊檢查已完成,則設定。 |
| 19. | VIF : 虛擬中斷標誌。IF 的虛擬映象。 |
| 20. | VIP : 虛擬中斷掛起標誌。如果中斷掛起,則設定。 |
| 21. | ID : 標識標誌。支援 CPUID 指令(如果可以設定)。 |
EIP 暫存器包含如果未執行分支則要執行的下一個指令的地址。
EIP 只能透過堆疊在 call 指令後讀取。
X86 架構是小端,這意味著多位元組值以最低有效位元組優先寫入。(這僅指位元組的順序,而不是位的順序。)
因此,X86 上的 32 位值 B3B2B1B016 將在記憶體中表示為
B0
|
B1
|
B2
|
B3
|
例如,32 位雙字 0x1BA583D4(0x 表示十六進位制)將寫入記憶體中為
D4
|
83
|
A5
|
1B
|
這在進行記憶體轉儲時將被視為 0xD4 0x83 0xA5 0x1B。
二進位制補碼是表示負整數的標準方法。符號透過反轉所有位並加一來改變。
| 開始: | 0001
|
| 反轉: | 1110
|
| 加一: | 1111
|
0001 代表十進位制 1
1111 代表十進位制 -1
在 x86 組合語言中,定址模式決定了指令中如何指定記憶體運算元。定址模式允許程式設計師從記憶體中訪問資料或有效地對運算元執行操作。x86 架構支援各種定址模式,每種模式都提供了不同的方式來引用記憶體或暫存器。以下是一些 x86 中常見的定址模式
- 暫存器定址
- (運算元地址 R 在地址欄位中)
mov ax, bx ; moves contents of register bx into ax
- 立即數
- (實際值在欄位中)
mov ax, 1 ; moves value of 1 into register ax
或
mov ax, 010Ch ; moves value of 0x010C into register ax
- 直接記憶體定址
- (運算元地址在地址欄位中)
.data
my_var dw 0abcdh ; my_var = 0xabcd
.code
mov ax, [my_var] ; copy my_var content into ax (ax=0xabcd)
- 直接偏移定址
- (使用算術運算來修改地址)
byte_table db 12, 15, 16, 22 ; table of bytes
mov al, [byte_table + 2]
mov al, byte_table[2] ; same as previous instruction
- 暫存器間接定址
- (欄位指向包含運算元地址的暫存器)
mov ax, [di]
- 用於間接定址的暫存器是 BX、BP、SI、DI
64 位 x86 添加了 8 個通用暫存器,命名為 R8、R9、R10 等等,直到 R15。
- R8–R15 是新的 64 位暫存器。
- R8D–R15D 是每個暫存器的最低 32 位。
- R8W–R15W 是每個暫存器的最低 16 位。
- R8B–R15B 是每個暫存器的最低 8 位。
此外,64 位 x86 包括 SSE2,因此每個 64 位 x86 CPU 至少有 8 個暫存器(命名為 XMM0–XMM7),它們是 128 位寬,但只能透過 SSE 指令 訪問。它們不能用於四精度 (128 位) 浮點運算,但它們可以分別容納 2 個雙精度或 4 個單精度浮點值,用於 SIMD 並行指令。它們也可以作為 128 位整數或更短整數的向量進行操作。如果處理器支援 AVX,如較新的 Intel 和 AMD 桌上型電腦 CPU,則這些暫存器實際上是 256 位暫存器(命名為 YMM0–YMM7)的下半部分,整個暫存器可以透過 AVX 指令訪問,以實現進一步的並行化。
堆疊是一種後進先出 (LIFO) 資料結構;資料被壓入堆疊並以相反的順序彈出。
mov ax, 006Ah
mov bx, F79Ah
mov cx, 1124h
push ax ; push the value in AX onto the top of the stack, which now holds the value 0x006A.
push bx ; do the same thing to the value in BX; the stack now has 0x006A and 0xF79A.
push cx ; now the stack has 0x006A, 0xF79A, and 0x1124.
call do_stuff ; do some stuff. The function is not forced to save the registers it uses, hence us saving them.
pop cx ; pop the element on top of the stack, 0x1124, into CX; the stack now has 0x006A and 0xF79A.
pop bx ; pop the element on top of the stack, 0xF79A, into BX; the stack now has just 0x006A.
pop ax ; pop the element on top of the stack, 0x006A, into AX; the stack is now empty.
堆疊通常用於向函式或過程傳遞引數,以及在使用 call 指令時跟蹤控制流。堆疊的另一個常見用途是臨時儲存暫存器。
真實模式是最初的 Intel 8086 的遺留產物。通常你不需要了解它(除非你正在為基於 DOS 的系統或更可能是編寫由 BIOS 直接呼叫的引導載入程式進行程式設計)。
Intel 8086 使用 20 位地址訪問記憶體。但由於處理器本身是 16 位的,英特爾發明了一種定址方案,它提供了一種將 20 位地址空間對映到 16 位字的方法。如今的 x86 處理器從所謂的真實模式開始,這是一種操作模式,它模擬 8086 的行為,並有一些非常小的差異,以便向後相容。
在真實模式下,段暫存器和偏移暫存器一起使用以產生最終的記憶體地址。段暫存器中的值乘以 16(向左移 4 位),偏移值加到結果中。這提供了一個可用的 1 MB 地址空間。然而,定址方案中的一個怪癖允許在使用 0xFFFF(最高可能的)段地址時訪問超出 1 MB 限制的區域;在 8086 和 8088 上,對該區域的所有訪問都會繞回到記憶體的低端,但在 80286 及更高版本上,如果 A20 地址線啟用,則可以訪問超過 1 MB 標記的 65520 位元組。參見:A20 門事件。
真實模式分段和 保護模式多段記憶體模型 共享的一個好處是,所有地址都必須相對於另一個地址給出(即,段基地址)。程式可以擁有自己的地址空間並完全忽略段暫存器,因此不需要重新定位指標來執行程式。程式可以在同一個段內執行近呼叫和跳轉,並且資料始終相對於段基地址(在真實模式定址方案中,段基地址是從段暫存器中載入的值計算出來的)。
這就是 DOS *.COM 格式的工作原理;檔案的內容被載入到記憶體中並盲目執行。但是,由於真實模式段始終是 64 KB 長的,因此 COM 檔案不能大於此(事實上,它們必須適合 65280 位元組,因為 DOS 使用段的前 256 位元組用於內部資料);多年來,這並不是問題。
如果在現代 32 位作業系統(如 Linux、Windows)中進行程式設計,那麼你基本上是在扁平的 32 位模式下進行程式設計。任何暫存器都可以用於定址,並且通常使用完整的 32 位暫存器而不是 16 位暫存器部分更有效。此外,段暫存器在扁平模式中通常未使用,並且在扁平模式中使用它們不被認為是最佳實踐。
使用 32 位暫存器來定址記憶體,程式可以訪問現代計算機中的(幾乎)所有記憶體。對於早期處理器(只有 16 位暫存器),使用分段記憶體模型。'CS'、'DS' 和 'ES' 暫存器用於指向不同的記憶體塊。對於小型程式(小型模型),CS=DS=ES。對於更大的記憶體模型,這些'段'可以指向不同的位置。
術語“長模式”指的是 64 位模式。