Kurzusok
/
x86-64 Assembly
x86-64 Assembly
/
Feladatok
/
Magazinelrendezés
Magazinelrendezés

Magazinelrendezés

Tanulófeladat

Bevezetés

Makrók

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.

Egysoros makrók

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.

Note

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

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

Többsoros makrók

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.

Címkék egy makrón belül

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

Ismétlés assembly-időben

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.

Feltételes assembly

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

Felbontás fájlokra

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

Utasítások

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.

Caution

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.

1. Nevezd el a formátumkonstansokat

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ájt
  • RULE értéke 0x3D, a = bájt
  • RULE_WIDTH értéke 4
  • PLAIN értéke 0x2E, a . bájt
  • RULED értéke 0x2D, a - bájt
  • BOXED értéke 0x23, a # bájt

A motor ezeket a fenti sorrendben adja vissza.

out = {4, 18, 32, 61, 4, 46, 45, 35} // the eight named values, in order

2. Írd meg a fill_run makrót

A 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:

  1. a célcím, amelyet egy 64 bites általános célú regiszterben adunk át.
  2. a beírandó bájtok száma (N), amelyet egy 64 bites általános célú regiszterben vagy azonnali értékként adunk át.
  3. a beírandó bájt, amelyet azonnali értékként adunk át.
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

3. Írd meg a lay_columns makrót

A 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:

  1. a célcím, amelyet egy 64 bites általános célú regiszterben adunk át
  2. a bal margó, amelyet egy 64 bites általános célú regiszterben adunk át
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}

4. Írd meg a set_header makrót

A 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:

  1. a célcím.
  2. egy literál stílusazonosítót, amelyet azonnali értékként adunk át, ahol a 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} // '.', '-', '#'
Szerkesztés GitHubon A hivatkozás új ablakban vagy lapon nyílik meg
x86-64 Assembly Exercism

Készen állsz elkezdeni a(z) Magazinelrendezés feladatot?

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.