Assembly programozás - 4. rész

Hello, world

Nagy népszerűségnek örvend C-s oktatási körökben, hogy először "a világ legegyszerűbb" programját mutatjuk meg az olvasónak, ez annyiból áll, hogy kiírja a képernyőre: "Hello, world!", vagyis "Helló, világ!" szöveget.

Most azonban ez a legegyszerűbb program sem lesz túlságosan triviális, főleg azért nem, mert megpróbálom bemutatni, hogyan lehet stílusosan megírni egy Assembly programot.

A rendes, jól nevelt programozó (ilyenek nem léteznek, mert egy idő után megtöri őket a meló) stílusos programokat készít, olyanokat, amit dokumentálva fejlesztett, terv alapján, a program forrása jól kommentezett, tele van elnevezett konstansokkal, jól tagolt, könnyen áttekinthető és megérthető, lehetőleg moduláris, hogy a későbbiekben fel lehessen használni más programok készítéséhez. Ezek olyan alapvető dolgok, amiket nem csak addig kellene betartani, míg a mérnök hallgató megszerzi diplomáját...

Léteznek igazi programozók, ezek élből nem így dolgoznak - ők mindent az utolsó pillanatban kezdenek el, spórolnak a futási idővel és a program által kezelendő memóriahellyel, nem pazarolják a forráskódot sem, főleg nem arra, hogy elnevezett konstansokkal bíbelődjenek, sokkal rövidebb ideig tart magát a számot leírni, mint egy azonosítót. Már akkor optimalizálnak, amikor még le sem írták a program kódját, olyan szinten, hogy néha nyolc épelméjű utasításból kettő lesz, melyeknek nem biztos, hogy közük van a problémához, de mellékhatásukként véletlenül épp az eredményt adják...

Végül pedig léteznek olyanok, akik igazi programozónak hiszik magukat (pedig egyszerűen csak kuszán és csúnyán programoznak), verik a mellüket, de sokszor a legegyszerűbb problémánál elvéreznek...

Első feladatunk tisztázni, mire lesz szükségünk. Kell egy olyan program, ami kiírja a képernyőre azt a szöveget, hogy "Hello, world!". Ezek szerint kell egy sztring típusú változó, ami ezt a szöveget tárolja. De mi mutatja meg a szöveg végét? Erre két megoldás létezik, az elsőt a Turbo Pascal környezetek használják (de a Sun XDR protokollja is hasonlóan ábrázolja a sztringeket). A nulladik karakter a sztring hossza, (számszerű érték), a többi a szting karakterei. Ezen tehát egy iterációval végig lehet mászni, mivel determinisztikus a ciklusmag lefutása. A C nyelv a szövegeket nullterminált sztringként ábrázolja, tehát a NULL karaktert (azt a karaktert, amelynek bináris kódja zéró) használja a sztring terminálására (megtörésére), végjelként. A C sztringet másképpen (a múltból eredeztetve) ASCIIZ (ejtsd: eszki zé) sztringnek is hívják, mert az ASCII karaktereket egy Zéró karakter zárja. Ezt a jelölést nem ajánlom, mert a mai karakterkódtáblák nem csak ASCII táblák, hiszen az ASCII egy 7 bites karakterábrázolási szabvány (ANSI ASCII: American Standard Code for Information Interchange), a mai kódtáblák viszont minimum 8 bitesek, amelyeknek az alsó 128 karaktere az ASCII tábla, vagyis az egész kódtábla már nem kizárólag ASCII.

Következő problémánk: hogyan jelenítjük meg a szöveget a képernyőn? Erre több megoldás is létezik, nézzük a legegyszerűbbet. Most IBM PC-t programozunk, valós módban, MS-DOS operációs rendszer (vagy Windows / MS-DOS parancssor) alatt. Ebben az esetben rendelkezésünkre áll az úgynevezett BIOS (Basic Input-Output System), amelynek az a feladata, hogy az alapvető I/O tevékenységeket támogassa. Mivel a képernyőre írás az alapvető I/O tevékenységek közé tartozik, a BIOS ad hozzá segítséget.

A BIOS függvényei úgynevezett megszakításokon keresztül érhetjük el. Ezt anno azért nevezték el megszakításnak, mert ilyenkor megszakad az eredeti program futása és egy más (általában rendszer-) program rutin futása kezdődik. A megszakítás lefutása után a vezérlés visszakerül a hívó programhoz, tehát folytatódik annak futása. Eredetileg 32 BIOS megszakítás létezett (0x00..0x1F), de találhatók még nem szabványos bővítmények, ezek általában a 0x80 fölötti címeken ülnek, és a hardver konfigurációjától függenek.

A 0x10, vagyis "10h" a képernyővezérlő megszakítás (interrupt). Az interruptok mögött egy függvénykönyvtár áll, melyekből a megfelelő funkciót az AH regiszterbe töltött értékkel választahtjuk ki. A karakter kiíró függvény például a decimális 14-es (0xE) függvény. A kiírandó karakter kódját AL regiszterbe kell mozgani.

A billentyűzet megszakítása a 0x16, ahol a 0x0 funkció jelenti a billentyűlenyomásra való várakozást. Ha leütöttek egy billentyűt, annak karakterkódja az AL regiszterben jön vissza, a billentyű Scan kódja AH-ba kerül. A Scan kód azt jelöli, hányadik billentyűt nyomták le, vagy engedték fel a konzolon. Az ESC billentyű karakterkódja például 27 (0x1B), Scan kódja 0x1, az Enter karakter kódja 13, Scan kódja 0x1C, az F1 billentyű karakterkódja 0 (mert F1, mint karakter nem létezik - ezeket hívják funkcióbillentyűknek), Scan kódja 0x3B.

Kiírni (és akár még beolvasni is) tudunk már. Tudjuk, hogyan lehet ábrázolni egy sztringet - mi maradjunk a C sztringeknél (később, az MS-DOS részben megértjük, miért). Tudjuk, hogyan kell nem determinisztikus ciklust írni, itt például elöltesztelő ciklust szoktak használni - mert előfordulhat, hogy nulla hosszú egy sztring, vagyis végjellel kezdődik.

Érdemes még kis feladatokhoz is apró függvényeket irogatni, ezek megkönnyítik munkánkat. A függvények korábban már volt szó, ha még emlékszünk, ezek olyan feladatok elvégzésére használhatóak, amelyeket egy programban sokszor el kell végezni - de valamilyen okból kifolyólag ciklusba szervezni nem lehet. Summa summarum, az eljárások, függvények "véletlenszerűen" meghívható programrészletek.

Ismerjük az alapokat a TASM szintaktikájából. Már csak azt nem tudjuk, hogyan kell mindezt egy programmá szerveznünk. Kis programok készítésénél TASM alatt a "tiny" modellt érdemes használni. Az utasításokat a ".code" kulcsszó mögé kell írni, az adatokat a ".data" kulcsszó mögé. A program végét az "end" kulcsszó jelzi.

Még egy utolsó dolgot megemlítek: a 16 bites regisztereket érdemes használni, a jó öreg BIOS ugyanis csak ezeket ismeri.

    .model tiny ; picike program lesz

 

    .code ; következzék az kód...

 

        push cs ; CS, vagyis a kódszegmens címét letolni a verembe

        pop ds  ; kicibáljuk DS-be, vagyis beállítjuk az adatszegmenst

 

        mov si, offset szoveg ; szoveg címe SI-be kerül

 

    kiir:            ; szövegfeldolgozó ciklus feje

        mov al, [si] ; AL-be betöltjük az SI címén

                     ; található adatot

        inc si       ; növeljük eggyel (inkrementáljuk) SI értékét

 

        cmp al, 0 ; ha AL-ben 0 van, az a szöveg végét jelenti

        je vege   ; ugrás a program végére

 

        call putchar ; karakter kiíró függvény meghívása

 

        jmp kiir ; ugrás vissza, a ciklus elejére

 

    vege:

        mov ah, 4Ch ; kilépés függvénykódja MS-DOS esetén

        int 21h     ; MS-DOS interrupt meghívása

 

    putchar:

        mov ah, 0Eh ; karakter kiírás függvénykódja

        int 10h     ; BIOS video interrupt

        ret ; visszatérés

 

 

    .data ; innentől kezdve már csak adatok jönnek

 

    szoveg:

        db "Hello, world!"

        db 0

 

    end

  Idemásolom kommentek nélkül is, akkor talán jobban láthatóak lesznek az utasítások :)

    .model tiny

 

    .code

 

        push cs

        pop ds

 

        mov si, offset szoveg

 

    kiir:

        mov al, [si]

        inc si

        cmp al, 0

        je vege

        call putchar

        jmp kiir

 

    vege:

        mov ah, 4Ch

        int 21h

 

    putchar:

        mov ah, 0Eh

        int 10h

        ret

 

 

    .data

 

    szoveg:

        db "Hello, world!"

        db 0

 

    end

 

Előzetes

Ezennel elérkezünk arra a pontra, hogy megtanuljuk, hogyan olvassunk be adatokat bemeneti eszközről. A következő cikk témája a BIOS billentyűzet függvénykönyvtára lesz.

 

Németh Róbert - nrobcsi@freemail.hu