rflags regiszterVan egy speciális regiszter, amelynek a neve rflags.
A bitjei különféle feltételek jelzőbitjeiként viselkednek.
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 rflags jelzőbitjeit nem lehet közvetlenül módosítani.
Ehelyett sok különböző utasítás állítja be őket.
Például a ZF-t sok aritmetikai vagy bitenkénti művelet beállítja, amikor az eredmény nulla.
Az egyik leggyakoribb, feltételek vizsgálatára használt utasítás a cmp.
Két operandust vesz, és frissíti a jelzőbiteket, de nem módosítja az operandusait.
cmp utasításA cmp utasítás az első operandusból vonja ki a másodikat, és az eredmény alapján állítja be a jelzőbiteket.
Ha A az első operandus, 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ásnál |
Alapértelmezés szerint az x86-64 kódja felülről lefelé, sorban hajtódik végre.
De sok helyzetben szükség van ennek a viselkedésnek a megváltoztatására. Például hogy egy feltétel hatására más utasításokat hajtsunk végre.
A magasabb szintű nyelvekben ezt általában olyan absztrakciókkal oldják meg, mint az if...else elágazás.
Az x86-64-ben azonban ilyenek nincsenek.
Helyette az x86-64 olyan utasításokat kínál, amelyek lényegében a kód egy másik pontjára adják át a végrehajtást.
Ezt hívják branching-nek.
Két ilyen utasítást már láttunk: a call-t és a ret-et.
Amikor egy függvényt meghívnak, a végrehajtás a hívótól a meghí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 következőbe.
Ezt néha a kódfolyam optimalizálására lehet használni.
A jmp utasítás feltétel nélkül a kód egy másik pontjára irányítja a program végrehajtását. Egyetlen operandusa egy címke, amely annak a pontnak a címét tartalmazza, ahol a végrehajtás folytatódik.
Vegyük például a következő függvényt:
fn:
mov rax, 5
jmp end
add rax, 10
end:
ret
Amikor meghívják az fn-t, a végrehajtás a mov rax, 5-nél kezdődik.
Ez abban a pontban 5-re állítja a rax értékét.
A következő utasítás a jmp end, amely átadja a végrehajtást az end címkének.
Az end után a következő utasítás a ret, amely visszaadja a végrehajtást a hívó függvénynek.
Vegyük észre, hogy mivel az add rax, 10 a jmp end után és az end előtt található, soha nem hajtódik végre.
Az rax értéke 5 lesz, amikor az fn visszatér.
A jcc utasításcsalád csak akkor adja át a program végrehajtását egy másik pontnak, ha egy adott feltétel teljesül. Ellenkező esetben a végrehajtás sorban folytatódik.
Minden feltétel a rflags egy vagy több jelzőbitjének felel meg.
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, hanem a vizsgált jelzőbithoz tartozó konkrét utótagra utal.
Sok utótag létezik, és közülük sok ugyanazt a feltételt vizsgálja, mint egy másik. Némelyik közvetlenül egy jelzőbitre utal, így az utasítás egy címkére ugrik, 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 e-t az l, b, g vagy a után lehet tenni, hogy a feltétel az egyenlőséget is tartalmazza:
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ő viselkedést vizsgálják.
A szintaxisuk ugyanaz, csak az utótag előtt egy n áll.
Például a jnz akkor ugrik, ha a ZF nincs beállítva.
Hasonlóképpen a jnae akkor ugrik, ha A nem nagyobb vagy egyenlő, mint B (A és B előjel nélküli egészként értelmezve).
Néhány utótag ugyanazoknak a feltételeknek a másik neve.
Például a jz (a z utótag, a ZF-re) és a je (az e utótag, az egyenlőségre) is akkor ugrik, amikor a ZF be van állítva.
Ez azért van, mert a cmp-nél a ZF akkor áll be, amikor a kivonás eredménye nulla, ami azt jelenti, hogy a két operandus egyenlő.
Más utótagok azonban jelzőbitek kombinációját vizsgálják, és nem helyettesíthetők közvetlenül egyetlen jelzőbites utótaggal.
Azt az utótagot válaszd, amelyik jobban leírja az összehasonlításod szemantikáját.
A címkék az egész forrásfájlban láthatók, nem egy függvényre korlátozódnak. Ezért egy címkenevet nem lehet újra felhasználni.
Ahhoz, hogy utánozza egy helyi címke viselkedését, a NASM-ben külön jelölés létezik az olyan címkékre, amelyek neve előtt pont (.) áll.
Ez a jelölés olyan címkét definiál, 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 jmp fn1.example.
Egy olyan ugrás azonban, amely a címkének a ponttól kezdődő részét használja, a felső függvényen belüli címkére történik.
Például a .example úgy viselkedik, mintha a függvényen belül lenne helyi:
section .text
fn1:
...
.example:
...
jmp .example ; this jumps to fn1.example
fn2:
...
.example:
...
jmp .example ; this jumps to fn2.example
Ebben a feladatban a Blackjack néhány szabályát fogod megvalósítani, például azt, hogyan zajlik a játék, és hogyan számolják a pontokat.
Ebben a feladatban a lapokat számok képviselik; a számkártyákat a számértékük jelöli, a bubi, a dáma, a király és az ász pedig rendre 11, 12, 13 és 14. (A jokereket nem használjuk a játékban.)
Hogy könnyebb legyen ezzel a reprezentációval dolgozni, néhány konstans a fájl elején van definiálva: a C2-től a C10-ig a számkártyáké, a CJ a bubié, a CQ a dámáé, a CK a királyé, a CA pedig az ászé.
Szabványos, francia színekből álló 52 lapos paklit feltételezünk, a legtöbb változatban azonban több paklit kevernek össze a játékhoz.
Ezek a fogalomban említett utasítások:
| Utasítás | Leírás |
|---|---|
| cmp a, b | a jelzőket a - b alapján állítja be |
| jmp a | a kód itt leáll, és az a címkén folytatódik |
| jcc a | a kód az a címkén folytatódik, ha a cc feltétel teljesül |
Ezeket a feltételeket ellenőrzi a jcc egy cmp a, b után:
| Utasítás | Mikor ugrik |
|---|---|
| je | a == b |
| jl | a < b (előjeles) |
| jg | a > b (előjeles) |
| jb | a < b (előjel nélküli) |
| ja | a > b (előjel nélküli) |
| jle | a <= b (előjeles) |
| jge | a >= b (előjel nélküli) |
| jbe | a <= b (előjel nélküli) |
| jae | a >= b (előjel nélküli) |
| jne | a != b |
| jnl | !(a < b) (előjeles) |
| jng | !(a > b) (előjeles) |
| jnb | !(a < b) (előjel nélküli) |
| jna | !(a > b) (előjel nélküli) |
| jnle | !(a <= b) (előjeles) |
| jnge | !(a >= b) (előjeles) |
| jnbe | !(a <= b) (előjel nélküli) |
| jnae | !(a >= b) (előjel nélküli) |
A Blackjackben a CA értéke 1 vagy 11, a kéztől függően (erről később még lesz szó).
A figurás lapok (CJ, CQ és CK) 10 pontot érnek, minden más lap a számértékét éri.
Definiáld a value_of_card függvényt a card paraméterrel, amely egy lapot képviselő szám.
A függvénynek a kapott lap számértékét kell visszaadnia.
Mivel a CA több értéket is felvehet (1 vagy 11), egyelőre rögzítsük a CA értékét 1-ben.
Később majd írsz egy függvényt, amely egy meglévő kéz alapján meghatározza a CA értékét.
value_of_card(13)
// => 10
value_of_card(4)
// => 4
value_of_card(14)
// => 1
Definiáld a higher_card függvényt a card_one és card_two paraméterekkel, amelyek két lapot képviselő számok.
A pontozás szempontjából a CJ, a CQ és a CK értéke egyaránt 10.
A függvénynek azt kell visszaadnia, hogy melyik lapnak nagyobb a pontozási értéke.
Ha a két lap értéke egyenlő, mindkettőt add vissza.
A CA több értéket is felvehet, ezért ebben a részfeladatban 1-ben rögzítjük az értékét.
higher_card(13, 11)
// => {13, 11}
higher_card(4, 6)
// => 6
>>> higher_card(13, 14)
// => 13
Ahhoz, hogy egy függvényből két egész számot adj vissza, a rax és az rdx regisztert is használnod kell:
returning_two_values:
mov rax, rdi
mov rdx, rsi
ret
Ha csak egy lapot adsz vissza, a rdx-et 0-ra kell állítani.
Ahogy korábban említettük, a CA vagy 1, vagy 11 pontot ér, a kéztől függően.
A Blackjack szabályai szerint a CA-k értékét úgy kell megválasztani, hogy maximalizálja a kéz pontszámát, de ne lépje túl a 21-et (azaz ne „égjen be").
Definiáld a value_of_ace függvényt a card_one és card_two paraméterekkel; ez a két szám a kézben már meglévő két lapot képviseli, mielőtt megkapod az utolsó CA-t.
A függvényednek azt kell visszaadnia, hogy melyik értéket, 1-et vagy 11-et kapja majd a következő CA.
Ne feledd: az új kéz értékének (a CA-val együtt) a lehető legnagyobbnak kell lennie, anélkül hogy túllépné a 21-et.
Tipp: ha már van a kezünkben CA, akkor a következő CA értéke 1 lesz.
value_of_ace(6, 13)
// => 1
value_of_ace(7, 3)
// => 11
Ha a játékos első két lapként egy CA-t és egy tízes lapot (C10, CJ, CQ vagy CK) kap, akkor 21 pontja lesz.
Ezt a kezet nevezik blackjacknek.
Definiáld az is_blackjack függvényt a card_one és card_two paraméterekkel, amelyek két lapot képviselő számok.
A függvénynek 1-et kell visszaadnia, ha a kétlapos kéz blackjack, egyébként 0-t.
Hogy könnyebb legyen az értékekkel dolgozni, a fájl elején definiálva vannak a TRUE és FALSE konstansok, amelyek rendre 1-et és 0-t jelentenek.
Megjegyzés: A pontszám kiszámítását többféleképpen is meg lehet oldani.
De ha lehet, szeretnénk, ha azt ellenőriznéd, hogy van-e a kézben egy CA és egy tízes lap, ahelyett hogy összeadnád a lapok értékét.
is_blackjack(14, 13)
// => 1
is_blackjack(10, 9)
// => 0
Ha a játékos első két lapja azonos értékű (például két C6, vagy egy CQ és egy CK), a játékos dönthet úgy, hogy két külön kézként kezeli őket.
Ezt nevezik „párok szétválasztásának".
Definiáld a can_split_pairs függvényt a card_one és card_two paraméterekkel, amelyek két lapot képviselő számok.
A függvénynek 1-et kell visszaadnia, ha a kétlapos kéz két kézre választható szét, egyébként 0-t.
Hogy könnyebb legyen az értékekkel dolgozni, a fájl elején definiálva vannak a TRUE és FALSE konstansok, amelyek rendre 1-et és 0-t jelentenek.
can_split_pair(12, 13)
// => 1
can_split_pair(10, 14)
// => 0
Ha az eredeti két lap összesen 9, 10 vagy 11 pontot ér, a játékos az eredeti tétével megegyező további tétet tehet. Ezt nevezik „duplázásnak".
Definiáld a can_double_down függvényt a card_one és card_two paraméterekkel, amelyek két lapot képviselő számok.
A függvénynek 1-et kell visszaadnia, ha a kétlapos kéz lehetővé teszi a játékos számára a „duplázást", egyébként 0-t.
Hogy könnyebb legyen az értékekkel dolgozni, a fájl elején definiálva vannak a TRUE és FALSE konstansok, amelyek rendre 1-et és 0-t jelentenek.
can_double_down(14, 9)
// => 1
can_double_down(10, 2)
// => 0
Iratkozz fel az Exercism-re, hogy megtanuld és elsajátítsd a(z) x86-64 Assembly nyelvet 22 fogalom130 feladat segítségével, valódi emberi mentorálással, mindez ingyen.