El

Elágazások ebben a kurzusban: x86-64 Assembly

4 feladat

A(z) Elágazások fogalomról

RFLAGs

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.

Összehasonlító utasítások

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.

A cmp utasítás

A 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

Elágazás

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.

Note

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ó.

Feltétel nélküli ugrás

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.

Feltételes ugrás

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).

Note

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.

Helyi címkék

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
Szerkesztés GitHubon A hivatkozás új ablakban vagy lapon nyílik meg

Tanuld meg a(z) Elágazások fogalmat