A kézzel írt assembly tele van ismétlődéssel. Ugyanaz a rövid utasítássor jelenik meg újra meg újra, alig némi eltéréssel, és kevés absztrakció akad, amellyel csökkenteni lehetne a sablonkódot. Ráadásul az assemblyben való programozáshoz olyan értékeket kell kezelni, amelyeknek nincs nyilvánvaló szemantikájuk. Nincsenek nevesített paraméterek, és a háttérben működő gépezet részleteit nagyrészt a programozónak kell kézzel elintéznie.
A NASM, a kurzus által használt assembler, néhány olyan eszközt kínál, amely mindkét problémában segíthet a programozónak. Ezek az eszközök assembly-időben futnak, mielőtt bármely utasítás eljutna a processzorhoz. Segítenek az assemblert abban, hogyan írja át a forrásszöveget, mielőtt az egyáltalán gépi kódra fordulna.
Ezek közül a legfontosabbak a makrók, amelyekre úgy gondolhatunk, mint egy receptre az assembler számára. A használat helyén fejtődnek ki, mintha a kódot oda kézzel írták volna.
A makrók számos literál tokent vehetnek át paraméterként, így különböző helyzetekben újra felhasználhatók.
Ugyanakkor nem függvények: nincs call, nincs ret, és nincs vezérlésátadás a kód egy másik pontjára.
A legegyszerűbb definíció nevet ad egy értéknek vagy egy rövid kifejezésnek a %define segítségével:
%define WIDTH 8
%define BUFFER_SIZE (WIDTH * 16)
A WIDTH minden későbbi előfordulását 8-ra cseréli az assembly előtt.
A csere szöveges, ezért a név mindenhol helyettesítődik, ahol megjelenik, akár egy operandusban, akár egy másik definícióban.
Számszerű konstansokhoz inkább az equ-t részesítsd előnyben a %define helyett.
A %define-nal deklarált egysoros makró csak használatkor fejlődik ki, és később újraértékelve vagy törölve is lehet.
Az equ konstans ezzel szemben már a definíció helyén kiértékelődik, és soha nem módosul.
A %define nevesített paramétereket is átvehet, és úgy viselkedik, mint egy kis inline függvény:
%define double(x) ((x) + (x))
mov eax, double(WIDTH) ; assembled as mov eax, ((8) + (8))
Tegyél zárójelbe mind a teljes törzset, mind az egyes paramétereket.
A %define square(x) x*x esetén a square(1+1) hívás 1+1*1+1-re fejlődik ki, ami 3.
%define square(x) ((x)*(x)) formában ugyanez a hívás ((1+1)*(1+1))-re fejlődik ki, ami 4.
A %define-nal létrehozott nevek kis- és nagybetűre érzékenyek.
A %idefine változat kis- és nagybetűtől függetlenné teszi a nevet, ha éppen erre van szükség.
Az egysoros makrók arra jók, hogy értelmet adjanak a kód különböző részeinek. Óvatosan használva javítja az olvashatóságot és csökkenti az elgépelések esélyét. Például egy regiszter, amelynek egy függvényen belül kijelölt szerepe van, definiálható a szerepe szerint:
%define COUNTER rcx
...
.loop:
...
dec COUNTER
jnz .loop
...
A kód egy teljes sorozatát, akármennyi sorral, a %macro rögzíti, és a %endmacro zárja le.
A név utáni szám jelzi, hány argumentumot vesz át a makró, ami nulla is lehet.
Az átadott argumentumokat vessző (,) választja el:
%macro example_macro 0
...
%endmacro
A többsoros makrók argumentumai nem nevesítettek.
%1, %2 és így tovább néven hivatkozunk rájuk, az átadás sorrendjében:
; Multiplies two values, returning the result in the first argument
%macro mult_two 2 ; %1 = accumulator, %2 = value
imul %1, %2
%endmacro
mult_two eax, ecx ; multiplies eax and ecx and returns the result into eax
mult_two r8, r9 ; multiplies r8 and r9 and returns the result into r8
Vedd figyelembe, hogy az argumentumoknak érvényes szintaxist kell alkotniuk a makró törzsében végzett műveletekhez.
Itt a mult_two eax, ecx és a mult_two r8, r9 is lefordul, de a mult_two eax, cl nem, mivel az imul eax, cl nincs definiálva.
Egy olyan makró, amely közönséges címkét definiál, abban a pillanatban elromlik, amint kétszer használják, mert ilyenkor a címke is kétszer lenne definiálva.
Ahhoz, hogy címkéket használj egy olyan makróban, amelyet többször is meghívhatnak, a címke elé %%-t kell írni.
Ez arra utasítja az assemblert, hogy minden kifejtéskor más, egyedi nevet adjon a címkének:
; clobbers rcx
%macro clear_n 2 ; %1 = base address, %2 = count of dwords to clear
xor ecx, ecx
%%loop:
mov dword [%1 + rcx*4], 0
inc ecx
cmp ecx, %2 ; %2 is compared with ecx: it is an immediate or a 32-bit operand
jb %%loop
%endmacro
clear_n rdi, 4 ; gets its own %%loop
clear_n rsi, 8 ; a different %%loop, no clash
Lehetőség van egy egysoros kódrészlet ismétlésére a times segítségével.
Ez különösen hasznos, amikor ismétlődő értékeket deklarálunk a memóriában:
section .data
negative_block: times 16 db -1
; this assembles to 16 db -1 in sequence, i.e., an array of 16 bytes, all equal to -1
Többsoros kód ismétléséhez ehelyett a %rep-et használhatjuk:
%rep 2 ; number of repetitions: 2
add rcx, 32
mov dword [rsi + rcx], 0
%endrep
Vedd figyelembe, hogy az ismétlések számát közvetlenül a %rep után adjuk meg, és egy %endrep zárja le az ismételt blokkot.
Gyakori, hogy assembly-időben futó számlálót használunk egy %rep blokkon belül, nagyjából úgy, ahogy egy futásidejű ciklus számlálót használ.
Míg egy assembly-időben értelmezett egész konstans az equ-val deklarálható, assembly-időben értelmezett egész változókhoz ehelyett a %assign-t használjuk.
A %define-nal ellentétben a %assign azonnal egy számmá értékelődik ki, és újra hozzárendelhető:
xor eax, eax
%assign i 0
%rep 4
add eax, i ; add 0, then 1, then 2, then 3
%assign i i + 1
%endrep
Miután a preprocesszor lefut, a ciklus eltűnik, és négy add utasítás marad, a 0, 1, 2 és 3 azonnali értékkel beégetve.
Ez az assembler által végzett cikluskibontás: nincs számlálóregiszter és nincs elágazás, csak lineáris kód.
A preprocesszor számos feltételes szerkezetet kínál. Számos különböző útvonal közül választanak, és a helyükön fejtik ki a törzsüket, de csak akkor, ha a feltételük igaz.
A %if ... %elif ... %else ... %endif egy numerikus vizsgálat alapján választ ágat:
%if WIDTH > 4
mov eax, 2
%elif WIDTH > 2
mov eax, 1
%else
mov eax, 0
%endif
A makrókat gyakran külön fájlban tartják, és minden forrásfájlba, amelynek szüksége van rájuk, a %include segítségével húzzák be:
%include "patterns.mac"
A megnevezett fájl szó szerint beillesztődik az adott pontra, az assembly előtt, pontosan úgy, mintha a tartalmát oda gépelték volna. Ez egy helyen tartja a definíciókat, és lehetővé teszi, hogy több forrásfájl is megossza őket.
Ha a makrófájlt önállóan is le szeretnéd fordítani, akkor .asm fájlkiterjesztéssel kell rendelkeznie.
Vedd figyelembe, hogy a makróknak nincs linkage-jük.
Még ha a makrófájl le is fordul, a makrói csak egy olyan másik fájl számára láthatók, amely %include-olja.
Ellenkező esetben egy csak %include-olt fájlnak bármilyen fájlkiterjesztése lehet.
Az .asm vagy a .mac használata egyaránt elterjedt konvenció.
Egy magazin tördelőmotorjának makrókönyvtárát gondozod. Maga a motor, vagyis azok a függvények, amelyek az egyes oldalakat elrendezik, adott, és nem te írod meg. Te azokat az összeállítási idejű mechanizmusokat írod meg, amelyekből felépül.
Ebben a feladatban nem kell függvényt írnod, csak makrókat. Ezeket a makrókat aztán egy másik fájlban már előre definiált függvényekben illesztik be és hívják meg.
Négy részfeladat vár rád.
Minden makrót pontosan azzal a névvel és argumentumsorrenddel hívunk meg, amelyet a részfeladata megad, ezért pontosan úgy írd meg őket. Ellenkező esetben a fájl nem áll össze.
A magazin formátuma néhány rögzített számból áll, és a tördelés többi része csak név szerint hivatkozik rájuk. Adj mindegyiknek egy olvasható nevet összeállítási időben:
COLUMN_COUNT értéke 4
COLUMN_STRIDE értéke 18
SPACE értéke 0x20, az üres bájtRULE értéke 0x3D, a = bájtRULE_WIDTH értéke 4
PLAIN értéke 0x2E, a . bájtRULED értéke 0x2D, a - bájtBOXED értéke 0x23, a # bájtA motor ezeket a fenti sorrendben adja vissza.
out = {4, 18, 32, 61, 4, 46, 45, 35} // the eight named values, in order
fill_run makrótA motor úgy üríti ki a hasábot, és úgy rakja ki a fejlécvonalakat, hogy egyetlen bájtból álló sorozatot ír le. Fogd meg ezt a lépést egy többsoros makróként, amelyet a motor bárhol meghívhat, ahol egy sorozatot ki kell tölteni.
Először definiálj egy olvasható COUNTER nevet az rcx regiszterhez.
Ezután definiáld a fill_run makrót, amelynek egy tömböt kell feltöltenie egy bájt N példányával.
Három argumentumot vár, ebben a sorrendben:
fill_run rdi, rax, SPACE
fill_run rdx, RULE_WIDTH, RULE
A fill_run makró szabadon felülírhatja a COUNTER-t, más regisztert azonban nem.
Vedd figyelembe, hogy a bájtok száma átadható egy 64 bites általános célú regiszterben, például rax-ban vagy rsi-ben, vagy azonnali értékként, például 10 vagy 4 alakban.
Felteheted, hogy ez a szám soha nem negatív.
Ezt a makrót háromszor hívják meg, két különböző függvényben.
length = 8
page = {32, 32, 32, 32, 32, 32, 32, 32}
width = 8
page = {61, 61, 61, 61, 32, 32, 32, 32} // '====' then spaces
lay_columns makrótA rács a formátumhoz kötött, ezért az oszlopok kezdetei már összeállítási időben kiszámíthatók.
Definiáld a lay_columns makrót, amely COLUMN_COUNT darab eltolást ír ki, mindegyiket 64 bites egészként.
Két argumentumot vár, ebben a sorrendben:
lay_columns rsi, rax
A célcímre kiírandó eltolások száma rögzített, és megegyezik a COLUMN_COUNT értékével.
Minden eltolás egy 64 bites érték, amelyet a offset[i] = margin + i * COLUMN_STRIDE képlettel számítunk ki, ahol i az eltolás 0-tól induló indexe, a margin pedig a második argumentum.
Ez a makró felülírhatja a két regiszterargumentumát, más regisztert azonban nem. Egyszer hívják meg.
margin = 3
out = {3, 21, 39, 57}
set_header makrótA három állandó szekció mindegyikének fejléctílusa a formátum által rögzített, ezért a bájt már összeállítási időben ismert.
Definiáld a set_header makrót, amely két argumentumot vár, ebben a sorrendben:
0 a sima, az 1 a vonalas, a 2 pedig a keretezett.A célcím egy effektív memóriacím kifejezése.
Egy alapregiszterből áll, amelyhez hozzáadható egy 1-gyel, 2-vel, 4-gyel vagy 8-cal skálázott indexregiszter, továbbá egy azonnali érték:
set_header rdx+8*rcx+2, 1
A fejlécbájtot (PLAIN, RULED vagy BOXED) feltételes összeállítással (%if / %elif / %else) válaszd ki, ne futásidejű elágazással.
Ez a makró egyetlen regisztert sem írhat felül. Ugyanabban a függvényben háromszor hívják meg.
pitch = 4
out = {46, 0, 0, 0, 45, 0, 0, 0, 35} // '.', '-', '#'
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.