x86 彙編/控制流
幾乎所有程式語言都有改變語句執行順序的能力,彙編也不例外。指令指標 (EIP) 暫存器包含要執行的下一條指令的地址。為了改變控制流,程式設計師必須能夠修改 EIP 的值。這就是控制流函式發揮作用的地方。
mov eip, label ; wrong
jmp label ; right
| test 引用,累加器 | GAS 語法 |
| test 累加器,引用 | Intel 語法 |
對 累加器 和 引用 進行按位邏輯 and 操作,我們將結果稱為 commonBits,並根據 commonBits 設定 ZF(零)、SF(符號)和 PF(奇偶校驗)標誌。然後丟棄 CommonBits。
運算元
引用
- 暫存器
- 立即數
累加器
- 暫存器(如果
引用是立即數,則可以對AL/AX/EAX進行特殊編碼) - 記憶體
修改後的標誌
SF≔ MostSignificantBit(commonBits)ZF≔ (commonBits= 0),因此設定ZF意味著累加器和引用沒有共同的設定位PF≔ BitWiseXorNor(commonBits[Max-1:0]),因此當且僅當commonBits[Max-1:0] 具有偶數個 1 位時,PF設定為 1CF≔ 0OF≔ 0AF未定義
應用
- 將同一個暫存器傳遞兩次:
test rax, raxSF成為rax的符號,這是一個簡單的非負性測試- 如果
rax為零,則設定ZF - 如果
rax具有偶數個設定位,則設定PF
| cmp 被減數,減數 | GAS 語法 |
| cmp 減數,被減數 | Intel 語法 |
在 被減數 和 減數 之間執行比較操作。比較操作透過從 被減數 中減去 減數(稱為 差)來執行。然後丟棄 差。如果 減數 是一個立即數,它將被符號擴充套件到 被減數 的長度。EFLAGS 暫存器以與 sub 指令相同的方式設定。
請注意,GAS/AT&T 語法可能相當令人困惑,例如 cmp $0, %rax 後跟 jl branch 將分支到 %rax < 0(而不是像運算元順序可能預期的那樣相反)。
運算元
被減數
AL/AX/EAX(僅當減數是立即數時)- 暫存器
- 記憶體
減數
- 暫存器
- 立即數
- 記憶體
修改後的標誌
SF≔ MostSignificantBit(difference),因此未設定SF意味著difference為非負(被減數≥減數[注意:帶符號比較])ZF≔ (difference= 0)PF≔ BitWiseXorNor(difference[Max-1:0])CF、OF和AF
跳轉指令允許程式設計師(間接)設定 EIP 暫存器的值。作為引數傳遞的位置通常是一個標籤。跳轉後執行的第一條指令是緊隨標籤之後的指令。除了 jmp 之外,所有跳轉指令都是條件跳轉,這意味著只有在條件為真時才會改變程式流。這些指令通常在比較指令(見上文)之後使用,但由於許多其他指令也設定標誌,因此此順序不是必需的。
有關標誌及其含義的更多資訊,請參見章節“X86 架構”,§ “EFLAGS 暫存器”。
jmp loc
用指定的地址載入 EIP(即,執行的下一條指令是 jmp 指定的指令)。
je loc
ZF = 1
如果先前 cmp 指令的運算元相等,則用指定的地址載入 EIP。je 與 jz 相同。例如
mov ecx, $5
mov edx, $5
cmp ecx, edx
je equal
; if it did not jump to the label equal,
; then this means ecx and edx are not equal.
equal:
; if it jumped here, then this means ecx and edx are equal
jne loc
ZF = 0
如果先前 cmp 指令的運算元不相等,則用指定的地址載入 EIP。jne 與 jnz 相同
jg loc
SF = OF 且 ZF = 0
如果先前 cmp 指令的 被減數 大於第二個(執行帶符號比較),則用指定的地址載入 EIP。
jge loc
SF = OF 或 ZF = 1
如果先前 cmp 指令的 被減數 大於或等於 減數(執行帶符號比較),則用指定的地址載入 EIP。
ja loc
CF = 0 且 ZF = 0
如果先前 cmp 指令的 被減數 大於 減數,則用指定的地址載入 EIP。ja 與 jg 相同,只是它執行無符號比較。
這意味著,以下程式碼段始終跳轉(除非 rbx 也為 -1),因為負一在二進位制補碼中表示為所有位都設定。
mov rax, -1 // rax := -1
cmp rax, rbx
ja loc
將所有位都設定為 1(不將任何位視為符號)的值為 2ⁿ-1(其中 n 是暫存器的長度)。這是暫存器可以儲存的最大無符號值。
jae loc
CF = 0 或 ZF = 1
如果之前 `cmp` 指令的 `被減數` 大於或等於 `減數`,則將 `EIP` 載入到指定地址。`jae` 與 `jge` 相同,區別在於它執行無符號比較。
jl loc
`jl` 所需的條件是 `SF` ≠ `OF`。如果滿足該條件,則將 `EIP` 載入到指定地址。因此,`SF` 或 `OF` 可以設定,但不能同時設定以滿足該條件。如果以 `sub`(基本上等同於 `cmp`)指令為例,我們有
- `被減數` - `減數`
關於 `sub` 和 `cmp`,有幾種情況滿足該條件
- `被減數` < `減數` 且操作沒有溢位
- `被減數` > `減數` 且操作溢位
在第一種情況下,`SF` 將被設定,但 `OF` 不會被設定;在第二種情況下,`OF` 將被設定,但 `SF` 不會被設定,因為溢位會將最高有效位重置為零,從而阻止 `SF` 被設定。`SF` ≠ `OF` 條件避免了以下情況
- `被減數` > `減數` 且操作沒有溢位
- `被減數` < `減數` 且操作溢位
- `被減數` = `減數`
在第一種情況下,`SF` 和 `OF` 都不設定;在第二種情況下,`OF` 將被設定,`SF` 也將被設定,因為溢位會將最高有效位重置為 1;在最後一種情況下,`SF` 和 `OF` 都不設定。
jle loc
`SF` ≠ `OF` 或 `ZF = 1`。
如果之前 `cmp` 指令的 `被減數` 小於或等於 `減數`,則將 `EIP` 載入到指定地址。有關條件的更詳細說明,請參閱`jl` 部分。
jb loc
CF = 1
如果之前 CMP 指令的第一個運算元小於第二個運算元,則將 EIP 載入到指定地址。`jb` 與 `jl` 相同,區別在於它執行無符號比較。
mov rax, 0 ; rax ≔ 0
cmp rax, rbx ; rax ≟ rbx
jb loc ; always jumps, unless rbx is also 0
jbe loc
CF = 1 或 ZF = 1
如果之前 `cmp` 指令的 `被減數` 小於或等於 `減數`,則將 `EIP` 載入到指定地址。`jbe` 與 `jle` 相同,區別在於它執行無符號比較。
jz loc
ZF = 1
如果之前算術表示式的零位被設定,則將 `EIP` 載入到指定地址。`jz` 與 `je` 相同。
jnz loc
ZF = 0
如果之前算術表示式的零位未設定,則將 `EIP` 載入到指定地址。`jnz` 與 `jne` 相同。
js loc
SF = 1
如果之前算術表示式的符號位被設定,則將 `EIP` 載入到指定地址。
jns loc
SF = 0
如果之前算術表示式的符號位未設定,則將 `EIP` 載入到指定地址。
jc loc
CF = 1
如果之前算術表示式的進位標誌被設定,則將 `EIP` 載入到指定地址。
jnc loc
CF = 0
如果之前算術表示式的進位標誌未設定,則將 `EIP` 載入到指定地址。
jo loc
OF = 1
如果之前算術表示式的溢位標誌被設定,則將 `EIP` 載入到指定地址。
jno loc
OF = 0
如果之前算術表示式的溢位標誌未設定,則將 `EIP` 載入到指定地址。
jcxz loc
CX = 0
jecxz loc
ECX = 0
jrcxz loc
RCX = 0
如果計數器暫存器為零,則將 `EIP` 載入到指定地址。
- 該指令的存在使得計數器暫存器特別適合儲存(高階語言)指標:在大多數程式語言中,“空指標”(無效指標)由數字值
0實現。通常,您不想取消引用這樣的空指標,因為結果將是錯誤的,甚至會導致 GPF。透過使用jecx跳轉到一些處理此錯誤的程式碼,您可以在嘗試取消引用指標值之前避免陷入這種狀況。您不需要額外的test ecx, ecx。 - 如果您使用
loop指令實現迴圈,但請求的迭代次數可能為零,您可能希望在迴圈主體之前插入jecx。否則,loop將遞減零,從而最終執行 232 次迭代。
call proc
將 `call` 呼叫後面的指令地址(即通常是原始碼中的下一行)壓入堆疊頂部,然後跳轉到指定位置。這主要用於子例程。
ret [val]
將堆疊上的下一個值彈出到 `EIP` 中,然後從堆疊中彈出指定數量的位元組。如果未提供 `val`,則該指令在返回後不會從堆疊中彈出任何值。
loop arg
loop 指令遞減 ECX 並跳轉到 arg 指定的地址,除非遞減 ECX 使其值變為零。例如
mov ecx, 5 ; ecx ≔ 5
head:
; the code here would be executed 5 times
loop head
loop 不設定任何標誌。
loopcc arg
這些迴圈指令遞減 ECX 並跳轉到 arg 指定的地址,如果它們的條件滿足(即,特定的標誌被設定),除非遞減 ECX 使其值變為零。
loope如果相等則迴圈loopne如果不相等則迴圈loopnz如果不為零則迴圈loopz如果為零則迴圈
這樣,只有測試非零 ECX 可以與測試 ZF 相結合。其他標誌不能被測試,比如沒有 loopnc “當 ECX ≠ 0 且 CF 未設定時迴圈”。
enter arg
enter 在堆疊上分配指定的空間建立堆疊幀。
leave
leave 銷燬當前堆疊幀,並恢復之前的幀。使用英特爾語法,這等效於
mov esp, ebp ; esp ≔ ebp
pop ebp
這將使 EBP 和 ESP 設定為函式序言開始之前各自的值,從而逆轉序言期間對堆疊進行的任何修改。
hlt
停止處理器。執行將在處理下一個硬體中斷後恢復,除非 IF 被清除。
nop
無操作。此指令不執行任何操作,但在處理器中浪費(一個)指令週期。
此指令通常表示為 xchg 操作,運算元為 EAX 和 EAX(一個沒有副作用的操作),因為沒有指定的操作碼用於不執行任何操作。這只是一個順便提及,這樣你不會與反彙編程式碼混淆。
lock
在下一條指令上斷言 #LOCK 字首。
wait
等待 FPU 完成其最後一個計算。