A memóriát egy program számára általában az operációs rendszer (OS) képezi le egy általános elrendezés szerint:
| cím | memóriaterület |
|---|---|
| magas | verem |
| ... | |
| kupac | |
| olvasható-írható szegmens | |
| kód/csak olvasható szegmens | |
| alacsony | fenntartott |
A szegmensekre osztott memória különböző jogosultságú szakaszokra tagolódik.
Az eddig definiált függvényeink mind a .text szakaszban voltak. Ez a szakasz csak olvasható, végrehajtható adatokat tartalmaz. Más szakaszok adatváltozók deklarálására szolgálnak, amelyek lehetnek csak olvashatók vagy olvasható-írhatók, de nem végrehajthatók.
Az inicializált adatokat a .data szakaszban deklaráljuk.
A NASM-ben (The Netwide Assembler, az ezen a kurzuson használt assembler) egy inicializált változónak van neve, egy direktívája, amely az adat méretét jelzi, és egy vesszővel elválasztott értéklistája.
Ezeket szóköz választja el egymástól, a címkét pedig opcionálisan : követheti.
A fő direktívák és a hozzájuk tartozó adatméretek:
| direktíva | méret |
|---|---|
| db | 1 bájt |
| dw | 2 bájt |
| dd | 4 bájt |
| dq | 8 bájt |
Például ez egy space nevű, 1 bájtos változót deklarál 10 értékkel:
section .data
space db 10
A section .data szakaszban deklarált változók módosíthatók, azaz olvashatók és írhatók.
Emellett statikus tárolási időtartammal rendelkeznek, ami azt jelenti, hogy a program teljes futamideje alatt léteznek.
A .rodata szakasz hasonló a section .data szakaszhoz.
Mindkét szakasz inicializált adatokat tartalmaz, amelyeket ugyanúgy deklarálunk, és amelyeknek ugyanaz a tárolási időtartama.
A fő különbség köztük az, hogy a section .rodata szakaszban lévő adatok változhatatlanok, azaz csak olvashatók.
Az equ direktívával definiált konstansok eltérnek a section .rodata szakaszban definiáltaktól.
Az equ direktívával definiált konstans nem foglal helyet a memóriában, és az assembler közvetlenül behelyettesíti az értékével.
Valójában ez csupán az adott érték helyőrzője.
Ezzel szemben a section .rodata szakaszban definiált konstansok ténylegesen a memóriában tárolódnak, és van címük.
A deklarált adatokhoz névnek kell kapcsolódnia. Ezt a nevet címkének nevezzük.
A címke egy szimbólum, amely a memóriában lévő adat konkrét címét kódolja. Az x86-64-ben a címek 64 bites értékek.
A NASM-ben, ha egy adatot közvetlenül a címkéjével próbálunk elérni, nem a lefoglalt memóriát kapjuk meg, hanem annak címét:
section .data
example dq 27 ; this declares a 8-byte variable initialized with 27
section .text
fn:
mov rax, example ; this stores the address of the declared variable in rax, not its contents
...
Ahhoz, hogy egy memóriacím tartalmát elérjük, dereferálni kell azt. Ezt indirekciónak nevezzük.
A NASM-ben erre a [] szolgál:
section .data
example dq -27 ; this declares a 8-byte variable initialized with -27
section .text
fn:
mov rax, [example] ; this dereferences example and access the value stored in memory (-27)
...
Vannak azonban olyan helyzetek, amikor kérdéses lehet a dereferált memória mérete. Ilyen esetekben a méretet megadó előtagot kell használni.
Ezek a legfontosabb előtagok és méreteik egy tipikus x86-64 programban:
| előtag | méret |
|---|---|
| byte | 1 bájt |
| word | 2 bájt |
| dword | 4 bájt |
| qword | 8 bájt |
Ugyanez a betöltés kiírható úgy is, hogy a méretet egyértelműen megadjuk:
mov rax, qword [example] ; same dereference, size stated explicitly
Jó gyakorlat, ha memória dereferálásakor mindig használunk előtagot.
A memóriába írás ugyanígy történik, egy cím dereferálásával:
section .data
example1 db 10 ; example1 is a 1-byte memory location initialized with value 10
example2 dq -456 ; example2 is a 8-byte memory location initialized with value -456
example3 dd 54 ; example3 is a 4-byte memory location initialized with value 54
section .text
fn:
mov byte [example1], 20 ; example1 now has value 20
mov qword [example2], rdx ; example2 now has value equal to the contents in rdx
mov dword [example3], eax ; example3 now has value equal to the contents in eax
Megjegyzendő, hogy a legtöbb utasításban használhatsz memóriaoperandusokat anélkül, hogy előbb betöltenéd a tartalmukat egy regiszterbe. Általában azonban nem lehet egyszerre a forrás- és a céloperandusban használni őket, csak az egyikben:
section .data
example4 dw 4
example5 dq -8
example6 dd 15
section .text
fn:
add word [example4], 5 ; example4 is now a 2-byte memory location with the value 4 + 5 = 9
imul rax, qword [example5] ; rax = rax * (-8)
; this is not possible -> sub dword [example6], dword [example6]
Bár a mov is használható egy változó címének regiszterbe mentésére, erre a konkrét célra külön utasítás létezik: a lea.
Ez az utasítás memóriaformájú operandust használ, de nem olvassa a memóriát. Ehelyett kiértékeli az effektív cím kifejezését, és az eredményt a céloperandusba írja:
lea rax, [example] ; this stores the address of 'example' in rax
Idiomatikusabb a lea utasítást használni memóriacímek kiszámítására és regiszterekbe mentésére.
Amikor memóriahelyeket érünk el, a NASM alapértelmezett viselkedése, hogy abszolút címeket, azaz rögzített memóriacímeket állít elő.
Biztonsági okokból a futtatható fájlokat gyakran PIE (pozíciófüggetlen futtatható fájl) formában készítik el, ahol a memóriaterületek véletlenszerűen kiválasztott helyekre kerülnek.
Egy PIE-ban egy változó végleges címe nem ismert a linkelés idején.
Ezért a kód inkább egy rip nevű speciális regiszter értékéhez viszonyított eltolásként számítja ki a címeket; ez a regiszter a végrehajtandó következő utasításra mutat.
Ezt általában RIP-relatív címzésnek nevezik.
A NASM-ben a rel operátorral kérhetsz RIP-relatív elérést:
mov rax, qword [rel variable]
A relatív címzés egy forrásfájl alapértelmezésévé is tehető a fájl elején szereplő default rel direktívával.
Ezen a kurzuson minden feladatot PIE-ként fordítunk és linkelünk, ezért a relatív címek előállításához a rel-t kell használni.
Bármely szakaszban (például .text, .data, .rodata) definiált címke (függvény és adat) látható ugyanazon forrásfájlon belül.
Ha global-ként vannak deklarálva, más forrásfájlokból is láthatók.
Fordítva, a más forrásfájlokban definiált címkék akkor láthatók az aktuális forrásfájlból, ha extern-ként vannak deklarálva.
Ebben az esetben az assemblyben nincs utalás az adat méretére, azt előre ismerni kell.
default rel
section .data
global number1 ; 'number1' is a variable visible to other source files
number1 db 200
extern number2 ; 'number2' is a variable visible to the current source file, but defined in another
section .text
extern sum ; sum is a function visible to the current source file, but defined in another
fn:
mov dil, byte [number1]
mov sil, byte [number2]
call sum
...
A barátod, José tanár egy helyi iskolában. Eszébe jutott néhány szórakoztató kísérlet, amivel megmutathatja, hogyan lehet a színeket összekeverni, hogy különböző színek szülessenek belőlük.
Ezekhez a kísérletekhez kérte a segítségedet.
Ebben a feladatban egy színt egy 32 bites (4 bájtos) szám képvisel, amely a szín RGB értékét kódolja.
Egy RGB érték 3 csatornából áll, Red, Green és Blue, amelyek mindegyike 8 bitet (1 bájtot) foglal el.
A negyedik bájtot általában az Alpha csatorna számára tartják fenn, ebben a feladatban azonban az értéke üres (0) lesz.
Az egyes színek értékeit egy táblázat tárolja, amely egy másik forrásfájlban van definiálva. Egy színt a táblázatban egy egyedi cím azonosít.
Definiálj egy get_color_value függvényt, amely visszaadja egy szín 32 bites értékét.
A függvény paraméterként átveszi a szín érvényes címét a színtáblázatban.
get_color_value(black)
// => 0
Tipp - 32 bit 4 bájtnak felel meg.
Hogy különböző színeket keverhessen, José először rögzít egy alapszínt, és csak a hozzá kevert másodlagos színt változtatja.
Definiálj egy add_base_color függvényt, amely elmenti egy szín 32 bites értékét a base_color változóba, hogy később felhasználható legyen.
A függvénynek nincs visszatérési értéke, és paraméterként a színnek a színtáblázatban lévő címét veszi át.
A base_color változót neked kell definiálnod, és más forrásfájlokból is elérhetőnek kell lennie.
Egyszerre legfeljebb 1 alapszín lehet. Ha új alapszínt adsz hozzá, a régi elvész.
Alapértelmezés szerint, a program indulásakor a base_color kezdőértéke a white 32 bites értéke, azaz a 0xFFFFFF00 legyen.
Tipp - A NASM elfogadja a hexadecimálisan, a 0x előtaggal megadott számokat, mint például a 0xFFFFFF00.
José sok kombinációt szeretne készíteni az elsődleges színek felhasználásával, ezért külön szeretné őket tárolni a gyors elérés érdekében.
Mivel a színek ábrázolására az RGB-t használja, az elsődleges színek a következők:
RED, 0xFF000000 értékkel.GREEN, 0x00FF0000 értékkel.BLUE, 0x0000FF00 értékkel.Definiálj egy-egy konstansot mindegyik színhez. Ezeknek a konstansoknak más forrásfájlokból is elérhetőnek kell lenniük.
A színeket egy másik forrásfájlban definiált combining_function szerint kell összekeverni.
A függvény paraméterként a base_color és a vele keverendő másodlagos szín 32 bites értékét veszi át.
Visszaadja a kombinált szín 32 bites értékét.
Definiálj egy make_color_combination függvényt, amely összekever két színt, és az eredményt a memóriában tárolja.
A függvénynek nincs visszatérési értéke, és ebben a sorrendben a következő paramétereket veszi át:
Figyeld meg, hogy a combining_function módosíthatja az általad használt regiszterek értékeit.
Mielőtt meghívod a függvényt, minden szükséges változót ments el a memóriába.
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.