跳轉到內容

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 設定為 1
  • CF ≔ 0
  • OF ≔ 0
  • AF 未定義

應用

  • 將同一個暫存器傳遞兩次:test rax, rax
    • SF 成為 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])
  • CFOFAF

跳轉指令

[編輯 | 編輯原始碼]

跳轉指令允許程式設計師(間接)設定 EIP 暫存器的值。作為引數傳遞的位置通常是一個標籤。跳轉後執行的第一條指令是緊隨標籤之後的指令。除了 jmp 之外,所有跳轉指令都是條件跳轉,這意味著只有在條件為真時才會改變程式流。這些指令通常在比較指令(見上文)之後使用,但由於許多其他指令也設定標誌,因此此順序不是必需的。

有關標誌及其含義的更多資訊,請參見章節“X86 架構”,§ “EFLAGS 暫存器”

無條件跳轉

[編輯 | 編輯原始碼]

jmp loc

用指定的地址載入 EIP(即,執行的下一條指令是 jmp 指定的指令)。

如果相等則跳轉

[編輯 | 編輯原始碼]

je loc

ZF = 1

如果先前 cmp 指令的運算元相等,則用指定的地址載入 EIPjejz 相同。例如

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 指令的運算元不相等,則用指定的地址載入 EIPjnejnz 相同

如果大於則跳轉

[編輯 | 編輯原始碼]

jg loc

SF = OF 且 ZF = 0

如果先前 cmp 指令的 被減數 大於第二個(執行帶符號比較),則用指定的地址載入 EIP

如果大於或等於則跳轉

[編輯 | 編輯原始碼]

jge loc

SF = OF 或 ZF = 1

如果先前 cmp 指令的 被減數 大於或等於 減數(執行帶符號比較),則用指定的地址載入 EIP

如果高於(無符號比較)則跳轉

[編輯 | 編輯原始碼]

ja loc

CF = 0 且 ZF = 0

如果先前 cmp 指令的 被減數 大於 減數,則用指定的地址載入 EIPjajg 相同,只是它執行無符號比較。

這意味著,以下程式碼段始終跳轉(除非 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`,有幾種情況滿足該條件

  1. `被減數` < `減數` 且操作沒有溢位
  2. `被減數` > `減數` 且操作溢位


在第一種情況下,`SF` 將被設定,但 `OF` 不會被設定;在第二種情況下,`OF` 將被設定,但 `SF` 不會被設定,因為溢位會將最高有效位重置為零,從而阻止 `SF` 被設定。`SF` ≠ `OF` 條件避免了以下情況

  1. `被減數` > `減數` 且操作沒有溢位
  2. `被減數` < `減數` 且操作溢位
  3. `被減數` = `減數`

在第一種情況下,`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

這將使 EBPESP 設定為函式序言開始之前各自的值,從而逆轉序言期間對堆疊進行的任何修改。

其他控制指令

[編輯 | 編輯原始碼]

hlt

停止處理器。執行將在處理下一個硬體中斷後恢復,除非 IF 被清除。

nop

無操作。此指令不執行任何操作,但在處理器中浪費(一個)指令週期。

此指令通常表示xchg 操作,運算元為 EAXEAX(一個沒有副作用的操作),因為沒有指定的操作碼用於不執行任何操作。這只是一個順便提及,這樣你不會與反彙編程式碼混淆。

lock

在下一條指令上斷言 #LOCK 字首。

wait

等待 FPU 完成其最後一個計算。

華夏公益教科書