Egy egész szám minden bitje alkalmas egy bináris érték tárolására. Mivel sok helyzet hordoz bináris információt, például igaz vagy hamis, bevonás vagy kizárás, be vagy ki, egy N bites egész szám bináris reprezentációja tömör módot kínál N elem bináris állapotának kódolására. Emiatt az assemblyben elengedhetetlen, hogy tudjunk bitekkel és bájtokkal manipulálni. Az x86-64 utasításkészlet a bitmanipulációs utasítások széles választékát kínálja.
Ezek az utasítások egyetlen biten működnek egy operanduson belül.
Mindegyikük két operandust vár; a második adja meg annak a bitnek az indexét, amelyre az első operandusban hatnak. Mindegyikük átmásolja a kiválasztott bitet az átvitel jelzőbitbe (CF).
| Név | Leírás |
|---|---|
bt |
átmásolja a bitet a CF-be, anélkül hogy módosítaná bármelyik operandust |
bts |
átmásolja a bitet a CF-be, és beállítja a céloperandusban |
btr |
átmásolja a bitet a CF-be, és törli a céloperandusban |
btc |
átmásolja a bitet a CF-be, és invertálja (átbillenti) a céloperandusban |
A bitműveletek egy operandus összes bitjén hajtódnak végre.
Mindegyikhez tartozik egy utasítás, amelynek a neve megegyezik az elvégzett bitművelet nevével:
| Név | Leírás |
|---|---|
and |
1, ha mindkét bit 1 |
or |
1, ha a bitek közül legalább az egyik 1 |
xor |
1, ha a bitek eltérnek |
not |
1, ha a bit 0 volt; 0, ha a bit 1 volt |
A legtöbbjük két operandust vár, bitműveletet végez mindkettőn, és az eredményt a céloperandusban tárolja.
Kivétel a not, amely csak egy céloperandust vár.
Amikor az egyest bevonásként, a nullát pedig kizárásként értelmezzük, az egész számot bitmaszknak (vagy egyszerűen maszknak) nevezzük.
A bitmaszk „kiszűri” az elemeket, mert az i-edik biten álló nulla kizárja az i-edik elemet, az egyes pedig bevonja.
A bitmaszkot arra is gyakran használjuk, hogy egy egész szám bizonyos bitjeit bevonjuk, másokat pedig kizárjunk.
Például legyen A olyan egész szám, amelynek bináris reprezentációja:
| index | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|
| bitek | 1 | 0 | 0 | 1 | 0 | 1 | 0 | 1 |
Legyen továbbá M olyan egész szám, amelynek bináris reprezentációja:
| index | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|
| bitek | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 1 |
Mindkettő 8 bites egész szám.
Ebben az esetben azt mondhatjuk, hogy M kijelöli A 0, 2 és 3 bitjét, a többit pedig kizárja.
A korábban tárgyalt bitműveleti utasítások hasznosak, amikor maszkokkal manipulálunk egész számokat. Például:
A azon bitjeit, amelyeket M nem jelöl ki, végezd el a bitenkénti és műveletet: A AND M.A azon bitjeit, amelyeket M kijelöl, végezd el a bitenkénti vagy műveletet: A OR M.A test utasítás bitenkénti és műveletet végez a két operandus között, és az eredménynek megfelelően állítja be a jelzőbiteket.
Ha A az első operandus, B pedig a második:
| jelzőbit | beáll, amikor |
|---|---|
CF |
mindig törlődik |
ZF |
A AND B == 0 |
SF |
az A AND B előjelbitje be van állítva |
OF |
mindig törlődik |
Ez az utasítás két operandust vár, és frissíti a jelzőbiteket, de nem módosítja az operandusait.
Ezek az utasítások a céloperandus bitjeit annyi pozícióval mozgatják el, amennyit a második operandus megad.
A második operandusnak konstans számnak (egy immediate-nek) vagy a cl regiszternek (az rcx legalsó 8 bitjének) kell lennie.
| Név | Leírás |
|---|---|
shl/sal
|
Balra tolja a biteket |
shr/sar
|
Jobbra tolja a biteket |
Fontos, hogy a második operandusban szereplő érték 5 bitre van maszkolva, 64 bites céloperandus esetén pedig 6 bitre.
Az ezen túli biteket a processzor gyakorlatilag figyelmen kívül hagyja.
Ez azt jelenti, hogy a legnagyobb eltolás 31, 64 bites operandus esetén pedig 63.
A shl és a sal pontosan ugyanazt a műveletet végzi, az egyik a másik álneve.
Balra tolásnál azok a bitek, amelyek az eltolás hosszánál közelebb vannak a sorozat végéhez, először a CF-be kerülnek, majd elvesznek.
Ugyanakkor a sorozat elejére az eltolás hosszával megegyező számú, nullára törölt bit kerül.
Mivel egy egész szám minden bitje a 2 egy hatványát képviseli, az n pozícióval balra tolás megszorozza az egész számot 2ⁿ-nel.
Két utasítás tolja jobbra a biteket: a shr és a sar.
Bármelyik utasítás használatakor azok a bitek, amelyek az eltolás hosszánál közelebb vannak a sorozat elejéhez, először a CF-be kerülnek, majd elvesznek.
Ugyanakkor a sorozat végére az eltolás hosszával megegyező számú új bit kerül.
A kettő közötti különbség, hogy a shr 0-t ír a bal oldali végre, a sar pedig 1-et, ha a legmagasabb helyiértékű bit be volt állítva, egyébként 0-t.
Ez azt jelenti, hogy a sar megőrzi az előjelet előjeles egész szám eltolásakor.
Mivel egy egész szám minden bitje a 2 egy hatványát képviseli, az shr utasítással n pozícióval jobbra tolva előjel nélküli osztást végzel 2ⁿ-nel.
Hasonlóképpen a sar utasítással n pozícióval jobbra tolva előjeles osztást végzel 2ⁿ-nel.
Ezek az utasítások a céloperandus bitjeit annyi pozícióval mozgatják el, amennyit a második operandus megad.
A második operandusnak konstans számnak (egy immediate-nek) vagy a cl regiszternek (az rcx legalsó 8 bitjének) kell lennie.
A forgatás és az eltolás között az a különbség, hogy a forgatás nem dob el és nem is ad hozzá biteket. Azok a bitek, amelyeket egy eltolás elveszítene, helyette a túloldali végre kerülnek. Így minden bit megmarad, csak helyet cserélnek.
| Név | Leírás |
|---|---|
rol |
Balra forgatja a biteket |
ror |
Jobbra forgatja a biteket |
Fontos, hogy a második operandusban szereplő érték 5 bitre van maszkolva, 64 bites céloperandus esetén pedig 6 bitre.
Az ezen túli biteket a processzor gyakorlatilag figyelmen kívül hagyja.
Ez azt jelenti, hogy a legnagyobb forgatás 31, 64 bites operandus esetén pedig 63.
Vannak más hasznos bitmanipulációs utasítások is:
| Név | Leírás |
|---|---|
popcnt |
Megszámolja a beállított bitek számát |
bsr |
Megadja a legmagasabb helyiértékű beállított bit indexét. Ha egy bit sincs beállítva, az eredmény nem meghatározott |
bsf |
Megadja a legalacsonyabb helyiértékű beállított bit indexét. Ha egy bit sincs beállítva, az eredmény nem meghatározott |
Ezek az utasítások mind 16 bites, 32 bites vagy 64 bites operandusokkal működnek.
8 bites operandusokkal nem használhatók.
A barátod épp most küldött neked egy üzenetet egy fontos titokkal. Mivel nem akarta, hogy mások könnyen elolvashassák, az üzenetet egy sor bitmanipulációval titkosította. Neked kell megírnod azokat a függvényeket, amelyek segítenek visszafejteni az üzenetet.
Ezek az ebben a fogalomban említett egybites utasítások:
| Név | Leírás |
|---|---|
| bt | átmásolja a bitet a CF-be anélkül, hogy bármelyik operandust módosítaná |
| bts | átmásolja a bitet a CF-be, és beállítja a céloperandusban |
| btr | átmásolja a bitet a CF-be, és törli a céloperandusban |
| btc | átmásolja a bitet a CF-be, és komplementálja (megfordítja) a céloperandusban |
Ezek az ebben a fogalomban említett bitenkénti utasítások:
| Név | Leírás |
|---|---|
| and | 1, ha mindkét bit 1 |
| or | 1, ha a bitek közül legalább az egyik 1 |
| xor | 1, ha a bitek eltérnek |
| not | 1, ha a bit 0 volt; 0, ha a bit 1 volt |
Ezek az ebben a fogalomban említett eltoló utasítások:
| Név | Leírás |
|---|---|
| shl/sal | A biteket balra tolja el |
| shr/sar | A biteket jobbra tolja el |
Ezek az ebben a fogalomban említett forgató utasítások:
| Név | Leírás |
|---|---|
| rol | A biteket balra forgatja |
| ror | A biteket jobbra forgatja |
Ezek az ebben a fogalomban említett egyéb utasítások:
| Név | Leírás |
|---|---|
| popcnt | Megszámolja a beállított bitek számát |
| bsr | A legnagyobb helyiértékű beállított bit indexét adja vissza. Ha egy bit sincs beállítva, az eredmény nem definiált |
| bsf | A legkisebb helyiértékű beállított bit indexét adja vissza. Ha egy bit sincs beállítva, az eredmény nem definiált |
Az üzenet egy 16 bites egész számba van kódolva. Ebből azonban a 8 legmagasabb bit valójában nem része az üzenetnek, hanem egy maszk, amelyet a visszafejtés során kell használni.
Valósítsd meg az extract_higher_bits függvényt, amely egy 16 bites egész számot kap, és visszaadja annak 8 legmagasabb bitjét.
extract_higher_bits(0b1010010011000101)
// => 0b10100100
A maszk kinyerésének képessége önmagában nem elég, az üzenetet is el kell különítened.
Valósítsd meg az extract_lower_bits függvényt, amely egy 16 bites egész számot kap, és visszaadja annak 8 legalacsonyabb bitjét.
extract_lower_bits(0b1010010011000101);
// => 0b11000101
Néhány bit egyszerre van beállítva az üzenetben és a maszkban is. Ez egy nagyon fontos információ, amelyet később felhasználunk.
Valósítsd meg az extract_redundant_bits függvényt, amely egy 16 bites egész számot kap, amely egyszerre kódolja az üzenetet és egy maszkot, és egy olyan 8 bites egész számot ad vissza, amelyben csak a redundáns bitek vannak beállítva.
A visszaadott számban egy bit ott legyen 1-re állítva, ahol az az üzenetben és a maszkban is 1.
Minden más bitet törölni kell.
extract_redundant_bits(0b1010010011000101);
// => 0b10000100
Ezután van néhány bit, amelyet a maszknak megfelelően 1-re kell állítani az üzenetben.
Valósítsd meg a set_message_bits függvényt, amely egy 16 bites egész számot kap, amely egyszerre kódolja az üzenetet és egy maszkot, és visszaadja az üzenet bitjei 1-re állításának eredményét.
Az üzenet egy bitjét ott kell 1-re állítani, ahol a maszkban lévő bit 1.
Minden más bitet változatlanul kell hagyni, hogy beállítva maradjanak, ha már be voltak állítva, és törölve, ha már törölve voltak.
set_message_bits(0b1010010011000101);
// => 0b11100101
Van a rejtvénynek egy darabja, amely nincs benne nyíltan az üzenetben: a 16 bites 0b1011001100111100 szám.
Ez a szám a közös privát kulcsod, amelyet a visszafejtéshez kell használnod.
Ehhez először el kell forgatnod a privát kulcsod bitjeit balra egy bizonyos számú pozícióval. A pozíciók száma megegyezik az üzenetben és a maszkban is beállított redundáns bitek számával.
Valósítsd meg a rotate_private_key függvényt, amely egy 16 bites egész számot kap, amely egyszerre kódolja az üzenetet és egy maszkot, és visszaadja a privát kulcsod elforgatásának eredményét.
Ez az eredmény egy 16 bites egész szám.
rotate_private_key(0b1010010011000101);
// => 0b1100110011110010
A NASM (The Netwide Assembler, a kurzus által használt assembler) támogatja a bináris formátumú konstansokat 0b előtaggal.
Azt is támogatja, hogy egy konstansban alulvonást (_) használj elválasztóként az olvashatóság érdekében:
PRIVATE_KEY equ 0b1011_0011_0011_1100
Ahhoz, hogy a visszafejtésben használható legyen, a privát kulcsodat formázni kell, hogy különválaszd a releváns biteket.
Egy privát kulcs teljes formázásához a következőket kell tenned:
Egy megfordított bit 1, ha 0 volt, és 0, ha 1 volt.
Valósítsd meg a format_private_key függvényt, amely egy 16 bites egész számot kap, amely egyszerre kódolja az üzenetet és egy maszkot, és visszaad egy teljesen formázott 8 bites privát kulcsot.
format_private_key(0b1010010011000101);
// => 0b11000001
Miután megvan az üzenet az összes releváns bittel beállítva, valamint a formázott privát kulcs, ideje összeilleszteni őket, hogy megkapd a végeredményül kapott üzenetet.
A kapott üzenet egy 16 bites egész szám, amelynek:
Valósítsd meg a decrypt_message függvényt, amely egy 16 bites egész számot kap, amely egyszerre kódolja az üzenetet és egy maszkot, és visszaad egy 16 bites egész számot a teljesen visszafejtett üzenettel.
Ennek a függvénynek fel kell használnia a format_private_key segítségével előállított formázott privát kulcsot, valamint az üzenetet, amelynek minden releváns bitjét a set_message_bits állította be.
decrypt_message(0b1010010011000101);
// => 0b1100000111100101
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.