Létezik egy speciális regiszter, amelynek a neve rflags.
A bitjei jelzőbitként szolgálnak különféle feltételekhez.
Ezek közül néhányat az alábbi táblázat sorol fel:
| név | szimbólum | bit |
|---|---|---|
| átvitel | CF | 0 |
| nulla | ZF | 6 |
| előjel | SF | 7 |
| túlcsordulás | OF | 11 |
A teljes listáért lásd az Intel kézikönyvét.
A rflags jelzőbitjeit nem lehet közvetlenül módosítani.
Helyette számos különböző utasítás állítja be őket.
Például a ZF-et sok aritmetikai vagy bitenkénti művelet beállítja, ha az eredmény nulla.
A feltételek vizsgálatához használt utasítások közül az egyik leggyakoribb a cmp.
Két operándust vesz át, és frissíti a jelzőbiteket, de magukat az operándusokat nem módosítja.
cmp utasításA cmp utasítás kivonja az első operándusból a másodikat, és az eredménynek megfelelően állítja be a jelzőbiteket.
Ha A az első operándus, B pedig a második:
| jelzőbit | beáll, ha |
|---|---|
| CF | A < B (előjel nélküli) |
| ZF | A == B |
| SF | A < B (előjeles, túlcsordulás nélkül) |
| OF | túlcsordulás előjeles kivonáskor |
Alapértelmezés szerint az x86-64 kódja fentről lefelé, egymás után fut le.
Ám sok esetben módosítani kell ezt a viselkedést. Például hogy egy feltétel hatására más utasításokat hajtsunk végre.
A magasabb szintű nyelvekben erre általában valamilyen absztrakciót használunk, például az if...else elágazást.
Az x86-64-ben viszont ilyenek nincsenek.
Helyette az x86-64 olyan utasításokat kínál, amelyek a végrehajtást a kód egy másik pontjára irányítják.
Ezt nevezzük branching-nek.
Már láttunk két ilyen utasítást: a call-t és a ret-et.
Amikor meghívunk egy függvényt, a végrehajtás a hívótól a hívott függvényhez kerül. Visszatéréskor pedig a végrehajtás visszakerül a hívóhoz.
Ha nincs ret, a végrehajtás átcsúszik az egyik függvényből a másikba.
Ez néha a kódfolyam optimalizálására is használható.
A jmp utasítás feltétel nélkül a program egy másik pontjára irányítja a végrehajtást. Egyetlen operándusa egy címke, amely annak a pontnak a címét tartalmazza, ahol a végrehajtás folytatódik.
Nézzük például a következő függvényt:
fn:
mov rax, 5
jmp end
add rax, 10
end:
ret
Amikor meghívjuk az fn-et, a végrehajtás a mov rax, 5 utasítással kezdődik.
Ekkor a rax értéke 5 lesz.
A következő utasítás a jmp end, amely a végrehajtást az end címkére viszi.
Az end után a következő utasítás a ret, amely visszaadja a végrehajtást a hívó függvénynek.
Figyeld meg, hogy mivel az add rax, 10 a jmp end után és az end előtt található, soha nem fut le.
Amikor az fn visszatér, a rax értéke 5.
A jcc utasításcsalád csak akkor viszi a program végrehajtását egy másik pontra, ha egy adott feltétel teljesül. Ellenkező esetben a végrehajtás egymás után folytatódik.
Minden feltétel a rflags egy vagy több jelzőbitjéhez kapcsolódik.
A jcc egyes változatai azt vizsgálják, hogy egy jelzőbit be van állítva, mások azt, hogy nincs.
A jcc-ben a cc nem szó szerinti értelmű, hanem a vizsgált jelzőbithez tartozó konkrét utótagra utal.
Sok utótag létezik, és közülük több ugyanazt a feltételt vizsgálja, mint egy másik. Némelyik közvetlenül egy jelzőbitre utal, így az utasítás akkor ugrik egy címkére, ha az adott jelzőbit be van állítva:
| utótag | ugrik, ha |
|---|---|
| z | ZF == 1 |
| c | CF == 1 |
| s | SF == 1 |
| o | OF == 1 |
Sok másikat azért választottak, hogy a cmp utasításban betöltött jelentésükre utaljanak.
Például:
| utasítás | utótag | ugrik, ha |
|---|---|---|
| cmp A, B | e | A == B |
| cmp A, B | l | A < B (előjeles) |
| cmp A, B | b | A < B (előjel nélküli) |
| cmp A, B | g | A > B (előjeles) |
| cmp A, B | a | A > B (előjel nélküli) |
Az l, b, g vagy a után írhatsz e-t, hogy a feltétel az egyenlőséget is magában foglalja:
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
Minden utótaghoz léteznek olyan változatok, amelyek az ellenkező esetet vizsgálják.
A szintaxisuk ugyanaz, csak az utótag elé egy n kerül.
Például a jnz akkor ugrik, amikor a ZF nincs beállítva.
Hasonlóképpen a jnae akkor ugrik, amikor A nem >= B (A és B előjel nélküli egészként értelmezve).
Néhány utótag ugyanazt a feltételt jelöli, csak más néven.
Például a jz (utótagja z, a ZF-re utalva) és a je (utótagja e, az egyenlőségre utalva) is akkor ugrik, amikor a ZF be van állítva.
Ez azért van, mert a cmp esetében a ZF akkor áll be, amikor a kivonás eredménye nulla, ami azt jelenti, hogy a két operándus egyenlő.
Más utótagok viszont jelzőbitek kombinációját vizsgálják, ezért nem helyettesíthetők közvetlenül egyetlen jelzőbit utótagjával.
Válaszd inkább azt az utótagot, amely jobban leírja az összehasonlításod jelentését.
A címkék az egész forrásfájlban láthatók, nem csak egy függvényen belül. Ezért nem lehet ugyanazt a címkenevet kétszer használni.
Hogy egy helyi címke viselkedését utánozhassa, a NASM külön jelölést vezetett be azokra a címkékre, amelyek elé pont (.) kerül.
Ez a jelölés olyan címkét hoz létre, amely implicit módon magában foglalja az előző, pont nélküli címke nevét:
section .text
fn1:
...
.example: ; this is fn1.example
...
ret
fn2:
...
.example: ; this is fn2.example
...
ret
A kód bármely pontjáról lehet erre a címkére ugrani a teljes címkenév használatával, például így: jmp fn1.example.
Ha viszont az ugrásnál csak a ponttól kezdődő részt írod le, akkor az a befoglaló függvényen belüli címkére irányul.
Például a .example úgy viselkedik, mintha a függvényen belüli helyi címke lenne:
section .text
fn1:
...
.example:
...
jmp .example ; this jumps to fn1.example
fn2:
...
.example:
...
jmp .example ; this jumps to fn2.example
Vedd figyelembe, hogy egy pont nélküli címke egy függvényen belül a gyakorlatban egy másik függvényt hoz létre:
section .text
fn1:
...
.example: ; this is 'fn1.example'
...
non_dotted:
...
.example: ; this is 'non_dotted.example'
...
ret