條件

條件式 在 x86-64 Assembly

4 個練習

關於 條件式

RFLAGs

有一個叫做rflags的特殊暫存器。 它的位元就像各種條件狀態的旗標。

以下是其中一些:

名稱 符號 位元
進位 CF 0
零 ZF 6
正負號 SF 7
溢位 OF 11

完整清單請參閱 Intel 手冊。

比較指令

rflags裡的旗標並_不會_被直接修改。 而是由許多不同的指令設定。

例如,當結果為零時,許多算術或位元運算都會設定ZF。

最常用來測試條件的指令之一就是cmp。 它接受兩個運算元並更新旗標,但不會修改它的運算元。

CMP 指令

cmp指令會從第一個運算元減去第二個運算元,並根據結果設定旗標。

若 A 是第一個運算元、B 是第二個:

旗標 設定時機
CF A < B(無號)
ZF A == B
SF A < B(有號,無溢位)
OF 有號減法中發生溢位

分支

預設情況下,x86-64 的程式碼會由上而下循序執行。

但有很多情況需要改變這種行為。 例如,根據某個條件執行不同的指令組合。

在更高階的語言中,通常會用if...else條件式這類抽象來完成。 不過,x86-64 裡並沒有這些東西。

取而代之的是,x86-64 提供了一些指令,能實際把執行流程轉移到程式碼的另一個位置。 這就叫做branching。

Note

我們已經看過兩個這樣的指令:call和ret。

呼叫函式時,執行流程會從呼叫方轉移到被呼叫的函式。 而回傳時,執行流程會再轉移回呼叫方。

如果找不到ret,執行流程就會從一個函式落入下一個函式。 這有時可以用來最佳化程式碼的流向。

無條件跳躍

jmp 指令會無條件將程式的執行流程轉移到程式碼的另一個位置。 它唯一的運算元是一個標籤,該標籤帶有執行將繼續之處的位址。

舉例來說,看看下面這個函式:

fn:
    mov rax, 5
    jmp end

    add rax, 10
end:
    ret

呼叫fn時,執行會從mov rax, 5開始。 此時會把rax裡的值設為 5。

下一個指令是jmp end,會把執行轉移到標籤end。

end之後的下一個指令是ret,會把執行轉移回呼叫方的函式。

注意,由於add rax, 10位於jmp end之後、end之前,所以它永遠不會被執行。 fn回傳時,rax的值是 5。

條件跳躍

jcc 這一系列指令只有在特定條件成立時,才會把程式的執行流程轉移到另一個位置。 否則,執行會循序繼續下去。

每個條件對應到rflags中的一個或多個旗標。 有些jcc變體測試旗標是否被設定,有些則測試旗標是否被清除。

jcc裡的cc並不是字面上的意思,而是指與所測試旗標相關的特定後綴。

後綴有很多種,其中許多測試的條件與另一個後綴相同。 有些後綴直接對應到某個旗標,也就是說,若該特定旗標被設定,指令就會跳到某個標籤:

後綴 跳躍條件
z ZF == 1
c CF == 1
s SF == 1
o OF == 1

還有許多其他後綴的選擇,是為了呼應它們在cmp指令中的意義。 例如:

指令 後綴 跳躍條件
cmp A, B e A == B
cmp A, B l A < B (signed)
cmp A, B b A < B (unsigned)
cmp A, B g A > B (signed)
cmp A, B a A > B (unsigned)

可以在l、b、g或a後面加上e,把相等的情況也納入條件:

cmp rcx, r8
jge two      ; this jumps to 'two' if rcx is greater than, or equal to, r8 in a signed comparison
jbe two      ; this jumps to 'two' if rcx is lesser than, or equal to, r8 in an unsigned comparison

所有後綴都有檢查相反情況的變體。 它們的語法相同,只是在後綴前多了一個n。

例如,jnz會在ZF 沒有被設定時跳躍。 同樣地,jnae會在 A 沒有大於等於 B 時跳躍(A 與 B 以無號整數解讀)。

Note

有些後綴是相同條件的別名。 例如,jz(後綴z,代表ZF)與je(後綴e,代表相等)都會在ZF被設定時跳躍。 這是因為搭配cmp時,減法結果為零就會設定ZF,而這對應到兩個運算元相等。

不過,其他後綴測試的是多個旗標的組合,無法直接用單一旗標後綴取代。

請優先選用最能描述你比較語意的後綴。

區域標籤

標籤在整個原始碼檔案中都看得見,它們並不是函式內的區域標籤。 因此不可能重複使用同一個標籤名稱。

為了模擬區域標籤的行為,NASM 對於前面帶有句點(.)的標籤,有一種特殊的表示法。 這種表示法所定義的標籤,會隱含前一個不含句點標籤的名稱:

section .text
fn1:
    ...
.example: ; this is fn1.example
    ...
    ret

fn2:
    ...
.example: ; this is fn2.example
    ...
    ret

你仍然可以使用完整的標籤名稱,從程式碼中任何地方跳到這個標籤,例如jmp fn1.example。

不過,若跳躍只用標籤從句點開始的部分,就會跳到上層函式_內部_的那個標籤。 例如,.example的行為就像它是該函式的區域標籤:

section .text
fn1:
    ...
.example:
    ...
    jmp .example ; this jumps to fn1.example

fn2:
    ...
.example:
    ...
    jmp .example ; this jumps to fn2.example

注意,函式內不含句點的標籤,實際上等於定義了另一個函式:

section .text
fn1:
    ...
.example: ; this is 'fn1.example'
    ...
non_dotted:
    ...
.example: ; this is 'non_dotted.example'
    ...
    ret
透過 GitHub 編輯 連結會在新視窗或分頁中開啟

學習 條件式