有一個叫做rflags的特殊暫存器。
它的位元就像各種條件狀態的旗標。
以下是其中一些:
| 名稱 | 符號 | 位元 |
|---|---|---|
| 進位 | CF | 0 |
| 零 | ZF | 6 |
| 正負號 | SF | 7 |
| 溢位 | OF | 11 |
完整清單請參閱 Intel 手冊。
rflags裡的旗標並_不會_被直接修改。
而是由許多不同的指令設定。
例如,當結果為零時,許多算術或位元運算都會設定ZF。
最常用來測試條件的指令之一就是cmp。
它接受兩個運算元並更新旗標,但不會修改它的運算元。
cmp指令會從第一個運算元減去第二個運算元,並根據結果設定旗標。
若 A 是第一個運算元、B 是第二個:
| 旗標 | 設定時機 |
|---|---|
| CF | A < B(無號) |
| ZF | A == B |
| SF | A < B(有號,無溢位) |
| OF | 有號減法中發生溢位 |
預設情況下,x86-64 的程式碼會由上而下循序執行。
但有很多情況需要改變這種行為。 例如,根據某個條件執行不同的指令組合。
在更高階的語言中,通常會用if...else條件式這類抽象來完成。
不過,x86-64 裡並沒有這些東西。
取而代之的是,x86-64 提供了一些指令,能實際把執行流程轉移到程式碼的另一個位置。
這就叫做branching。
我們已經看過兩個這樣的指令: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 以無號整數解讀)。
有些後綴是相同條件的別名。
例如,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