Assembly programozás. szerkesztette: Iványi Péter. September 27, 2010

Méret: px
Mutatás kezdődik a ... oldaltól:

Download "Assembly programozás. szerkesztette: Iványi Péter. September 27, 2010"

Átírás

1 Assembly programozás szerkesztette: Iványi Péter September 27, 2010

2 2

3 Tartalomjegyzék 1 Bevezetés RISC és CISC processzor architektúrák Assembly elsőre Miért tanuljunk assembly nyelvet? Mikor ne használjunk assembly nyelvet? A magas szintű programozási nyelvek előnye Az assembly hátrányai Mielőtt elkezdenénk assembly-ben programozni Szintakszis Assemblerek MASM GAS TASM NASM Melyik assembler? Összefoglalás Ellenőrző kérdések A számítógép felépítése A processzor Végrehajtási ciklus A rendszer óra Címzési architektúra Három címes architektúra Két címes architektúra Egy címes architektúra Zéró cím architektúra Load/Store architektúra Regiszterek Végrehajtási sorrend Branching Memória Memória műveletek

4 2.5.2 Olvasási ciklus Olvasási ciklus Memória típusok Byte sorozatok tárolása Adat alignment problema Input/Output I/O eszközök elérése Összefoglalás Ellenőrző kérdések A processzor Általános regiszterek Szegmentált címzés először Címzési módok Direkt címzési mód Indirekt címzési mód Státusz regiszter Ellenőrző kérdések NASM assembler Egy forrás file szerkezete Pszeudo utasítások DB és társai RESB és társai Konstansok TIMES pszeudo utasítás SEG kulcsszó További hasznosítási területek WRT kulcsszó Parancssori opciók Hibaüzenetek DEBUG program Jelölések A DEBUG indítása A DEBUG parancsai Példák Példa Példa Példa Első programok Első program Egy karakter kinyomtatása

5 6.3 Egy szöveg kinyomtatása Egy karakter beolvasása Assembly nyelv utasításai Adatmozgató utasítások MOV XCHG XLAT LDS LES LEA PUSH PUSHF PUSHA POP POPF POPA LAHF SAHF Matematikai utasítások INC DEC ADD ADC SUB SBB MUL IMUL DIV IDIV NEG CBW CWD Bitforgató és bitléptető utasítások RCL RCR ROL ROR SAL, SHL SAR SHR Logikai utasítások AND

6 7.4.2 OR XOR NOT TEST CMP Vezérlésátadó utasítások JMP Feltételes utasítások JCXZ LOOP LOOPNZ LOOPZ CALL RET INT String kezelő utasítások MOVSB, MOVSW CMPSB, CMPSW LODSB, LODSW STOSB, STOSW SCASB, SCASW REP REPZ REPNZ Processzor vezérlő utasítások CLC STC CMC CLD STD CLI STI Egyéb utasítások NOP IN OUT Ellenőrző kérdések Assembly programokról Programozási módszer Megszakítások Hardware-es megszakítások Megszakítások 8086-os processzorokon

7 8.2.3 INT 21h megszakítás Kivételek Kitérő Linux-ra COM programok Program Segment Prefix EXE programok XOR használata Assembly integer aritmetika BCD aritmetika Ellenőrző kérdések Példa programok Egy byte bináris kinyomtatása Egy hexadecimális szám kinyomtatása Egy byte hexadecimális kinyomtatása Egy decimális számjegy ellenőrzött beolvasása és kinyomtatása Egy karakter beolvasása és módosítása Öt karakter bolvasása és kinyomtatása fordított sorrendben Két egyjegyű szám összeadása Egy karakter n-szeri kinyomtatása Téglalap kinyomtatása Sakktábla nyomtatása ASCII tábla kinyomtatása Szám kiírása decimális formában Olvasás a memóriából Közvetlen videó memóriába írás Szöveg beolvasása Beolvasott szövegben karakterek számlálása Beolvasott szöveg nagy betűsre konvertálása Feladatok Függvények A verem adatszerkezet A verem implementációja Verem műveletek A verem használata Értékek időleges tárolás Függvények definíciója Egymásba ágyazott függvényhívások Paraméter átadás függvényeknek Paraméter átadás regiszteren keresztül Paraméter átadás memórián keresztül Paraméter átadás vermen keresztül Érték és cím szerinti paraméter átadás

8 Változó számú paraméter átadása függvénynek Lokális változók függvényekben ENTER és LEAVE utasítások Rekurzív függvények Hatékonyság Ellenőrző kérdések Feladatok Makrók Egy soros makrók Több soros makrók Címkék makrókban Greedy makró paraméterek Makrók és függvények még egyszer Makrók gyűjtemények Ellenőrző kérdések String műveletek String utasítások String másolás Stringek összehasonlítása Keresés stringek-ben LODSB és STOSB utasítások használata String utasítások előnyei és hátrányai Ellenőrző kérdések Példák függvényekre és szöveg kezelésre Szöveg hosszának megállapítása C és assembly programok kapcsolata Függvény hívási konvenciók bites mód bites mód Optimalizálás Optimalizálás sebességre Sorrenden kívüli végrehajtás Utasítás betöltés és dekódolás Utasítás késleltetés és átbocsátási képesség Függőségi lánc megtörése Ugrások és függvény hívások Optimalizálás méretre Memória hozzáférés optimalizálása Ciklusok optimalizálása Vector programozás

9 15.6 Problémás utasítások Optimalizált példák ASCII tábla nyomtatása rövidebben Megjegyzések Szokásos hibák A ASCII táblázat 179 B Felhasznált irodalom 181 Példa programok listája Tárgymutató

10 10

11 1. Fejezet Bevezetés Ez a jegyzet egy összeállításnak, szerkesztett jegyzetnek indult inkább, mint egy önálló könyv. A jegyzet anyagát igyekeztem úgy összeszedni, hogy az egy egységes egészt alkosson. A jegyzet célja az assembly nyelv megismertetése a hallgatókkal. Ugyanakkor az assembly nyelv nagyon erősen kötődik a processzor architektúrához, így az assembly programozáshoz az architektúrával is meg kell ismerkedni egy kicsit. A jegyzet kifejezetten az Intel x86-os processzorának alacsony szintű programozásával foglalkozik, ezek közül is a 8086-os processzorral. Ez az egyik legkorábbi Intel processzor és furcsának tűnhet a választás, hogy 2010 környékén is erről a processzorról beszélünk. A jegyzet írása során fő célom az volt, hogy az alapokat minden hallgató meg tudja érteni és el tudja sajátítani. Ehhez egy viszonylag egyszerű processzorra volt szükség és főleg ezért választottam a 8086-os processzort alapul. A másik indok, hogy a szimulátorok és virtuális gépek biztos támogatják ezt a processzort és így bármilyen környezetben lehet az assembly programozást gyakorolni. Így a jegyzet főleg kezdőknek szól, de azt remélem, hogy a gyakorlottabb hallgatók is profitálnak a jegyzet elolvasásából. A jegyzet természetesen igyekszik foglalkozni az újabb utasításokkal és módszerekkel, amiket például a Pentium processzorokra fejlesztettek. 1.1 RISC és CISC processzor architektúrák A processzor architektúrák két nagy csoportba sorolhatók: CISC (Complex Instruction Set Computers) 1, RISC (Reduced Instruction Set Computers) 2. A domináns processzor architektúra a piacon a Pentium processzor, ami a CISC családhoz tartozik, de ugyanakkor a jelenlegi trendek szerint egyre inkább a RISC architektúra kerül előtérbe. A RISC processzorok közé tartoznak a MIPS, SPARC, PowerPC és ARM processzorok. A 64-bites Itanium is RISC alapú processzor. Mit jelent az, hogy komplex utasítás a CISC architektúra esetén? Két szám összeadása egyszerű műveletnek számít. Ugyanakkor, ha egy tömböt átmásolunk és közben a tömb mutatókat folyamatosan frissítjük, az már komplex utasításnak számít. 3 A RISC rendszerek csak egyszerű utasításokat használnak, és bizonyos feltételeket is szabnak. Például az utasítások argumentumainak a regiszterekben kell lenniük és nem a memóriában. 1 Szabad fordításban: Komplex utasításkészletű számítógép 2 Szabad fordításban: Egyszerűsített utasításkészletű számítógép 3 Létezik ilyen utasítás a CISC processzorokon, ez a MOVSB utasítás, lásd bekezdés. 11

12 1.2 Assembly elsőre Az assembly nyelven írt programokat processzálni kell egy másik program, assembler, által ami gépi kódot generál. A gépi kódot fogja futtatni a processzor. Nézzünk néhány assembly utasítást: inc [result] mov [meret], 45 add [mask1], 256 Az első sorban megadott utasítás megnöveli a result változó értékét. A második sorban megadott utasítás a 45-ös értéket tölti a méret változóba, míg a harmadik utasítás 256-ot add a mask1 változóhoz. A fenti kódrészlet C programozási nyelven a következőképpen néz ki: result++; meret = 45; mask1 = mask1+ 256; A példa alapján a következőket lehet megállapítani az assembly nyelvről: Az assembly nyelv utasításai kriptikusak. Az assembly nyelv műveleteit mnemonikok írják le, például add vagy mov. Az assembly nyelv utasításai nagyon alacsony szintűek. Például a következőt már nem írhatjuk le 4 : mov [meret], [adat] A 1.1. táblázat néhány assembly utasítást és a neki megfelelő gépi kódot mutatja. A táblázatnál az első észrevétel, hogy a RISC processzorokon az utasítások hossza fix. (Ezzel is csökkentve a komplexitást.) A másik fontos észrevétel, hogy a gép kód megértése nagyon nehéz az emberek számára, hiszen több ezer szám kombinációt kellene megjegyezni. Ugyanakkor közvetlen, egy az egyes megfeleltetés van az assembly utasítás és a gépi kód között ezért ha az utasítást írjuk le az pontosan megfelel a szándék szerinti gépi kódnak és így csak mazochisták programoznának gépi kódban. Mindenki inkább az emberek számára jobban értelmezhető assembly parancsokat, mnemonikokat használja. Persze a digitális forradalom elején néhány programot még gépi kódban írtak. 1.3 Miért tanuljunk assembly nyelvet? Az assembly programozás nem annyira népszerű mint néhány éve volt. Ugyanakkor még mindig több oka van annak, hogy megtanuljunk assembly-ben programozni: Tanulás: Fontos tudni hogyan működnek a processzorok és fordítók az utasítás szinten. Ezen ismeretek segítségével meg lehet állapítani mely programozási módok a leghatékonyabbak, illetve, hogy a magasabb szintű programozási szerkezetek hogyan működnek. Debuggolás: Több szempontból is hasznos lehet ha a fordítók által generált kódot meg tudjuk érteni illetve meg tudjuk állapítani, hogy mennyire jó, optimalizált kódot generál egy fordító. Fordítók: Az assembly kód megértése elengedhetetlen ahhoz, hogy fordítót, debugger-t vagy egyéb fejlesztő eszközöket fejlesszünk. Beágyazott rendszerek: A beágyazott rendszereknek nincs annyi erőforrása mint egy hagyományos PC-nek és ezért szükség lehet az assembly nyelvre, hogy ilyen rendszerekre gyors és hatékony kódot írjunk. 4 A magyarázat a 3. fejezetben található. 12

13 Pentium processzor Assembly Művelet Gépi kód (hex) nop Üres művelet 90 inc result Növelés FF060A00 mov result, 45 Másolás C7060C002D00 and mask, 128 Maszkolás 80260E0080 MIPS processzor Assembly Művelet Gépi kód (hex) nop Üres művelet mov $t2, $t15 Másolás 000A2021 and $t2, $t1, 15 Logikai ÉS 312A000F addu $t3, $t1, $t2 Összeadás 012A tábla: Assembly parancsok és a megfelelő gépi kód Hardware eszközök: A magas szintű programozási nyelvek korlátozott (absztrakt) hozzáférést engednek a hardware elemekhez, így a hardware eszközök használatát és elérését biztosító eszközvezérlőt írni magas szintű nyelven nehéz vagy lehetetlen. Ilyen esetben is jól jöhet az assembly nyelv ismerete. Olyan utasításokat is használhatunk assembly-ben aminek a magasabb szintű nyelvekben nincs megfelelője. Méretre való optimalizálás: A méretre való optimalizálás azt jelenti, hogy Program A kevesebb helyet foglal mint Program B de ugyanazt a feladatot látja el. A memória ma már olyan olcsó, hogy tulajdonképpen nem éri meg assembly-ben kódot írni a program méretének csökkentése miatt. Ugyanakkor a cache még midig kis méretű és drága, így az erre optimalizált kód esetén még mindig fontos az assembly nyelv használata. Sebességre való optimalizálás: A sebességre optimalizált program a lehető legrövidebb idő alatt végzi el a feladatot. Habár a modern fordítók viszonylag jól optimalizálják a generált kódot, bizonyos esetekben a kézzel optimalizált assembly program részlet drámaian fel tudja gyorsítani a programot. Az utolsó két szempontból az utóbbi a fontosabb. Egyrészt a hely megtakarítás csak a program kódra vonatkozik és az adatra nem, másrészt a memória méretének növekedése miatt. Assemblyben azért lehet hatékony kódot írni, mivel a nyelv sajátossága, hogy a generált kód csak azt tartalmazza amit beleírtunk, vagyis ami a feladat megoldásához kell. Semmi más, extra információt nem fordít bele az assembler. A sebességre optimalizált alkalmazások két kategóriába sorolhatók: idő hatékony alkalmazások: ezeknél a programoknál a gyorsabb futás jelent előnyt, de nincs különösebb probléma ha a sebesség lassabb; idő kritikus alkalmazások: ebben az esetben a feladatot adott idő alatt kell elvégezni. Általában ezek a valós idejű alkalmazások (real-time systems), például: repülőgép navigációs eszközei, robot kontroll rendszerek, kommunikációs szoftverek. 1.4 Mikor ne használjunk assembly nyelvet? Olyan sok hátránya van az assembly nyelven való programozásnak, hogy mielőtt elkezdenénk programozni assembly-ben más alternatívákat is vegyünk figyelembe. 13

14 1.4.1 A magas szintű programozási nyelvek előnye A magas szintű programozási nyelvek viszonylag kényelmes absztrakciót tesznek lehetővé, hogy az adott problémát megoldjuk. A magas szintű programozási nyelvek előnyei: A programfejlesztés gyorsabb: A magas szintű programozási nyelvekben sokféle programozási konstrukció áll rendelkezésre. Általában rövidebbek is a programok. A programokat könnyebb karbantartani: A magas szintű programozási nyelven írt programokat egyszerűbb megérteni és ezért könnyebb mások által írt programot áttekinteni és megérteni. A programok hordozhatóak: A program nem tartalmaz processzor specifikus részleteket és ezért bármilyen rendszeren használhatóak Az assembly hátrányai Az assembly-ben való programozás ellen szóló legfontosabb érvek: 1. Fejlesztési idő: Az assembly-ben való programozás szinte mindig több időt igényel mint a magasabb szintű programozási nyelv használata. 2. Megbízhatóság és biztonság: Assembly nyelven könnyű hibát véteni. Az assembler csak szintaktikai ellenőrzéseket végez. 3. Debuggolás és ellenőrzés: Az assembly nyelven írt programokban nehezebb hibát keresni, illetve nehezebb ellenőrizni a kódot, hogy az előírt feladatot oldja meg. 4. Karbantartás: Az assembly nyelven írt programokat nehezebb módosítani és karbantartani. A nyelv megengedi a spagetti kód írási technikát és egyéb trükkök is megengedettek a nyelvben, melyeket más nyelven nem lehet megvalósítani. 5. Hordozhatóság: Az assembly kód a hardware platformhoz kapcsolódik, csak az adott processzoron, architektúrán lehet lefuttatni. 6. Modern fordítók: A modern fordítók sokat fejlődtek az elmúlt években és már nagyon jó kódot tudnak generálni és gyakran nehezebb jobb assembly kódot generálni. 1.5 Mielőtt elkezdenénk assembly-ben programozni... Van néhány szempont amit figyelembe kell venni mielőtt egy komplex alkalmazást elkezdenénk assemblyben programozni: Ha az a célunk, hogy egy program sebességét optimalizáljuk, akkor először azonosítsuk, hogy a program mely része fut a legtöbbet a processzoron. Ellenőrizzük, hogy mivel tölti a legtöbb időt a program, például a memória eléréssel, CPU utasítások végrehajtásával, file-ok elérésével vagy valami mással. Döntsük el, hogy a fejlesztett program újrahasznosítható vagy csak egy egyedi alkalmazás. Ha a kódot újra fel akarjuk használni, akkor érdemes több időt tölteni az optimalizálással. El kell dönteni, melyik assemblert használjuk, mivel a különböző assemblerek más-más szintakszist használhatnak. A jelentős mértékben optimalizált kódot nehéz lehet olvasni, így a karbantartás miatt érdemes kisebb egységekbe szervezni a programot melyeknek jól definiált interface-e van és megfelelően van dokumentálva. 5 Itt főleg a forráskódról beszélünk, nem a futtatható, már lefordított gépi kódú programról. 14

15 1.6 Szintakszis Kétféle jelentősebb szintakszis alakult ki az évek során, amiket az assembly programok írásánál használhatunk: AT&T szintakszis Intel szintakszis 1.7 Assemblerek Több assembler is létezik az Intel processzorokra, melyek az x86-os utasítás készletet használják, vagyis a mnemonikokból Intel gépi kódot hoznak létre. Az alábbiakban csak néhányat mutatunk be MASM Ez a Microsoft Assembler, mely a mai napig része a Microsoft fejlesztő környezetének, a Visual Studionak. A program neve: ml.exe. A MASM sokáig a de-facto ipari szabvány volt és több magasabb szintű programozási konstrukciót is tudott kezelni. A formátuma nem teljesen tiszta, vannak inkonzisztens részek benne. Microsoft továbbra is fejleszti, de igazából minimális módon GAS GAS rövidités megfelelője a GNU Assembler, mely a GNU binutils csomag része is. A GNU fordítók olyan formátumot generálnak, melyet ez az assembler képes lefordítani. GAS az úgynevezett AT&T szintakszist használja, bár ma már az Intel szintakszisnak megfelelő kódot is el tud fogadni. Ez az assembler használható Linux, Mac OS X és Windows alatt is TASM Az egyik legnépszerűbb fejlesztői eszközöket a Borland cég készítette. Az általuk készített programfejlesztő családba tartozik a Turbo Assembler is. Sajnos ma már nem fejlesztik, az újabb utasítások nem kerülnek bele, de még mindig elérhető az Interneten. Az assembler által használt szintakszis nagyon hasonló a MASM assembler szintakszisához NASM NASM megfelel a Netwide Assembler névnek és egy szabad forráskodú assembler, mely többféle objektum formátumot képes generálni és így több operációs rendszert támogat (Linux, Windows, Mac OS X, FreeBSD, stb). A szintakszisa tisztább mint a MASM assembler-é, de kevesebb magas szintű programozási konstrukciót képes kezelni Melyik assembler? Ez a jegyzet a NASM assemblert használja két fő ok miatt: Az egyszerű szintakszis nagyon logikus és konzisztens. Windows és Linux rendszeren is használható, melyek manapság a legjobban elterjedt operációs rendszerek. 15

16 1.8 Összefoglalás Az assembly nyelv tanulása mind gyakorlati és pedagógia célokat szolgálhat. Még ha nem is szándékozunk assembly-ben programozni, akkor is érdemes megtanulni, mivel egy nagyon jó alapot ad ahhoz hogy megértsük, hogyan működnek a számítógépek. Amikor magas szintű programozási nyelvet használunk, akkor a rendszert egy fekete dobozként kezeljük. Ezzel szemben assembly programozás esetén a rendszert részleteit is ismerni kell, például a regisztereket. 1.9 Ellenőrző kérdések 1. Soroljon fel különböző processzorokat! 2. Mit jelent a CISC kifejezés és mi jellemző az ilyen processzorokra? 3. Soroljon fel indokokat miért érdemes assembly nyelvet tanulni? 4. Soroljon fel indokokat mikor kell assembly nyelvet tanulni? 5. Soroljon fel indokokat mikor ne használjunk assembly nyelvet? 6. Mi az assembly nyelv és a gépi kód kapcsolata? 7. Magas szintű programozási nyelvben miért nem tudjuk teljes mértékben kontrollálni a hardwaret? 8. Miért hívjuk az assembly programozási nyelvet alacsony szintű nyelvnek és a C programozási nyelvet magas szintűnek? 9. Soroljon fel néhány különbséget a CISC és RISC processzorok között? 10. Hasonlítsa össze a két féle assembly szintakszist! 11. Soroljon fel assemblereket! 12. Miért lehet szükség az assembly használatára idő kritikus alkalmazások esetén? 13. Soroljon fel idő kritikus alkalmazásokat! 16

17 2. Fejezet A számítógép felépítése A számítógépnek alapvetően három fő komponense van: a központi egység vagy processzor (CPU), a memória, és az Input/Output eszközök. Lásd a 2.1. ábra. A részek közötti kapcsolatot a rendszer busz biztosítja. A memória tárolja a programokat és az adatokat is egyszerre. Az Input/Output eszközök lehetnek a billentyűzet, a képernyő és így tovább. A 2.2. ábra egy részletesebb nézetét adja a számítógépnek, ahol jól látható hogy a rendszer busz három részből áll: cím busz, adat busz és kontroll busz. A cím busz szélessége határozza meg az elérhető memória kapacitást, illetve az adat busz adja meg, hogy milyen méretű adatok mozoghatnak a CPU, a memória és az I/O eszközök között. Például a 8086-os processzornak 20 bites a cím busza és 16 bites az adat busza. Ezek alapján a processzor 2 20 byte-ot tud megcímezni, vagyis 1 MByte-ot és minden alkalommal 16 bit mozog az egységek között. A Pentium processzoroknak 32 cím vonaluk van a cím buszban és 64 adat vonala. Így a Pentium 4 GByte memóriát tud megcímezni. A 2.2. ábrán az is fontos, hogy a buszok milyen irányba képesek adatot küldeni. Látható, hogy a CPU a kontroll buszon keresztül ad utasításokat a memóriának és az I/O alrendszernek, adatot viszont az adat buszon keresztül fogad. A kontroll buszon kiadható jelek: memória olvasás, memória írás, I/O olvasás, I/O írás, megszakítás és így tovább. 2.1 A processzor A processzor kontrollálja a legtöbb tevékenységet a rendszerben. Úgy érdemes rá gondolni, hogy a következő ciklust hajtja végre: 1. Egy utasítás betöltése a memóriából (fetch), 2. Az utasítás dekódolása, azonosítása (decode), CPU Memória Kapcsolat Input/Output 2.1. ábra: Absztrakt értelmezése a számítógépnek 17

18 CPU Memória Cím busz Adat busz Kontroll busz I/O alrendszer 2.2. ábra: Egy számítógép egyszerűsített diagramja 3. Az utasítás végrehajtása (execute). Ez a végrehajtási ciklus, vagy fetch-decode-execute ciklus Végrehajtási ciklus Fetch Decode Execute A processzor a betöltendő utasítás címét felrakja a cím buszra. A processzor a kontroll buszon keresztül memória olvasásra ad utasítást a memória egységnek. A processzor addig vár amíg az utasítás meg nem jelenik az adat buszon. A memória egységnek idő kell míg hozzáfér a szükséges részhez. Ez a hozzáférési idő. A memória a beolvasott utasítást az adat buszra helyezi. A processzor beolvassa az adat buszról az utasítást Azonosítani kell a beolvasott utasítást. Ezt segítendő az utasítások bizonyos kódolási mintát követnek, melyet majd a 7. fejezetben tárgyalunk. A végrehajtáshoz két egységre van szükség: egy kontrol és egy aritmetikai (ALU) egységre. A kontroll egység segít az időzítésben, míg az ALU egység a matematikai számításokat végzi el. Megjegyezzük, hogy az adatok és az utasítások nem mindig közvetlenül a memóriából jönnek, hanem az úgynevezett cache-ből. A cache memóriához való hozzáférés gyorsabb. A Pentium processzoron 16 KB-os cache van a chipen, melynek fele adat és fele utasítás cache. Szerencsére a cache használata hardware-ben van megoldva és teljesen transzparens a programozó számára. 18

19 2.1.2 A rendszer óra A rendszer óra a system clock. A rendszer óra teszi lehetővé, hogy a műveleteket összeszinkronizáljuk. Az órajel 1-eseket és 0-kat ad ki sorozatban, szekvenciában. Az óra frekvencia értékét a másodpercenkénti ciklusok száma adja meg és a mértékegysége Hertz (Hz). A MHz és GHz 10 6 és 10 9 ciklust jelent másodpercenként. 1 óra frekvencia = (2.1) óra ciklus hossza A rendszer óra adja meg a számítógép sebességét. Minden processzor művelet végrehajtása több órajel ciklust igényel. Például egy 1 GHz-es Pentium processzoron egy adat továbbítása a memóriából a processzorra három órajel ciklust igényel. Egy óra ciklus hossza: és így az adattovábbításhoz 3 ns-ra van szükség. 1 = 1ns (2.2) A számítógépek sebességét növelhetjük úgy, hogy nagyobb órajel frekvenciát használunk. Például egy 2 GHz-es processzoron az adat továbbítás már csak 1.5 ns-ig fog tartani. 2.2 Címzési architektúra Érdekes módon az egyik legfontosabb tulajdonsága egy architektúrának hogy hány címet használunk az utasításokban. A legtöbb művelethez egy vagy két argumentumra van szükség. Ezek alapján szokták a műveleteket binary és unary műveleteknek nevezni, ahol a a bi- kettőt, az un- egyet jelent. Unary művelet például a tagadás (NOT) művelet, míg binary művelet az összeadás és a kivonás. Ezek a műveletek egy eredményt adnak. Természetesen vannak kivételek, például az osztás. Az osztásnál két argumentumra van szükség, az osztandóra és az osztóra, viszont két eredmény is keletkezik: az eredmény és a maradék. Mivel a binary műveletek a leggyakoribbak és ebben az esetben két input argumentumra és egy eredmény, output argumentumra van szükség ezért ezért általában három címre van szükség egy utasításnál. Ebben a részben azt nézzük meg, hogyan lehet három, kettő, egy és zérus címet használni az utasításokkal Három címes architektúra A három címet használó utasításkészlettel rendelkező processzoroknál a két input argumentumot és az egyetlen output argumentumot tudjuk megadni. A legtöbb modern processzor ilyen utasításkészletet használ. Nézzünk egy példát: A = B + C * D - E + F + A mely pszeudo assembly-ben a következőképpen néz ki: mult T,C,D ; T = C * D add T,T,B ; T = B + C * D sub T,T,E ; T = B + C * D - E add T,T,F ; T = B + C * D - E + F add A,A,T ; A = B + C * D - E + F + A A példában az látható, hogy matematikai műveletre egy utasítást kell megadni. Ami szintén szembetűnő, hogy az első utasítást kivéve az első két argumentum azonos. Mivel az esetek jelentős részében így van, ezért a sok duplikáció elkerülése végett két-címes utasításkészleteket is szoktak implementálni processzorokban. 19

20 2.2.2 Két címes architektúra Ebben az esetben az utasításoknak csak cím argumentuma van és az egyik cím inputként és outputként is szolgál. Az Intel processzorok, például a Pentium is ilyen utasításokat használ. Nézzük az előző példát újra: A = B + C * D - E + F + A mely pszeudo assembly-ben a következőképpen néz ki: load T,C ; T = C mult T,D ; T = C * D add T,B ; T = B + C * D sub T,E ; T = B + C * D - E add T,F ; T = B + C * D - E + F add A,T ; A = B + C * D - E + F + A Mivel csak két argumentum áll rendelkezésre ezért az első utasítással betöltjük az adatot T-be. Ebben az esetben az a feltűnő, hogy az első 6 utasításban a T argumentum közös. Ha ez lesz az alap eset, akkor már csak egy cím, argumentum kell az utasításokhoz Egy címes architektúra Ha a memória drága vagy lassú akkor egy speciális regisztert használ a processzor. Ez a regiszter szolgáltatja az input és az output argumentumot egy utasításnak. Ezt a regisztert akkumulátor regiszternek is szokták nevezni, mivel benne gyűlik össze, akkumulálódik, az eredmény. A legtöbb arhitektúra esetén csak egy akkumulátor regiszter van. Ezt a regisztert nem kell megadni az utasításnak csak a másik argumentumot Zéró cím architektúra Arra is van lehetőség, hogy mindkét argumentum speciális helyen tárolódik és így nem kell megadni őket az utasításoknál. Ezek a processzorok egy vermet használnak. Az argumentumok a verem tetején vannak amiket az utasítás levesz onnan, majd az eredményt is a verem tetejére teszi vissza Load/Store architektúra Ebben az esetben a műveleteket a processzor belső regiszterein végezhetjük el és külön utasítással kell beolvasni az adatokat a memóriából a regiszterekbe, illetve a regiszterekből kiírni a memóriába. A fenti példa a következőképpen módosul: A = B + C * D - E + F + A mely pszeudo assembly-ben a következőképpen néz ki: load R1,B load R2,C load R3,D load R4,E load R5,F load R6,A mult R2,R2,R3 ; R2 = C * D add R2,R2,R1 ; R2 = B + C * D 20

21 sub R2,R2,R4 ; R2 = B + C * D - E add R2,R2,R5 ; R2 = B + C * D - E + F add R2,R2,R6 ; R2 = B + C * D - E + F + A store A,R2 A fenti példában hat regisztert is használunk. Bár nincs ennyire szükség, de ez általában jellemző ezekre az architektúrákra, hogy sok regiszterük van. A RISC processzoroknak több regiszterük van mint a CISC processzoroknak. A MIPS processzornak 32 regisztere van, az Intel Itanium processzornak 128 regisztere és az Intel Pentium processzornak csak 10 regisztere van. 2.3 Regiszterek Minden processzorban vannak regiszterek, melyeket két fő csoportba sorolhatunk: általános célú regiszterek, speciális célú regiszterek. A speciális célú regisztereket további két csoportba oszthatjuk: felhasználó által elérhető regiszterek és csak a rendszer által elérhető regiszterek. A Pentium regisztereit a 3. fejezetben tárgyaljuk. 2.4 Végrehajtási sorrend A program végrehajtása általában szekvenciálisan történik, az utasításokat egymás után hajtjuk végre. Az egyik regiszter, a Program Counter (PC) vagy Instructon Pointer (IP) regiszter, fontos szerept játszik a végrehajtási sorrend kezelésében. A processzor mindig azt az utasítást tölti be (fetch) amire a PC regiszter mutat. A betöltés után a PC regiszter értékét megnöveljük, hogy a következő utasításra mutasson. Ez a megnövelés lehet fix méretű, például a RISC processzoroknál, vagy változó méretű a CISC processzoroknál, ahogy ez látható a 1.1. A CISC processzorok esetén minden utasításnál külön meg kell állapítani, hogy mennyivel növeljük meg a PC regiszter értékét. A magasabb szintű programozási nyelvekben ugyanakkor vannak feltételes végrehajtási és ciklikus programozási konstrukciók, melyek a végrehajtási sorrendet változtatják meg valamilyen futás közbeni feltételtől függően. Ezek megvalósítása a processzorokban speciális módon történik Branching A branching szó fordítása talán az elágazás lehet. Arról van szó, hogy az eredeti szekvenciát megszakítva, máshol folytatódik a program végrehajtása. Két változata van: a feltétel nélküli és feltételes ugró utasítás. Ezeknek az utasításoknak egy argumentuma van, mely explicit módon megadja az új utasítás címét. Ez azt jelenti, hogy amikor máshol kell folytatni a végrehajtást, akkor a PC regiszterbe az új címet töltjük be és így a következő fetch -nél már ezt a címet fogja használni a processzor. A végrehajtási sorrend a feltétel nélküli ugró utasítás esetén a 2.3. ábrán látható. Feltételes ugrás A feltételes ugrás esetén az új cím csak akkor töltődik be a PC regiszterbe, ha valamilyen feltétel teljesül. Kétféle módon szokták ezt megadni a különböző processzorokban: Set-Then-Jump : Az ilyen architektúrájú processzorokban a vizsgálat és az ugrás szét van választva. A két, különálló rész közötti kapcsolatot egy regiszter biztosítja. A vizsgálat beállítja a regiszter értékét, majd az ugró utasítás ezt a regisztert vizsgálja meg hogy bekövetkezzen-e az ugrás vagy sem. A Pentium processzorok ezt a technikát használják. 21

22 utasítás jump utasítás a cím b utasítás utasítás c d 2.3. ábra: Ugró utasítás Test-And-Jump : A legtöbb processzor összekombinálja a két részt, például a MIPS processzorok. Például: beq Rsrc1, Rsrc2, célcím összehasonlítja az Rsrc1 és Rsrc2 regiszterek tartalmát és ha egyenlőek, akkor a célcím-nél folytatódik a végrehajtás. 2.5 Memória A számítógép memóriáját úgy érdemes elképzelni mint sok elektronikus kapcsoló összessége. Ezek a kapcsolók két állapotban lehetnek: nyitott vagy zárt állapotban. Ugyanakkor ezeket az állapotokat érdemesebb 1 és 0 állapottal jellemezni. Így minden kapcsolót reprezentálni lehet egy bináris számmal vagy bittel. A memória millió szám tartalmaz biteket. A jobb kezelhetőség miatt a memória a biteket csoportokba szervezik. 8 bit csoportja egy byte. Így a memória mint egy byte sorozat képzelhető el. Minden byte-ra egy index számmal lehet hivatkozni. Az első index értéke 0. Az utolsó index értéke 2 n 1, ahol az n az adatbusz szélessége (hány bites). A memória sematikus képe a 2.4. ábrán látható Memória műveletek Két alapvető művelet van: adat olvasás a memóriából és adat írás a memóriába. Mindkét esetben szükség van egy memória címre ahonnan olvashatunk, vagy ahova írhatunk. Ezenkívűl az írási művelet még egy adatot is igényel Olvasási ciklus 1. A processor elhelyezi az olvasandó adat címét a cím buszon. 2. A kontroll buszon a processzor kiadja a memória olvasási jelet. 3. A processzor várakozik amíg az olvasás megtörténik és az adat megjelenik az adat buszon. 4. A processzor beolvassa az adatot az adat buszról. 22

23 FFFF FFFF FFFF FFFE ábra: A memória sematikus képe 5. A kontroll buszon jelzi a processzor, hogy véget ért az olvasás. Egy Pentium processzor olvasási ciklusa három órajel ciklusnak felel meg. Az első órajel ciklus alatt az 1. és 2. lépés hajtódik végre. A második órajel ciklus alatt a processzor várakozik. A harmadik órajel ciklus alatt az utolsó két lépés fut le. Ha a memóriának mégsem sikerül az olvasás, akkor ezt jelzi a processzornak ami egy újabb órajel ciklusig vár Olvasási ciklus 1. A processzor elhelyezi az írandó adat címét a cím buszon. 2. A processzor elhelyezi az adatot az adat buszra. 3. A kontroll buszon a processzor kiadja a memória írási jelet. 4. A processzor várakozik amíg az írás megtörténik. 5. A kontroll buszon jelezzük az írás végét. A Pentium processzor írási cíklusa is három órajel ciklust igényel. Az 1. és 3. lépés az első órajel alatt következik be. A 2. lépés csak a második órajel ciklus alatt történik. A második órajel ciklus végén jelzi az írás végét Memória típusok A memóriákat különböző kategóriákba lehet csoportosítani. Az egyik legfontosabb tulajdonsága a memóriáknak, hogy csak olvashatók vagy írhatók-olvashatók. Szintén fontos tulajdonság, hogy a memória minden részének elérése azonos időben lehetséges (random-access) vagy csak szekvenciálisan. A szekvenciális elérés magyarázatához a legjobb példa egy kazetta, amikor is addig kell olvasni a kazettát, amíg el nem értük a keresett adatot. Végül vannak a volatile memóriák, melyeknél amíg feszültség alatt van az egység csak addig őrzi meg a tartalmat. A nonvolatile memória akkor is megőrzi a tartalmát ha nincs feszültség alatt az egység. Csak olvasható memóriák A csak olvasható memóriák (Read Only Memory vagy ROM) csak olvasási műveletet enged. Ebbe a memóriába nem tudunk írni. A fő előnyük, hogy egyben nonvolatile memóriák is. A ROM memóriák 23

24 tartalmát a gyárban égetik bele. Ezeket a memóriákat olcsó gyártani. A régebbi számítógépekben a BIOS általában ROM. Vannak úgynevezett programozható ROM-ok is (PROM), illetve törölhető (erasbale) PROM-ok (EPROM). Írható-olvasható memóriák Az írható-olvasható memóriákat általában RAM-nak (random access memory-nak) is szokták nevezni, habár a ROM-ok esetén is igaz az, hogy minden része azonos időben érhető el. Ezeket a memóriákat két csoportba lehet sorolni: statikus és dinamikus. A statikus RAM memóriák (SRAM) megőrzi az adatot a beírás után, minden további menipuláció nélkül, amíg a rendszer feszültség alatt van. Ilyen memória a cache vagy a regiszterek. Ezzel szemben a fő memória dinamikus (DRAM). A DRAM egy komplex eszköz, mely kondenzátorok segítségével tárol egy bitet. A feltöltött kondenzátor jelöli az 1-es értéket. Mivel a kondenzátorok idővel vesztenek a töltésükből ezért időközönként frissíteni kell. Tipikusan 64 ms a frissítési periódus. Az olvasás során azt teszteljük, hogy a kondenzátor fel van-e töltve. Ugyanakkor ez a tesztelés tönkre is teszi a töltést. Ebben az értelemben a DRAM egy speciális memória, mivel az olvasás is destruktív, nem csak az írás. A legtöbb memória esetén csak az írás destruktív. A destruktív olvasás következménye, hogy az olvasás után egy helyreállítási ciklus szükséges. Ennek az a következménye, hogy az olvasás kétszer olyan sokáig tart mint más memóriák esetén. Modern memória típusok: FPM DRAM: Fast page-mode DRAM EDO DRAM: Extended Data Output DRAM SDRAM: synchronous DRAM DDR SDRAM RDRAM: Rambus DRAM Byte sorozatok tárolása Természetesen általában nem csak egy byte-ot kell tárolni, hanem több byte-ot is. Például egy egész számot a C programozási nyelvben általában 4 byte-on tárolunk. Felmerülhet a kérdés, hogy hogyan tároljuk ezt a 4 byte-ot a memóriában? A 2.5. ábra két megoldást is mutat. Az ábrán az MSB jelölés a Most Significant Byte -nek felel meg, míg az LSB a Least Significant Byte. Mindkét megoldás esetén a 100-as címet adtuk meg, és ehhez képest történik a tárolás. A Little-endian tárolási módban a legkisebb helyiértékű byte (LSB) tárolódik először. Ezzel szemben a Big-endian tárolási módban a legnagyobb helyiértékű byte (MSB) tárolódik legelőször. Nézzünk egy másik példát a Little-endian tárolási módra. Az 1234h hexadecimális szám esetén először a 34h, majd a 12h byte-ot tárolja a rendszer. Az h szám esetén a tárolási sorrend: 78h, 56h, 34h, 12h. Melyik tárolási módszer a jobb? Mindkettő ugyanolyan jó. Csak a processzor tervező döntése, hogy melyiket használja. A Pentium processzorok a Little-endian tárolási módot használják. A MIPS és PowerPC processzorokon a Big-endian tárolási mód az alapértelmezett, de át lehet konfigurálni őket Little-endian tárolási módra is. Általában a különböző tárolási mód nem okoz problémát, ha mindig csak egyféle processzort használunk. Az igazi problémák akkor jelennek meg, ha különböző tárolási módszert használó processzorok között akarunk egy programot hordozni. Ebben az esetben az adatokat konvertálni kell! 24

25 MSB LSB cím cím Little-endian Big-endian 2.5. ábra: Little-endian és Big-endian tárolási mód Adat alignment problema Egy program végrehajtási sebességét több tényező is befolyásolja. A tényezők közül néhány a programozó befolyása alatt van, míg másokat nem tudnak befolyásolni. Ebben a fejezetben az egyik fontos tényezőt vizsgáljuk meg. Tegyük fel, hogy egy 32 bites adatot szeretnénk olvasni a memóriából. Azt is tegyük fel, hogy az adat busz szintén 32 bites. Ha az olvasni kívánt adat címe néggyel osztható, akkor a memóriában pont úgy van elhelyezve, hogy egy sorba esik. Ez látható a 2.6. ábrán és ezt szoktuk illesztett, aligned adatnak nevezni. Mivel az adatbusz 32 bites, ezért egyszerre 4 byte-ot, egy sort lehet beolvasni a memóriából. Ez azt jelenti, hogy ha a cím nem osztható néggyel, akkor az adat két sorba kerül és kétszer kell olvasni a memóriából, majd ezekből fogja a processzor összeállítani a szükséges 32 bites adatot. A kétszeri olvasásnak hatása van a program futására, mivel a nem illesztett adatok miatt lassabban fog futni! n+3 k n+2 k n+1 k 0-7 n+0 k+3 CPU adatbusz 32 bit memória adat1 adat ábra: Adat illesztés - data alignment Az adat illesztés problémája teljesen transzparans módon, vagyis nem jelenik meg a felhasználó számára, kivéve, hogy a program lassabban fut. A 16 bites adatokat 2 byte-ra kell illeszteni. Ez azt 25

26 Cím busz Adat busz Kontroll busz Adat Státusz Parancs I/O kontroller I/O eszköz 2.7. ábra: Input/Output eszköz sematikus ábrája jelenti, hogy a cím legkisebb helyiértékű bite zérus, vagyis a cím páros. A 32 bites adatokat 4 byte-ra kell illeszteni vagyis a cím két legkisebb helyiértékű bite zérus. És így tovább. A Pentium processzorok megengedik az illesztett ( aligned ) és nem illesztett adattárolást is. Bizonyos processzorok az előbb leírt hatékonysági probléma miatt nem engedik meg, hogy az adat ne legyen illeszteve. 2.6 Input/Output Az Input/Output eszközök teszik lehetővé, hogy a számítógépek kommunikáljanak a külvilággal. Input/Output eszköz lehet hogy csak adatszolgáltatásra alkalmas, input-ra, például az egér, vagy csak output-ra képes, például a monitor, vagy input-ra és output-ra is képes. Így lényegében az I/O eszközöknek két fő célja van, a külvilággal kommunikálni és adatot tárolni. Mindegyik kommunikáció a rendszer buszon keresztül történik, bár az I/O eszközök nem közvetlenül kapcsolódnak a buszhoz, hanem van egy I/O kontroller az eszköz és a rendszer busz között, ahogy ez 2.7. ábrán látható. Két fontos ok miatt van szükség ezekre az I/O kontrollerekre: 1. A különböző I/O eszközöket különböző módon kell kezelni. Ez azt jelenti, hogy a különböző eszközökkel különböző módon kell kommunikálni, néha várni kell az adat megérkezésére vagy vezérlő jeleket kell adni. Ha a processzornak kellen mind ezt a feladatot ellátni, akkor több időt töltene ezzel, mint a felhasználó kiszolgálásával, vagyis a programok futtatásával. Az I/O eszköz kontroller elvégzi a processzor helyett ezeket a feladatokat. 2. A másik ok, hogy a rendszer buszon keresztül küldött elektromos jel igen alacsony, ami azt is jelenti, hogy a rendszer busz nem lehet túl hosszú. Emiatt az I/O eszköz kontrollerek közel vannak a processzorhoz, például a számítógép házban, és majd a kontroller tud külön, erősebb jelet küldeni az eszköznek. Az 2.7. ábra azt is mutatja, hogy az I/O eszköz kontrollerekben általában három regiszter is van. Például egy nyomtató esetén a Státusz regiszter jelzi, hogy az eszköz készen áll-e, az Adat regiszterbe kell tenni a nyomtatandó karaktert és a Parancs regiszterben kell utasítást adni az eszköznek, hogy nyomtassa ki a karaktert. A processzor I/O portokon keresztül éri el ezekete a regisztereket. Az I/O port nem más mint az I/O eszközön levő regiszter címe. Az I/O portok lehetnek a memóriára illesztettek, memory-mapped I/O. Ilyen rendszer például a MIPS processzorban van. A Pentium processzorok egy I/O cím tartományt használnak. Ez a cím tartomány különbözik a memória cím tartományától. Ebben az esetben külön I/O utasításokat kell használni. Ugyanakkor ez az utóbbi technika a memóriára illesztett I/O-t is lehető teszi. Később látni fogjuk, hogy például a képernyő a memóriára illeszthető és úgy is írhatunk a képernyőre, hogy egy speciális memória területre írunk. Ezzel szemben a billentyűzettel lehet I/O utasításokkal is kommunikálni. 26

27 2.6.1 I/O eszközök elérése Amikor assembly-ben programozunk közvetlenül vezérelhetjük az I/O eszközöket. Bár erre lehetőségünk van, de leírások és segítség nélkül gyakran nagyon bonyolult lehet, illetve minden esetben saját input és output függvényeket kellene kifejlesztenünk. Ezenkívül, ha mindenkinek teljesen szabad hozzáférése van az I/O eszközökhöz, akkor rosszindulatú emberek ezt ki is használhatják. Ezért van az, hogy általában az operációs rendszer kontrollálja az eszközökhöz való hozzáférést, illetve biztosítja a rutinokat is amiket használhatunk. A rutinok általában valamilyen megszakítást használnak. A megszakításokat a 8.2. bekezdésben tárgyaljuk. 2.7 Összefoglalás Ebben a fejezetben a számítógép alapvető elemeit ismertük meg olyan mélységben amire szükségünk lehet az assembly programozás során. Ezek az ismeretek lehetnek újak, illetve bizonyos fogalmak előfordulhattak más tárgyak keretében. 2.8 Ellenőrző kérdések 1. Milyen részekből áll egy sematikus számítógép? 2. Mi a fetch-decode-execute ciklus? Melyik lépésben, mi történik? 3. A rendszer busz milyen fő részekből áll? Melyiknek mi a szerepe? 4. Ha a processzornak 64 cím vonala van, mekkora lehet maximálisan a memória mérete? Mi az utolsó byte címe? 5. Mi határozza meg, hogy a memória és a processzor között adatok mérete mekkora? 6. Egy 2GH-es processzorban mekkora egy óra jel ciklus? 7. Miben különbözik a load/store architektúra a többi architektúrától? 8. Adjon magyarázatot arra hogy a RISC processzorokon az utasítások egymás utáni végrehajtása miért lehet gyorsabb mint a CISC processzorokon! 9. Mit jelent a három címes architektúra? 10. Miben különbözik a három és két címes architektúra? 11. Hogyan lehet olyan architektúrát megvalósítani, amelyikben az utasításoknak nem kell argumentumot megadni? 12. Adja meg a következő matematikai műveleteket pszeudo assembly-ben két címes architektúra esetén: E = A * B + C - D 13. A RISC vagy a CISC processzorokra jellemző a nagy számú regiszter? Melyik címzési architektúra esetén van szükség erre a nagyszámú regiszterre? 14. A Pentium processzor a RISC vagy CISC processzorok családjába tartozik? 15. A MIPS processzor a RISC vagy CISC processzorok családjába tartozik? 16. Hogyan lehet megvalósítani a feltételes ugró utasítást különböző architektúrákon? 17. Mi a PC regiszter szerepe a feltételes ugró utasítás végrehajtása során? 27

28 18. Hogyan néz ki a memória sematikus képe? Jelölje a minimum és maximum indexet 16 bites adatbusz esetén. 19. Mi jellemzi a ROM memóriákat? 20. Mi a különbség a statik és dinamikus RAM-ok között? 21. Mi a DRAM működésének alapelve? Miért dinamikus memória? 22. Miért kell frissíteni a DRAM memóriát? 23. Mi a különbség a volatile és nonvolatile memória között? 24. Mit jelent a little-endian tárolási mód? 25. Hogyan tárolódik a h hexadecimális szám a big-endian tárolási móddal? 26. Milyen tárolási módot használ a Pentium processzor? 27. Mit jelent az adat alignment? 28. Miért van hatással a nem illesztett adat tárolás a programok sebességére? 29. Miért van szükség I/O kontrollerre? 30. A processzor milyen módokon kommunikálhat az I/O kontrollerrel? 28

29 3. Fejezet A processzor Az Intel cég 1969-ben vezette be az első processzorát, a 4004 processzort. Ezt követte a 8080 és 8085 processzorok. Ezek a processzorok vezettek az Intel Architektúra (IA) kidolgozásához, a os processzorhoz, 1979-ben. A 8086 processzornak 20 bites cím busza és 16 bites adatbusza van. A következő generáció a processzor volt, melyben újabb utasításokat vezettek be, de a cím és adat busz mérete változatlan maradt. Mivel ezt a processzort nem igazán használták, így az igazi következő generációs processzor a 8086 után a processzor volt. A processzornak 24 bites cím busza van, amivel 16 MByte memóriát lehet megcímezni. Ugyanakkor az adatbusz megmaradt 16 bites. A másik újítás a védett mód (protected mode) bevezetése volt. Az Intel cég első igazi 32 bites processzora a os processzor volt, melynek 32 bites cím és adat busza van. Ezzel a processzorral 4GByte memóriát lehet megcímezni, ráadásul akár egyben, ami lehetővé tette a flat módot. A processzor 1989-ben jelent meg. A processzorba beépült a matematikai ko-processzor, egy 8KB-os L1-es cache is került a hardware-be, támogatta az L2 cache-t is és lehetővé vált a párhuzamos futtatás. A Pentium processzorok a legújabbak az Intel-től, bár itt a Pentium nevet mint a processzorok egy családjának a neveként használjuk. Az első Pentium processzort 1983-ban mutatták be. Azóta megjelentek a Pentium Pro, Pentium II, Pentium III és Pentium 4 processzorok. A processzorokról ad áttekintést a 3.1. táblázat. A hagyományos Intel architektúrától jelentősen eltér az Itanium processzor, mely RISC alapú, eltérően az Intel más processzoraitól, és más újításokat is tartalmaz. Ugyanakkor, ma (2009. szeptember) már kijelenthetjük, hogy ez a processzor nem váltotta be a hozzá fűzött reményeket és nem terjedt el olyan mértékben a számítógépes piacon, hogy jelentős szereplője legyen. Processzor Év Frekvencia Regiszter Adat busz Maximum (MHz) méret méret memória MB MB GB GB Pentium GB Pentium Pro GB Pentium II GB Pentium III GB Pentium GB 3.1. tábla: Processzorok áttekintése 29

30 3.1 Általános regiszterek A 3.2. táblázat tartalmazza a 8086-os processzor általános célú regisztereinek listáját. A táblázatban az is látható, hogy a 16 bites regiszterek közül melyeket lehet 8 bites darabokban, regiszterekként használni. Fontos megjegyezni, hogy 8 bites regiszterek esetén az egyik regiszter értékének megváltoztatása nincs hatással a regiszter többi részére. Például ha az AL regiszter résznek értéket adunk attól még az AH regiszter rész nem fog megváltozni! Speciális regiszter az IP vagy instruction pointer regiszter, mely a következő végrehajtandó utasításra mutat. Ezt a regisztert kontroll regiszternek is szoktuk nevezni. Végül a szegmens regiszterek listáját a 3.3. táblázat tartalmazza. Ezek a regiszterek támogatják a szegmentált memória kezelését. Név Teljes regiszterek 0-15 bit 8-15 bit 0-7 bit Akkumulátor AX AH AL Bázis BX BH BL Számláló CX CH CL Adat DX DH DL Forrás index SI Cél index DI Bázis pointer BP Stack pointer SP 3.2. tábla: Általános célú regiszterek Név Kód szegmens Data szegmens Extra szegmens Stack szegmens Regiszterek 0-15 bit CS DS ES SS 3.3. tábla: Szegmens regiszterek 3.2 Szegmentált címzés először Ebben a fejezetben csak az úgynevezett valós módú memória architektúrát (real-mode memory architecture) tárgyaljuk, mely a 8086-os processzorra jellemző. A processzor 1MB memóriát tud megcímezni. A memória megcímzéséhez 20 bites értékre van szükség. Az első memória hely címe: 00000h, míg az utolsó memória hely címe: FFFFFh. Mivel minden regiszter 16 bites a 8086 processzorban, ezért az így megcímezhető memória mérete korlátozott: 2 16 vagy byte. Ennek következtében a memóriát szegmensekre kell osztani, melyek mérete byte. Így, ebben a szegmentált memóriában meg kell adni egy szegmens bázis címet és egy offszetet. Ez a két érték adja meg a logikai címet. A szegmens bázis cím adja meg, hogy a szegmens hol kezdődik a memórián belül, míg az offszet a szegmensen belüli helyet adja meg. A 3.1. ábra a fizikai memória címek és a szegmentált, logikai címzés közötti kapcsolatot mutatja. Amint látható, a szegmens fizikai címe 20 bites (12000h). Hogyan lehet 20 bites címet tárolni 16 bites regiszterekben? A válasz az, hogy sehogy, és egy trükköt kell alkalmazni. A lényeg, hogy a szegmens regiszter a 20 bites címből a 16 legnagyobb helyiértékű bitet tárolja (most significant bit) és feltételezzük, 30

31 fizikai cím offszet (345) szegmens bázis (1200) ábra: Kapcsolat a fizikai és logikai címek között hogy az utolsó 4 bit zérus, amit nem kell tárolni. Ez persze azt is jelenti, hogy a szegmensek fizikai címe csak olyan lehet, aminek a 4 legkisebb helyiértékű bite zérus lesz, vagyis: 00000h, 00010h, 00020h, 00030h,... FFFE0h, FFFF0h. A szegmensen belüli memória helyet az offszet adja meg. A programozónak csak a logikai címzéssel, vagyis a szegmens:offszet párral kell foglalkoznia, ami automatikusan konvertálódik 20 bites fizikai címmé, ahogy ez a 3.2. ábrán látható. Lényegében a szegmens regiszter tartalmához jobbról hozzáillesztünk négy darab zérus bitet, majd ehhez az értékhez hozzáadjuk az offszet értékét. Ha hexadecimális számrendszerben dolgozunk, akkor a szegmens regiszter tartalmához egy nullát kell hozzáírni és ehhez kell az offszet értékét hozzáadni. Nézzünk egy példát, ahol a logikai cím hexadecimális számrendszerben: 1200:0345 és így a fizikai cím: 12345, mivel: Minden logikai címhez tartozik egyetlen fizikai cím. Ugyanakkor a fordítottja nem igaz, vagyis egy fizikai címhez több logikai cím is tartozhat. Például a 1200:0345 és 1000:2345 logikai címek ugyanazt a fizikai címet adják meg. A logikai cím tárolásához tehát kell egy szegmens regiszter és egy offszet regiszter. A szegmens regisztereket a 3.3. táblázat sorolta fel. Ezek a regiszterek teljesen függetlenek egymástól és a megcímzett szegmensek lehetnek szomszédosak, átfedők, ugyanazok illetve egymástól távoli memória tartományok, ahogy ezt a 3.3. ábra is mutatja. Offszet regiszterként csak azok a regiszterek használhatók, melyeket a címzésben lehet használni: BX, BP, SP, SI, DI. 3.3 Címzési módok A CISC processzorok nagy számú címzési módot támogatnak, szemben a RISC processzorokkal. A 8086-os processzor, mint egy CISC típusú processzor, három fő címzési módot támogat: 1. Regiszter mód: Ebben az esetben regiszterek adják az utasítás a bemeneti paraméterét és az 31

32 19 szegmens regiszter offszet regiszter 0 Összeadó bites fizikai cím ábra: Fizikai cím generálása 8086 processzoron 3.3. ábra: A szegmensek egymáshoz való viszonya eredményt is regiszterben tároljuk. Például: MOV AX, BX. Ez a leghatékonyabb címzési mód, mivel az adat a processzoron belül marad, nincs szükség memória műveletre. 2. Közvetlen mód: Ebben az esetben az adat az utasítás része és bár az adat a memóriában van, de a kód szegmensben, nem az adat szegmensben. Az adat mindig egy konstans szám és csak forrás lehet, vagyis a közvetlenül megadott számot tároljuk, vagy manipuláljuk az utasítással. Az utasítás másik operandusa mindig valamilyen más típusú, címzésű kell legyen, például regiszter. Például: MOV AX, 1234h 3. Memória mód: Amikor az egyik operandus a memóriára hivatkozik, akkor több címzési lehetőségünk is van. Itt érdemes az előző bekezdésre emlékezni, hogy egy szegmens és egy offszet cím komponenst kell megadni a végső, fizikai címhez. A szegmens részt vagy explicit módon adjuk meg, vagy a használt regiszterek és kontextus határozza meg az egyik szegmens regisztert. A különböző címzési módok csak az offszet formáit adja meg. A címzési módok formáit a 3.4. táblázat foglalja össze. Ezekben az esetekben az adat mindig az adat szegmensben van és a hozzáférés lassabb mint az előző két esetben. Memória címzésre csak a BX, BP, SP, SI, DI regiszterek használhatók. A BP és SP regiszter esetén az SS szegmens regisztert feltételezzük, míg a többi regiszter esetén (BX, SI, DI) a DS szegmens reg- 32

33 Címzési mód Direkt Indirekt Regiszter Bázisrelatív Indexelt Bázisrelatív indexelt eltolás nélkül eltolással [disp] [BX] [BX+disp] [SI+disp] [BX+SI] [BX+SI+disp] [BP] [BP+disp] [DI+disp] [BX+DI] [BX+DI+disp] [SI] [BP+SI] [BP+SI+disp] [DI] [BP+DI] [BP+DI+disp] 3.4. tábla: Címzési módok Szegmens regiszter Offszet Címzési példa DS [disp] [DS:4423] DS [BX+disp] [DS:BX+3] SS [BP+disp] [SS:BP+5512] DS [DI+disp] [DS:DI+6201] DS [SI+disp] [DS:SI+18] DS [BX+SI+disp] [DS:BX+SI+35] DS [BX+DI+disp] [DS:BX+DI+43] SS [BP+SI+disp] [SS:BP+SI+12] SS [BP+DI+disp] [SS:BP+dI+22] 3.5. tábla: Címzési mód példák isztert feltételezzük a címzésnél, ha nincs szegmens regiszter megadva az utasításban. Természetesen a szegmens regiszter explicit módon is megadható, így például a BX regiszterrel az ES szegmens regiszter is használható. Címzési példákat a 3.5. táblázat tartalmaz Direkt címzési mód Ez az egyik legegyszerűbb címzési mód. Az adat az adat szegmensben található és általában elegendő az offszetet megadni. Ilyenkor automatikusan a DS regisztert feltételezhetjük. Természetesen explicit módon elő is írhatjuk a szegmens regisztert. Míg a végső gépi kódban konkrét számnak, címnek kell szerepelnie, assembly-ben lehetőségünk van szimbólikus címek használatára. Ha szimbólikus címet használunk az assembler majd kiszámolja és behelyettesíti a konkrét címet a gépi kódba. Vegyük a következő adat definíciókat a fejezet alapján: valasz DB i tabla1 DB 0, 0, 0, 0, 0 nev1 DB Jim Doe majd nézzünk néhány példát ezeknek az adatoknak a direkt címzésére: MOV AL, [valasz] MOV [valasz], n MOV [nev1], K MOV [tabla1], 32 ; AL-be az i karakter ; valasz-t felulírjuk n karakterrel ; Ezután Kim Doe lesz a név ; az els}o tárolt érték 32 lesz Nagyon fontos, hogy a szögletes zárójelet használjuk, ha magát az adatot akarjuk betölteni vagy kiírni. Hasonlítsuk össze az alábbi két utasítást: MOV BX, [tabla1] MOV BX, tabla1 33

34 Az első utasítás a tabla1 címen található 16 bites, word értéket tölti be a BX regiszterbe. A második utasításban a tabla1 címét töltjük be a BX regiszterbe! Ez utóbbi jelölésre például az indirekt címzésnél van szükségünk Indirekt címzési mód A direkt címzési módot az egyszerűsége folytán leginkább arra használjuk, hogy egy-egy változót közvetlenül olvassunk vagy írjunk. Ugyanakkor arra már nem alkalmas, hogy egy tömb n-edik elemét módosítsuk, 1 erre inkább az indirekt címzés alkalmas. Az indirekt címzés során valamelyik cím regisztert használjuk arra, hogy például egy tömb címet a regiszterbe tötltsük, majd a regiszter módosításával a tömb különböző elemeit elérjük. Vegyük azt a példát amelyben egy 10 elemű tömb elemit érjük el: tomb DB 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ; ; [BX+4]... MOV BX, tomb MOV [BX], byte 11 ; tomb[0] = 11 ADD BX, 4 MOV [BX], byte 66 ; tomb[4] = 66 Fontos megjegyezni, hogy az adat méretét is figyelembe kell venni a címzés során. A fenti példában 10 darab byte adatot kezeltünk, míg a következő példában word méretű adatokat használunk: tomb DW 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ; ; [BX+4]... MOV BX, tomb MOV [BX], word 11 ; tomb[0] = 11 ADD BX, 4 MOV [BX], word 66 ; tomb[2] = 66 Ennél a példánál arra érdemes emlékezni, hogy egy word adat két byte-ból áll, és a BX regiszterhez négyet hozzáadva a BX által mutatott címet négy byte-al toljuk el. A két példa közötti különbséget a 3.4. ábra mutatja. Az ábrán nem véletlen a adat, mivel bár a programban csak 66 szerepel, de ez word méretű adatként 0066 lesz, amit viszont az Intel processzorok a Little endian módon tárolnak. Az indirekt címzéshez szükséges címet másképpen is betölthetjük a regiszterbe: MOV BX, tomb helyett LEA BX, [tomb] A fő különbség az, hogy az első esetben a címet az assembler a gépi kód generálása közben számolja ki addig a második esetben a címet futási időben határozza meg a rendszer. Bár ebben az esetben nincs közvetlen előnye, de összetettebb címzés esetén már igen, például: MOV BX, tomb ADD BX, SI 1 Hacsak nincs minden elemnek a tömbben saját neve. 34

35 tomb[0] tomb[4] byte tomb[0] tomb[1] tomb[2] word ábra: Byte és word méretű tömbök közötti különbség helyett LEA BX, [tomb+si] lehet használni. 3.4 Státusz regiszter A státusz regiszter 16 bitből áll. Ebből a 16 bitből kilencet használ a 8086-os processzor. A további 7 bit nincs definiálva illetve nem használja a processzor. A státusz bitek függetlenek egymástól és természetesen az értékük csak 1 vagy 0 lehet. A státusz bitek alapvetően jelzések. Lényegében értesítik a programozót hogy a processzor valamilyen állapotban van így a program reagálni tud ezekre az állapotokra. Minden státusz bitnek külön jelentése van és külön két betűs szimbólummal jelöljük őket. A 3.6. táblázat mutatja a bitek jelölését. OF : Overflow bit. Értéke 1 ha az utolsó eredmény az előjeles szám ábrázolási tartományon kívülre esik. Minden más esetben az értéke nulla. DF : Direction bit. Ez egy furcsa státusz bit, mivel ebben az esetben nem a processzor jelez a programozónak, hanem fordítva, a programozó jelez a processzornak. Ez a bit adja meg hogy string műveletek során a címek növekedjenek vagy csökkenjenek. Amikor a bit értéke 1, akkor a string műveletek során a címek csökkennek, ha a bit értéke 0, akkor pedig csökkennek a címek. Ezt a bitet még újra megvizsgáljuk a 7.6. fejezetben. IF : Interrupt enable bit. Ez egy két irányú státusz bit, vagyis nem csak a processzor jelezhet egy állapotot, hanem mi is képesek vagyunk jelezni a processzornak. Amikor ennek a bitnek az értéke 1, akkor a megszakítások engedélyezettek és bármikor bekövetkezhetnek. Ha a bit értéke zérus akkor CPU nem veszi figyelembe a megszakításokat. TF : Trap bit. Ennek a bitnek a segítségével lehet programokat lépésenként végrehajtani. Ha be van állítva (értéke 1), akkor a processzor egy utasítást hajt végre majd meghív egy megszakítást. Általános esetben a programozók nem használják, de például a DEBUG programnál elengedhetetlen. SF : Overflow bit. Értéke 1 ha az utolsó eredményben a legmagasabb helyiértékű bit értéke 1, vagyis negatív szám az eredmény. Ha az eredmény pozitív szám, akkor a legmagasabb helyiértékű bit értéke zérus és a ez a bit is zérus lesz. ZF : Zérus bit. Értéke 1 ha az utolsó eredmény zérus volt. Ha az utolsó eredmény bármilyen zérustól különböző értékű, akkor ennek a bitnek az értéke zérus. 35

36 OF DF IF TF SF ZF AF PF CF 3.6. tábla: Státusz regiszter bitjei AF : Auxiliary bit. Ezt a bitet csak a BCD aritmetika során használjuk. A BCD aritmetikát a fejezetben tárgyaljuk. PF : Parity (paritás) bit. Ez a bit azt jelzi, hogy az utolsó művelet eredményében (mint bináris számban) hány darab 1-es bit van. Például a 0F2h hexadecimális szám binárisan amiben páratlan számú 1-es van és így a paritás bit értéke zérus lesz. A 03Ah binárisan és mivel páros számú 1-es található az eredményben így a paritás bit értéke egy lesz. Ez bit tulajdonképpen abból az időből származik, amikor még minden kommunikáció soros porton keresztül zajlott. A soros kommunikáció esetén a hiba érzékelésének egyik módja volt, hogy a küldés előtt megállapítjuk az adat paritását, majd átküldjük az adatot és a paritás bitet. A túloldalra megérkezett adatot a paritás bittel lehet ellenőrizni. CF : Carry bit. Értéke 1 ha az utolsó eredmény az előjel nélküli szám ábrázolási tartományon kívülre esik. Például ha egy aritmetikai vagy shift művelet során egy 9. vagy 17. bit is keletkezik, akkor ennek a bitnek az értéke 1 lesz. Minden más esetben az értéke nulla. Példa a 8.7. fejezetben látható. 3.5 Ellenőrző kérdések 1. Mi a szegmens? Miért kell a Pentium processzorokon szegmentált memória architektúrát használni? 2. Valós módban miért 64KByte méretű egy szegmens? 3. Valós módban egy szegmens nem kezdődhet bárhol a memóriában. Miért? 4. A 8086-os processzoron négy szegmens lehet egyszerre kezelni. Miért? 5. Mutassa be a fizikai cím kiszámításának módját logikai címből! 6. Konvertálja az alábbi logikai címet fizikai címmé: 3911:0200, 3000: Az IP regiszterhez, melyik szegmens regiszter tartozik? 8. Lehet-e a CS és DS regiszter értéke ugyanaz? 9. Lehet-e a ES regiszter értéke: 1234h? 10. Melyik szegmens regiszter járul az SP regiszterhez? 11. Mire való a státusz regiszter? 12. Soroljon fel néhány dolgot, hogy mit jelezhet a státusz regiszter! 13. Mi a különbség a sor orientált és oszlop orientált tömb tárolási módok között? 14. Adva van a következő adat: tomb resb 12 töltse ki a hiányzó részeket, hogy a 4. és 5. elemet hasonlítsuk össze: MOV SI, MOV AX, [tomb+si] CMP AX, 36

37 4. Fejezet NASM assembler 4.1 Egy forrás file szerkezete Az assembly nyelvben, illetve a legtöbb assembler alatt és így a NASM assembler alatt is, a forráskódban a sor a következő négyféle dolgot tartalmazhatja: címke: utasítás operandus ; megjegyzés A négyféle rész közül a legtöbb opcionális, hiszen egy sorban lehet csak megjegyzés, csak címke, ezek kombinációja, esetleg címke és utasítás és így tovább. Az operandus jelenléte mindig az utasítástól függ. Ha a sor végén a backslash (\) karakter áll, ez azt jelenti, hogy a következő sor is az adott sorhoz tartozik, annak folytatása. A NASM assemblerben nincs semmilyen megkötés a SPACE-ek és tabulátorok használatára. Ezekből a karakterekből, a sor részei között bármennyit használhatunk. Rááadásul a címke után a kettőspont is opcionális. Ez sajnos hibákhoz vezethet! Például, habár a lodsb utasítást szeretnénk leírni egy sorba, de a lodab szöveget írtuk le, az assembler nem fog szólni mivel úgy tekint rá mintha abban a sorban csak egy címkét definiáltunk volna. A NASM assemblernek van egy kapcsolója (-w+orphan-labels) melynek megadása esetén az assembler szólni fog ha egy sorba, kettőspont nélkül, írunk le egy címet. A címkékben használható érvényes karakterek a betűk és számok, _, $, és?. A címke első karaktere betű, _,? vagy pont (.) lehet. Ha egy címke ponttal kezdődik, annek sepciális jelentése van. A NASM assembler képes lefordítani a 8086, 386, 486, Pentium, P6, FPU és MMX utasításkészleteket. Az utasítások előtt szerepelhet egy prefix: REP, REPE/REPZ, REPNE/REPNZ, LOCK. A szegmens regiszter is megadható prefixként: es mov [bx], ax ami egyenértékű azzal, hogy mov [es:bx], ax Az utóbbi jelölés preferált, mivel más szintaktikai elemekkel ez konzisztens. Ha nem adtunk meg prefixet, például nem adtunk meg szegmens regisztert, akkor a NASM automatikusan generálni fogja. 4.2 Pszeudo utasítások A pszeudo utasítások nem igazi processzor utasítások, de egy forrás sorban az utasítás részben szerepelhet. 37

38 4.2.1 DB és társai A DB, DW, DD, DQ, DT adatokat ad meg a forráskódban. A fő különbség, hogy milyen méretű adatot/adatokat definiálnak. DB : egy byte-ot vagy byte sorozatot definiál. DW : egy word-ot vagy word sorozatot definiál. Egy word két byte-ból áll. Itt azt is figyelembe kell venni, hogy az Intel processzor Little endian tárolási módot használ, vagyis a tárolás során a byte-okat felcseréli. DD : egy vagy több double word-ot definiál. Egy double word 4 byte-ból áll. Ez az adatméret használható egyszeres precizitású floating-point számok (float) megadására is a matematikai koprocesszor számára. DQ : egy vagy több quad word-ot definiál. Egy quad word 8 byte-ból áll. Ez az adatméret használható dupla precizitású floating-point számok (double) megadására is a matematikai koprocesszor számára. DT : egy vagy több ten byte-ot definiál. Egy ten byte adat 10 byte-ból áll. Az alábbi lista több példát is mutat az adatok definiálására: db 0x55 ; egy byte 0x55 db 0x55,0x56,0x57 ; 3 bytes egymás után db a,0x55 ; egy karakter és egy byte keverve db hello,13,10, $ ; karakter sorozat és számok keverve dw 0x1234 ; 0x34 0x12 dw a ; 0x41 0x00 dw ab ; 0x41 0x42 dw abc ; 0x41 0x42 0x43 0x00 dd 0x ; 0x78 0x56 0x34 0x12 dd e20 ; floating-point konstans dq e20 ; double-precision konstans A fenti példában érdemes megfigyelni a 6. és 8. sort. A 6. sorban úgy tűnik, hogy csak egy karaktert definiálunk, de mivel a sor elején a DW pszeudo utasítás szerepel, ami word adatméretet, vagyis két byte-ot jelent. Ennek megfelelően a lefordítás után valójában két byte-ot foglal le az assembler, amelyből az egyik zérus lesz. A fenti példákból az is látható, hogy a karakterek (ASCII kódok) és számok szabadon keverhetők. A karaktersorozatok többféleképpen is megadhatók, egyszeres vagy dupla aposztrofok között, illetve egyben vagy karakterenként: db hello db h, e, l, l, o db "hello" RESB és társai A RESB, RESW, RESD, RESQ és REST pszeudo utasítások inicializálás nélküli adatoknak foglal helyet. Az inicializálás nélküli adat azt jelenti, hogy csak az adat tárolására szükséges helyet foglalja le az assembler, de a helyen tárolandó adat bármi lehet kezdetben. (Lehet zérus vagy a memória egy részlete, bármi.) Ezek a pszeudo utasítások több adatnak foglalnak helyet, például: buffer: resb 64 ; 64 byte-nyi helyet foglal wordvar: resw 1 ; 1 word-nyi helyet foglal db "hello" 38

39 4.2.3 Konstansok Ugyanúgy mint a legtöbb programozási nyelvben, például a C programozási nyelvben, assembly-ben is lehet konstansokat definiálni a forráskódban. A konstansokat egy helyen definiáljuk és a programban mindenhol csak egy névvel, szimbolikusan hivatkozunk rájuk. Ezeknek a konstansoknak kettős lehet szerepe: ha későbbi fejlesztések során a konstans értékét meg kellene változtatni, akkor csak a definíció helyén kell megváltoztatni az értéket, hiszen a programban csak a szimbólikus név szerepel és ha a konstans neve olvasmányos, vagyis jelentéssel bír, akkor a program olvasása során könnyebb értelmezni a forráskódot. Például nem csak egy szám szerepel az adott helyen, argumentumban, hanem a szám funkcióját leíró név. A C programozási nyelvben a konstansok definíciója makrókkal lehetséges: #define PI A NASM assemblerben az ennek megfelelő konstans definíció: hossz EQU TIMES pszeudo utasítás Ha a TIMES pszeudo prefixet használjuk, akkor arra utasítjuk a NASM Assemblert, hogy az adott utasítást vagy adatot többször fordítsa bele az eredmény programba. Például: zerobuffer: TIMES 64 db 0 Ennek a sornak a hatására 64 darab zérus byte kerül az eredmény programba. Ezt a sort érdemes összevetni az alábbi sorral, ami csak a helyet lefoglalja le, de a helyen tárolt adatról nem mond semmit. (Lásd: fejezetet) zerobuffer: resb 64 A TIMES pszeudo utasítás egyéb, összetett módon is használható, illetve egyszerűen utasításoknál is. Például az alábbi sor azt adja meg, hogy a MOVSB ( fejezet) utasítást háromszor kell a programba belefordítani: TIMES 3 movsb 4.3 SEG kulcsszó Ha egy olyan programot írunk amelyik több szegmensből áll, akkor egy memória hely (például változó) elérésénél szükség lehet a memória hely preferált szegmens címére is. 1 A preferált itt annyit jelent, hogy az assembler preferálja ezt a címet. Ilyen esetben lehet használni a seg kulcsszót. Például: mov ax, SEG valami mov es, ax mov bx, valami aminek hatására a ES:BX regiszterek a valami változó memória címét fogja tartalmazni. 1 A szegmens és offszet regisztereket használó memória címzésről részletesebb leírás található a 3.2. fejezetben. 39

40 4.3.1 További hasznosítási területek A seg kulcsszó használható távoli függvény hívásnál is, amikor a függvénynek a szegmense és offszet címe is kell. Például: call (SEG nyomtat):nyomtat Ha egy memória hely teljes címét, szegmens és offszetet, is tárolni kell akkor a következő módon lehet ezt megadni: db valami, SEG valami Itt se felejtsük el, hogy a fordított sorrend a Little endian tárolási mód miatt van. 4.4 WRT kulcsszó Előfordulhat, hogy nem a preferált szegmensen keresztül akarunk hivatkozni egy memória helyre. (Ezt megtehetjük, hiszen a szegmensek átfedik egymást a 8086-os gépeken.) Ilyen esetben a wrt (With Reference To) kulcsszót lehet haználni. Például: mov ax, egy_masik_szegmens mov es, ax mov bx, valami wrt egy_masik_szegmens 4.5 Parancssori opciók 4.6 Hibaüzenetek 40

41 5. Fejezet DEBUG program Ez a fejezet a DEBUG program használatát mutatja be. A DEBUG program segítségével már lefordított programokat tudunk debuggolni, lépésenként végrehajtani és közben a rendszer állapotát megvizsgálni, illetve hibákat (bug-okat) keresni. Bár a program igen egyszerű, azt is mondhatnánk fapados, azért is érdemes megismerni ezt a programot, mivel minden Microsoft Windows rendszeren létezik. 5.1 Jelölések cím - címleírás segmens:offset - pl. 0044:0f57 segmens reg:offset - pl. ES:0f30 offset - pl. 400 tartomány - egy memóriatartomány kijelölése cím cím cím, cím cím L hossz lista - egymás után leírt hexadecimális számok szöveg - dupla aposztrofok között karaktersorozat 5.2 A DEBUG indítása D:\DEBUG <ENTER> : A program elindul és egy minusz jel jelzi, hogy a program várja a felhasználó parancsait D:\DEBUG file<enter> : Betölti a file programot és belép a DEBUG programba 5.3 A DEBUG parancsai q - (Quit) kilépés a programból h val1 val2 - (Hex) kiírja a két érték összegét és különbségét 41

42 -h A h A FFF8 d tartomány - (Dump) memória tartalmának kiírása a képernyőre -d c000:0010 C000: FF FF $ IB C000:0020 4D F 4D C D M COMPATIBLE MAT C000: F 58 2F 4D D ROX/MGA-G100 VGA C000:0040 2F F E /VBE BIOS (V1.2 C000: DB 87 DB 87 DB-87 DB 87 DB 87 DB 87 DB )... C000: B PCIR+... C000: D FF C000:0080 E B D8 E8 C C C8 3D 00 C0 74.&V...Vt"..=..t - -d xxxx:0100 EB 24 0D 0A D $..This is my f xxxx: F irst DEBUG progr xxxx: D 21 0D 0A 24 B4 09-BA CD 21 B4 00 CD am!..$...!... xxxx: ! - s tartomány szöveg - (Search) memória tartományban megkeresi a szöveg valamennyi előfordulását -s fe00:0 ffff "BIOS" FE00:0021 FE00:006F -d fe00:0 FE00: F Award SoftwareIB FE00:0010 4D F 4D C M COMPATIBLE 486 FE00: F F BIOS COPYRIGHT FE00: F Award Software I FE00:0040 6E 63 2E 6F E 63 2E 20 nc.oftware Inc. FE00: C F E Aw...oftw... C FE00:0060 1B D-6F C Award Modular B FE00: F E DB 32 EC 33 IOS v4.51pg..2.3 c tartomány cím - (Compare) összehasonlít két memória tartományt f tartomány szöveg - (Fill) memória tartomány feltöltése a szöveggel -f f BUFFER -d f xxxx: BUFFERBUFFERBUFF xxxx: ERBUFFERBUFFERBU xxxx: FFERBUFFERBUFFER e cím lista - (Enter) értékek bevitele a memóriába. Ezzel a paranccsal byte sorozatot lehet beírni a memóriába. 1 -e 100 B4 09 BA 0B 01 CD 21 B4 00 CD 21 g [cím] - (Go) programvégrehajtás folytatása. Ha a cím is adva van akkor a megadott címre egy töréspontot tesz a debugger és az IP regiszter által megadott címtől folytatja a végrehajtást. A töréspont azt jelenti, hogy ha a végrehajtás során a töréspont címét elérjük, akkor a végrehajtás 1 Mivel a gépi kód is csak egy byte sorozat, ezért amit így beírunk a memóriába az lehet egyszerűen adat, de lehet program is! 42

43 leáll (megtörik) és egy parancssor jelenik meg aminek segítségével szétnézhetünk a rendszerben. Megvizsgálhatjuk a regiszterek állapotát vagy a memória tartalmát. Persze az is előfordulhat, hogy a végrehajtás során soha nem jutunk el a töréspontig. Ebben az esetben a program végigfut és végül vagy hibával vagy sikeresen leáll a futás. a [cím] - (Assemble) az opcionálisan megadott címtől kezdve assembly parancsokat gépelhetünk be. Ebben az esetben a DEBUG program, mint egy assembler működik. -a 100 xxxx:0100 jmp 126 ; adat átugrása xxxx:0102 db 0d,0a,"This is my first DEBUG program!" xxxx:0123 db 0d,0a,"$" xxxx:0126 mov ah,9 ; 09-es funkcionalitás xxxx:0128 mov dx,102 ; DS:DX -en a $-al lezárt string xxxx:012b int 21 ; string kiírása xxxx:012d int 20 ; kilépés xxxx:012f u [tartomány] vagy u [cím] - (Unassemble) az aktuális vagy a megadott címtől az opcionálisan megadott tartományban assembly mnemonikra fordítja a gépi kódot. -u F xxxx:0126 B409 MOV AH,09 xxxx:0128 BA0201 MOV DX,0102 xxxx:012b CD21 INT 21 xxxx:012d B400 MOV AH,00 xxxx:012f CD21 INT 21 - i port - (Input) a megadott port-ról beolvas egy byte-ot o port byte - (Output) a megadott port-ra kiír egy byte-ot Portra való kiírás, vagy portból való beolvasás közvetlen kommunikációt jelent a hardware-rel. -o ; Kérdezzük le az órát -i 71 ; Olvassuk vissza az értéket 18 ; 18 óra -o ; Kérdezzük le a percet -i 71 ; Olvassuk vissza az értéket 55 ; 55 perc n filenév - (Name) a file nevének megadása. Erre a parancsra akkor van szükség, ha a memória egy darabját mint programot szeretnénk kiírni. p - egy függvény hívás vagy megszakítás végrehajtása egészben. utasításait nem lépésről-lépésre hajtjuk végre, hanem egyben. Ebben az esetben a függvény r [reg] - (Register) alapesetben a regiszterek tartalmát írja ki a képernyőre. A státusz bitek szövegesen jelennek meg, melyek értelmezését a 5.1. táblázatban jelennek meg. Ha a regiszter is meg van adva, akkor lehetővé teszi a megadott regiszter értékének megadását. -r cx CX 0100 :273 t - (Trace) egy utasítás végrehajtása w - (Write) a BX és CX regiszterekben együttesen megadott byte-nyi adatot ír ki az n paranccsal megnevezett file-ba. Ha a file már létezett akkor a DEBUG felülírja! 43

44 Státusz bit 1 0 Carry CY NC Parity PO NE Aux. carry AU NA Zero ZR NZ Sign PL NG Trap Direction UP DW Interrupt EI DI Overflow OV NV 5.1. tábla: A státusz bitek szöveges megjelenése 5.4 Példák Nézzünk néhány példát a DEBUG program használatára. A példákban a szegmens címet négy darab x jelöli (xxxx), mivel a memóriában bárhova betöltődhetnek a programok. A példákban a DEBUG programot kell elindítani a megadott módon, illetve a minusz ( - ) jel utáni részt kell begépelni Példa Írjunk egy programot mely egy csillag karaktert nyomtat ki. A programot assembly mnemonikok segítségével adjuk meg. Utána a programot lefuttatjuk a DEBUG programban, majd kiírjuk a merev lemezre, az aktuális könyvtárba. C:\> DEBUG -a 100 xxxx:100 mov ah,02 xxxx:102 mov dl,2a xxxx:104 int 21 xxxx:106 int 20 xxxx:108 ; csak ENTERT nyomjunk -r AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=xxxx ES=xxxx SS=xxxx CS=xxxx IP=0100 NV UP EI PL NZ NA PO NC xxxx:0100 MOV AH,02 -g * Program terminated normally -r cx :8 -n csillag.com -w -q C:\>csillag.com * C:\> Példa Használjuk a DEBUG programot arra, hogy megnézzük az előző program futását lépésről-lépésre. Az egyszerű utasításoknál a t parancsot, a megszakítások végrehajtásánál a p parancsot használjuk. A megszakításoknál azért kell a p parancsot használni, hogy a megszakítás során végrehajtandó utasításokat ne lépésenként, hanem egyszerre hajtsuk végre. C:\> DEBUG csillag.com 44

45 -r AX=0000 BX=0000 CX=0008 DX=0000 SP=FFFE BP=0000 SI=0000 DI=0000 DS=xxxx ES=xxxx SS=xxxx CS=xxxx IP=0100 NV UP EI PL NZ NA PO NC xxxx:0100 B402 MOV AH,02 -u xxxx:0100 B402 MOV AH,02 xxxx:0102 B22A MOV DL,2A xxxx:0104 CD21 INT 21 xxxx:0106 CD20 INT 20 xxxx: ADD [BX+SI],AL xxxx:010a 0000 ADD [BX+SI],AL xxxx:010c 0000 ADD [BX+SI],AL xxxx:010e 0000 ADD [BX+SI],AL xxxx: ADD [BX+SI],AL xxxx: ADD [BX+SI],AL xxxx: ADD [BX+SI],AL xxxx: ADD [BX+SI],AL xxxx: ADD [BX+SI],AL xxxx:011a 0000 ADD [BX+SI],AL xxxx:011c 0000 ADD [BX+SI],AL xxxx:011e 0000 ADD [BX+SI],AL -t AX=0200 BX=0000 CX=0008 DX=0000 SP=FFFE BP=0000 SI=0000 DI=0000 DS=xxxx ES=xxxx SS=xxxx CS=xxxx IP=0100 NV UP EI PL NZ NA PO NC xxxx:0102 B402 MOV DL,2A -t AX=0200 BX=0000 CX=0008 DX=002A SP=FFFE BP=0000 SI=0000 DI=0000 DS=xxxx ES=xxxx SS=xxxx CS=xxxx IP=0100 NV UP EI PL NZ NA PO NC xxxx:0104 CD21 INT 21 -p * AX=022A BX=0000 CX=0008 DX=002A SP=FFFE BP=0000 SI=0000 DI=0000 DS=xxxx ES=xxxx SS=xxxx CS=xxxx IP=0100 NV UP EI PL NZ NA PO NC xxxx:0106 CD20 INT 20 -p Program terminated normally -q C:\> Példa Írjuk meg az első példában szereplő programot gépi kódban. Ebben az esetben a programot mint byte sorozatot visszük be. C:\> DEBUG -e 100 b4 02 b2 2a cd 21 cd 20 -u xxxx:0100 B402 MOV AH,02 xxxx:0102 B22A MOV DL,2A xxxx:0104 CD21 INT 21 xxxx:0106 CD20 INT 20 xxxx: ADD [BX+SI],AL xxxx:010a 0000 ADD [BX+SI],AL xxxx:010c 0000 ADD [BX+SI],AL xxxx:010e 0000 ADD [BX+SI],AL xxxx: ADD [BX+SI],AL xxxx: ADD [BX+SI],AL xxxx: ADD [BX+SI],AL xxxx: ADD [BX+SI],AL xxxx: ADD [BX+SI],AL xxxx:011a 0000 ADD [BX+SI],AL xxxx:011c 0000 ADD [BX+SI],AL xxxx:011e 0000 ADD [BX+SI],AL -q 45

46 C:\> 46

47 6. Fejezet Első programok Ez a fejezet néhány egyszerű programot mutat be részletes magyarázattal. A magyarázat néha nagyon részletes és több különböző variációt mutat be, hogy a teljesen kezdők is megszerezzék a szükséges alapokat bonyolultabb programok megírásához. 6.1 Első program Nézzük meg az első assembly programot NASM assemblerre írva. A program az 6.1. táblán látható. Ez a program az egyik legkisebb program amit írni lehet Microsoft Window alatt. 1 1 org 100h 2 INT 20h 6.1. tábla: Első program Az 1. sor nem assembly utasítás, hanem azt jelöli, hogy a program a 100-as hexadecimális címen kezdődik. A hexidecimális számot a szám után írt kis h betű jelenti. Ha a h betű nem szerepel 100-as szám után, akkor a program a 100-as decimális címen kezdődik, ami 64 hexadecimális számnak felelne meg és ez komoly hiba! A magyarázat arra, hogy miért a 100-as hexadecimális címen kell kezdődjön a program a 8.4. fejezetben található. A 2. sor egy szoftveres megszakítást hív meg. Az INT az utasítás míg a 20h a megszakítás számát jelenti. Ez a szám is hexadecimálisan van megadva. Természetesen, ha akarjuk, decimálisan is megadható a megszakítás száma. Ez a példa a 6.2. táblán látható. Az INT 20h megszakítás arra való, hogy egy COM programból kilépjünk. A kilépés azt jelenti, hogy visszatérünk az operációs rendszerhez. 1 org 100h 2 INT tábla: Első program egy változata Ha a programot az elso.asm file-ba mentettük, akkor a lefordítása a NASM segítségével a következő sor begépelésével lehetséges: 1 Lehet kisebb programot is írni, de ennek most nincs jelentősége. 47

48 C:\> nasm -o elso.com elso.asm A NASM alap esetben COM programokat fordít, így a típust nem kell megadni. A -o opcióval azt adjuk meg, hogy a fordítás eredményét milyen file-ba írjuk. Az utolsó paraméter adja meg, hogy melyik assembly forrás file-t kell lefordítani. A NASM program paramétereinek részletes listáját a 4. fejezet tartalmazza. A fordítás eredménye egy bináris file lesz, mely csak két byte-ot (!) fog tartalmazni. A file tartalma hexadecimális formában: CD 20 Ez a példa azt mutatja, hogy az INT utasítás gépi kódja a CD hexadecimális érték. Az is látható, hogy az org 100h sorból nem generálódik bináris kód, hiszen ez a sor csak azt jelöli, hogy a program milyen címen kezdődjön a memóriában. Mivel minden COM program a 100h címen kezdődik a memóriában, ezért semmilyen extra utasításra nincs szükség a bináris programban. Mit jelent az, hogy a program a 100 hexadecimális címen kezdődik? Nézzük meg a programot a DEBUG program segítségével. (A DEBUG program használatát a 5. fejezetben tárgyaltuk.) C:\> DEBUG elso.com -r AX=0000 BX=0000 CX=0002 DX=0000 SP=FFFE BP=0000 SI=0000 DI=0000 DS=1481 ES=1481 SS=1481 CS=1481 IP=0100 NV UP EI PL NZ NA PO NC 1481:0100 CD20 INT 20 -q A példában az látható, hogy a programot az 1481 szegmensre tölti be az operációs rendszer. A szegmens cím lehet más is! Ami ennél fontosabb, hogy az offszet cím éppen a 100 hexadecimális cím! 6.2 Egy karakter kinyomtatása A következő program egy csillag karaktert nyomtat ki. A program listája az 6.3. táblán látható. 1 org 100h 2 MOV AH, 2 3 MOV DL, 2ah 4 INT 21h 5 INT 20h 6.3. tábla: Egy karakter kinyomtatására szolgáló program A program nagyon egyszerű, mivel az 2. és 3. sor az INT 21h szoftveres megszakítás paramétereit állítja be. Ebben az esetben az AH regiszter határozza meg a megszakítás által végrehajtandó műveletet és a DL regiszter adja a kinyomtatandó karakter ASCII kódját. A MOV utasítás a jobb oldali argumentum értékét átmásolja a bal oldali argumentumba. A 2. sorban azt adjuk meg, hogy az AH regiszter értéke a 2-es szám legyen. Ez lényegében megfelel az értékadás műveletnek más programozási nyelvben. A 3. sorban a DL regiszternek a 2A hexadecimális számot adjuk meg. A 2A hexadecimális szám a csillag ( * ) karakternek felel meg. (Lásd az A. függelék.) A bekezdésben azt láttuk, hogy egy byte adatot számmal és karakterrel is megadhatunk. Ráadásul a karaktert egyszeres ( ) vagy kétszeres ( ) aposztrofok között is megadhatjuk. A 6.4. táblán látható programban a nyomtatandó karakter nem számmal, hanem ténylegesen karakterként van megadva. Ezt az utóbbi írásmódot érdemes használni, mert ebben az esetben egyértelmű, ASCII táblázat használata nélkül, hogy melyik karaktert akarjuk kinyomtatni. 48

49 1 org 100h 2 MOV AH, 2 3 MOV DL, * 4 INT 21h 5 INT 20h 6.4. tábla: Alternatíva a második programra 6.3 Egy szöveg kinyomtatása A harmadik program egy szöveget nyomtat ki a képernyőre. A program listája az 6.5. táblán látható. 1 org 100h 2 MOV AH, 9 3 MOV DX, adat 4 INT 21h 5 INT 20h 6 adat: db HELLO$ 6.5. tábla: Egy szöveg kinyomtatására szolgáló program Ebben a programban is az INT 21h szoftveres megszakítást kell használni. Az AH regiszter most is megszakítás által végrehajtandó műveletet határozza meg. Mivel most egy egész sor karaktert kell kinyomtatni a DL regiszter nem elegendő, de a DX regiszter is csak két byte-nyi karaktert tud tárolni. Ebben az esetben a DX regiszter a karaktersorozat címét tartalmazza. Az assembly programban szerencsére nem kell pontosan megadni a címet. Miért szerencse? Mert ahhoz, hogy pontosan megadjuk az adat címét minden assembly utasítás esetén tudnunk kellene, hogy hány byte-os gépi kód generálódik belőle és ezek segítségével kellene kiszámolnunk az aktuális címet. A programban szimbólikusan lehet megadni a karaktersorozat címét. Maga a karaktersorozat a program végére került. A karaktersorozat a bekezdésnek megfelelően van definiálva: egy cím, utána egy kettőspont, a db kulcsszó és egyszeres aposztrofok között maguk a karakterek. Az utolsó karakternek a dollár jelnek ( $ ) kell lennie. Ez a dollár jel zárja le a szöveget. 2 Ennek a speciális karakternek a segítségével állapítja meg a rendszert, hogy meddig kell a byte-okat kinyomtatni. 3 Ha a dollár jelet nem adnánk meg, akkor a rendszer addig nyomtatná a karaktereket amíg el nem ér egy dollár jelet. Elvileg előfordulhat, ha nincs dollár jel a memóriában, hogy az egész memóriát kinyomtatja a program és soha nem áll le. Ezt a programot is érdemes megnézni a DEBUG programban, hogy több dolgot is megnézzünk: C:\> DEBUG hello.com -u xxxx:0100 B409 MOV AH,02 xxxx:0102 BA0901 MOV DX,109 xxxx:0105 CD21 INT 21 xxxx:0107 CD20 INT 20 xxxx: DEC AX xxxx:010a 45 INC BP xxxx:010b 4C DEC SP xxxx:010c 4C DEC SP xxxx:010d 4D DEC DI 2 Korábbi tanulmányokból ismert lehet, hogy a C programozási nyelvben zéró áll a szöveg végén. 3 A másik stratégia, hogy megadjuk a karaktereket és a karakterek számát. Ezt módszert a Pascal programozási nyelv használja. 49

50 xxxx:010e 2400 AND AL,00 xxxx: ADD [BX+SI],AL xxxx: ADD [BX+SI],AL xxxx: ADD [BX+SI],AL xxxx: ADD [BX+SI],AL xxxx: ADD [BX+SI],AL xxxx:011a 0000 ADD [BX+SI],AL xxxx:011c 0000 ADD [BX+SI],AL xxxx:011e 0000 ADD [BX+SI],AL -d 100 xxxx:0010 B4 09 BA CD 21 CD C 4C 4D !. HELLO$. xxxx: xxxx: xxxx: xxxx: xxxx: xxxx: xxxx: q A következő észrevételeket tehetjük: Az assembler a fordítás során kiszámolta a karaktersorozat címét, ami jelenleg a 109h címtől indul. A cím két byte-ból áll és ezt a rendszer little-endian módon tárolja: BA A DEBUG program teljesen azonos módon kezeli az adatot és a programot is. Igazából nincs is különbség, hiszen mind a kettő egy byte sorozat. Például látható, hogy az unassemble parancs során a karaktersorozatot is utasításként értelmezi a DEBUG program. Ezért van az, hogy az adatokat és a programot nem szabad keverni. Erre a hibára egy példát mutat az 6.6. tábla. Ha ezt a programot megnézzük a DEBUG programban, akkor azt fogjuk tapasztalni, hogy az adatot is mint utasításokat fogja értelmezni a DEBUG és ráadásul értelmetlen kódot kapunk. A példa azt mutatja, hogy az adatokat és a programot nem szabad összekeverni! 1 org 100h 2 adat: db HELLO$ 3 MOV AH, 9 4 MOV DX, adat 5 INT 21h 6 INT 20h 6.6. tábla: Hibás program C:\> DEBUG hello.com -u xxxx: DEC AX xxxx: INC BP xxxx:0102 4C DEC SP xxxx:0103 4C DEC SP xxxx:0104 4D DEC DI xxxx: B4 AND AL,B4 xxxx: BA0001 OR [BP+SI+0100],DI xxxx:010b CD21 INT 21 xxxx:010d CD20 INT 20 xxxx:010f 0000 ADD [BX+SI],AL xxxx: ADD [BX+SI],AL xxxx: ADD [BX+SI],AL 50

51 xxxx: ADD [BX+SI],AL xxxx: ADD [BX+SI],AL xxxx: ADD [BX+SI],AL xxxx:011b 0000 ADD [BX+SI],AL xxxx:011d 0000 ADD [BX+SI],AL xxxx:011f 0000 ADD [BX+SI],AL -q 6.4 Egy karakter beolvasása A 6.7. táblázat egy nagyon egyszerű programot mutat, ami beolvas egy karaktert a felhasználótól és újra kinyomtatja. A karakter beolvasásra is használható az operációs rendszer megszakítása és ekkor a funkció kódnak 1-nek kell lennie. A funkicó kódot a 2. sorban állítjuk be. A 3. sorban az INT 21h megszakítás lefutása után a beolvasott karakter ASCII kódja az AL regiszterben lesz. Ezt akarjuk újra kinyomtatni, ezért másoljuk át a DL regiszterbe a 4. sorban, majd az 5. sorban az új funkció kódot is beállítjuk. Végül az INT 21h megszakítás meghívásával végezzük a nyomtatást a 6. sorban. 1 org 100h 2 MOV AH, 1 3 INT 21h 4 MOV DL, AL 5 MOV AH, 2 6 INT 21h 7 INT 20h 6.7. tábla: Egy karakter beolvasása és kinyomtatása 51

52 52

53 7. Fejezet Assembly nyelv utasításai Ebben a fejezetben az assembly utasítások közül a legfontosabbakat tekintjük át. A fontossági sorrendet úgy próbáltam megállapítani, hogy amelyeket a leggyakrabban használjuk a programokban, vagy amelyekre leginkább szükség lehet a tanulás során. Azt is meg kell jegyezni, hogy a fejezetben csak a 8086 processzor utasításkészletéből kerültek kiválasztásra az itt felsorolt utasítások. Ennek az a magyarázata, hogy véleményem szerint ezek az utasítások elegendőek a fontosabb koncepciók bemutatására. A fejezet végén a be nem mutatott parancsokat azért felsoroljuk. Szintakszis: mem: memória cím reg: regiszter op: memória cím vagy regiszter 53

54 7.1 Adatmozgató utasítások MOV Szintakszis MOV op1, op2 Az op2 tartalmát az op1-be írja, felülírva annak korábbi értékét. Az utasítás operandusai a következők lehetnek: MOV regiszter, számadat MOV regiszter, regiszter MOV regiszter, memória MOV memória, számadat MOV memória, regiszter Mint látható, az nem fordulhat elő, hogy egy utasítás mindkét operandusa a memóriára hivatkozzon! Egy utasítás csak egyszer hivatkozhat a memóriára! Szintén fontos, hogy közvetlenül nem lehet egyik szegmens regiszterbe sem írni, csak áttételesen, például: MOV AX, 1234h MOV DS, AX Példák MOV AX, 1234 MOV AX, ES MOV ES, AX MOV AL, 0ffH MOV AH, AL MOV AL, [BX] MOV [SI], DL MOV AX, [BX] MOV [DI], BP MOV AX, [0ffffh] MOV [0200h], DX MOV [ES:0100h], CX XCHG Szintakszis XCHG op1, op2 Az op1 és op2 tartalmát felcseréli. Mind a két operandusnak azonos méretűnek kell lennie, 8 vagy 16 bitesnek. Az utasítás előnye, hogy a cserét segédváltozó nélkül hajtja végre. Az operandusok cseréje általában a következőképpen történik: MOV temp, op2 MOV op2, op1 MOV op1, temp 54

55 Példák XCHG AL, BL XCHG CX, DX XCHG DH, [4351h] XCHG [DS:3333h], BP XLAT Szintakszis XLAT A BX regiszter egy maximum 256 bytes táblázatra mutat és az AL regiszterben levő értéknek megfelelő elemet veszi ki a táblázatból és tölti be az AL regiszterbe. Az utasítás tulajdonképpen egy konverziót hajt végre. Példák MOV BX, tabla MOV AL, 0fh XLAT MOV DL, AL MOV AH, 2 INT 21h tabla: db ABCDEF LDS Szintakszis LDS reg, mem A utasítás második operandusa által megadott memóriahelyen található 4 byte-os mutatót betölti a DS szegmens regiszterbe és az első operandusként megadott regiszterbe. Ilyen módon egyetlen utasítással lehet betölteni egy másik szegmensben levő változó címét. Az utasítás végrehajtása után azonnal címezhető a memórapozíció. Példák LDS BX, [valtozo] MOV AX, [DS:BX] LES Szintakszis LES reg, mem 55

56 A utasítás második operandusa által megadott memóriahelyen található 4 byte-os mutatót betölti az ES szegmens regiszterbe és az első operandusként megadott regiszterbe. Ilyen módon egyetlen utasítással lehet betölteni egy másik szegmensben levő változó címét. Az utasítás végrehajtása után azonnal címezhető a memórapozíció. Példák LES BX, [valtozo] MOV AX, [ES:BX] LEA Szintakszis LEA reg, mem Az utasítás első operandusaként megadott regiszterbe betölti a második operandus offszet címét. A címet futás közben számolja ki, nem előre, illetve bármilyen címzési mód használható. Példák LEA BX, [valtozo+bx] LEA DI, [BX+4] LEA DI, [AX+CX] PUSH Szintakszis PUSH op A két byte-os operandust a veremre helyezi az utasítás. utasítással: Az utasítás egyenértékű a következő két SUB SP, 2 MOV [SS:SP], op Az operandus lehet szegmens regiszter vagy bármilyen 16 bites regiszter, memória cím vagy konstans szám adat. Az utasítás fordítottja a POP utasítás. Példák PUSH AX PUSH DS PUSH [0003h] PUSH 7 ; [DS:0003] tartalma a veremre ; 0007h word a veremre PUSHF Szintakszis PUSHF 56

57 A státusz regiszter teljes tartalmát a verem tetejére másolja. Az utasítás egyenértékű a következő két utasítással: SUB SP, 2 MOV [SS:SP], statusz-regiszter Az utasítás fordítottja a POPF utasítás, mellyel együtt jól használható olyan esetekben, amikor a státusz regiszter egy-egy vagy több bitjét akarjuk módosítani vagy lekérdezni. Példák PUSHF AND [SS:SP], 1 POPF ; statusz regiszter elmentése ; csak az 1-es bitet tartjuk meg, maszkoljuk ; az új értéket a statusz regiszterbe írjuk Tegyük fel, hogy azt szeretnénk megállapítani, hogy a carry bit értéke nulla vagy egy: PUSHF POP AX AND AL, b JZ nulla_volt egy_volt:... Az első sor elmenti a státusz biteket a vermen, amit ezután letöltünk az AX regiszterbe. Az AX regiszter alsó byte-jának a nulladik bitje felel meg a carry bitnek így a harmadik sorban ezt a bitet maszkoljuk. Ezután ugró utasítást végzünk attól függően, hogy a maszkolás eredménye zérus vagy nem zérus volt. Persze ez a komplikált utasítás sorozat egyszerűen helyettesíthető a JC cim és az JNC cim utasításokkal, melyek attól függően ugranak a megadott címre, hogy a carry bit egy vagy éppen nulla volt PUSHA Szintakszis PUSHA Az utasítás 8 regisztert tölt fel a veremre. A veremmutatót (SP) 16-al csökkenti, majd a következő sorrendben a regiszterek tartalmát felmásolja a veremre: AX, CX, DX, BX, SP, BP, SI, DI. Az SP regiszter esetén az utasítás végrehajtása előtti értéket menti el! Az utasítás fordítottja a POPA utasítás POP Szintakszis POP op Az utasítás a veremmutató (SP) regiszter által mutatott word értéket az op operandusba írja bele. Az utasítás egyenértékű a következő két utasítással: MOV op, [SS:SP] ADD SP, 2 57

58 Az operandus lehet szegmens regiszter, bármilyen 16 bites regiszter vagy memória cím. Fontos, hogy az operandus nem lehet a CS regiszter. Ez azért van, mert ha ilyen módon engednénk felülírni a CS regisztert akkor ezzel áttételesen szegmensek közötti ugró utasítást engednénk meg, hiszen a következő utasításra a CS:IP regiszter páros mutat. Példák POP AX POP DS POP [0003h] POP [ES:0003h] POPF Szintakszis POPF A státusz regiszter teljes tartalmát felülírja a verem tetején tárolt 16 bites értékkel. Az utasítás egyenértékű a következő két utasítással: MOV statusz-regiszter, [SS:SP] ADD SP, 2 Az utasítás a PUSHF utasítással együtt használható jól olyan esetben, amikor a státusz regiszter egy-egy vagy több bitjét akarjuk módosítani vagy lekérdezni. Példák PUSHF AND [SS:SP], 1 POPF ; statusz regiszter elmentése ; csak az 1-es bitet tartjuk meg, maszkoljuk ; az új értéket a statusz regiszterbe írjuk POPA Szintakszis POPA Az utasítás 8 regisztert állít helyre a vermen eltárolt értékekből. Az utasítás a PUSHA fordítottja és megfelel a következő utasításoknak: POP DI POP SI POP BP ADD SP, 2 POP BX POP DX POP CX POP AX 58

59 LAHF Szintakszis LAHF Az utasítás betölti az AH regiszter megfelelő pozíciójába a SF, ZF, AF, PF és CF státusz biteket. A 7.1. tábla mutatja a bitek helyét az AH regiszterben SF ZF AF PF CF 7.1. tábla: LAHF utasítás után az AH regiszter tartalma Példák LAHF SHR AH, 6 AND AH, 1 ; AH értéke 1 vagy 0 a ZF bittol fuggoen SAHF Szintakszis SAHF Az utasítás az AH regiszter megfelelő bitjeivel felülírja a SF, ZF, AF, PF és CF státusz biteket. A 7.1. tábla mutatja a bitek helyét az AH regiszterben. 59

60 7.2 Matematikai utasítások Az ebben a fejezetben tárgyalt utasítások esetén az is feltüntetésre kerül, hogy az utasítás hogyan befolyásolja a státusz regiszter egyes bitjeit. A táblázatokban az x jelzi a megváltoztatott biteket, míg a? jel azt jelenti, hogy a bit meghatározatlan, vagyis lehet 1 és 0 is INC Szintakszis INC op Az utasítás az operandus értékéhez 1-et ad hozzá a CF státusz bit megváltoztatása nélkül. Érintett státusz bitek OF DF IF TF SF ZF AF PF CF x x x x x Példák INC AX INC BL INC [100h] INC [ES:220h] DEC Szintakszis DEC op Az utasítás az operandus értékéből 1-et von le a CF státusz bit megváltoztatása nélkül. Érintett státusz bitek OF DF IF TF SF ZF AF PF CF x x x x x Példák DEC AX DEC BL DEC [100h] DEC [ES:220h] ADD Szintakszis ADD op1, op2 60

61 Az utasítás az op1 operandushoz adja az op2 operandus értékét és az eredményt az op1-be írja. Az operandusok méretének meg kell egyeznie, két 8 bites vagy két 16 bites értéket lehet csak összeadni. Érintett státusz bitek OF DF IF TF SF ZF AF PF CF x x x x x x Példák ADD AX, BX ADD [adat], CX ADD CX, [valtozo] ADD BL, 4 ADD SI, [BP+8] ADC Szintakszis ADC op1, op2 Az utasítás az op1 operandushoz adja az op2 operandus értékét és a Carry flag (CF) értékét is, majd az eredményt az op1-be írja. Az operandusok méretének meg kell egyeznie, két 8 bites vagy két 16 bites értéket lehet csak összeadni. Érintett státusz bitek OF DF IF TF SF ZF AF PF CF x x x x x x Példák ADC AX, BX ADC [adat], CX ADC CX, [valtozo] ADC BL, 4 ADC SI, [BP+8]... ; összeadás átvitellel MOV CX, 0 MOV AL, 80h MOV AH, 80h MOV CL, AL ADD CL, AH ADC CH, 0 ; eredmény: CX tartalma 100h lesz SUB Szintakszis SUB op1, op2 61

62 Az utasítás az op1 operandusból kivonja az op2 operandus értékét és az eredményt az op1-be írja. Az operandusok méretének meg kell egyeznie, két 8 bites vagy két 16 bites értéket lehet csak összeadni. Érintett státusz bitek OF DF IF TF SF ZF AF PF CF x x x x x x Példák SUB AX, BX SUB [adat], CX SUB CX, [valtozo] SUB BL, 4 SUB SI, [BP+8] SBB Szintakszis SBB op1, op2 Az utasítás az op1 operandusból kivonja az op2 operandus értékét és a Carry flag (CF) értékét is, majd az eredményt az op1-be írja. Az operandusok méretének meg kell egyeznie, két 8 bites vagy két 16 bites értéket lehet csak összeadni. Érintett státusz bitek OF DF IF TF SF ZF AF PF CF x x x x x x Példák SBB AX, BX SBB [adat], CX SBB CX, [valtozo] SBB BL, 4 SBB SI, [BP+8] MUL Szintakszis MUL op Ez az utasítás 8 vagy 16 bites előjel nélküli számok közötti szorzást hajt végre. Az operandus határozza meg, hogy 8 vagy 16 bites számokat szoroz-e össze. 8 bites szorzás esetén az AL regiszter tartalmát összeszorozza az op operandussal és az eredményt az AX regiszterben tárolja el. Ha az eredményben az AH regiszter tartalma zérus akkor a CF és OF státusz bitek értéke zérus lesz, egyébként bites szorzás esetén az AX regiszter tartalmát összeszorozza az op operandussal és az eredményt az DX:AX regiszterekben tárolja el. Az operandusok előjel nélküli számok és bármilyen címzési mód használható. Ha az eredményben az DX regiszter tartalma zérus akkor a CF és OF státusz bitek értéke zérus lesz, egyébként 1. 62

63 Érintett státusz bitek OF DF IF TF SF ZF AF PF CF x???? x Példák MOV AL, 2 MUL 4... adat: db 4... MOV AL, 2 MUL [adat] ; AX tartalma 8 lesz ; AX tartalma 8 lesz IMUL Szintakszis IMUL op Ez az utasítás 8 vagy 16 bites előjeles szorzást hajt végre. Az operandus határozza meg, hogy 8 vagy 16 bites számokat szoroz-e össze. 8 bites szorzás esetén az AL regiszter tartalmát összeszorozza az op operandussal és az eredményt az AX regiszterben tárolja el. Ha az eredményben az AH regiszter tartalma zérus akkor a CF és OF státusz bitek értéke zérus lesz, egyébként bites szorzás esetén az AX regiszter tartalmát összeszorozza az op operandussal és az eredményt az DX:AX regiszterekben tárolja el. Az operandusok előjel nélküli számok és bármilyen címzési mód használható. Ha az eredményben az DX regiszter tartalma zérus akkor a CF és OF státusz bitek értéke zérus lesz, egyébként 1. Érintett státusz bitek OF DF IF TF SF ZF AF PF CF x???? x Példák MOV AL, 2 IMUL 4... adat: db 4... MOV AL, 2 IMUL [adat] ; AX tartalma 8 lesz ; AX tartalma 8 lesz DIV Szintakszis DIV op Ez az utasítás előjel nélküli osztást hajt végre. Ha byte, 8 bites, operandust adunk meg az utasításban, akkor az AX regiszter tartalmát az operandussal elosztja és a hányadost az AL regiszterben, a maradékot az AH regiszterben tárolja el. Word vagy szó méretű operandus esetén a DX:AX regisztrerek tartalmát 63

64 elosztja az operandussal majd a hányadost a AX regiszterben és a maradékot a DX regiszterben tárolja el. Ha az osztó zérus vagy a hányados túl nagy, hogy elférjen az AL vagy AX regiszterben, akkor az INT 0 megszakítás hívódik meg. Érintett státusz bitek OF DF IF TF SF ZF AF PF CF?????? Példák MOV AX, 12 DIV 10 ; AL = 1, AH = IDIV Szintakszis IDIV op Ez az utasítás előjeles osztást hajt végre. Ha byte, 8 bites, operandust adunk meg az utasításban, akkor az AX regiszter tartalmát az operandussal elosztja és a hányadost az AL regiszterben, a maradékot az AH regiszterben tárolja el. Word vagy szó méretű operandus esetén a DX:AX regisztrerek tartalmát elosztja az operandussal majd a hányadost a AX regiszterben és a maradékot a DX regiszterben tárolja el. Ha az osztó zérus vagy a hányados túl nagy, hogy elférjen az AL vagy AX regiszterben, akkor az INT 0 megszakítás hívódik meg. Érintett státusz bitek OF DF IF TF SF ZF AF PF CF?????? Példák MOV AX, 12 IDIV 10 ; AL = 1, AH = NEG Szintakszis NEG op Az operandust nullából kivonja és hozzáad egyet, majd az eredményt eltárolja az operandusban, felülírva annak korábbi értékét. Az utasítás lényegében kettes komplemensét képzi az operandusnak. Ha az eredmény operandus zérus, akkor a CF bit értéke zérus lesz, egyébként pedig 1. Érintett státusz bitek OF DF IF TF SF ZF AF PF CF x x x x x x 64

65 Példák NEG AX NEG BL NEG [DS:100h] CBW Szintakszis CBW Az utasítás az AL regiszterben található előjeles byte-ot előjeles szóvá alakítja az AX regiszterben. Ez úgy történik, hogy az AL regiszter legnagyobb helyiértékű bitjét bemásolja az AH regiszter minden bitjébe. Assembly-ben ez a következő utasításoknak felel meg: TEST AL, 128 JNZ egy zerus: MOV AH, 00h JMP vege egy: MOV AH, 0FFh vege: Az utasítás egy 8 bites szám előkészítését végzi előjeles osztáshoz CWD Szintakszis CWD Az utasítás az AX regiszterben található előjeles word-ot előjeles értékké alakítja az DX:AX regiszterekben. Az utasítás egy 16 bites szám előkészítését végzi előjeles osztáshoz. 65

66 7.3 Bitforgató és bitléptető utasítások Az alábbi utasításokban az op operandus lehet 8 vagy 16 bites és tetszőleges címzési módot alkalmazhatunk. Az utasításoknál a kilépő bit minden esetben megjelenik Carry bitben. Az utasítások két csoportba sorolhatók: Bitforgató, rotáló utasítások: RCL, RCR, ROL, ROR Bitléptető, shiftelő utasítások: SAL, SAR, SHL, SHR RCL Szintakszis RCL op, 1 RCL op, CL RCL op, szamlalo Az utasítás balra forgatja az operandus értékét olyan módon, hogy a legnagyobb helyiértékű bit a Carry bitbe kerül, a Carry bit pedig a legkisebb helyiértékű bit helyébe. Minden más bit eggyel balra tolódik. Az utasítás működését a 7.1. ábra is szemlélteti. Az utasítás képes a forgatást egyszer vagy többször végrehajtani, a második operandustól függően. CF 7.1. ábra: Az RCL utasítás működése Érintett státusz bitek OF DF IF TF SF ZF AF PF CF x x Ha a CF bit az operandus magas bitjével egyenlő, akkor az OF bit értéke zérus lesz, míg ha nem egyenlő akkor 1 lesz. Példák RCL AL, 1 RCL AX, 3 RCL BX, CL RCL [DS:2456h], CL Az utasítás használatára a 9.1. fejezet mutat egy példa programot RCR Szintakszis RCR op, 1 RCR op, CL RCR op, szamlalo 66

67 Az utasítás jobbra forgatja az operandus értékét olyan módon, hogy a Carry bit a legnagyobb helyiértékű bitbe másolódik, a legkisebb helyiértékű bit pedig a Carry bitbe kerül. Minden más bit eggyel jobbra tolódik. Az utasítás működését a 7.2. ábra is szemlélteti. Az utasítás képes a forgatást egyszer vagy többször végrehajtani, a második operandustól függően. CF 7.2. ábra: Az RCR utasítás működése Érintett státusz bitek OF DF IF TF SF ZF AF PF CF x x Példák RCR AL, 1 RCR AX, 3 RCR BX, CL RCR [DS:2456h], CL ROL Szintakszis ROL op, 1 ROL op, CL ROL op, szamlalo Az utasítás az első operandust balra rotálja önmagán és a kicsorgó legmagasabb helyiértékű bit kerül a Carry bitbe. A működést a 7.3. ábra mutatja be. Az utasítás képes a forgatást egyszer vagy többször végrehajtani, a második operandustól függően. CF 7.3. ábra: Az ROL utasítás működése Érintett státusz bitek OF DF IF TF SF ZF AF PF CF x x Példák ROL AL, 1 67

68 ROL AX, 3 ROL BX, CL ROl [DS:2456h], CL ROR Szintakszis ROR op, 1 ROR op, CL ROR op, szamlalo Az utasítás az első operandust jobbra rotálja önmagán és a kicsorgó legalacsonyabb helyiértékű bit kerül a Carry bitbe. A működést a 7.4. ábra mutatja be. Az utasítás képes a forgatást egyszer vagy többször végrehajtani, a második operandustól függően. CF 7.4. ábra: Az ROR utasítás működése Érintett státusz bitek OF DF IF TF SF ZF AF PF CF x x Példák ROR AL, 1 ROR AX, 3 ROR BX, CL ROR [DS:2456h], CL SAL, SHL Szintakszis SAL op, 1 SAL op, CL SAL op, szamlalo SHL op, 1 SHL op, CL SHL op, szamlalo Az utasítás balra léptet minden bitet. A legmagasabb helyiértékű bit a Carry bitbe kerül, míg a legalacsonyabb helyiértékű bit törlődik. Az utasítás működését a 7.5. ábra mutatja be. Az utasítás képes a bit léptetést egyszer vagy többször végrehajtani, a második operandustól függően. Érdemes megjegyezni, hogy ha egyszer hajtjuk végre az utasítást, akkor ez megfelel a 2-vel való szorzásnak. Az esetleges szorzási túlcsordulás a Carry bitben jelenik meg. 68

69 CF 7.5. ábra: Az SAL utasítás működése 0 Érintett státusz bitek OF DF IF TF SF ZF AF PF CF x x x x Példák SHL AL, 1 SHL AX, 3 SHL BX, CL SHL [DS:2456h], CL Az utasítás használatára a 8.7. fejezet mutat néhány példát SAR Szintakszis SAR op, 1 SAR op, CL SAR op, szamlalo Az utasítás jobbra léptet minden bitet. A legalacsonyabb helyiértékű bit a Carry bitbe kerül, míg a legmagasabb helyiértékű bit ismétlődik. Az utasítás működését a 7.6. ábra mutatja be. Az utasítás képes a bit léptetést egyszer vagy többször végrehajtani, a második operandustól függően. Érdemes megjegyezni, hogy ha egyszer hajtjuk végre az utasítást, akkor ez megfelel a 2-vel való előjeles osztásnak. A hányados az operandusban marad, míg a maradék a Carry bitbe kerül. CF 7.6. ábra: Az SAR utasítás működése Érintett státusz bitek OF DF IF TF SF ZF AF PF CF x x x x SHR Szintakszis SHR op, 1 69

70 SHR op, CL SHR op, szamlalo Az utasítás jobbra léptet minden bitet. A legalacsonyabb helyiértékű bit a Carry bitbe kerül, míg a legmagasabb helyiértékű bit törlődik. Az utasítás működését a 7.7. ábra mutatja be. Az utasítás képes a bit léptetést egyszer vagy többször végrehajtani, a második operandustól függően. Érdemes megjegyezni, hogy ha egyszer hajtjuk végre az utasítást, akkor ez megfelel a 2-vel való előjel nélküli osztásnak. A hányados az operandusban marad, míg a maradék a Carry bitbe kerül. CF ábra: Az SHR utasítás működése Érintett státusz bitek OF DF IF TF SF ZF AF PF CF x x x x Példák Az alábbi programrészlet azt ellenőrzi, hogy a legalacsonyabb helyiértékű bit zérus vagy egy:... SHR AL, 1 JNC zerus ; kód végrehajtása ha a bit 1 JMP vege zerus: ; kód végrehajtása ha a bit 0 vege:... 70

71 7.4 Logikai utasítások AND A B A AND B tábla: AND utasítás igazság táblája OR A B A OR B tábla: OR utasítás igazság táblája XOR A B A XOR B tábla: XOR utasítás igazság táblája NOT A NOT A tábla: NOT utasítás igazság táblája TEST CMP 71

72 Feltétel Előjeles Előjel nélkül = JE, JZ JE, JZ nem = JNE, JNZ JNE, JNZ > JG, JNLE JA, JNBE >= JGE, JNL JAE, JNB < JL, JNGE JB, JNAE <= JLE, JNG JBE, JNA 7.6. tábla: Feltételes utasítások. 7.5 Vezérlésátadó utasítások JMP Feltételes utasítások Az assembly programozási nyelvben nincsennek magasabb szintű programozási konstrukciók, például ciklus. Minden ilyen szerkezetet feltételes ugrásokkal kell megvalósítani. A feltételes ugrás azt jelenti, hogy a program futása nem a következő utasítással folytatódik, hanem a feltételes ugrás által megadott címen, ha a feltétel teljesül. Itt a feltétel nem jelenti azt, hogy az utasítás valamilyen összehasonlítást végezne, csak annyit, hogy egy korábbi utasítás által beállított státusz bit értéke alapján történhet ugrás. Például a JE cím utasítás azt jelenti, hogy ha a ZF (zérus státusz bit) értéke egy (1) akkor a cím -en folytatódik a program végrehajtása. Ha a zérus státusz bit értéke nulla (0) akkor a JE cím utasítás utáni utasítással folytatódik a program végrehajtása. A 7.6. táblázat sorolja fel a különböző feltételes ugró utasításokat. Fontos, hogy lehetőség szerint ne keverjük az előjeles és előjel néküli feltételes ugró utasításokat JCXZ LOOP Szintakszis LOOP cim Az utasítás 1-el csökkenti a CX regiszter értékét és ha az így kapott érték nem nulla akkor a cim címre adja át a vezérlést. Ha a csökkentés után a CX értéke zérus lesz, akkor a LOOP utasítás utáni utasítással folytatja program a végrehajtást. Fontos megérteni, hogy ez az utasítás egy hátul tesztelő ciklusnak felel meg. Így először mindig a csökkentés következik be és csak utána az ellenőrzés LOOPNZ Szintakszis LOOPNZ cim LOOPNE cim Az utasítás angol neve: Loop while Non Zero vagy Loop while Not Equal. Az utasítás először is megvizsgálja a ZF státusz bit értékét. Ha a státusz bit értéke nulla, akkor csökkenti a CX regiszter értékét 1-el és ha így a CX regiszter értéke még nem nulla, akkor végrehajtja az ugrást a cim címre. Ellenkező esetben az utasítás után folytatja a program a végrehajtást. 72

73 7.5.6 LOOPZ Szintakszis LOOPZ cim LOOPE cim Az utasítás angol neve: Loop while Zero vagy Loop while Equal. Az utasítás először is megvizsgálja a ZF státusz bit értékét. Ha a státusz bit értéke 1, akkor csökkenti a CX regiszter értékét 1-el és ha így a CX regiszter értéke még nem nulla, akkor végrehajtja az ugrást a cim címre. Ellenkező esetben az utasítás után folytatja a program a végrehajtást. Ennél az utasításnál a ZF státusz bit jelöli, hogy miért ér véget a ciklus. Ha CX regiszter lett zérus, akkor a ZF státusz bit értéke 1 lesz, ha pedig az utolsó státusz bit művelet eredménye volt nullától különböző, akkor a ZF státusz bit értéke nulla lesz CALL Szintakszis CALL cim Az utasítás először elmenti a CALL utasítást követő utasítás címét a verem tetején. Ez lesz a függvény visszatérési címe. Ezután az argumentumként megadott címre adja át a vezérlést, vagyis a cim címen folytatódik a vgérehajtás. Az utasítással lehet közeli vagy távoli vezérlés átadást végrehajtani. Más szavakkal az argumentum lehet csak offszet vagy szegmens és offszet cím. Az utasítást a függvények meghívására használjuk. A függvényből visszatérést a RET utasítás végzi, lásd bekezdés. A függvényekről részletesebb leírás a 10. fejezetben található RET Szintakszis RET vagy RET n Az utasítás egy függvény végén szokott szerepelni. Feladata, hogy a verem tetejéről levegyen egy visszatérési címet, majd a vezérlést átadja erre a címre. Fontos megérteni, hogy az utasítás nem vizsgálja meg a verem tetejét, akármit talál ott az utasítás azt visszaatérési címnek fogja tekinteni. Az utasításhoz optcionálisan tartozhat egy argumentum. Ebben az esetben az argumentum egy szám n, ami megadja, hogy a visszatérési cím levétele után még hágy byte-ot kell levenni a veremről. Ezeket a levett értékeket eldobjuk. A különböző függvény hívás utáni takarítási stratégiákról a bekezdésben olvashatunk INT 73

74 7.6 String kezelő utasítások A string kezelő utasítások memóriablokkokkal végeznek műveletet. A string, tulajdonképpen szöveg, a szöveg pedig nem más mint egy karakter sorozat vagy byte sorozat és egy byte sorozat amikor a memóriában tároljuk akkor pedig megfelel egy memóriablokknak. Az utasítások általában a DS:SI, ES:DI és AX regisztereket használják. A regisztereken kívül fontos szerepet játszik még a Direction (Irány) státusz bit, mivel ez határozza meg, hogy a művelet során a címekhez hozzáadunk vagy a címekből kivonunk értékeket. A Direction (Irány) státusz bitet a CLD ( bekezdés) és STD ( bekezdés) utasítások állítják be. A string kezelő parancsokat részletesen tárgyaljuk a 12. fejezetben MOVSB, MOVSW Szintakszis MOVSB MOVSW Az utasítások angol neve: MOVe String Byte illetve MOVe String Word. Az utasítás a DS:SI regiszterek által megcímzett byte-ot átmásolja az ES:DI címre. Az SI és DI regiszterek értéke 1-el növekszik ha a Direction státusz bit értéke nulla vagy a regiszterek értéke 1-el csökken, ha a Direction státusz bit értéke egy. A MOVSW utasítás csak annyiban különbözik, hogy két byte-ot vagyis egy szót (word) másol át és az SI és DI regiszterek értéke kettővel módosul a Direction státusz bit értékétől függően. Ezek az utasítások a kivételek közé tartoznak, mivel egy utasítás kétszer is hozzáfér a memóriához. A másik fontos megjegyzés, hogy ezek az utasítások csak egy szegmensnyi adatot képesek átmásolni, mivel ha a DI regiszter értéke eléris a FFFFh értéket és 1-el megnöveljük az értékét akkor a nulla értéket kapjuk, ami a szegmens első byte-jára mutat, így körbefordulás következik be. Ez azért van, mert az utasítás nem módosítja a szegmens regisztereket, azokat nem növeli és nem csökkenti. A osnál modernebb processzorokon azért lehet több adatot átmásolni, mivel az ESI és EDI 32 bites regisztereket használjuk a műveletben. Nagyon fontos, hogy az utasítás a használt regiszterek kezdeti értékét nem állítja be, azt a programozónak kell megadni! Példák Az alábbbi kódrészlet átmásol 4 word méretű adatot: forras: dw 1111h, 2222h, 3333h, 4444h cel: dw 0000h, 0000h, 0000h, 0000h... MOV SI, forras MOV DI, cel MOV CX, 4 CLD ujra: MOVSW LOOP ujra... Ezt a példát érdemes összehasonlítani egy egyszerűsített változattal, ami a bekezdésben található. 74

75 7.6.2 CMPSB, CMPSW Szintakszis CMPSB CMPSW Az utasítások angol neve: CoMPare String Byte illetve CoMPare String Word. A CMPSB utasítás a DS:SI regiszterek által megcímzett byte-ot összehasonlítja az ES:DI által megcímzett byte-al. Az SI és DI regiszterek értéke 1-el növekszik ha a Direction státusz bit értéke nulla vagy a regiszterek értéke 1-el csökken, ha a Direction státusz bit értéke egy. Az utasítások a státusz biteket is beállítja úgy, mintha egy CMP utasítást (lásd bekezdés) hajtottunk volna végre. A CMPSW utasítás hasonlóan működik, de word méretű adatokkal dolgozik, és az SI és DI regiszterek értéke is 2-vel változik LODSB, LODSW Szintakszis LODSB LODSW Az utasítások angol neve: LOaD String Byte illetve LOaD String Word. A LODSB utasítás a DS:SI regiszterek által megcímzett byte-ot betölti az AL regiszterbe. Az SI regiszter értéke 1-el növekszik ha a Direction státusz bit értéke nulla vagy az SI regiszter értéke 1-el csökken, ha a Direction státusz bit értéke egy. A LODSW utasítás csak abban különbözik, hogy a DS:SI regiszterek által megadott címről egy word-öt (2 byte-ot) töltünk be az AX regiszterbe STOSB, STOSW Szintakszis STOSB STOSW Az utasítások angol neve: STOre String Byte illetve STOre String Word. A STOSB utasítás az AL regiszter értékét az ES:DI regiszterek által megadott címre írja. Az DI regiszter értéke 1-el növekszik ha a Direction státusz bit értéke nulla vagy az DI regiszter értéke 1-el csökken, ha a Direction státusz bit értéke egy. A STOSW utasítás csak abban különbözik, hogy az AX regiszter tartalmát írja ki az ES:DI regiszterek által megadott címre. A DI regiszter értéke 2-vel változik a Directioin státusz bit értékétől függően SCASB, SCASW Szintakszis SCASB SCASW Az utasítások angol neve: SCAn String Byte illetve SCAn String Word. A SCASB utasítás az AL regiszter értékét összehasonlítja az ES:DI regiszterek által megcímzett byte-al. A státusz biteket az [ES:DI] - AL különbség szerint lesznek beállítva. Az DI regiszter értéke 1-el növekszik ha a 75

76 Direction státusz bit értéke nulla vagy az DI regiszter értéke 1-el csökken, ha a Direction státusz bit értéke egy. A SCASW utasítás csak abban különbözik, hogy az AX regiszter tartalmát hasonlítja össze az ES:DI regiszterek által megcímzett word-el. A DI regiszter értéke 2-vel változik a Directioin státusz bit értékétől függően. Amikor a REP prefix-el együtt használjuk, akkor ez az utasítás, a megadott memória blokkban az AL vagy AX regiszter tartalmát keresi meg REP Szintakszis REP string-utasítás Ez tulajdonképpen nem is utasítás, hanem egy prefix, amit a string kezelő utasítások elé tehetünk. A neve a REPeat, ismétlés angol szóból származik. Ennek a prefixnek az a feladata, hogy az utána megadott string kezelő utasítást többször hajtsa végre. Az ismétlések számát a CX regiszter adja meg, amit előre be kell állítani. Fontos, hogy a CX értéke az adatok számát adja meg, nem pedig az adatok byte-jainak számát. Például 4 word adat másolása esetén az alább két kódrészlet egyenértékű, de figyeljük meg a különböző CX értékeket:... MOV CX, 4 REP MOVSW... egyenértékű a következő kóddal:... MOV CX, 8 REP MOVSB... Ennek a prefixnek az a nagy előnye, hogy egyetlen utasítással akár egész szegmenst át lehet másolni (MOVSB), szegmensnyi adatokat össze lehet hasonlítani (CMPSB), felül lehet írni (STOSB) vagy egy értéket a szegmensben megkeresni (SCASB). További részletek a 12. fejezetben található. Ezzel a prefix-el sem lehet a szegmensek határát átlépni. Így például word adatokkal végzett műveletek esetén a 8086-os processzoron nincs értelme a 7FFFh értéknél nagyobb értéket adni a CX regiszternek a REP prefix esetén. Példák Az alábbbi kódrészlet átmásol 4 word méretű adatot. A programrészlet egyenértékű a bekezdéssben bemutatott példával. forras: dw 1111h, 2222h, 3333h, 4444h cel: dw 0000h, 0000h, 0000h, 0000h... MOV SI, forras MOV DI, cel MOV CX, 4 CLD REP MOVSW... 76

77 7.6.7 REPZ Szintakszis REPZ string-utasítás REPE string-utasítás Ennek a prefixnek a neve az REPeat while Zero illetve REPeat while Equal kifejezésekből származik. Ez a prefix is a CX regisztert használja, az értékét csökkenti amíg el nem éri a zérust. A prefix ezen kívül a Zérus státusz bitet (ZF) is megvizsgálja és ha a bit értéke zérus (vagyis a művelet eredménye nem volt zérus) akkor abbahagyja az utasítás ismétlését. Fontos, hogy a kilépés okát a Zérus (ZF) státusz bitből lehet megtudni. Ha az ismétlés azért állt le, mert az utolsó művelet eredménye nullától különbözött, akkor a ZF értéke zérus lesz, ha pedig a CX regiszter lett zérus, akkor a ZF értéke egy lesz. Érdekes, hogy a REPZ prefixnek és REP prefixnek az operáció kódja megegyezik. Ez azt jelenti, hogy a MOVS, LODS és STOS utasítások esetén csak a REP prefixnek van érteme. A SCAS és CMPS utasítások esetén a prefix pedig mindig REPZ-nek felel meg. Ez azért van, mert a MOVS, LODS és STOS utasítások nem módosítják a státusz biteket, így nem gond, hogy csak a REP prefixet lehet használni REPNZ Szintakszis REPNZ string-utasítás REPNE string-utasítás Ennek a prefixnek a neve az REPeat while Non Zero illetve REPeat while Not Equal kifejezésekből származik. Ez a prefix is a CX regisztert használja, az értékét csökkenti amíg el nem éri a zérust. A prefix ezen kívül a Zérus státusz bitet (ZF) is megvizsgálja és ha a bit értéke egy (vagyis az utolsó művelet eredménye zérus volt) akkor abbahagyja az utasítás ismétlését. Fontos, hogy a kilépés okát a Zérus (ZF) státusz bitből lehet megtudni. Ha az ismétlés azért állt le, mert az utolsó művelet eredménye zérus volt, akkor a ZF értéke egy lesz, ha pedig a CX regiszter lett zérus, akkor a ZF értéke zérus lesz. 77

78 7.7 Processzor vezérlő utasítások CLC Szintakszis CLC Az utasítás angol neve: CLear Carry. Az utasítás nullára állítja a Carry státusz bit értékét STC Szintakszis STC Az utasítás angol neve: SeT Carry. Az utasítás 1-re állítja a Carry státusz bit értékét CMC Szintakszis CMC Az utasítás angol neve: CoMplement Carry. Az utasítás nullára állítja a Carry státusz bit értékét CLD Szintakszis CLD Az utasítás angol neve: CLear Direction. Az utasítás 0-ra állítja a Direction (Irány) státusz bitet, aminek a string kezelő utasítások esetén van jelentősége. Ebben az esetben a MOVS, LODS, STOS, SCAS és CMPS parancsok növelik a DI és/vagy SI regiszter értékét STD Szintakszis STD Az utasítás angol neve: SeT Direction. Az utasítás 1-re állítja a Direction (Irány) státusz bitet, aminek a string kezelő utasítások esetén van jelentősége. Ebben az esetben a MOVS, LODS, STOS, SCAS és CMPS parancsok csökkentik a DI és/vagy SI regiszter értékét CLI Szintakszis CLI Az utasítás angol neve: CLear Interrupt. Az utasítás nullára állítja az Interrupt státusz bit értékét. Ez az utasítás letíltja a hardware megszakítások fogadását. 78

79 7.7.7 STI Szintakszis STI Az utasítás angol neve: SeT Interrupt. Az utasítás 1-re állítja az Interrupt státusz bit értékét. Ez az utasítás engedélyezi a hardware megszakítások fogadását. 79

80 7.8 Egyéb utasítások NOP Szintakszis NOP Ez egy nagyon egyszerű utasítás, mivel nem csinál semmit. Angol nevén: No OPeration IN OUT 80

81 7.9 Ellenőrző kérdések 1. Tegyük fel, hogy az Intel processzorokon nem lenne LDS utasítás. Hogyan implementálná ezt az utasítást? Írja le a kódot ami egyenértékű az LDS utasítással. 2. Tegyük fel, hogy a következő adatok vannak definiálva: num1 DW 100 num2 DB 225 char1 DB Y num3 DD 0 Érvényesek-e az alábbi utasítások: (a) MOV AX, BX (b) MOV AX, num2 (c) MOV BL, num1 (d) MOV BL, [num1] (e) MOV DH, char1 (f) MOV char1, num2 (g) MOV [char1], [num2] (h) MOV IP, num1 (i) ADD 75, AX (j) CMP 75, AX (k) SUB char1, A (l) SUB [char1], A (m) XCHG AL, num2 (n) XCHG AL, [num2] (o) XCHG AL, 23d (p) XCHG AL, [23d] (q) INC num3 (r) INC [num3] 3. Az alábbi kódrészletekben a MOV AX, 1 vagy az MOV BX, 1 utasítás fog lefutni: (a) (b) mov CX,5 sub DX,DX cmp DX,CX jge jump1 mov BX,1 jmp skip1 jump1: mov AX,10 skip1:... mov CX,5 mov DX,10 shr DX,1 cmp CX,DX 81

82 je jump1 mov BX,1 jmp skip1 jump1: mov AX,10 skip1: Magyarázza el szövegesen, hogy mit csinál a következő kód részlet: (a) NOT AX ADD AX, 1 (b) NOT BX ADD BX, 1 (c) SUB AH, AH SUB DH, DH MOV DL, AL ADD DX, DX ADD DX, DX ADD DX, AX ADD DX, DX (d) SUB AH, AH SUB DH, DH MOV DL, AL MOV CL, 3 SHL DX, CL SHL AX, 1 ADD DX, AX 5. Kell-e tudni az AX regiszter kezdeti tartalmát ahhoz, hogy megállapítsuk az AX regiszter tartalmát az alábbi kódrészlet lefutása után? Ha igen, magyarázza meg miért! Ha nem, magyarázza el mi lesz az AX regiszter tartalma? (a) MOV DX, AX NOT AX OR AX, DX (b) MOV DX, AX NOT AX AND AX, DX 82

83 8. Fejezet Assembly programokról Talán csodálkozunk, hogy az eddigi fejezetek miért kellettek. Ahhoz, hogy assembly nyelven programot tudjunk írni, szükséges tudni: a CPU regiszterek neveit a verem hogyan működik hogyan lépjünk ki hogyan definiáljunk szimbólumokat, lokális és globális hogyan használjuk az eszközöket (fordítókat, linkereket) 8.1 Programozási módszer Az assembly programozásra jellemző, hogy mindent explicit módon meg kell adni a programban. Nincsennek magas szintű programozási konstrukciók, azokat magunknak kell létrehoznunk. A 8.1. táblán látható egy elöltesztelő ciklus megvalósítása. A táblán látható hogyan alakul át az elöltesztelő ciklus feltételes ugró utasításokká, hiszen csak ezek állnak rendelkezésre az assembly programozási nyelvben. Ugyanakkor az is látható, hogy a magas szintű programban a feltételben az szerepel, hogy ismételjük meg az utasításokat ha az AX regiszter nem egyenlő a zérus értékkel. Ezzel szemben az assembly kódban a feltételes ugrás akkor ugrik a ciklus végére, ha az utolsó műveletben a zérus státusz bit be lett állítva, vagyis az összehasonlításnál az AX regisztert zérusnak találtuk. Vagyis a feltétel megfordult az assembly kódban. Valójában nem kell ennek így lenni, lehetőségünk van arra, hogy ugyanazt a feltételt használjuk az assembly-ben, mint a magasabb szintű programozási nyelvben. Lényegében arról van szó, hogy a feltételes assembly utasítások megfordíthatók. Ez látható a 8.2. táblán. Így a program egy kicsit komplikáltabb. while(ax!= 0) {... } ujra: CMP AX, 0 JZ vege... JMP ujra vege: 8.1. tábla: Elöltesztelő ciklus megvalósítása 83

84 while(ax!= 0) {... } ujra: CMP AX, 0 JNZ tovabb JMP vege tovabb:... JMP ujra vege: 8.2. tábla: Alternatív megvalósítása az elöltesztelő ciklusnak 8.1. ábra: A megszakítások osztályozása 8.2 Megszakítások A megszakítás, vagy angol nevén interrupt, egy mechanizmus ami által a program végrehajtásának folyamata megváltozhat. Ilyen mechanizmus az ugró utasítások és ide tartozik a függvény is (lásd 10. fejezet). Lényegében a megszakítás felfüggeszti a jelenlegi program futását és a vezérlést átadja egy megszakítás kezelőnek (interrupt service routine, ISR). Amikor a megszakítás kezelő befejezte a műveleteit, visszaadja a vezérlés arra a pontra, ahol a programot megszakítottuk, úgy mintha a programot meg sem szakítottuk volna. A fő különbség a függvények és a megszakítások között, hogy a megszakítást szoftver is meghívhatja és hardware esemény is kiválthatja, míg egy függvényt csak szoftver hívhatja meg. Tulajdonképpen ez a különbség nagyon fontos, mivel amikor egy hardware-s esemény bekövetkezik, akkor a hardware csak megszakítás segítségével tud a processzortól számítási időt szerezni, hogy az adott eseményt lekezeljük. Így a megszakítások egy hatékony lehetőséget biztosítanak nem várt események kezelésére is. A másik nagyon fontos különbség a függvények és a megszakítások között, hogy a megszakítás kezelők memória rezidensek (állandóan a memóriában vannak), míg a függvényeket a programmal együtt töltünk be a memóriába csak egy időre. A szoftveres megszakítások kiváltására az INT utasítást lehet használni. Például amikor a felhasználótól szeretnénk egy karaktert beolvasni, akkor a megfelelő INT utasítás végrehajtásával a megfelelő megszakítás indul el, amely a billentyűzetről beolvassa a karaktert. Valójában a szoftveres és hardware-es magszakításokon kívűl van egy harmadik típusú megszakítás is, az úgynevezett kivételek (exception). A kivételek az utasítás hibákat kezelik, mint például a zérussal való osztás. A 8.1. ábra mutatja a megszakítások osztályozását Hardware-es megszakítások A hardware-es megszakítások is két csoportba sorolhatók: maszkólható és 84

85 nem maszkolható hardware-es megszakítások. A nem maszkolható megszakításokat (Non-maskable Interrupt, NMI) a processzor mindig azonnal kezeli. Ilyen megszakítás generálódik például a RAM paritás hiba esetén ami memória hibát jelöl. A maszkolható megszakítások esetén a megszakítás végrehajtása késleltethető addig, amíg a végrehajtás egy kedvezőbb pontot ér el. A maszkolható megszakítások működésére a következő példával mutatható be. Egy program futása során egy megszakítás következik be. Ekkor a program futása felfüggesztődik és elindul a megszakítás kezelő futása. Ha a megszakítás nem akarja, hogy egy újabb megszakítás beavatkozhasson akkor a processzor maszkolhatja a megszakításokat. Ha a megszakításokat maszkoljuk, akkor egy újabb megszakításnak várnia kell. (Kivéve a nem maszkolható megszakítások!) Megszakítások 8086-os processzorokon Ez a fejezet csak a 8086-os processzorok megszakítás kezelésével foglalkozik. Ez a modern processzorokon a valós modú (real mode) megszakítás kezelésnek felel meg. A védett modú (protected mode) megszakítás kezeléssel itt nem foglalkozunk. A processzor 256 megszakítást támogat, amelyek száma nullától 255-ig tart. A megszakítás száma nagyon fontos mivel ezzel azonosíthatjuk a megszakításokat és ez a szám a megszakítás tábla indexét is megadja. A megszakítás tábla a nullás fizikai címen található. A megszakítás táblában minden bejegyzés 4 byte-ból áll. Egy ilyen bejegyzést megszakítás vektornak szoktunk nevezni. Egy bejegyzés egy CS:IP regiszter párost ad meg, 2 byte a CS regiszternek és 2 byte az IP regiszternek. Ezek után ha egy megszakítás címét szeretnénk megtalálni a megszakítás táblában, akkor a megszakítás indexét szorozni kell 4-el és a kapott hexadecimális szám adja meg az offszetet. Például az INT 10h megszakítás címe: 10h 4 = 40h, ami a 0000:0040h címnek felel meg. Ha már ki tudjuk számolni egy megszakítás címét, akkor az INT 10h megszakítást másképpen is meg tudjuk hívni: mov bx,0h mov es,bx mov bx,40h mov ah,0eh ; megszakítás paraméterei mov al, 1 pushf call far es:[bx] popf Részletesen a következő történik egy megszakítás végrehajtása során: 1. Először a státusz biteket elmentjük a vermen 2. Töröljük a TRAP és INTERRUPT státusz biteket, hogy más megszakítás ne szakíthassa meg az aktuális megszakítás kezelő végrehajtását 3. A CS és IP regisztereket feltöltjük a veremre 4. A megszakítás táblából betöltjük a CS regiszter értékét: (index 4) A megszakítás táblából betöltjük az IP regiszter értékét: (index 4) 6. Megszakítás kezelése 7. Helyreállítjuk az IP regisztert a veremből 85

86 8.2. ábra: Kommunikációs lehetőségek alkalmazások és a hardware között 8. Helyreállítjuk a CS regisztert a veremből 9. Helyreállítjuk a státusz regisztert a veremből Az utolsó három műveletet az IRET utasítás végzi el INT 21h megszakítás A szoftveres megszakításokat az INT utasítás kiadásával lehet elindítani. A DOS rendszereken (lényegében a Windows alatt is) az INT 21h szoftveres megszakítás nagyon sokféle szolgáltatást biztosít a programozó számára. A 8.2. ábra mutatja, hogy egy alkalmazásnak több lehetősége is a hardware eszközökkel való kommunikációra. A legmagasabb szintű szolgáltatásokat az INT 21h adja, például file kezelés. A BIOS szolgáltatás azt jelenti, hogy olyan megszakításokat hívunk meg amelyek közvetlenebbül kommunikálnak a hardware-el. Ilyen megszakítás az INT 10h amelyik a grafikus megjelenítő kártyával és az INT 16h amelyik a billentyűzettel kommunikál. Egy harmadik lehetőség, amikor közvetlenül egy porton keresztül küldünk ki adatokat a hardware-re és ugyanígy a portból olvasunk adatokat. Ez a legalacsonyabb szintű hardware vezérlés, amihez már az adott hardware eszköz kézikönyve is szükséges, hiszen tudnunk kell, hogy milyen byte-okat küldjünk, milyen sorrendben és milyen időzítéssel Kivételek A kivételeket három csoportja soroljuk: 1. faults (hibák), 2. traps (csapdák) és 3. aborts (kilépések). A faults és traps kivételek utasítás határon jelennek meg. A faults kivételek az utasítás végrehajtása előtti állapotot használják a bekövetkeztük során. Ilyen például a nullával való osztás, ami a DIV és IDIV utasítások végrehajtása során következhet be. Így ha ilyen hiba következik be a kivétel a hiba végrehajtása előtti állapotra fogja visszaállítani a processzort. Másik tipikus fault kivétel a segmentnot-present hiba, ami azt jelenti, hogy az adott szegmens nincs a memóriában. A hiba bekövetkezte után a hiányzó szegmenst be kell tölteni és a program végrehajtása ezután folytatódhat. Itt is az utasítás 86

87 előtti állapottól folytatódik a végrehajtás, hiszen az utasításhoz szükséges szegmens már jelen van a memórában és így újra végrehajtható az utasítás. Traps kivételek ezzel szemben az utasítást követő határon hajtódnak végre, vagyis ebben az esetben nincs utasítás újravégrehajtás. Például a túlcsordulás (overflow) megszakítás egy ilyen trap-nek felel meg. A felhasználó által definiált megszakítások is trap-nek felelnek meg. Az abort megszakítások hibákat jelentenek, például hardware hibákat vagy nem konzisztens rendszer táblákról jelentenek a felhasználó felé. 8.3 Kitérő Linux-ra Bár eddig nem sok szó esett a Linuxról de egy rövid bekezdésig itt megemlíteném. A Linux rendszer nagyon sok szolgáltatást biztosít a 80h megszakításon keresztül. Valójában 180 különböző rendszer hívást biztosít ez a megszakítás. A szolgáltatást itt is az EAX regiszter értékével lehet beállítani. 8.4 COM programok A DOS 1.0 operációs rendszer csak COM típusú programokat használt. Az utána következő DOS 2.0 során vezették csak be az EXE programokat. A DOS 1.0 rendszer alatt egy igen egyszerű szabály szerint működtek a programok: miután elindult a program a teljes memóriát kezelhette a program. Ebben az időben az operációs rendszer még nem használt memória menedzselést (memory management), így egy program bármit csinálhatott a teljes memóriában. Az operációs rendszer csak azt tartotta nyilván, hogy hol van az első szabad hely ahova a következő programot betöltheti. Amikor a DOS egy új programot akart elindítani, akkor előkészített egy speciális területet a program számára a szabad terület első 256 byte-nyi helyén. A speciális terület neve: Program Segment Prefix (PSP). Az előkészítés után a DOS egyszerűen betöltötte a programot a 256 byte-nyi PSP utáni területre. Mindenféle ellenőrzés és további feldolgozás nélkül. A betöltés után még egy-két regisztert előkészített a rendszer, beállított egy visszatérési címet, majd a program elejére ugorva elindította azt. Mivel 256 byte egyenlő 100h hexadecimális értékkel ezért van az, hogy a COM programok a 100h címen kezdődnek. Mindig! A DOS által elvégzett beállítások egy COM programhoz: A CS, DS, ES, SS regisztereket beállítja a PSP elejére. Az IP regiszternek a 100h értéket adja, ahol majd a program futása elkezdődik. Az SP regiszter a verem tetejére van állítva. Ez általában az FFFEh értéknek felel meg. (Ez a szegmens tetejének címe.) A BX:CX regiszter pár a COM program méretét fogja tükrözni. A BX regiszter a 32 bites értéknek a felső 16 bitjét míg a CX az alsó 16 bitjét tárolja. Így egy 256d byte méretű program esetén: BX = 0000h CX = 0100h Az AX, DX, SI és DI regiszterek értéke zérus lesz. 87

88 8.4.1 Program Segment Prefix 8.5 EXE programok 8.6 XOR használata 8.7 Assembly integer aritmetika BCD aritmetika 88

89 8.8 Ellenőrző kérdések 1. Mik a különbségek függvények és megszakítások között? 2. Amikor egy megszakítás végrehajtódik, akkor a státusz bitek automatikusan elmentődnek. Ugyanakkor egy függvényhívás során a státusz bitek nem mentődnek el automatikusan. Magyarázza meg miért van ez így? 3. Hogyan lehet kikapcsolni a maszkolható megszakításokat? 4. Hogyan csoportosítjuk a megszakításokat? 89

90 90

91 9. Fejezet Példa programok Ebben a fejezetben néhány példa program kerül bemutatásra, melyek remélhetőleg elősegítik az assembly programozás elsajátítását. A programok nincsennek optimalizálva és ebben a fejezetben a lehető legegyszerűbb változatban kerülnek bemutatásra. A későbbi fejezetek átolvasása után arra biztatnám az olvasót, hogy térjen vissza ezekhez a programokhoz és próbálja meg őket átalakítani, rövidebb, más módon is megírni. 9.1 Egy byte bináris kinyomtatása Ez a program egy szokásos gyakorlat az assembly nyelv tanulás során. A program azt mutatja be, hogy egy byte hogyan épül fel bitekből, és a biteket hogyan lehet megfelelő sorrendben kinyomtatni. A 9.1. tábla mutatja a program listáját. 1 org 100h 2 MOV BL, [adat] 3 MOV AH, 02 4 MOV CX, ujra: 6 MOV DL, 0 7 RCL BL, 1 8 ADC DL, 30h 9 INT 21h 10 LOOP ujra 11 INT 20h 12 adat: 13 db b 9.1. tábla: Egy byte bináris kinyomtatására szolgáló program A 2. sorban betöltjük az adat cím alatt tárolt értéket. Az adat a program végén van tárolva és bináris formátumban van megadva. A 3. sorban előkészítjük a nyomtatást, vagyis megadjuk az INT 21h megszakítás funkció kódját. A 4. sorban a CX regiszterbe betöltünk 8-at, mivel egy byte-ban 8 bit van és a nyomtatást nyolcszor kell megismételni, vagyis nyolcszor kell 1-et vagy 0-át nyomtatni. Mivel egy karaktert fogunk kinyomtatni, ezért a nyomtatandó karakternek végül a DL regiszterben kell lennie, így a 6. sorban nullával inicializáljuk a DL regisztert. A 7. sorban az RCL utasítást használjuk (Lásd fejezet). Miért? Először is az L betű az utasítás nevének végén azt jelenti, hogy balra 91

92 Byte Lépés CF Kezdeti?? ?? ?? ?? ?? ?? ?? ?? ?? tábla: Egy byte balra forgatása a Carry biten keresztül végezzük a bit léptetést vagy bit forgtást. Ez azért fontos, mert a legmagasabb helyiértékű bit a byte bal oldalán található (lásd??. ábra) és a képernyőre is balról-jobbra végezzük a nyomtatást, vagyis először a legmagasabb helyiértékű bitet kell kinyomtatni. Ha megnézzük a 7.1. vagy 7.5. ábrákat, akkor azt látjuk, hogy egy léptetés vagy forgatás során a legmagasabb helyiértékű bit a Carry bitbe kerül. A Carry bitet a CF doboz jelöli. Amikor egy bitet ilyen módon betöltünk a Carry bitbe, akkor később használhatjuk ezt a bitet egy feltételes ugrásnál, lásd JC ( fejezet) és JNC ( fejezet) utasításokat. Ugyanakkor ebben a programban a Carry bitet másképp használjuk. A programnak végül vagy az 0 vagy a 1 karaktert kell kinyomtatnia amelyek ASCII kódja 30h és 31h. Ez azt jelenti, hogy a DL regiszterhez hozzá kell adni 30h és még a Carry bit értékét. Erre szerencsére van külön utasítás, az ADC utasítás (lásd fejezetet). Mivel ez az utasítás hozzáadja az értékeket a DL regiszterhez, ezért kellett a 6. sorban nullát tölteni a DL regiszterbe. Végül a 9. sorban az INT 21h megszakítás segítségével kinyomtatjuk a karaktert. A 10. sorban a LOOP utasítás (lásd fejezet) csökkenti a CX regiszter tartalmát eggyel és ha a CX regiszter értéke nem zérus, akkor a megadott címre ugrik. Ez azt jelenti, hogy a legbaloldalibb bit nyomtatása után ismét a legbaloldalibb bitet fogja nyomtatni, hiszen a byte-ot mindig balra forgatjuk. A 9.2. táblázat mutat egy példát arra, hogy hogyan alakul a byte és a Carry bit értéke az egyes lépések, forgatás során. 92

93 9.2 Egy hexadecimális szám kinyomtatása Egy hexadecimális szám 0 és 16 közötti érték lehet. Ezeket az értékeket 4 biten lehet reprezentálni, így ez a program csak egy byte (8 bit) felét tudja kinyomtatni. A hexadecimális számrendszerben 16 számjegy van: 0-9 számjegyek ASCII kódja: 30h - 39h A - F számjegyek ASCII kódja: 41h - 46h A programban feltételezzük, hogy a hexadecimális szám a BL 8 bites regiszterben van eltárolva. Ez azt jelenti, hogy a byte értéket meg kell vizsgálni, hogy a fenti tartományokból melyikbe esik és aszerint kell hozzáadni a megfelelő értéket, hogy számjegy ASCII kódját kapjuk meg. A pszeudo kódban az algoritmus a következőképpen néz ki: IF BL < 0Ah THEN BL = BL + 30h ELSE BL = BL + 37h Ez azt jelenti, hogy ha a BL regiszter tartalma kisebb mint 10, akkor csak 30h értékkel kell módosítani, egyébként 37h értéket kell a BL regiszter tartalmához hozzáadni. Bár az assembly programozási nyelvben megvalósítható a feltételes utasítás, de közvetlen ELSE ág nincs. (Az ELSE ágba eső utasítások akkor hajtódnak végre, ha a feltétel hamis volt.) Ennek következtében a kódot egy kicsit át kell szervezni, ami pszeudo kódban ez lesz: BL = BL + 30h IF BL >= 03Ah THEN BL = BL + 07h Az előző változatban 30h vagy 37h értéket kellett a regiszterhez hozzáadni, így ebben a változatban 30h-at mindenképpen hozzáadunk és 07h-et már csak akkor kell hozzáadni, ha a kapott érték nagyobb vagy egyenlő mint 3Ah. (Számoljuk végig, hogy a nulla szám ASCII karakterének száma 30h, az egy számé 31h és így tovább. Végül a kilences szám karakterének száma 39h, vagyis a 3Ah már a tizes számjegynek felelne meg, de a tizes számjegyet már az A betű jelöli aminek az ASCII kódja már 41h.) Az assembly program listája az 9.3. táblán látható. A hexadecimális érték a programban van definiálva a szam címen, és ezt az értéket töltjük be a BL regiszterbe. A program végül kinyomtatja a hexadecimális számjegyet, ebben az esetben a B betűt. Az 9.3. táblán látható programban a második sorban a BL regiszterbe töltjük a szam címen tárolt byte értéket. A harmadik sorban az AH regiszterbe a 2-es értéket töltjük. Ennek az értéknek csa később van jelentősége de mivel a későbbi utasítások nem módosítják az AH regiszter tartalmát ezért itt is nyugodtan beállíthatjuk ezt az értékét. A negyedik sorban a BL regiszter tartalmát átmásoljuk a DL regiszterbe. Most már feltűnhet, hogy valójában arra törekszünk, hogy a program végén az INT 21h megszakítást használjuk a karakter kinyomtatására. (Ha egy karaktert akarunk kinyomtatni a INT 21h megszakítással, akkor az AH adja meg a funkcó kódot 02h és a DL regiszter fogja tartalmazni a kinyomtatandó karakter ASCII kódját. Lásd 6.2. bekezdést.) Az 5-8 sorok valósítják meg az előbb tárgyalt pszeudo kódot. Először a DL regiszterhez hozzáadunk 30h értéket, majd az így kapott értéket a következő sorban összehasonlítjuk a 3Ah értékkel. A CMP utasítás csak összehasonlítást végez és beállítja a státusz regisztert. A JL utasítás a feltételes ugrás és a státusz bitek értékétől függően folytatódik a végrehajtés. Ez aztjelenti, hogy ha a DL regiszter értéke kisebb mint 3Ah akkor átugorjuk a 8. sort. Ha nagyobb, akkkor a 8. sorban folytatódik a végrehajtás és még hozzáadunk 7-et, hogy az A karakter ASCII kódjátólinduló értékeket kapjunk. A karakter kinyomtatása a 10. sorban történik az INT 21h megszakítással. 93

94 1 org 100h 2 MOV BL, [szam] 3 MOV AH, 02 4 MOV DL, BL 5 ADD DL, 30h 6 CMP DL, 3ah 7 JL szamjegy ; DL < 3ah 8 ADD DL, 07 9 szamjegy: 10 INT 21h 11 INT 20h 12 szam: 13 db 0bh 9.3. tábla: Egy hexadecimális szám kinyomtatására szolgáló program 94

95 9.3 Egy byte hexadecimális kinyomtatása Ez a program annyiban különbözik az előző programtól, hogy ez a program 2 hexadecimális számot fog kinyomtatni. Miért? Egy byte 8 bitet vagy kétszer 4 bitet tartalmaz. 4 biten pedig pontosan 16 féle számot lehet ábrázolni ami megfelel egy hexadecimális számjegynek. Bár mostanra már egyértelműnek kell lennie hogyan lehet 4 biten 16 számot ábrázolni, de a teljesség kedvéért a 9.4. táblázat felsorolja a 4 biten tárolható számokat decimális és bináris alakban. Ez azt jelenti, hogy egy byte-ban az alsó négy bit és a felső négy bit is megfelel egy hexadecimális számjegynek, vagyis ha egy byte-ot headecimálisan akarunk kinyomtatni, akkor két hexadecimális számjegyet kell kinyomtatni. A 9.5. tábla tartalmazza az assembly program listáját. Ez a program a 9.3. programot kétszer tartalmazza. (Igazából függvényeket kellene használni, hogy kevesebb legyen a kód ismétlés, de a függvényekről majd a 10. fejezetben tanulunk.) Az extra utasítások a 9.5. táblán látható programhoz képest azt biztosítják, hogy mindig az alsó 4 bit tartalmazza a hexadecimális számot és a felső 4 bit pedig mindig zérus legyen! A második sorban betöltjük a byte-ot a BL regiszterbe, majd a harmadik sorban az INT 21h megszakítás funkció kódját állítjuk be az AH regizterben. Mivel kétszer kell kezelnünk a BL regisztert (alsó és felső négy bit) ezért az eredeti értéket a BL regiszterben tároljuk és majd a DL regisztert módosítjuk. Ezért a BL regiszter tartalmát átmásoljuk a DL regiszterbe a 4. sorban. Mivel a számokat balról jobbra írjuk, ezért először a magasabb helyiértékű biteket kell figyelembe venni, vagyis a felső 4 bitnek megfelelő számot kell kinyomtatni. Bár ez igaz, de azt is figyelembe kell venni, hogy a 9.3. program az alsó négy bittel dolgozott. Ezért nincs más teendőnk, mint a felső négy bitet eltolni az alsó négy bitbe. Sokféleképpen lehet ezt megtenni, de a legegyszerűbb bitléptető utasítást használni. A bitléptető utasítások közül az SHR utasítás diagramja a 7.7. ábrán látható. Fontos, hogy ez az utasítás a jobbra léptetés során balról nullákat léptet be, vagyis a felső 4 bitet lenullázza. Miután az alsó 4 biten van a kinyomtatandó hexadecimális számjegy, a 9.3. programot lehet használni a nyomtatásra. (A 9.3. program jelölve van a 9.5 táblán.) A 13. sorban helyreállítjuk a DL regiszter értékét a BL regiszterből. (Itt kell megjegyezni, hogy természetesen a szam cimről újra betölthetnénk az értéket, de itt néhány órajel ciklust megtakarítva a BL regisztert használjuk mint gyors, időleges tárolóhely. Ha valamiért mégis szükségünk lenne a BX regiszterre a programban, például címzésnél, akkor természetesen célszerűbb lenne a szam címről betölteni az értéket a DL regiszterbe.) A második szám kinyomtatásánál csak az alsó 4 bitre van szükségünk. Igen ám, de figyelni kell arra, hogy a felső 4 bit is ott van byte-ban. A 9.3. program feltételezi, hogy a felső 4 bit zérus. Ennek elérésére maszkolást fogunk használni, vagyis a felső 4 bitet ki kell nullázni, úgy hogy az alsó 4 bit értéke ne változzon. A maszkolás a 14. sorban az AND utasítással valósítsjuk meg. A 0Fh hexadecimális szám binárisan b és ezt használjuk az AND utasítással, vagyis ahol a bit értéke 1 ott a DL regiszter tartalma változatlan marad, ahol pedig nulla, ott a DL regiszter bitje biztos hogy zérus lesz. (Ez elég egyszerűen belátható, ha megnézzük az AND utasítás igazság tábláját, lásd 7.2. tábla.) Miután a DL regiszter megfelelően be van állítva, nincs más hátra mint kinyomtatni a 9.3. program szerint. Ez is jelölve van a 9.5. táblán.) 95

96 Decimális Bináris tábla: 4 biten ábrázolható számok bináris alakban 1 org 100h 2 MOV BL, [szam] 3 MOV AH, 02 4 MOV DL, BL 5 MOV CL, 4 6 SHR DL, CL ; bit léptetés jobbra 7 ADD DL, 30h ; -- 8 CMP DL, 3ah ; 9 JL szam1 ; elozo program 10 ADD DL, 07 ; 11 szam1: ; 12 INT 21h ; MOV DL, BL ; DL helyreallitasa 14 AND DL, 0Fh ; maszkolás 15 ADD DL, 30h ; CMP DL, 3ah ; 17 JL szam2 ; elozo program 18 ADD DL, 07 ; 19 szam2: ; 20 INT 21h ; INT 20h 22 szam: 23 db 0FFh 9.5. tábla: Egy byte hexadecimális formátumú kinyomtatására szolgáló program 96

97 9.4 Egy decimális számjegy ellenőrzött beolvasása és kinyomtatása A 9.6. program azt mutatja be, hogyan lehet megvizsgálni, hogy a beolvasott karakter számjegy-e. Az érvényes karakterek tartománya: 0-9, melyek ASCII kódja egymás utáni az ASCII táblában, lásd A.1. táblázat. Bár a program számjegyeket fogad csak el, természetesen a programot könnyű úgy átírni, hogy más, egymás utáni karaktereket fogadjon el. 1 org 100h 2 ujra: 3 MOV AH, 9 4 MOV DX, uzenet 5 INT 21h 6 MOV AH,1 7 INT 21h ; beolvasott karakter AL-ben 8 CMP AL, 0 ; also korlat 9 JB hiba 10 CMP AL, 9 ; felso korlat 11 JA hiba 12 MOV DL, AL ; nyomtatas elokeszitese 13 MOV AH, 2 14 INT 21h 15 INT 20h 16 hiba: 17 MOV DX, hiba_str 18 MOV AH, 9 19 INT 21h 20 JMP ujra 21 uzenet: 22 db 0Dh,0Ah, Adjon meg egy szamot: $ 23 hiba_str: 24 db 0Dh,0Ah, Ervenytelen karakter!$ 9.6. tábla: Egy számjegy beolvasása és kinyomtatása. A 3-5. sorokban egy üzenetet írunk ki a képernyőre a felhasználó számára. A 6. és 7. sorban a INT 21h megszakítást használjuk, hogy egy karaktert beolvassunk a felhasználótól. A beolvasott karakter az AL regiszterbe kerül. A 8. sorban az AL regiszter tartalmát összehasonlítjuk a 0 karakter ASCII kódjával. A CMP utastás beállítja a státusz regiszter bitjeit a két érték egymáshoz való viszonya alapján. Ezeknek a biteknek az állapotát vizsgálja meg a 9. sorban a JB utasítás és ha az AL regiszter tartalma kisebb mint a 0 karakter ASCII kódja, akkor a hiba címre ugrik a program. Ellenkező esetben a következő, 10., sorban folytatódik a program. A 10. és 11. sorban az AL regiszter tartalmát a 9 karakter ASCII kódjával hasonlítjuk össze és ha az AL regiszter tartalma nagyobb akkor szintén a hiba címre ugrik a program. Ha az AL regiszter tartalma a megfelelő tartományban van, akkor a program a 12. sorban folytatódik. A sorok között csak annyi történik, hogy a megfelelő regiszterek tartalmát beállítjuk, olyan módon, hogy egy karaktert ki tudjunk nyomtatni a képernyőre. A 15. sorban kilépünk a programból. A sorok közötti utasítások egy hiba üzenetet írnak ki a képernyőre. A 20. sorban egy feltétel nélküli ugrással a program elejére ugrunk, hogy újra be lehessen olvasni egy karaktert. A 22. és 24. ssorban a karaktersorozat elején a 0Dh és 0Ah byte-ok azért vannak megadva, hogy egy sort emeljenek a képernyőn, vagyis a következő sorba nyomtassuk ki a szöveget. (Próbáljuk ki a programot úgy, hogy ezeket a byte-okat kitöröljük.) 97

98 9.5 Egy karakter beolvasása és módosítása Ez a program egy karaktert olvas be és az utána következő karaktert nyomtatja ki. Az a betű helyett a b betűt, a b betű helyett a c beűt és így tovább. 1 org 100h 2 MOV AH, 1 3 INT 21h 4 MOV DL, AL 5 INC DL 6 MOV AH, 2 7 INT 21h 8 INT 20h 9.7. tábla: Egy karakter beolvasása és az utána következő kinyomtatása. A fenti programban semmilyen ellenőrzés nincs így a programot most kiegészítjük annak vizsgálatval hogy a bolvasott karakter tényleg kis betű-e. A programnak van még egy rejtett hibája: Mi történik ha a z betűt adjuk meg? Mivel a karakter számszerű értékéhez hozzáadunk egyet és a z betű ASCII kódja (ASCII száma) 122 decimális vagy 7A hexadecimális ezért a program a 123-as ASCII kódú karaktert nyomtatja ki, a { karaktert. A 9.8. program ezt a problémát is kijavítja. 98

99 1 org 100h 2 MOV AH, 1 3 INT 21h 4 MOV DL, AL 5 CMP DL, a 6 JB nem_betu 7 CMP DL, z 8 JA nem_betu 9 JNE novel 10 MOV DL, a 11 JMP nyomtat 12 novel: 13 INC DL 14 nyomtat: 15 MOV AH, 2 16 INT 21h 17 vege: 18 INT 20h 19 nem_betu: 20 MOV AH, 9 21 MOV DX, nem_betu_szoveg 22 INT 21h 23 JMP vege 24 nem_betu_szoveg: 25 db 10,13, Nem betut adtal meg!$ 9.8. tábla: Egy karakter beolvasása és az utána következő kinyomtatása. 99

100 9.6 Öt karakter bolvasása és kinyomtatása fordított sorrendben A 9.9. táblán látható program beolvas öt karaktert, ezeket eltárolja, majd fordított sorrendben kinyomtatja a beolvasott karaktereket. A program jó példa az regiszteres címzésre. (A címzési módokat a 3.3. fejezet tárgyalja.) A 2. sorban a CX regiszter értékét 5-re állítjuk, mivel egy ciklusban fogjuk beolvasni a karaktereket és a CX regiszter lesz a ciklus változó. A 3. sorban a DI regiszterbe a tárolásra használt hely címét töltjük be. Az 5. és 6. sorban az INT 21h megszakítással beolvasunk egy karaktert az AL regiszterbe. A 7. sorban eltároljuk a beolvasott karaktert a DI regiszter által megadott címre. (Bár nincs megadva szegmens regiszter, de adat esetén automatikusan feltételezhetjük a DS szegmens regisztert, így a 7. sor a következő is lehetne: MOV [DS:DI], AL A 8. sorban a DI regiszter értékét 1-el növeljük meg, mivel minden karakter 1 byte méretű, így a következő karaktert majd a következő byte-on kell eltárolni. A 9. sorban található LOOP utasítással valósítjuk meg a ciklust. Az utasítás csökkenti a CX regiszter értékét 1-el és ha a regiszter nem nulla, akkor a megadott címre ugrik, jelen esetben újabb karaktert olvas be. A ciklus végén a 10. sor fog végrehajtódni, ahol a DI regiszter értékét 1-el csökkentjük. Erre azért van szükség, mert az 5. karakter eltárolása után is megnöveljük a DI regiszter tartalmát, így az már egy hatodik karakterre mutatna, ami nem definiált a programban. Így a 10. sorban végrehajtott csökkentés után a DI regiszter megint az 5. karakterre fog mutatni, amit majd kinyomtatunk először. A 11. sorban ismét a ciklus változót állítjuk be, míg a 12. sorban az INT 21h megszakítás funkciókódját adjuk meg. A cikluson belül a 14. sorban a DL regiszterbe töltjük be a karaktert amit a 15. sorban nyomtatunk ki. A 16. sorban a DI regisztert ismét csökkentjük, mivel fordított sorrendben akarjuk kinyomtatni a karaktereket mint ahogy beolvastuk őket. A ciklust itt is a LOOP utasítással valósítjuk meg. 1 org 100h 2 MOV CX, 5 3 MOV DI, karakterek 4 ujra: 5 MOV AH, 1 6 INT 21h 7 MOV [DI], AL 8 INC DI 9 LOOP ujra 10 DEC DI 11 MOV CX, 5 12 MOV AH, 2 13 nyomtat: 14 MOV DL, [DI] 15 INT 21h 16 DEC DI 17 LOOP nyomtat 18 INT 20h 19 karakterek: 20 db 0,0,0,0, tábla: Öt karakter beolvasása és kinyomtatása fordított sorrendben. 100

101 9.7 Két egyjegyű szám összeadása A program beolvas két decimális számjegyet, összeadja őket, majd az eredménynek megfelelő számú csillag ( * ) karaktert nyomtat ki. A programban a számjegyek beolvasása kétszer szerepel. A kód ismétlés elkerülésére a legjobb lenne függvényt használni, de erről csak később lesz szó a 10. fejezetben. Az első beolvasott számot a CL regiszterben tároljuk el a 13. sorban. A második beolvasott számjegy az AL regiszterben alakul ki a 24. sorban. A 25. sorban összeadjuk a két számot és az eredményt a CL regiszterben tároljuk el. A 26. sorban a CH regisztert kinullázzuk, hogy a teljes CX regiszter tartalmazza az eredményt és a LOOP utasítást tudjuk használni. A karakterek nyomtatásának előkészítése a 27. és 28. sorban történik, míg a nyomtatási ciklus a 30. és 31. sorban található. A program egy másik lehetőséget mutat a program megvalósítására. Ebben a programban a beolvasást egy ciklussal végezzük el. Mivel a ciklushoz szükség van a CL regiszterre ezért a beolvasott számokat itt a memóriában tároljuk el. Ebben a programban a számok összeadását a 21. és 22. sorban végezzük el, majd a nyomtatást az előzőekhez hasonlóan, ciklussal oldjuk meg. 101

102 1 org 100h 2 szam_1: 3 MOV AH, 9 ; elso szam bekerese 4 MOV DX, uzenet 5 INT 21h 6 MOV AH, 1 7 INT 21h 8 CMP AL, 0 9 JB nem_szam_1 10 CMP AL, 9 11 JA nem_szam_1 12 SUB AL, 0 ; karakterbol szam 13 MOV CL, AL ; karakter tárolasa 14 szam_2: 15 MOV AH, 9 ; masodik szam bekerese 16 MOV DX, uzenet 17 INT 21h 18 MOV AH, 1 19 INT 21h 20 CMP AL, 0 21 JB nem_szam_2 22 CMP AL, 9 23 JA nem_szam_2 24 SUB AL, 0 ; karakterbol szam 25 ADD CL, AL ; osszeadas 26 XOR CH, CH 27 MOV AH, 2 ; nyomtatas elokeszitese 28 MOV DL, * 29 ujra: 30 INT 21h 31 LOOP ujra 32 INT 20h 33 nem_szam_1: 34 MOV AH, 9 35 MOV DX, nem_szam_szoveg 36 INT 21h 37 JMP szam_1 38 nem_szam_2: 39 MOV AH, 9 40 MOV DX, nem_szam_szoveg 41 INT 21h 42 JMP szam_2 43 uzenet: 44 db 0Dh,0Ah, Adjon meg egy szamot: $ 45 nem_szam_szoveg: 46 db 10,13, Nem szamot adtal meg!$ tábla: Két szám összeadása és az eredmény kinyomtatása. 102

103 1 org 100h 2 MOV DI, szamok 3 MOV CL, 0 4 szam_be: 5 MOV AH, 9 ; elso szam bekerese 6 MOV DX, uzenet 7 INT 21h 8 MOV AH, 1 9 INT 21h 10 CMP AL, 0 11 JB nem_szam 12 CMP AL, 9 13 JA nem_szam 14 SUB AL, 0 15 MOV [DI], AL 16 INC DI 17 INC CL 18 CMP CL, 2 19 JNE szam_be 20 ; olvasas vege 21 MOV CL, byte [szamok] 22 ADD CL, byte [szamok+1] 23 XOR CH, CH 24 MOV AH, 2 ; nyomtatas elokeszitese 25 MOV DL, * 26 ujra: 27 INT 21h 28 LOOP ujra 29 INT 20h 30 nem_szam: 31 MOV AH, 9 32 MOV DX, nem_szam_szoveg 33 INT 21h 34 JMP szam_be 35 szamok: 36 db 0, 0 37 uzenet: 38 db 0Dh,0Ah, Adjon meg egy szamot: $ 39 nem_szam_szoveg: 40 db 10,13, Nem szamot adtal meg!$ tábla: Két szám összeadása és az eredmény kinyomtatása ciklussal. 103

104 9.8 Egy karakter n-szeri kinyomtatása A táblán látható program beolvas egy karaktert, utána egy decimális számjegyet és az elsőként beolvasott karaktert annyiszor nyomtatja ki, amekkora a másodiknak beolvasott szám volt. A program mostanra talán nem igényel magyarázatot, mivel minden részlete eddig már szerepelt egy korábbi programban. 1 org 100h 2 MOV AH, 9 3 MOV DX, uzenet1 4 INT 21h 5 MOV AH, 1 6 INT 21h 7 MOV [karakter], AL 8 olvas: 9 MOV AH, 9 10 MOV DX, uzenet2 11 INT 21h 12 MOV AH, 1 13 INT 21h 14 CMP AL, 0 15 JB hiba 16 CMP AL, 9 17 JA hiba 18 SUB AL, 0 19 XOR CX, CX 20 MOV CL, AL 21 MOV AH, 9 ; uj sor nyomtatasa 22 MOV DX, ujsor 23 INT 21h 24 MOV AH, 2 ; karakter sorozat nyomtatasa 25 MOV DL, [karakter] 26 nyomtat: 27 INT 21h 28 LOOP nyomtat 29 INT 20h 30 hiba: 31 MOV AH, 9 32 MOV DX, uzenet_nem_szam 33 INT 21h 34 JMP olvas 35 ujsor: db 10, 13, $ 36 karakter: db 0 37 uzenet1: 38 db 10, 13, Adjon meg egy karaktert: $ 39 uzenet2: 40 db 10, 13, Adjon meg egy számjegyet: $ 41 uzenet_nem_szam: 42 db 10, 13, Nem számjegyet adott meg! tábla: Egy karakter n-szeri kinyomtatására szolgáló program 104

105 9.9 Téglalap kinyomtatása A és táblákon bemutatott program beolvas két egyjegyű számot ellenőrzéssel, a sorok és oszlopok számát, majd a számoknak megfelelő méretű téglalapot nyomtat ki a képernyőre. A program igazából nem túl bonyolult, de az üzenetek nyomtatása és az ellenőrzések miatt olyan hosszú lett, hogy két táblán kerül bemutatásra. A program speciális abban az értelemben, hogy ez a program nem lineáris. Ez azt jelenti, hogy a 2. sorban már rögtön 19. sorra ugrunk és ott folytatódik a program végrehajtása. A 2. és 19. sor közé bekerült néhány adat, illetve a hiba kezelő programrészletek is. Itt ez jelenti azt, hogy a program nem lineáris, nem csak fentről, lefelé fut a program és adat is beékelődik a programba. A sorokban egy üzenete írunk ki a felhasználónak, majd a 23. és 24. sorokban beolvasunk egy karaktert. A 25. és 28. sorok között ellenőrizzük, hogy a beolvasott karakter számjegy-e. Ha nem számjegyet olvastunk be, a program egy hibaüzenetet ír ki és újra megpróbál beolvasni egy számjegyet. A 29. sorban a beolvasott számjegy ASCII kódját számmá konvertáljuk és eltároljuk a CH regiszterben. A sorokban egy újabb üzenetet írunk ki, majd ismét egy karaktert olvasunk be a 35. és 36. sorban. A sorok között ismét ellenőrzést hajtunk végre, hogy a beolvasott karakter szám-e. Érdemes megfigyelni, hogy a két beolvasásnál külön hiba üzenet nyomtató részt használtunk: hiba1 és hiba2. Miért? Ez azért van, mert ha csak egy hibaüzenet nyomtató programrészlet lenne, akkor a hiba üzenet kinyomtatása után két különböző helyre kellene valahogy visszatérnie. Egyszer a hiba1 címre, máskor pedig a hiba2 címre. Természetesen ezt nem lehet. A függvényeknél majd látni fogjuk, hogy ezt hogyan lehet megvalósítani, de itt most azt az egyszerű megoldást használjuk, hogy a kódot megismételjük. A másodszorra beolvasott értéket a BL regiszterben tároljuk el. A téglalap nyomtatását a tábla mutatja be. A sorokban csak egy soremelést nyomtatunk. A 50. sorban beállítjuk a karakter nyomtatási funkció kódot és az 51. sorban a nyomtatandó karaktert adjuk meg. Az 52. sorban a CL regiszterbe átmásoljuk a BL regiszter tartalmát, az oszlopok számát. A CL regiszter lesz az egyik ciklus változó. A másik ciklus változó a CH regiszter. Miért kell két ciklus változó? Nézzük meg a következő C kódot, ami egy téglalapot nyomtat ki: for(ch = n; ch > 0; ch--) { for(cl = m; cl > 0; cl--) { printf( o } printf( \n } A fenti kódban az látható, hogy van egy belső és egy külső ciklus. Ezeket jelöli a belso és kulso cím az assembly programban. Az is látható, hogy amikor a belső ciklus elkezdődik a CL regisztert mindig újra kell inicializálni. Ez történik a 52. sorban, amikor a CL regiszterbe átmásoljuk a BL regiszter tartalmát. (A BL regiszter tartalma nem változik meg a nyomtatás során.) A belső ciklusban a 54. sor végzi a nyomtatást. A 55. sor végzi a ciklus változó csökkentését, majd a 56. sor valósítja meg a feltételes ugrást. Ha a CL regiszter tartalma a csökkentés után nem zérus, akkor megismételjük a nyomtatást, mivel a belso címre ugrik a program. Ha zérus lett a CL regiszter, akkor a 57. sorban folytatódik a program futása. A sorokban egy soremelést nyomtatunk ki. Mivel itt megváltozik a funkció kód az AH regiszterben, ezért kell az 50. sorban ismét beállítani a karakter nyomtató funkció kódot. A 60. és 61. sorok valósítják meg a külső ciklust. A 62. sorban lépünk ki a programból. 105

106 1 org 100h 2 JMP olvas1 3 hiba1: 4 MOV AH, 9 5 MOV DX, uzenet_nem_szam 6 INT 21h 7 JMP olvas1 8 hiba2: 9 MOV AH, 9 10 MOV DX, uzenet_nem_szam 11 INT 21h 12 JMP olvas2 13 uzenet_nem_szam: 14 db 10, 13, Nem számjegyet adott meg! 15 uzenet1: 16 db 10, 13, Adja meg a sorok szamat: $ 17 uzenet2: 18 db 10, 13, Adja meg az oszlopok szamat: $ 19 olvas1: 20 MOV AH, 9 21 MOV DX, uzenet1 22 INT 21h 23 MOV AH, 1 24 INT 21h 25 CMP AL, 0 26 JB hiba1 27 CMP AL, 9 28 JA hiba1 29 SUB AL, 0 30 MOV CH, AL 31 olvas2: 32 MOV AH, 9 33 MOV DX, uzenet2 34 INT 21h 35 MOV AH, 1 36 INT 21h 37 CMP AL, 0 38 JB hiba2 39 CMP AL, 9 40 JA hiba2 41 SUB AL, 0 42 MOV BL, AL ; folytatódik tábla: Egy téglalap kinyomtatására szolgáló program első része 106

107 45 ; nyomtatas itt kezdodik 46 MOV AH, 9 ; uj sor 47 MOV DX, ujsor 48 INT 21h 49 kulso: 50 MOV AH, 2 51 MOV DL, o 52 MOV CL, BL 53 belso: 54 INT 21h 55 DEC CL 56 JNE belso 57 MOV AH, 9 58 MOV DX, ujsor 59 INT 21h 60 DEC CH 61 JNE kulso 62 INT 20h 63 ujsor: 64 db 10, 13, $ tábla: Egy téglalap kinyomtatására szolgáló program második része 107

108 9.10 Sakktábla nyomtatása Ez a program tulajdonképpen nagyon hasonlít a téglalap nyomtató programhoz (9.9. fejezet), de itt nem egyféle karaktert kell nyomtatni, hanem felváltva különböző karaktereket. A program mutatja be a sakktábla nyomtatását megvalósító assembly program. Ebben a programban is két egymásba ágyazott ciklus van. Az egyik ciklus változó a BX regiszter, míg a másik a CX regiszter. Bár a program 8x8-as téglalapot nyomtat ki, de a CX regiszter kezdeti értéke csak 4. Ez azért van mert négy darab dupla karaktert nyomtatunk ki egy sorba: vagy XO vagy OX karaktereket. Azért van kétféle dupla karakter, mivel az egyiket a páros a másikat a páratlan sorokba nyomtatjuk. A 7. sorban vizsgáljuk meg, hogy a sor száma, BX regiszter, páros-e. Hogyan csináljuk? A 7. sorban a TEST utasítás egy maszkolást végez és a legkisebb helyiértékű bitet tartja meg a BX regiszterből. Ez azért elegendő, mivel a felsőbb bitek minden kettő hatványai azok csak párosak lehetnek, ezért a legalsó bit az ami eldönti, hogy a BX-ben tárolt érték páros vagy páratlan. Ha a legkisebb helyiértékű bit 1 akkor a BX regiszter tartalma páratlan, ha zérus, akkor páros. Miután eldőlt, hogy páros vagy páratlan sort nyomtatunk, kinyomtatjuk a két megfelelő karaktert a belső ciklusban. A belső ciklus a 6. és 21. sor között van. A 22. és 26. sorok között csak egy sor emelést nyomtatunk. A 27. sorban csökkentjük a BX regisztert, a külső ciklus ciklusváltozóját és ha nem zérus akkor a 28. sorban a külső ciklus elejére ugrunk. 1 org 100h 2 mov bx, 8 3 mov ah,2 4 kulso: 5 mov cx, 4 6 belso: 7 test bx,1 8 jz paros 9 paratlan: ; ez a cim igazábol nem kellene 10 mov dl, O 11 int 21h 12 mov dl, X 13 int 21h 14 jmp ciklus 15 paros: 16 mov dl, X 17 int 21h 18 mov dl, O 19 int 21h 20 ciklus: 21 loop belso 22 ; uj sor 23 mov dl, 0dh 24 int 21h 25 mov dl, 0ah 26 int 21h 27 dec bx 28 jnz kulso 29 int 20h tábla: Egy sakktábla kinyomtatására szolgáló program 108

109 Ez a sakktábla nyomtatási feladat arra is jó, hogy a XOR utasítás egy másik jellemző alkalmazását is bemutassuk. Két értékre alkalmazva a XOR utasítást egy harmadik értéket fogunk kapni a 7.4. táblázat szerint. Ha újra alkalmazzuk ugyanazt a XOR utasítást az eredményre, akkor az eredeti értéket kapjuk vissza. Nézzünk erre egy példát: MOV AL, 33h ; AL = 33h!!! XOR AL, 11h ; AL = 22h XOR AL, 11h ; AL = 33h!!! XOR AL, 11h ; AL = 22h XOR AL, 11h ; AL = 33h!!! A XOR utasításnak ezt a tulajdonságát használja ki a program. A 6. sorban a nyomtatandó karaktereket össze-xor-oljuk, (amiből kapunk valamilyen értéket) de ezután ezt az értéket újra XORolva az egyik karakterrel, hol az egyik, X, hol a másik, O, karaktert kapjuk meg a 10. sorban. Ebben a programban is a BX regiszter a külső ciklusváltozó és a CX regiszter a belső ciklusváltozó, aminek viszont 8 a kezdeti értéke, mivel itt egyesével nyomtatjuk ki a karaktereket. A belső ciklus után a 13. sorban elmentjük a DX regiszter tartalmát, mert a soremelés nyomtatásnál tönkretesszük a regiszter tartalmát. A sorokban nyomtatjuk ki a soremelés karaktereket és a 18. sorban visszaállítjuk a DX regiszter tartalmát. A 21. sor szorul még magyarázatra. Nézzük meg a következő két sort: XOXOXOXO OXOXOXOX Azt lehet látni, hogy a második sort ugyanazzal a karakterrel kell kezdeni, mint amivel az első sort lezártuk. A 21. sor ezt biztosítja, hogy a XOR utasítás alkalmazásával a X karaktert átugorjuk. Végül a 22. és 23. sor a külső ciklust valósítja meg. 109

110 1 org 100h 2 mov ah,2 3 mov bx, 8 4 mov dl, X 5 mov dh, O 6 xor dh, dl 7 kulso: 8 mov cx, 8 9 belso: 10 xor dl,dh 11 int 21h 12 loop belso 13 mov bp, dx ; elmenti DX-et 14 mov dl, 0dh ; új sor nyomtatása 15 int 21h 16 mov dl, 0ah 17 int 21h 18 mov dx, bp ; visszaállitás DX-et 19 ; mivel ugyanazzal a karakterrel 20 ; folytatódik a következö sor 21 xor dl,dh 22 dec bx 23 jnz kulso 24 int 20h tábla: Egy sakktábla kinyomtatására szolgáló program XOR utasítással 110

111 9.11 ASCII tábla kinyomtatása A táblán látható program az ASCII táblában található karaktereket nyomtatja ki a képernyőre. A program igen egyszerű. A 2. sorban adjuk meg, hogy 256 darab karaktert fogunk kinyomtatni. A 3. sorban megadjuk az első karakter ASCII kódját. A 4. sorban az INT 21h megszakítás funkció kódját adjuk meg. A 6. sor végzi a nyomtatást, majd a 7. sorban a DL regiszter megnövelésével a következő ASCII karaktert állítjuk be. A 8. sor csökkenti a CX regiszter tartalmát eggyel és ha még nem zérus, akkor az ujra címre ugrik. A LOOP utasítás segítségével képezzük a ciklust mely 256-szor fut le. Az utolsó sorban kilépünk a programból. 1 org 100h 2 MOV CX, MOV DL, 0 4 MOV AH, 2 5 ujra: 6 INT 21h 7 INC DL 8 LOOP ujra 9 INT 20h tábla: Az ASCII tábla kinyomtatására szolgáló program 111

112 9.12 Szám kiírása decimális formában Ha egy számot decimális formában akarunk kinyomtatni, speciális nyomtatási eljárást kell alkalmazni. A program erre mutat egy példát. Az algoritmus lényege, hogy a számot mindig 10-el osztjuk és az osztás maradéka mindig egy decimális számjegyet ad, hiszen a maradék 0 és 9 közötti lehet. Nézzünk egy példát: 152 / 10 -> 15 és a maradék: 2 15 / 10 -> 1 és a maradék: 5 1 / 10 -> 0 és a maradék: 1 Amint ez látható tényleg a szám számjegyeit kapjuk meg, de fordított sorrendben. Ezt is figyelembe kell venni az algoritmusnál. A 2. sorban az AX regiszterbe töltjük be azt a számot amit decimális formában szeretnénk kinyomtatni. Érdemes arra is gondolni, hogy mivel az AX regiszter 16 bites regiszter ezért = féle számot tud csak tárolni, ahol a legkisebb szám a nulla és a legnagyobb szám a Erre az információra azért van szükség, mivel így már tudjuk, hogy maximum 5 számjegyet kell majd kinyomtatni. Ezt mutatja a 18. sorban az szamstr változó definíciója, ami 5 darab SPACE karaktert tartalmaz és a végén egy dollár jelet ($). A dollár jel azért kell a végére, hogy majd egyben tudjuk a számokat kinyomtatni az INT 21h megszakítással. A 3. sorban az SI regiszterbe azt a címet töltjük be ami az utolsó SPACE karakterre mutat a szamstr változóban. Az 5. sorban adjuk meg az osztót. A 6. sorban azt készítjük elő, hogy majd az DX:AX számot osztjuk egy 16 bites regiszterrel és így majd az eredmény is egy 16 bites regiszterbe kerül. Az osztást a 7. sorban végezzük el. A hányados az AX regiszterbe, a maradék a DX regiszterbe kerül. A maradék csak 0 és 9 közötti szám lehet. A nyomtatáshoz a számot ASCII karakterré kell konvertálni, vagyis a számhoz hozzáadjuk a 0 karakter ASCII kódját. A 8. sor után a DL regiszter tartalma a 30h és 39h értékek közötti szám lesz, mely megfelel a 0 és 9-es számok ASCII kódjának. A 9. sorban eltároljuk az ASCII kódot az SI regiszter által megadott helyre. A 10. sorban az SI regisztert azért csökkentjük eggyel, hogy a következő karaktert az előző elé írjuk. A 11. sor a leállítási feltétel. Itt azt vizsgáljuk, hogy a hányados zérus-e, vagyis nincs további számjegy amit konvertálni kellene. A 12. sor tartalmazza a feltételes ugrást, ami az ujra címre ugrik ha ha van még szám amit konvertálni kell. Ha a hányados zéros, AX regiszter tartalma zérus, akkor nincs más hátra mint kinyomtatni az eltárolt számokat. A program olyan értelemben trükkös, hogy a szamstr változó alap esetben SPACE karaktereket tartalmaz, és így ha csak két jegyű számot tárolunk el, azért még a program jól fog működni, mivel ebben az esetben a program legfeljebb 3 SPACE karaktert nyomtat a számok előtt. 112

113 1 org 100h 2 MOV AX, MOV SI, szamstr ujra: 5 MOV BX, 10 ; 10-es osztassal valasztunk le egy jegyet 6 MOV DX, 0 ; DX:AX az osztando szam, BX az oszto 7 DIV BX ; hanyados -> AX, maradek -> DX 8 ADD DL, 0 ; szamjegy karakterre 9 MOV [SI], DL ; eltaroljuk 10 DEC SI ; visszafelé tároljuk 11 CMP AX, 0 12 JNZ ujra 13 MOV AH, 9 14 MOV DX, szamstr 15 INT 21h 16 INT 20h 17 szamstr: 18 db, $ tábla: Szám kiírása decimális formában 113

114 9.13 Olvasás a memóriából A program arra mutat egy példát, hogyan lehet a memóriában, egy fix címen lévő byte-ot megcímezni, illetve azt vizsgálni, hogy annak a byte-nak valahányadik bitje 1-e. A 0:417-es címen található byte írja le a billentyűzet LED-ek állapotát. Például ha a 7. bit értéke 1, akkor a CAPS LOCK be van kapcsolva. Az alábbi program azt fogja vizsgálni, hogy a CAPS LOCK be van-e kapcsolva és annek megfelelő üzenetet fog kiírni. A 2. és 3. sorban az ES szegmens regiszterbe a zérus szegmens címet töltjük. Mivel szegmens regiszterbe nem lehet közvetlenül értéket tölteni, ezért használjuk az AX regisztert. A 4. sorban direkt módon, szegmens regiszterrel együtt adjuk meg a vizsgálandó byte címét. A byte-ot betöltjük a BL regiszterbe. Az 5. sor egy maszkolást hajt végre. Lényegében a 40h = b értékkel és mivel az AND utasítást használjuk ezért a művelet után csak a 7. bit értéke marad meg. Például: <- BL AND <- Maszk Ez azt jelenti, hogy az 5. sor után a BL regiszter értéke vagy zérus vagy pedig 40h lesz. Ezt a feltételt használja ki a 6. sorban a feltételes ugrás. (Nem kell külön összehasonlító utasítás, CMP, mivel az AND utasítás már megfelelően beállítja a Zérus bitet, ZF.) Mivel csak egy üzenetet akarunk kinyomtatni és csak a nyomtatandó üzenet más ezért attól függően hogy a zérust kaptunk-e a DX regiszterbe más címet töltünk. Fontos lehet még kiemelni a 8. sorban a feltétel nélküli ugrást, JMP. Erre azért van szükség, mert miután beállítottuk a DX regiszter tartalmát a 7. sorban, már csak nyomtatni kell és nem szabad engedni hogy a DX regiszter tartalmát felülírjuk. Ha nincs a feltétel nélküli ugrás akkor pedig ez történne, mivel a 9. sorban folytatódna a program. A 12. sortól már ugyanazt kell csinálni mind a két esetben, beállítani a funkció kódot az INT 21h megszakításnak, meghívni a megszakítást, majd kilépni a programból. 1 org 100h 2 mov ax,0 3 mov es,ax 4 mov bl,[es:417h] 5 and bl, 40h 6 jnz eg 7 mov dx,kikapcs 8 jmp kiir 9 eg: 10 mov dx,bekapcs 11 kiir: 12 mov ah,9 13 int 21h 14 int 20h 15 kikapcs: 16 db Ki van kapcsolva$ 17 bekapcs: 18 db Be van kapcsolva$ tábla: CAPS LOCK állapotának nyomtatására szolgáló program 114

115 0.bit 1.bit 2.bit 3.bit 4.bit 5.bit 6.bit 7.bit az előtér kék színösszetevője az előtér zöld színösszetevője az előtér piros színösszetevője az előtér intenzitása a háttér kék színösszetevője a háttér zöld színösszetevője a háttér piros színösszetevője a villogás ki-bekapcsolása (a bit 1 értékénél villog) tábla: A képernyő byte attribútumának bitjei 9.14 Közvetlen videó memóriába írás Lehetőség van arra, hogy egy program közvetlenül a videokártya memóriájába írjon adatot és ilyen módon közvetlenül a képernyőre írjunk. Ehhez persze tudnunk kell, hogy melyik szegmensen kezdődik a képernyő memória, vagyis hova van leképezve a videokártya memóriája. Általában a 0B800h a szegmens címe, kivéve például a Herkules video kártya (ma már nem használják ezt a kártyát). Tehát a 0B800h a szegmens címet fogjuk használni a 80x25 karakteres szöveges képernyő esetén. A nyomtatáshoz még azt is tudnunk kell, hogy milyen a képernyő felépítése. Minden megjelenített karakterhez két byte tartozik: az egyik byte maga a karakter, a másik byte pedig a karakter attribútuma. Az attribútum a karakter színét, intenzitását és villogását jelenti a táblázat szerint. A képernyő bal felső sarkához tartozik a zérus oszlop és zérus sor karakter pozíció. Hogyan lehet egy tetszőleges pozíciójú karaktert kinyomtatni? 115

116 9.15 Szöveg beolvasása Karakterenként olvasunk. ESC-re vége Az INT 21 előkészítése és használata. 116

117 9.16 Beolvasott szövegben karakterek számlálása 117

118 9.17 Beolvasott szöveg nagy betűsre konvertálása 118

119 9.18 Feladatok 1. Írjunk programot, mely megszámolja, hogy egy byte-ban hány darab 1-es értékű bit van. 2. Írjunk programot, mely bekér egy decimális számjegyet. A számjegyről eldönti, hogy nagyobb-e mint öt és ennek megfelelő üzenetet ír ki: Nagyobb mint öt, Kisebb mint öt, Egyenlő öttel. 119

120 120

121 10. Fejezet Függvények 10.1 A verem adatszerkezet A verem tulajdonképpen egy LIFO (Last In First Out) adatszerkezet. Ez azt jelenti, hogy az utoljára elltárolt értéket olvashatjuk ki legelőször. A vermet angolul stack-nek is szokták nevezni. Ha analógiát keresünk a verem adatszerkezetre, akkor talán egy raktárat képzelhetünk el. A padló a legalsó szint, amire tehetünk egy dobozt, amire újabb dobozt tehetünk és így tovább. Ha a legfelső dobozra van szükségünk, akkor csak levesszük a kupac tetejéről. Ezzel szemben, ahhoz hogy a legalsó dobozt kivegyük, az összes többi felette levő dobozt le kell emelni. Amint látható egy verem esetén mindig csak a tetejéhez férünk hozzá (top-of-stack, TOS). A verembe helyezés műveletét PUSH-nak, a verem tetejéről való kivételt pedig POP-nak szoktuk nevezni. A ábra bemutatja a verem egyszerűsített működését A verem implementációja Az x86-os architektúra esetén a verem a memóriában található. Erre fontos lesz emlékezni később, mivel ezek szerint a vermet kezelhetjük LIFO adatszerkezetként, de mint véletlen hozzáférésű adatszerkezetként is! Mivel a verem a memóriában van ezért a megvalósításához az SS szegmens és SP regisztert kell használni. Ez a regiszter páros (SS:SP) mutat mindig a verem tetejére. Az SS szegmens regiszter a verem szegmensének a címét határozza meg, míg az SP regiszter a szegmensen belül adja meg a verem tetejének az offszetjét. Az 10.2.a. ábra azt mutatja, hogy az eddigiek alapján hogyan képzelhetjük el a vermet x86-os architektúra esetén. Sajnos ez a kép helytelen. Az x86-os architektúra esetén a verem helyes képe a 10.2.b. ábrán látható. Ezek alapján a verem adatszerkezet legfontosabb tulajdonságai: Csak word méretű adat tölthető fel a veremre. Byte önmagában nem. A verem a magasabb memória címtől az alacsonyabb memória cím felé növekszik. Ez a 10.2.b. ábra alapján azt jelenti, hogy a verem lefelé növekszik ábra: A verem működése 121

122 (a) (b) ábra: a) A verem adatszerkezet helytelen képe. b) A verem adatszerkezet valódi képe x86-os architektúrán. (a) (b) (c) ábra: a) A verem elméleti képe üres állapotban. b) A verem állapota FEABh érték tárolása után. c) A verem állapota 1234h érték tárolása után. Az SS:SP regiszter páros mindig az utoljára eltárolt elemre mutat. eltárolt szó (word) alsó byte-jára. Pontosabban az utoljára A 10.3.a. ábra azt mutatja, hogy amikor a verem üres, akkor a verem hogyan néz ki elméletileg. A 10.3.b. és 10.3.c. ábra azt mutatja, mi történik amikor adatokat teszünk a verembe. Egy word feltöltése során először az SP regiszter értékét 2-vel csökkentjük majd a word-öt eltároljuk az SS:SP által mutatott memória címen. Amikor egy word-öt kiveszünk a veremből, először kimásoljuk az értéket az SS:SP által mutatott címről, majd az SP regiszter értékét 2-vel csökkentjük. Itt kell megjegyeznünk, hogy a 10.3.a. ábra valóban csak elméleti képe a verem üres állapotának. A magyarázathoz tegyük fel, hogy a veremben egy érték, egy szó (word) van eltárolva. Amikor csak egy elem van a vermen akkor az SP regiszter értéke FFFEh. Amikor ezt az értéket kivesszük a veremből, akkor az SP regisztert meg kell növelni 2-vel. Ebben az esetben az SP értéke 10000h, ami nagyobb mint amit 16 biten tárolni lehetne, így az SP regiszter valójában a 0000h értéket tárolja majd. Ez azt jelenti, hogy a regiszter értéke körbefordul (wrap around) és az SP regiszter a szegmens elejére fog mutatni. 122

123 Verem műveletek Az x86-os architektúrán többféle utasítás is létezik a verem kezelésére. Az alapvető verem műveleteknek mint push és pop van megfelelő assembly utasítása: PUSH és POP. (Lásd és bekezdés.) További assembly utasítások, melyek a vermet kezelik: PUSHF ( bekezdés) PUSHA ( bekezdés) POPF ( bekezdés) POPA ( bekezdés) 10.2 A verem használata A veremnek több haszna is van az assembly nyelvben: értékek időleges tárolása paraméterátadás függvényeknek lokális változóknak memória terület vezérlés átadás Értékek időleges tárolás A verem jól használható arra, hogy változókat és értékeket időlegesen eltároljunk. Például vegyük azt az esetet, hogy két változót fel akarunk cserélni: xchg [valtozo1], [valtozo2] Sajnos ez nem működik, mivel egy utasítás kétszer nem férhet hozzá a memoriához. Az egyik megoldás a következő lehet: mov ax, [valtozo1] mov bx, [valtozo2] mov [valtozo1], bx mov [valtozo2], ax de ekkor két regisztert is használnunk kell és 4 memória műveletet végzünk. Ez a megoldás gondot jelenthet, mivel a végrehajtásához találnunk kellene 2 szabad regisztert, és sokszor problémás lehet egy programban. De mi van akkor, ha a fenti példában az AX és BX regiszterek értékeire a csere után is szükség van? A megoldás az, hogy időlegesen el kell menteni az értéküket, el kell végezni a műveletet, majd helyre kell állítani a regiszterek értékét. ; mentsük el a regisztereket push ax push bx ; végezzük el a cserét mov ax, [valtozo1] mov bx, [valtozo2] mov [valtozo1], bx mov [valtozo2], ax 123

124 ; állítsuk helyre a regisztereket pop bx pop ax Bár ez a megoldás működik, de szükséges néhány megjegyzést tenni: 1. Először is a fenti megoldásban nyolcszor férünk hozzá a memóriához. Ne felejtsük, hogy a verem műveletek is hozzá férnek a memóriához. 2. Másodszor, fontos észre venni, hogy ha a regiszterek eredeti értékét szeretnénk visszakapni, akkor amilyen sorrendben a regisztereket elmentjük a vermen azzal ellentétes sorrendben kell helyreállítani a regisztereket. Egy másik példa az időleges tárolásra: push dx push bx push cx... ; utasítások pop cx pop bx pop dx 3. Harmadszor, létezik egy elegánsabb megoldás a változók cseréjére. push [valtozo1] push [valtozo2] pop [valtozo1] pop [valtozo2] Ebben a megoldásban pont az előző szabályt sértjük meg, de szándékosan, mivel így pontosan azt a hatást érjük el, hogy a regiszterek értékei felcserélődnek. Ezen kívül az is érdekes itt, hogy a fenti POP utasítások kétszer férnek hozzá a memóriához, hiszen a veremből (memóriából) olvasunk és az argumentumban megadott memória címre írunk. A POP utasítás egy kivétel a szabály alól, vagyis hogy általában egy utasítás nem férhet hozzá kétszer a memóriához. A másik kivétel ez alól a szabály alól a szöveg kezelő műveletek lesznek (lásd 12. fejezet). Nagyon fontos megérteni a fenti listában a 2. és 3. pont közötti különbséget. Ebből a bekezdésből is jól látható, hogy a verem mérete folyamatosan növekszik és csökken a program futása során. A paraméter átadást, lokális változók kezelését és vermen keresztüli vezérlés átadást a következő fejezetben tárgyaljuk Függvények definíciója A függvények önálló logikai program egységek amelyek valamilyen konkrét feladatot végeznek el. A függvényeket szokták alprogramnak is nevezni és nagyon fontos szerepet játszanak a moduláris program fejlesztésben. Bár bizonyos programozási nyelvek különbséget tesznek függvények és procedurák között, az assembly programozási nyelvben erre nincs szükség. Az assembly programozási nyelvben csak függvényeket definiálhatunk, amelyeknek paramétereket adhatunk át és kaphatunk vissza értéket. Ez a felfogás nagyban hasonlít a C programozási nyelvhez. Nagyon egyszerű definiálni egy függvényt assembly-ben. Két utasításra van szükség: CALL (lásd bekezdés) és RET (lásd bekezdés) utasításokra. Nézzünk egy egyszerű példát: 124

125 org 100h xor ax, ax call fvg int 20h fvg: add ax, 2 ret Ebben a példában a függvény neve fvg. Más assemblerekkel ellentétben, ahol speciálisan kell egy függvényt definiálni a NASM assembler esetén igen egyszerűen csak egy memória címet kell megadni, ami a függvény eleje és a végére egy RET utasítás kell. Érdemes megvizsgálnunk a függvények működését is. A CALL utasítás végrehajtása során, mivel a CALL utasítás már betöltődött az utasítás értelmezőbe ezért az IP regiszter a következő utasításra mutat, ahol a programnak folytatnia kell a működését miután a függvény lefutott. Erre a processzor úgy fog emlékezni, hogy ezt a címet elmenti a veremre. Ezután a CALL utasítás a megadott címre adja át a vezérlést, vagyis az IP regiszter értékét megváltoztatja. Nézzük meg az előző példából generált gépi kódot: xxxx: C0 xxxx:0102 E80200 xxxx:0105 CD20 org 100h xor ax, ax call fvg int 20h fvg: xxxx: add ax, 2 xxxx:010a C3 ret Látható, hogy a CALL fvg utasításból E gépi kód keletkezik. Az E8-as érték adja meg az utasítás kódját és a következő két szám pedig az fvg szimbólikus címnek felel meg. A értékek viszont nem felelnek meg a 107-es címnek. Mi történik itt? Arról van szó, hogy a CALL utasítás képes közeli és távoli ugrásra is. Amikor a CALL utasítás és a függvény egy szegmensen belül van, akkor elegendő csak az offszetet megadni. A szegmens regiszterre nincs szükség. Ez látható a jelenlegi példában is, bár itt az fvg szimbólikus címből nem konkrét címet számol ki az assembler, hanem egy eltolási értéket. Azt számolja ki, hogy hány byte-nyit kell ugrania a CALL utasításnak ahhoz, hogy a program a függvény kezdő címénél folytassa a végrehajtást. A gépi kódban a byte sorozat található az E8-as utasítás kód után, ami valójában 0002-nak felel meg, hiszen little-endian tárolási módot használ az x86-os architektúra. A 0002 érték azt adja meg, hogy a függvény kezdete két byte-nyi távolságra van és így a 105+2=107-es címen kell folytatni a végrehajtást. A függvényeknél az is nagyon fontos, hogy a függvény végén a RET utasításnak szerepelnie kell. Ez azért nagyon fontos, mivel a CALL utasítás által a vermen eltárolt címet ez az utasítás veszi le a veremről majd az így kapott címre adja át a vezérlést. Az utasítás működésének egy különleges demonstrálására nézzünk egy utasítás sorozatot ami megfelel egy feltétel nélküli ugró utasításnak:... push cim ret... cim: ; jmp cim A PUSH utasítás a cim szimbólikus címet tölti fel a veremre, amit a RET utasítás levesz és erre a címre adja át a vezérlést. Tulajdonképpen a RET utasítás így vezérlés átadást végez a verem felhasználásával, ami a verem egyik felhasználási módja. 125

126 Egymásba ágyazott függvényhívások A függvények egymásba ágyazhatók, ami azt jelenti, hogy egy függvény is hívhat egy másik függvényt. A tábla egy mesterséges példát mutat arra, hogy a függvényeket hogyan lehet egymásba ágyazni. A ábra pedig azt mutatja be, hogy a táblán bemutatott program végrehajtása során az IP regiszter milyen értékeket vesz fel (az utasításokat milyen sorrendben hajtjuk végre) és a verem milyen értékeket tárol egy adott pillanatban. A veremben tárolt értékek ebben a példában csak visszatérési címek, a forrás program sorszámai. Az ábrában a nyíl az SS:SP regiszter páros által mutatott címet jelöli. Nézzünk egy példát, ahol a 10.4.d. ábrán a verem tetején a visszatérési érték 6, ami a jelen példában a 6. sort jelenti a forráskódban, és a következő végrehajtandó utasítás a 10. sorban található (IP=10). A 10. sorban egy RET utasítás van, ami a verem tetejéről leveszi a 6-os értéket és ezt tölti be az IP regiszterbe, így nem véletlen, hogy a 10.4.e. ábrán már eggyel kevesebb érték van és a következő utasítás amit végre kell hajtani az a 6. sorban található, vagyis IP=6. 1 org 100h 2 call muvelet 3 int 20h 4 muvelet: 5 call torol 6 call addketto 7 ret 8 torol: 9 xor ax, ax 10 ret 11 addegy: 12 inc ax 13 ret 14 addketto: 15 call addegy 16 call addegy 17 ret tábla: Példa az egymásba ágyazott függvényekre 10.4 Paraméter átadás függvényeknek A paraméter átadás egy kicsit komplikáltabb mint más magas szintű programozási nyelvekben. Assemblyben az a program részlet amelyik szeretne egy függvényt meghívni a paramétereket egy olyan helyre másolja amit önmaga és a függvény is elér, és csak ezután történik a függvényhívás. Háromféle lehetőségünk van a paraméter átadásra közös területen keresztül: regiszteren, memórián és vermen keresztül. 126

127 (a) IP=2 (b) IP=5 (c) IP=9 (d) IP=10 (e) IP=6 (f) IP=15 (g) IP=12 (h) IP=13 (i) IP=16 (j) IP=12 (k) IP=13 (l) IP=17 (m) IP=7 (n) IP= ábra: A táblán látható program nyomonkövetése 127

128 Paraméter átadás regiszteren keresztül A függvény működéséhez szükséges paramétereket regiszterekbe tesszük a függvény meghívása előtt. A módszerre nézzünk egy példát, melyet a tábla mutat be. A program beolvas egy karaktert majd azt egy függvény segítségével ismét kinyomtatja, de a nyomtatást egy függvény végzi. 1 org 100h 2 MOV AH, 01 3 INT 21h 4 CALL charnyomtat 5 INT 20h 6 ; nyomtatandó karakter AL-ben adódik át 7 charnyomtat: 8 MOV AH, 02 9 MOV DL, AL 10 INT 21h 11 RET A módszernek vannak előnyei és hátrányai: Előnyök tábla: Paraméter átadás regiszteren keresztül A módszer kényelmes és könnyű kis számú paramétert átadni egy függvénynek. A módszer nagyon gyors, mivel minden paraméter már a regiszterekben lesz a függvényben. Hátrányok A fő hátrány, hogy csak néhány paramétert lehet regiszteren keresztül átadni, mivel csak limitált mennyiségű általános regiszter áll rendelkezésre. Mivel a hívási metódus során bizonyos regisztereknek speciális szerepük lehet, ezért szükség lehet ezeket addig a vermen elmenteni, amíg a paraméterátadás meg nem történik. Ugyanakkor ez azt is jelenti, hogy a második előnyt elveszítjük, hiszen a paraméterátadásban verem műveletre is szükség van Paraméter átadás memórián keresztül A memórián keresztüli paraméter átadás hasonló a regiszteren keresztüli paraméter átadáshoz. Ebben az esetben lényegében a memóriában kijelölünk egy területet ahova a paramétereket a hívó program részlet bemásolhatja és ahonnan a függvény majd kiveheti a paramétereket. Ezt a megoldást is érdemes egy példával demonstrálni, ami táblán látható. A módszer előnye, hogy akárhány paraméter átadható egy függvénynek. Fő hátránya, hogy memória elérésre van szükség az összes paraméter átadása során. Erre egy példát a tábla mutat. Ebben a példában a függvény három számot ad össze. 128

129 1 org 100h 2 MOV AH, 01 3 INT 21h 4 MOV [char], AL 5 CALL charnyomtat 6 INT 20h 7 8 ; nyomtatandó karakter a memóriában 9 charnyomtat: 10 MOV AH, MOV DL, [char] 12 INT 21h 13 RET 14 char: db tábla: Paraméter átadás memórián keresztül 1 org 100h 2 ; regiszterek beállítása, 3 ; amit valamilyen számításból kaptunk 4 MOV AX, 0001h 5 MOV BX, 0010h 6 MOV CX, 0100h 7 ;... 8 ; paraméter átadás memórián keresztül 9 MOV SI, paramtabla 10 MOV [SI], AX 11 MOV [SI+2], BX 12 MOV [SI+4], CX 13 CALL osszead 14 ; az eredmeny a DX regiszterben 15 INT 20h 16 ; regiszterek összeadása 17 osszead: 18 MOV SI, paramtabla 19 MOV DX, [SI] 20 ADD DX, [SI+2] 21 ADD DX, [SI+4] 22 RET 23 paramtable: dw 0, 0, tábla: Második példa a paraméter átadásra memórián keresztül 129

130 10.5. ábra: Verem állapota amikor két paramétert adunk át egy függvénynek a vermen keresztül Paraméter átadás vermen keresztül Ez az egyik leggyakoribb paraméter átadási módszer. Ebben az esetben a függvény paramétereit a vermen tároljuk el és csak utána hívjuk meg a függvényt. Ezt a módszert is egy példán keresztül lehet a legjobban bemutatni. Vegyünk egy olyan függvényt aminek két számot kell átadni: push [szam1] push [szam2] call osszead Miután a CALL utasítást végrehajtottuk a verem állapota a ábrán látható. Az ábrából kitűnik, hogy a függvényen belül igen nehéz hozzáférni a paraméterekhez, mivel a verem tetején a visszatérési cím is ott van. A függvényen belül például megtehetjük a következőt: pop ax pop bx pop cx push ax ; IP ide kerül ; szam2 ide kerül ; szam1 ide kerül ; IP cím vissza kerül a veremre Lényegében először a visszatérési címet vesszük le, utána a paramétereket és végül a visszatérési címet vissza kell tenni a veremre. A probléma ezzel a megoldással az, hogy félre kell tenni regisztereket a paraméterek kinyerésére, vagyis ezeket a regisztereket másra nem használhatjuk és ráadásul azt a helyzetet sem tudjuk kezelni amikor például több mint 10 paramétert szeretnénk átadni a függvénynek. A problémára a legjobb megoldás, hogy a paramétereket hagyjuk a vermen és csak akkor férjünk hozzájuk amikor a paraméterek kellenek. Ez az a pont ahol fontos arra emlékezni, hogy a verem a memóriában található és így memóriaként is kezelhető (lásd bekezdés). Ez azt jelenti, hogy az adatok a veremben egymás utáni memória címeken találhatók és hivatkozhatunk rájuk SP, SP+2 és SP+4 címekkel. Ebben az esetben az SP (Stack Pointer) regiszter mint egy viszonítási pont szolgál. Így egy paraméter kiolvasását a következőképpen tehetjük meg: MOV BX, [SP+2] ; szam2 ide kerül MOV CX, [SP+4] ; szam1 ide kerül Sajnos még ezzel a megoldással is van egy kis probléma. Az jelenti itt a problemát, hogy az SP regiszter értéke folyamatosan változhat a függvényen belül, mivel a függvényben bárhol használhatunk PUSH és POP utasításokat. Ha mégis ennél a megoldásnál maradunk, akkor arra kell nagyon figyelni, hogy az SP regiszternek mindig az aktuális értékét vegyük alapul. Például az előző programnál maradva a paraméterek kiolvasása módosul amikor a függvényen belül további verem műveleteket végzünk. PUSH [szam1] PUSH [szam2] CALL osszead 130

131 10.6. ábra: Verem állapota amikor két paramétert adunk át egy függvénynek a vermen keresztül, de függvényben az SP regiszter értéke is módosul... osszead: PUSH AX PUSH BX ; verem állapota az ábrán MOV AX, [SP+6] ; szam2 ide kerül MOV BX, [SP+8] ; szam1 ide kerül... A ábra a verem állapotát mutatja be akkor, amikor éppen a szam2 paramétert szeretnénk kiolvasni. Az ábrából látható, hogy az SP regiszter által mutatott cím és a például a szam2 paraméter címe közötti távolság megváltozott. Mindezek után nézzük a legjobb megoldást. Az előző megoldással az volt a probléma, hogy az SP regiszter állandóan változhat. Ennek megoldására amikor belépünk a függvénybe készítünk egy fix pontot, amihez képest a paramétereket elérhetjük. Az SP regiszter helyett a BP regisztert is használhatjuk referencia pontként, miután az SP értékét belemásoltuk. Az előző példa végleges megoldását a vermen kereszt-li paraméter átadásra a tábla mutatja be. A programhoz tartozó verem állapotait a ábra mutatja be. A következőkben nézzük meg, hogyan alakul ki a verem állapota. A programban a paraméterek eltárolása ugyanúgy történik mint eddig, két PUSH utasítással feltöltjük az értékeket a veremre a 4. és 5. sorban. Ez látható a 10.7.a. ábrán. A 6. sorban található CALL utasítás feltölti a 7. sor címét a veremre, lásd a 10.7.b. ábra. Ez az ábrában az IP értékkel van jelölve. Ezután a CALL utasítás átadja a vezérlést az osszead címre. Itt először eltároljuk a BP regiszter értékét a 12. sorban, ahogy ez a a 10.7.c. ábrán látható. Erre azért van szükség, mivel minden függvény ezt a regisztert használja és felül fogjuk írni, de a függvényből kilépve szükség lehet az eredeti értékére. (Így a 12. sorban a vermet időleges érték tárolásra használjuk.) Ezután az SP regiszter aktuális értékét átmásoljuk a BP regiszterbe a 13. sorban. Ezzel lényegében létrejött a stack frame vagy függvény keret. Ez látható a 10.7.d. ábrán. A függvényhez tartozó stack frame a függvényre vonatkozó minden fontos információt tartalmaz: a függvény paramétereit, a függvény visszatérési értékét, az elmentett BP regiszter értékét és a lokális változókat is. (A lokális változókról a bekezdésben olvashatunk.) A fenti kialakítás miatt szokták a BP regisztert frame pointer -nek is nevezni. Ezután a 14. és 15. sorban elmentünk 2 regisztert, mely utasítások módosítják az SP regisztert. Ez látható a 10.7.e. ábrán. 131

132 Végül a 16. és 17. sorban kiolvassuk a két paramétert és összeadjuk őket. A 10.7.f. ábrán látható az, hogy a paraméterek kiolvasásához szükséges címeket hogyan számolhatjuk ki. Természetesen ahogy felépítettük a vermet, úgy a függvény végén vissza is kell állítani, hogy az eredeti állapotot kapjuk vissza, mintha a függvény hívással semmi sem történt volna. Ez azt jelenti, hogy amilyen sorrendben feltöltöttük az értékeket a veremre, azzal ellentétes sorrendben le kell vennünk a veremről. Így először helyreállítjuk az elmentett regisztereket a 18. és 19. sorban. A függvény végén a BP regisztert is vissza kell állítani, hiszen lehet hogy ezt a függvényt egy másik függvény hívta meg és a BP regiszter a hívó függvény frame pointer-ét tartalmazza. A BP regiszter visszaállítását a 20. sorban végezzük el. Végül a függvényből a RET paranccsal lépünk ki a 21. sorban. Ahogy ezt már fent leírtuk, a RET utasítás leveszi a függvény visszatérési értékét. Ebben a pillanatban a verem állapotát a 10.7.a. ábrának felel meg. Az ábra jól mutatja, hogy a függvény hiába tért vissza a 7. sorhoz a veremben még vannak adatok. Ezeket az adatokat el kell távolítani a veremről, mivel a függvényhívás után nincs értelme a vermen tartogatni, hiszen nincs többé hasznuk. Ezen kívül ha nem távolítjuk el, akkor a sokszori függvényhívás megtöltené a vermet. Két lehetőségünk van ezt a problémát orvosolni: a hívó programrészlet takarít vagy. a hívott programrészlet takarít. Ezeket a stratégiákat tárgyaljuk a következő bekezdésben. 1 org 100h ; paraméter átadás vermen keresztül 4 PUSH [szam1] 5 PUSH [szam2] 6 CALL osszead 7 ADD SP, 4 8 ; az eredmeny az CX regiszterben INT 20h 11 osszead: 12 PUSH BP 13 MOV BP, SP 14 PUSH AX 15 PUSH BX 16 MOV CX, [BP+6] ; szam1 kiolvasása 17 ADD CX, [BP+4] ; szam2 hozzáadása 18 POP BX 19 POP AX 20 POP BP 21 RET tábla: Példa a paraméter átadásra a vermen keresztül Függvényhívás utáni takarítás Az előző bekezdésben, a táblán bemutatott programban a függvényhívás után törölni kell 4 bytenyi értéket. Használhatnánk például ezt a megoldást is: POP CX POP CX 132

133 (a) (b) (c) (d) (e) (f) ábra: Verem állapotai a táblán bemutatott programban 133

134 de ezzel az a gond, hogy tönkretesszük a CX regiszter értékét. Így itt is inkább a vermet, mint memória tömböt kezeljük és ezért csak az SP regisztert módosítjuk: ADD SP, 4 Mivel a ábrán a memória címek lentről felfelé növekednek, ezért ahhoz hogy eldobjunk 4 byte-nyi értéket a veremről az SP regiszter értékét meg kell megnövelni. Az eldobás szó azért szerepel idézőjelek között, mert valójában az értékeket nem töröljük, csak annyi történik, hogy az SP regiszter mozgatásával átállítjuk a foglalt és a szabad részek közötti határt. Az SP regiszter által mutatott címnél nagyobb címen elhelyezkedő értékek foglaltak, a kisebb címen elhelyezkedő értékek szabadok, azokat bármikor felülírhatjuk, törölhetjük. Azt is fontos megérteni, hogy a következő teljesen helytelen: osszead:... ADD SP, 4 RET mivel így a visszatérési értéket is eltávolítanánk az ADD utasítással. Ebben az esetben a LIFO szabály is sérül, hiszen a függvény hívás során alkalmazott sorrenddel ellentétes módon kellene mindent helyreállítani, de itt előbb próbáljuk meg felszabadítani a paraméterek helyét és csak utána akarjuk levenni a veremről a visszatérési értéket. Ha mégis ezt a megoldást szeretnénk, vagyis hogy a hívott függvény takarítson a vermen, akkor a RET utasításnak egy opcionális paramétert kell megadni. Például a: RET 4 utasítás azt jelenti: IP = [SS:SP] SP = SP A fenti műveletben a 2 azért kell, hogy a visszatérési címet eltávolítsuk a veremről, a 4-es érték pedig a RET után megadott érték, és annyi byte-ot dobunk el a veremről. Így első ránézésre furcsa lehet, hogy két különböző stratégia is van egy függvény paramétereinek letakarítására a veremről: 1. a hívó programrészlet takarít vagy. 2. a hívott programrészlet takarít. Hogy melyiket haználjuk, az attól függ, hogy a függvény paramétereinek száma fix vagy változó lehet. Ha egy függvénynek fix számú argumentuma van, akkor assembly-ben a 2. megoldás preferált. Ez azért van, mert így csak egyszer, a függvény végén kell implementálni ezt a kódrészletet. Ugyanakkor ha egy függvénynek változó számú argumentuma lehet, akkor csak az 1. megoldás használható. Mit jelent az, hogy változó számú argumentum? Itt gondoljunk például a C programozási nyelvben használt printf függvényre. Ezt a függvényt többféleképpen is meghívhatjuk: printf( Hello ); printf( Az eredmeny: %d, ertek); printf( x: %lf -- y: %lf, x, y); Erre a kérdésre még visszatérünk a 14. fejezetben. 134

135 Állapotmegörzés A táblán bemutatott programban a függvény elején elmentünk regisztereket a PUSH utasítással, illetve a végén helyreállítjuk őket a POP paranccsal. Miért lehet erre szükség? Vegyük a következő programrészletet: MOV CX, szam ciklus: CALL szamolo... LOOP ciklus... Ebben a programrészletben a CX regiszter tárolja a ciklus változó értékét. Ha a szamolo függvény módosítja a CX regisztert, akkor a program logikája helytelen lesz, hiszen a CX regiszter értéke nem fut végig az általunk megadott tartományon. Ezért nagyon fontos, hogy minden olyan regisztert amit a függvényben használunk, azt a függvény elején elmentsünk, majd a függvény végén helyreállítsunk. Itt is felmerülhet a kérdés, hogy a hívó vagy a hívott programrészlet végezze a mentést és a helyreállítást. Mi történik akkor, ha a hívó programrészletnek kell a regisztereket elmenteni? A program karbantartása hihetetlenül nehéz lenne, mivel ha később a hívott függvény módosítjuk és az eredetitől eltérő regisztereket használna a függvény, akkor mindenhol ahol a függvényt meghívjuk módosítani kellene a programot. A program mérete megnövekedne, mivel ha egy függvényt többször meghívunk, akkor minden alkalommal a regiszterek elmentését és helyreállítását is le kell programozni. Ezen okoknál fogva a regiszterek mentését és helyreállítását csak a hívott függvényben szoktuk leprogramozni, ahogy ez a táblán bemutatott programban is láttuk. Ez az megfontolás megfelel a moduláris programozás elveinek is. Még egy kérdést érdemes tisztázni: miért nem használjuk mindig a PUSHA és POPA utasításokat, amelyek minden regisztert elmentenek és helyreállítanak? Először is előfordulhat, hogy egy függvény vissza akar adni egy értéket a hívó függvénynek. Ezt általában regiszteren keresztül szoktuk megtenni, méghozzá az AX regiszteren keresztül. Ebben az esetben az AX regisztert nem kell elmenteni és nem szabad visszaállítani, vagyis felülírni a visszaadandó értéket. Másodszor a PUSHA utasítás végrehajtása 5 órajel ciklust igényel, míg egy PUSH utasítás csak 1 órajel ciklusig tart. Ez azt jelenti, hogy a PUSHA utasításnak csak akkor van értelme ha 5 regiszternél többet akarunk elmenteni Érték és cím szerinti paraméter átadás Csak a teljesség kedvéért érdemes itt megemlíteni az érték és cím szerinti paraméter átadás közötti különbséget. A témára még visszatérünk a 14. fejezetben is. A legtöbb programozási nyelvben, így a C programozási nyelvben is, az érték szerinti paraméter átadás az alapértelmezett módszer. Ez azt jelenti, hogy egy értéket amit szeretnénk átadni a függvénynek azt átmásoljuk a függvény területére. Az eddigiek alapján ez megfelel annak, hogy az értéket felmásoljuk a függvény stack frame-jébe. A cím szerinti paraméter átadás esetén a változó címét adjuk át a függvénynek, vagyis a változó címét felmásoljuk a függvény stack frame-jébe. A két módszer közötti különbség bemutatására a táblán és a táblán bemutatott programokat érdemes összehasonlítani. Mind a két program beolvas két karaktert, amelyeket eltárolunk a memóriában, majd 135

136 1 org 100h 2 ; két karakter beolvasása 3 MOV AH, 01 4 INT 21h 5 MOV [char], AL 6 MOV AH, 01 7 INT 21h 8 MOV [char+1], AL 9 10 PUSH word [char] 11 CALL char2nyomtat 12 INT 20h char2nyomtat: 15 PUSH BP 16 MOV BP, SP 17 PUSH AX 18 PUSH DX 19 PUSH BX 20 MOV BX, [BP+4] 21 MOV AH, MOV DL, BL 23 INT 21h 24 MOV DL, BH 25 INT 21h 26 POP BX 27 POP DX 28 POP AX 29 POP BP 30 RET 31 char: db 0, tábla: Érték szerinti paraméter átadás a vermen keresztül egy függvény segítségével a karaktereket kinyomtatjuk. A táblán látható programban, a 10. sorban a char változó értékét feltöltjük a veremre. Az így feltöltött értéket a frame stack-ből a 20. sorban visszaolvassuk a BX regiszterbe, majd külön-külön kinyomtatjuk a BL és BH regiszter tartalmát. A táblán látható program egy kicsit más. Ebben a programban a 10. sorban a char változó címét töltjük fel a verembe és a 20. sorban ezt a címet olvassuk ki a veremből majd másoljuk át a BX regiszterbe. Így a 22. és 24. sorban az átadott címről olvassuk ki a nyomtatandó karaktereket. Egy kicsit mesterkéltnek tűnhet ez a példa, de a valódi programok esetén is van jelentősége a cím szerinti paraméter átadásnak. Az egyik legfontosabb alkalmazási területe a cím szerinti paraméter átadásnak, amikor egy tömböt szeretnénk átadni egy függvénynek. Ilyenkor nem érdemes a teljes tömböt feltölteni a veremre, hanem elegendő csak a tömb címét átadni a függvénynek. A tábla egy másik példát mutat a cím szerinti paraméter átadásra, amiben egy szöveget nyomtatunk ki egy függvénnyel Változó számú paraméter átadása függvénynek A C programozási nyelvben arra is lehetőség van, hogy egy függvény változó számú paramétert fogadjon el. Ilyen függvények a scanf és a printf függvények. Ebben az esetben a hívott függvény 136

137 1 org 100h 2 ; két karakter beolvasása 3 MOV AH, 01 4 INT 21h 5 MOV [char], AL 6 MOV AH, 01 7 INT 21h 8 MOV [char+1], AL 9 10 PUSH char 11 CALL char2nyomtat 12 INT 20h char2nyomtat: 15 PUSH BP 16 MOV BP, SP 17 PUSH AX 18 PUSH DX 19 PUSH BX 20 MOV BX, [BP+4] 21 MOV AH, MOV DL, [BX] 23 INT 21h 24 MOV DL, [BX+1] 25 INT 21h 26 POP BX 27 POP DX 28 POP AX 29 POP BP 30 RET 31 char: db 0, tábla: Cím szerinti paraméter átadás a vermen keresztül nem tudja előre, hogy paramétert adunk át neki. Többféle megoldás is létezik ennek a szituációnak a kezelésére. Az egyik legegyszerűbb módszer, hogy az első paraméter megadja, hogy hány további paraméter kerül a veremre. Egy dologra azonban figyelni kell, hogy a paraméterek száma az utolsó legyen amit feltöltünk a veremre, éppen a visszatérési cím fölé. Ezt a helyzetet a ábra mutatja be. A tábla egy érdekes minta programot mutat be az előbb bemutatott változó paraméterű függvényekre. A program folyamatosan olvas számjegyeket ( bármennyit ), amíg nullát nem adunk meg. Ezeket a számjegyeket átadjuk egy függvénynek, ami összeadja a számokat és visszaadja az összegüket. Végül a program az összegnek megfelelő darab pontot nyomtat ki a képernyőre. Nézzük a program működését: A 2. sor egy előkészítés, a CX regisztert lenullázzuk, mivel a CX regiszterben fogjuk számolni, hogy hány számot adott meg a felhasználó. Az 4. és 5. sorban olvasunk be egy számjegyet. Itt ellenőrizni is kellene, hogy csak számjegyeket adhat meg a felhasználó, de ettől az ellenőrzéstől most eltekintünk. A 6. sorban a karakter ASCII kódját számmá konvertáljuk. Ha ennek a műveletnek az eredménye zérus, akkor a felhasználó a zérus számot adta meg, és véget ér a beolvasás. A 8. és 9. sor segítségével feltöltjük a beolvasott számot a veremre és a 10. sorban megnöveljük a számlálót. Így a CX regiszter azt fogja mutatni, hogy hány értéket töltöttünk fel a veremre. A 14. sorban magát a számlálót is feltöltjük a veremre. A 15. sorban szereplő CALL utasítással átadjuk a 137

138 1 org 100h 2 PUSH szoveg ; szöveg címe 3 CALL nyomtat 4 INT 20h 5 6 nyomtat: 7 PUSH BP 8 MOV BP, SP 9 PUSH AX 10 PUSH DX 11 MOV AH, MOV DX, [BP+4] ; a címet olvassuk ki 13 INT 21h 14 POP DX 15 POP AX 16 POP BP 17 RET 18 szoveg: db Hello vilag$ tábla: Szöveg nyomtatása függvénnyel, mely demonstrálja a cím szerinti paraméter átadást a vermen keresztül vezérlést a függvénynek. A 29. és 30. sor befejezi a függvény stack frame-jének előkészítését. Ekkor a ábrán látható állapot alakul ki a vermen. A 31. sorban elmentjük a CX regisztert, mivel a 32. sorban betöltjük a paraméterek számát a CX regiszterbe. A végeredmény az AX regiszterben fog kialakulni ezért a 33. sorban lenullázzuk az AX regisztert. A 34. sorban az SI regisztert úgy készítjük elő, hogy ha ezt az értéket hozzáadjuk a BP regiszterhez, akkor rögtön az első paraméterre fog mutatni. Ezután a 36. sorban az AX regiszterhez adjuk az aktuális paramétert. A 37. sorban az SI regiszter értékét kettővel növeljük meg, mivel a vermen 2 byte-onként, vagyis word-önként, vannak a paraméterek eltárolva. A 38. sorban található LOOP utasítás az előzőleg a CX regiszterbe betöltött számszor végrehajtja a ciklust. A sorok közötti részt meg lehetne oldani másképpen is, például: ADD BP, 6 ujra_ad: ADD AX, [BP] LOOP ujra_ad ábra: A verem állapota, változó számú paraméter esetén 138

139 de ennek a módszernek az a hátránya, hogy a BP regisztert folyamatosan módosítjuk és így többet nincs lehetőségünk az eredeti számlálót elérni. A sorok között megfelelően befejezzük a függvényt. Az egyik érdekesebb részlet a sorok között található, mivel a függvény előtt feltöltött értékeket le is kell takarítani a veremről. Ugyanakkor nem tudjuk előre, hogy hány darab értéket kell letörölni a veremről. Ezt a problémát itt úgy oldjuk meg, hogy először a CX regiszter értékét megnöveljuk 1-el. Így most a CX regiszter a paraméterket számát plusz egy értéket tartalmaz. A plusz egy arra kell, hogy a számlálót is feltöltöttük a veremre. Ezután megduplázzuk a CX értékét, mivel a vermen word méretű adatokat tárolunk. Végül a 18. sorban az SP regiszter módosításával eldobjuk a feltöltött értékeket a veremről. A sorok között annyi pont karaktert nyomtatunk ki, amekkora számot az AX regiszterben a függvény visszaadott. Erre már láttunk példát korábban. 139

140 1 org 100h 2 mov cx, 0 3 ujra_olvas: 4 mov ah, 1 5 int 21h 6 sub al, 0 7 jz olvas_vege 8 xor ah, ah 9 push ax 10 inc cx 11 jmp ujra_olvas olvas_vege: 14 push cx 15 call osszead 16 inc cx ; a szamlalo is bele tartozzon 17 add cx, cx ; cx = cx * 2 18 add sp, cx mov cx, ax 21 mov ah, 2 22 mov dl,. 23 nyomtat: 24 int 21h 25 loop nyomtat 26 int 20h osszead: 29 push bp 30 mov bp, sp 31 push cx 32 mov cx, [bp+4] 33 xor ax,ax 34 mov si, 6 35 ujra_ad: 36 add ax, [bp+si] 37 add si, 2 38 loop ujra_ad 39 pop cx 40 pop bp 41 ret tábla: Változó számú szám összeadása és annyi pont nyomtatása 140

141 10.5 Lokális változók függvényekben Az eddigi bekezdésekben nem esett arról szó, hogy hogyan kezeljük a függvények lokális válotzóit. Például vegyük a következő C program részletet: int szamol(int a, int b) { int temp, n; temp = a; n = b;... } Ebben a a program részletben az n és temp változók akkor jönnek létre amikor belépünk a függvénybe, és a változók megszünnek amikor kilépünk a függvényből. Így ezekre a változók lokálisak és dinamikusak. Az ilyen dinamikus változóknak helyet foglalhatnánk az adat szegmensben is, de ezt a módszert két oknál fogva nem szoktuk használni: 1. Helyfoglalás az adat szegmensben statikus és akkor is megmarad a terület amikor már nem a függvényben vagyunk, nem a függvény utasításait hajtjuk végre. 2. Ennél fontosabb indok, hogy ez a fajta helyfoglalás nem működik rekurzív függvényekkel. A rekurzív függvényekkel a bekezdésben foglalkozunk. Ezért a lokális változókat a vermen szoktuk létrehozni. A fenti C függvény esetén az assembly-ben létrehozott verem a ábrán látható módon néz ki. Ahogy ez látható, a BP regiszter, mint frame pointer, a lokális változók elérésére is alkalmas. A lokális változók lefoglalása igen egyszerűen megoldható, csak megint memória tömbként kell tekinteni a veremre és két lokális változó esetén így néz ki: SUB SP, 4 Mivel egy változót 2 byte-on tárolunk, ezért négyet vonunk ki az SP regiszterből. A kivonásra pedig azért van szükség, mert így a verem tetejét elmozdítjuk és szabad helyek jönnek létre. Ezek után nézzük meg, hogyan lehet a fenti C programrészletet megvalósítani assembly-ben: PUSH [b] PUSH [a] CALL szamol ADD SP, 4... szamol: PUSH BP MOV BP, SP SUB SP, 4 PUSH AX... MOV AX, [BP+4] ; a változó betöltése MOV [BP-2], AX ; temp változóban tárolás MOV AX, [BP+6] ; b változó betöltése MOV [BP-4], AX ; n változóban tárolás... POP AX ADD SP, 4 POP BP RET 141

142 10.9. ábra: Lokális változók helye a stack frame-ben Itt fontos megérteni, hogy a lokális változók az assembly kódban tulajdonképpen átalakulnak abban az értelemben, hogy például a fenti programrészletben a temp változóra [BP-2] módon lehet hivatkozni, illetve a n változóra [BP-4] módon lehet hivatkozni. Ezért az assembly programokban makrót (11. fejezet) szoktak használni az ilyen változók megnevezésére: %define temp [BP-2] %define n [BP-4]... PUSH [b] PUSH [a] CALL szamol ADD SP, 4... szamol: PUSH BP MOV BP, SP SUB SP, 4 PUSH AX... MOV AX, [BP+4] ; a változó betöltése MOV temp, AX ; temp változóban tárolás MOV AX, [BP+6] ; b változó betöltése MOV n, AX ; n változóban tárolás... POP AX ADD SP, 4 POP BP RET ENTER és LEAVE utasítások A 386-os Intel processzor óta van két speciális utasítás amely közvetlenül támogatja a lokális változók látrehozását és megszüntetését. Az ENTER utasítás formátuma: ENTER bytes, level A bytes paraméter azt adja meg, hogy hány byte-ot kell lefoglalni lokális változóknak. Ha nincs szükség lokális változókra, akkor az értéke lehet zérus is. A második paraméter a függvények egymásba 142

143 ágyazottságát adja meg. Ha itt nem zérust adunk meg, akkor level darab frame pointert másolunk a frame stack-be, a korábbi stack frame-ből. Így a: ENTER XXX, 0 utasítás megfelel az PUSH BP MOV BP, SP SUB SP, XXX utasításoknak. A LEAVE utasítás az ENTER utasítás által lefoglalt stack frame-t szabadítja fel Rekurzív függvények A rekurzív függvények abban speciálisak, hogy a függvény tulajdonképpen önmagát hívja meg. Más szóval úgy is elképzelhetnénk a dolgot, hogy a függvény egy újabb példányát hozza létre, és azt a függvényt hívjuk meg. Ez valójában nincs így, a függvény kódból csak egy van, de a függvény környezete az ami megduplázódik. Ezért nagyon fontos a verem használata, hiszen a vermen akárhány stack frame-t elhelyezhetünk. A regiszteren vagy memórián keresztűli paraméterátadásnál csak egy terület van ahova a paramétereket írhatjuk, ezért egy újabb függvény hívás felülírja az értékeket és így a függvény visszatérése után helytelen kódot hajtanánk végre. A rekurzív függvények bemutatására vegyünk először egy pseudo kódban írt függvényt: FÜGGVÉNY nyomtat(n) print n HA n!= 0 nyomtat(n-1) HA VÉGE FÜGGVÉNY VÉGE Ha ezt a függvényt meghívjuk a nyomtat(2) módon, akkor a program kinyomtatja a 210 értékeket. Ebben az az érdekes, hogy egy számsorozatot nyomtatunk ki, pedig a kódban semmilyen ciklus nincs! Ezért szokták a rekurziót a 4. ciklus képző programozási mószernek is nevezni. 1 A pseudo kódnak megfelelő assembly program a táblán látható, illetve a verem állapotai a program futása során a ábrán látható. Az egyes alábrák azt az állapotot mutatják, amit az alcímben megadott program sorban szereplő utasítás végrehajtása után kapnánk a vermen. Az ábrában a színes dobozok a foglalt memória területet jelölik a fehér dobozok a szabad memória területek. Az SS:SP regiszter páros, ami a verem tetejét jelöli, mindig a színes és a fehér dobozok közé mutat. Az ábrákban a dobozban szereplő IP=4 jelöli a függvény visszatérési értéket, a számok a dobozban pedig a függvény paraméternek (n) felelnek meg. Ezek után nézzük a program működését részletesen, hogy mindenki számára világos legyen a működési elv. A 2. sorban a 2 értéket töltjük fel a veremre, mint word adattípus (10.10.a. ábra). A 3. sorban meghívjuk a nyomtat függvényt, ami a visszatérési értéket feltölti a veremre (10.10.b. ábra), majd átadja a vezérlést a 8. sornak. A 8. sorban eltároljuk a a BP regiszter értékét és ezzel létrehoztuk a függvény stack frame-jét. Ez látható a c. ábrán. Mivel a függvényben az AX, BX 1 Emlékezzünk az algoritmusok tantárgy ismereteire: 1. elől tesztelő ciklus 2. hátul tesztelő ciklus 3. számláló ciklus 143

144 és DX regisztereket is használjuk, ezért ezeket a regisztereket elmentjük a sorokban (10.10.d f. ábrák). A 13. sorban az INT 21h megszakítás funkció kódját állítjuk be, majd a 14. sorban a veremről betöltjük a függvény paraméterét a BX regiszterbe. A 15. sorban átmásoljuk a paramétert a DL regiszterbe, mivel a nyomtatásnál az INT 21h megszakítás ezt a regisztert fogja használni. Miért nem rögtön a DX regiszterbe írtuk bele a függvény paraméterét a 14. sorban? Azért nem, mert a nyomtatáshoz a DL regiszter értékét módosítani kell, a számot a számnak megfelelő karakter ASCII kódjává kell alakítani úgy, hogy hozzáadunk 30h értékét. A 17. sorban elvégezzük a nyomtatást a megszakítással. A 18. sorban megvizsgáljuk, hogy a függvény paramétere, a BL regiszter, zéruse. Mivel nem zérus a BL regiszter értéke, ezért a 20. sorban folytatjuk. Először is csökkentjük a paraméter értékét, majd ezt az értéket feltöltjük a veremre (10.10.g. ábra) a 21. sorban. A 22. sorban a függvényhívás ismét feltölti a visszatérési értéket (10.10.h. ábra) és átadja a vezérlést a 8. sornak. A 8. és 9. sor befejezi a stack frame előkészítését (10.10.i. ábra). Itt látható az, hogy csak egy függvény van, de mivel a verem tetején más adatok vannak, ezért ez egy új függvénynek számít, hiszen más adatokkal fog számolni. Ezt követi a paraméter beolvasása (14. sor), a nyomtatás ( sor), illetve az ellenőrzés, hogy zérus-e a függvény paramétere. Mivel a függvény paramétere nem zérus, ezért csökkentjük a paraméter értékét 1-el (20. sor), majd feltöltjük a veremre a 21. sorban (10.10.m. ábra). A 22. sor újra meghívja a nyomtat függvényt, vagyis a 23. sor mint visszatérési érték felkerül a veremre (10.10.n. ábra) és átadja a vezérlést ismét a 8. sornak. Immár harmadszor a BP regiszter felkerül a veremre (10.10.o. ábra) a 8. sorban, ezzel a harmadik stack frame-et előkészítve. A sorokban a regisztereket ismét elmentjük, illetve a sorokban a nullás értéket kinyomtatjuk. A 18. sorban a BL regiszter értékét nullának fogjuk találni, így a 25. sorba ugrunk. A sorokban eldobjuk az elmentett AX, BX, DX és BP értékeket. Ez látható a a d. ábrákon. A 29. sorban a verem tetején látható címre (10.11.d. ábra) adódik át a vezérlés, vagyis a 23. sorra. A 23. sorban eldobjuk a verem tetejére feltöltött paramétert (10.11.e. ábra). Ezt követi a sorok végrehajtása, ahol megint helyreállítjuk az elmentett regisztereket (10.11.f j. ábrák), majd a 29. sorban a verem tetején található címre visszatér a függvény, vagyis a 23. sorba. Itt ismét eldobjuk a vermen található értékekeket (10.11.k p. ábrák). Amikor ismét a 29. sorba érünk akkor a verem tetejéről levesszük a visszatérési címet (10.11.p. ábra) és elugrunk az így megadott címre, a 4. sorba. A 4. sor végrehajtásával az utolsó értéket is letakarítjuk a veremről és visszaáll az eredeti állapot, amikor is semmi nem volt a vermen (10.11.r. ábra) Hatékonyság Ahogy ebben a fejezetben láttuk a függvények nagy mértékben támogatják a moduláris programozást. Ugyanakkor a függvények használatáért fizetnünk is kell, mivel a paraméter átadási módszerek megvalósításához extra utasításokat kell alkalmazni, amelyek nem közvetlenül az algoritmushoz tartoznak. Az ilyen extra utasításokat overhead -nek nevezzük. Vegyünk egy programrészletet, két változó cseréjét. Ez látható a táblán. A bal oldali programrészlet egyszerűen végrehajtja a cserét, a jobb oldali program függvényként végzi el ugyanezt. A második esetben lényegében csak a függvényhívással és a RET utasítással egészítjük ki a programot. Mind a két programrészletben ECX-szer hajtjuk végre a műveletet. (Jelen példában azért használjuk a 32 bites regisztereket, mert nagyon sokszor szeretnénk végrehajtani, hogy mérhető adatokat kapjunk.) Amennyiben mind a két kódrészletet lemérjük futás után, azt kapjuk, hogy alap esetben 160 ms-ig, míg függvényként való futtatás esetén 240 ms-ig tart a programrészlet futása. A különbség ezen mérések szerint másfélszeres. (Más mérések szerint ez lehet 2.3-szeres különbség is [2.]) Azt is érdemes meggondolni, hogy ha paramétereket is átadunk a függvénynek akkor ez az overhead még nagyobb lehet, így sebességre való optimalizálás esetén a függvény hivások minimalizálandók (lásd és bekezdés) Ellenőrző kérdések 1. Mi a különbség a sor és verem adatszerkezetek között? 144

145 (a) IP=2 (b) IP=3 (c) IP=8 (d) IP=10 (e) IP=11 (f) IP=12 (g) IP=21 (h) IP=22 (i) IP=8 (j) IP=10 (k) IP=11 (l) IP=12 (m) IP=21 (n) IP=22 (o) IP=8 (p) IP=10 (q) IP=11 (r) IP= ábra: A verem állapotai a táblán bemutatott rekurzív függvény végrehajtása során 145

146 (a) IP=25 (b) IP=26 (c) IP=27 (d) IP=28 (e) IP=29 (f) IP=23 (g) IP=25 (h) IP=26 (i) IP=27 (j) IP=28 (k) IP=29 (l) IP=23 (m) IP=25 (n) IP=26 (o) IP=27 (p) IP=28 (q) IP=29 (r) IP= ábra: A verem állapotai a táblán bemutatott rekurzív függvény végrehajtása során 146

147 1 org 100h 2 push 2 3 call nyomtat ; nyomtat(3) 4 add sp,2 5 int 20h 6 7 nyomtat: 8 push bp 9 mov bp,sp 10 push ax 11 push bx 12 push dx 13 mov ah, 2 ; - 14 mov bx, word [bp+4] ; 15 mov dl, bl ; print n 16 add dl, 30h ; 17 int 21h ; - 18 cmp bl, 0 ; HA n!= 0 19 jz nyomtat_vege 20 dec bl ; - 21 push bx ; nyomtat(n-1) 22 call nyomtat ; - 23 add sp,2 24 nyomtat_vege: 25 pop dx 26 pop bx 27 pop ax 28 pop bp 29 ret tábla: Számjegyek nyomtatása rekurzív függvénnyel 2. Mi az a stack underflow? Melyik művelet tudná ezt okozni? 3. Mi az a stack overflow? Melyik művelet tudná ezt okozni? 4. Melyek a verem fő felhasználási területei? 5. Melyik utasítással lehet feltölteni egy értéket a veremre? 6. Ha POP utasítás nem megengedett, akkor hogyan tudunk levenni egy értéket a veremről? 7. Mely regiszterek mutatják meg a verem tetejének címét? 8. Hogyan cserélhetjük fel két regiszter tartalmát a vermen keresztül? 9. Mi az a stack frame és mit tartalmaz? 10. Melyik regiszter a frame pointer? 11. Milyen módszerekkel adhatunk át paramétert egy függvénynek? 12. Ha a regiszteren keresztül adunk át paramétert, akkor ennek a módszernek milyen előnyei és hátrányai vannak? 147

148 ujra: mov eax, ertek1 mov ebx, ertek2 mov ertek2, eax mov ertek1, ebx dec ecx jnz ujra ujra: call csere dec ecx jnz ujra jmp vege csere: mov eax, ertek1 mov ebx, ertek2 mov ertek2, eax mov ertek1, ebx ret vege: tábla: Programrészlet két változó cseréjére függvény nélkül és függvénnyel 13. Mely memória területen definiálunk lokális változókat egy függvényben? 14. Miért nem az adat szegmensben definiáljuk a lokális változókat? 10.9 Feladatok 9. fejezetben bemutatott programokat írjuk át úgy, hogy az időleges tárolásra ne a MOV utasítást használjuk, hanem a vermet. 148

149 11. Fejezet Makrók A makrók nagyon érdekes és hasznos programozási konstrukciók. Mit nevezünk makrónak? Ez sajnos a programozási nyelvtől is függ. Az assembly programozási nyelvben a makrók hasonlók a C programozási nyelvben használt makrókhoz, de érdekes módon egy kicsit többet is tudnak. Persze erre az extra segítségre szükség is van, mivel az assembly programozási nyelv egy alacsony szintű vagyis gépközeli programozási nyelv. Assembly-ben a makró arra ad lehetőséget, hogy egy szöveg blokkhoz egy nevet rendeljünk hozzá, majd amikor az assembler az adott névvel találkozik a forráskódban, akkor a név helyére a szöveg blokkot illeszti be. A behelyettesítés angol neve: macro expansion. Egyszerűen fogalmazva a macro egy fejlett szöveg behelyettesítési mechanizmus Egy soros makrók A legegyszerűbb makrók egy sorosak. Erre egy példa: %define csereaxbx XCHG AX, BX amit egy programkódban a következőképpen is használhatunk: MOV AX, 1 MOV BX, 2... csereaxbx... Amikor az assembler ezt lefordítja, akkor azt két lépésben teszi. Először is elvégzi a makró behelyettesítéseket: MOV AX, 1 MOV BX, 2... XCHG AX, BX... majd ezután az assembler legenerálja a bináris kódot. Ez azt jelenti, hogy egy két lépéses (two-pass) fordítási folyamat végeredményeként jön létre a bináris kód. Ez a két lépéses folyamat jellemző a C programozási nyelvre is, ahol az első lépés az előfeldolgozás (preprocessing) és a második lépés a fordítás (compiling). 149

150 A makrók összetettebbek is lehetnek, mivel rendelkezhetnek argumentummal vagy paraméterrel, mint a függvények. Ugyanakkor ez felveti a makrók és függvények közötti kapcsolatot. Mikor, melyiket és hogyan használjuk? Egy függvény tulajdonképpen egy al-programnak felel meg. Egy gyakorlatiasabb megfogalmazás szerint a függvény egy olyan utasítás sorozatnak felel meg, amelyet sokszor, több különböző helyen használunk a programunkban. Így a függvény létrehozásához az ismétlődő kódrészletet kiemeljük, nevet adunk neki, majd amikor szükségünk van rá, akkor a nevével hivatkozunk rá és ezzel átadjuk a függvénynek a vezérlést (meghívjuk). Ez a definíció nagyon hasonló a fenti makró definícióhoz. Nézzük makrók és függvények közötti különbségeket: A legfontosabb különbség a makrók és függvények között a generált bináris kódban van. Egy függvény esetén a bináris kódot az assembler egyszer generálja le, majd minden alkalommal amikor a kódrészletre szükség van, akkor a CALL utasítással hívjuk meg. Ez azt is jelenti, hogy a függvényhívás helyére a CALL utasítás bináris kódja kerül. Egy makrók esetén, ahol a makró neve szerepel, oda lesz behelyettesítve a makró forráskódja és oda kerül a bináris kód is. Minden alkalommal. Ez azt jelenti, hogy mindenhol, ahol a makró neve szerepel a kódban, oda a makró teljes bináris kódja belefordul. A másik nagyon fontos különbség a paraméter átadási módszerben van. Függvények esetén a 10. fejezetben tárgyalt paraméter átadási módszerek lehetségesek: regiszteren, memórián és vermen keresztül. Makrók esetén közvetlen paraméter átadás lehetséges, úgy mintha egy magas szintű programozási nyelvet használnánk. Például: %define csere(a,b) XCHG a, b... csere(ax, BX)... Ebből a kódrészletből az alábbi kód generálódik a makró behelyettesítés után:... XCHG AX, BX... Ezek után a makró definiálás néhány további fontos szabálya a következő: Egy makró definíció során a kis és nagy betű közötti különbség számít. Rekurzív definíció esetén a behelyettesítés csak egyszer történik meg. Például: %define a(x) 1+a(x) mov ax, a(3) amiből a következő kód generálódik: mov ax, 1+a(3) Nagyon fontos, hogy a behelyettesítés a makró használatakor történik és nem a definiáláskor. Például: %define b(x) 2*x %define a(x) 1+b(x) mov ax, a(8) 150

151 A példában amikor az assembler meglátja a a(8) kifejezést, akkor először az a(x) makró helyettesítődik be: mov ax,1+b(8) ezután pedig a b(x) makró behelyettesítése történik meg: mov ax,1+2* Több soros makrók Több soros makrók esetén a szintakszis a következő: %macro név param_szám utasítások utasítások... %endmacro A név adja meg a makró nevét, amivel hivatkozni lehet rá. A param_szám adja meg a makrónak megadható paraméterek számát. Nézzük az első egyszerű példát, amely tulajdonképpen egy szorzásnak felel meg: %macro szorzasax_4el shl AX, 2 %endmacro Ez egy olyan makró, aminek nincs paramétere és használata igen egyszerű: mov AX, 3 szorzasax_4el amiből a generált kód a következő lesz: mov AX, 3 shl AX, 2 Ez a példa elég korlátozott, mivel csak az AX regisztert képes 4-el megszorozni. Ha szeretnénk ezt a korlátozást eltávolítani, akkor egy olyan makrót kellene definiálnunk, amelyiknek megadhatjuk, hogy melyik regisztert akarjuk megszorozni: %macro szorzas_4el 1 shl %1, 2 %endmacro A név után megadott szám adja meg, hogy a makrónak van egy argumentuma. Erre az argumentummal egy százalék jellel és utána a paraméter számával lehet hivatkozni, például: %1. Így ha ezt a makrót használjuk: mov BL, 3 szorzas_4el BL akkor bármilyen regiszter megadhatunk, például BL, és így a behelyettesített kód a következő lesz: 151

152 mov BL, 3 shl BL, 2 Ha több paramétert akarunk átadni egy makrónak, erre is lehetőség van: %macro csere 2 XCHG %1, %2 %endmacro Ennek a makrónak a használata a következő lesz: csere AX, BX Amint ez látható a makrók teljesen integrálódhatnak az assembly nyelvbe. Ennek sajnos az is a következménye, hogy a makrók és az utasítások néha egy kicsit össze is keverhetők, például: %macro push 2 push %1 push %2 %endmacro... push ax push bx, cx Itt is látható, hogy az első alkalommal a push egy utasítás, míg a második alkalommal a push egy makró, amit majd az assembler lecserél a definícióban megadott utasításokra Címkék makrókban Eddig csak olyan makrókat láttunk amelyekben nem használtunk semmilyen memória címet, ugyanakkor a címek egy kis figyelmet érdemelnek. Tegyük fel, hogy a következő makrót definiáljuk, amely egy értéket összehasonlít zérussal és aszerint állítja be az AX regiszter értékét, hogy zérus volt-e az érték: %macro cmp_zero 1 cmp %1, 0 jz nulla mov AX, 1 nulla: mov AX, 0 %endmacro Ha ezt a makrót többször is használjuk: cmp_zero CX... cmp_zero DX... akkor a következő kód generálódik: cmp CX, 0 jz nulla mov AX, 1 nulla: mov AX, 0 152

153 ... cmp DX, 0 jz nulla mov AX, 1 nulla: mov AX, 0... Ahogy ez itt is látható, ebben az esetben a nulla cím kétszer is megjelenik a kódban. Ezt az assembler nem engedi meg és hibát fog generálni. Ilyen esetben a megoldás az, hogy a nulla címnek lokálisnak, egyedinek kell lennie az egyes makró behelyettesítések során. Ezt úgy lehet elérni, hogy a cím elé kettő darab százalék jelet kell tenni: %macro cmp_zero 1 cmp %1, 0 jz %%nulla mov AX, 1 %%nulla: mov AX, 0 %endmacro Ha ezt az újabb makró definíciót használjuk: cmp_zero CX... cmp_zero DX... akkor már nem lesz semmi gond, hiszen a következő kód generálódik: cmp CX, 0 jz nulla2345 mov AX, 1 nulla2345: mov AX, 0... cmp DX, 0 jz nulla7453 mov AX, 1 nulla7453: mov AX, 0... A makrókban a dupla százalék jellel definiált címek esetén az assembler garantálja, hogy minden egyéb, a programban előforduló címtől eltérő címet fog generálni, vagyis minden makró behelyettesítés esetén a cím egyedi lesz Greedy makró paraméterek Az is előfordulhat, hogy nem csak fix számú argumentumot akarunk megadni egy makrónak, hanem néha többet is. Erre az esetre való a greedy paraméter, ami azt jelenti, hogy minden utána következő érték hozzá tartozik. Például: %macro PRINT

154 JMP %%atlep %%szoveg: DB %1, $ %%atlep: MOV DX, %%szoveg MOV AH,9 INT 21H %endmacro A példában a 1+ azt jelenti, hogy a makrónak egy paramétere biztosan van, de lehet több is. Abban az esetben ha több paramétert is megadunk, akkor mindegyik, egymás után a %1 helyére kerül. A fenti makró használatára a példa: PRINT Hello vilag, 10, 13 amiből a következő kód generálódik: JMP atlep87643 szoveg8964: DB Hello vilag, 10, 13, $ atlep87643: MOV DX, szoveg8964 MOV AH,9 INT 21H 11.3 Makrók és függvények még egyszer A fenti ismeretek tükrében érdemes még egyszer összehasonlítani a függvényeket és a makrókat egy példán keresztül. A példában az előző bekezdés makróját hasonlítjuk össze egy függvénnyel, amelyik szintén egy szöveget nyomtat ki. Az összehasonlítás a táblán található. A táblából úgy tűnik, hogy a makrók rövidebb kódot generálnak, de általában inkább az a helyzet, hogy a további makró hívások esetén újra és újra több kód kerül be a forráskódba, míg az újabb függvényhívások esetén csak újabb PUSH, CALL és ADD utasítások adódnak hozzá a programhoz Makrók gyűjtemények A gyakran használt makrókat össze lehet gyűjteni egy file-ba, majd ezeket később igen könnyen lehet használni. Például definiáljunk két egyszerű makrót. Az egyik makró egy billentyű leütésére vár, a másik makró pedig a programból való kilépést hajtja végre: %macro BillVar 0 MOV AH, 0 INT 16h %endmacro %macro Kilep 0 INT 20h %endmacro Ezeket a makrókat mentsük el egy makro.inc file-ba. Ha ezután ezeket a makrókat egy programban használni szeretnénk, akkor a következő módon járhatunk el: 154

155 %include "makro.inc" org 100h BillVar Kilep Ebből is látható, hogy ha megfelelő neveket használunk a makrók esetén, akkor nagyon beszédes (könnyen érthető, értelmezhető) programokat tudunk írni a segítségükkel. 155

156 Makró definíció %macro PRINT 1+ JMP %%atlep %%szoveg: DB %1, $ %%atlep: MOV DX, %%szoveg MOV AH,9 INT 21H %endmacro Makró használat PRINT Hello, 13, 10 PRINT Vilag, 13, 10 Makróból generált kód JMP atlep6345 szoveg9092: DB Hello, 13, 10, $ atlep6345: MOV DX, szoveg9092 MOV AH,9 INT 21H JMP atlep7231 szoveg1235: DB Vilag, 13, 10, $ atlep7231: MOV DX, szoveg1235 MOV AH,9 INT 21H Függvény definíció PRINT: PUSH BP MOV BP, SP PUSH AX PUSH DX MOV AH, 9 MOV DX, [BP+4] INT 21h POP DX POP AX POP BP RET Függvény használat PUSH szoveg1 CALL PRINT ADD SP, 2 PUSH szoveg2 CALL PRINT ADD SP, 2... szoveg1: db Hello, 13, 10, $ szoveg2: db Vilag, 13, 10, $ Függvényből generált kód PRINT: PUSH BP MOV BP, SP PUSH AX PUSH DX MOV AH, 9 MOV DX, [BP+4] INT 21h POP DX POP AX POP BP RET... PUSH szoveg1 CALL PRINT ADD SP, 2 PUSH szoveg2 CALL PRINT ADD SP, 2... szoveg1: db Hello, 13, 10, $ szoveg2: db Vilag, 13, 10, $ tábla: Makrók és függvények összehasonlítása 156

157 11.5 Ellenőrző kérdések 1. Mi az a makró? 2. Miben hasonlítanak és miben különböznek a makrók és a függvények? 3. Hogyan definiálhatunk lokális címeket makrókban? 4. Hogyan definiálhatunk több soros makrót? 5. Mi történik rekurzív makrók esetén? 6. Fel kell-e tölteni a makró paramétereit a veremre a makró meghívása előtt? 7. Mit jelent az, hogy greedy makró paraméter? 8. Mi lesz a forrás kód a következő makró használata után: %define bb(x) 2+x %define aa(x) 1+bb(x) %define cc(x) [BX+aa(x)]... MOV AX, cc(1) 157

158 158

159 12. Fejezet String műveletek A string kezelő utasítások memóriablokkokkal végeznek műveletet. A string, tulajdonképpen szöveg, a szöveg pedig nem más mint egy karakter sorozat vagy byte sorozat és egy byte sorozat amikor a memóriában tároljuk akkor pedig megfelel egy memóriablokknak. A szövegek tárolására két stratégiát alkalmazhatunk: fix méretű szövegek és változó méretű szövegek. A fix méretű szövegek esetén minden szöveg azonos méretű és így egyszerű a kezelésük. Ugyanakkor két probléma is lehet a fix méretű szövegekkel: Ha az adott szöveg hosszabb mint a fix méret, akkor azok a karakterek amelyek a fix méreten túlra esnek elvesznek. Ilyenkor lerövidítjük (truncation) a szöveget. Ha az adott szöveg rövidebb mint a fix méret, akkor a maradék karaktereket ki szokták tölteni (padding). Ezt a két problémát figyelembe kell venni akkor, amikor a fix méretű szövegek méretét próbáljuk meghatározni. Ösztönösen nagy értéket akarunk választani, így minden szöveg belefér és egy szöveget sem kell lerövidíteni. Ugyanakkor ha csak egy nagy és több kisebb szöveg van, akkor elég sok helyet elpazarolhatunk. Erre a dilemmára jelent megoldást a változó méretű szöveg. A változó méretű szövegek esetén a szöveg pontosant annyi karaktert tárol, mint amennyire szükség van. Ugyanakkor a szöveg karakterein kívül még egy adatra szükség van és ez a szöveg hossza. A szöveg hosszát kétféle stratégiával lehet megadni: explicit módon tároljuk a szöveg méretét egy lezáró karaktert (sentinel character) használunk. Nézzünk egy példát arra, amikor explicit módon tároljuk a szöveg méretét: szoveg: db Valamilyen szoveg hossz: db $-szoveg ahol a $ jel az aktuális címet jelenti. Ez a megoldás azért működik, mivel a $ jel éppen a szoveg címen található szöveg utolsó karaktere utáni címet jelenti és ebből vonjuk ki magát a szoveg címet. Feltételezve, hogy a NASM fordító a szoveg szimbólikus címet a 200d decimális címre fordítja le, akkor a fenti példában a $ jel értéke 217d lesz és így a hossz címen a 17d értéket fogjuk tárolni. Így természetesen ezt is írhatnánk: 159

160 szoveg: db Valamilyen szoveg hossz: db 17 de ezzel az a probléma, hogy ha később megváltoztatjuk a szöveg tartalmát akkor a hossz változót is meg kell vátloztatni. Az előző megoldást alkalmazva a szöveg hossza automatikusan számolódik ki így leveszi ezt a terhet rólunk. A másik megoldásban egy lezáró karakterrel jelöljük a szöveg végét, így nincs szükség explicit módon tárolni a szöveg hosszát, hiszen a szöveg elejétől a lezáró karakterig kell csak megszámolni a karakterek számát. Ennél a módszernél fontos feltételezés, hogy a lezáró karakter nem fog előfordulni magában a szövegben. Azt már láttuk, hogy ha az INT 21h megszakítást használjuk a 09h funkciókóddal akkor a szöveg végén a $ jelnek kell szerepelnie. Ezzel szemben a C programozási nyelvben a szöveg végén az ASCII NULL karakter szerepel. Ez a karakter nem összekeverendő a nullás szám ASCII kódjával, mivel ez az érték 30h lenne, míg az ASCII NULL karakter értéke 00h. Az ilyen nullával lezárt szövegeket ASCIIZ szövegnek is szokták nevezni. Nézzük erre is egy példát: szoveg: db Valamilyen szoveg, String utasítások A string utasítások a 7.6. bekezdésben találhatók. Amint a bekezdésből látható, az utasítások operandusai lehetnek egy forrás cím, egy cél cím vagy mindkettő. A 8086-os processzoron a forrás cím a DS:SI (SI = source index) regiszter pár adja meg, míg 32 bites rendszeren a DS:ESI regiszterek adják meg a forrás címet. Hasonlóan a 8086-os processzoron a cél címet az ES:DI (DI = destination index) regiszterek, míg 32 bites rendszeren az ES:EDI regiszterek adják meg. Az utasítások a leírtak alapján automatikusan frissítik az SI illetve DI regisztereket az adat méretével: byte, 2 byte (word) vagy 4 byte (dword). A frissítés lehet csökkentés vagy növelés is, az irány (Direction) státusz bittől függően. Az utasítások jelentősége abban van, hogy ismétlő prefix-el lehet használni, amelyeket a 7.6. bekezdés szintén tárgyal. A prefix lehet feltételes vagy feltétel nélküli String másolás Az adatok másolását érdemes egy kicsit jobban megvizsgálni, mivel érdekes működési módokat fedezhetünk fel, illetve későbbiekben azokat jól használhatjuk is. A másolás alapja a MOVS utasítás (lásd??. bekezdés), melynek működését a következő pseudo-kód tudja leírni: [ES:DI] = [DS:SI] if(df == 0) { DI = DI + 1 SI = SI + 1 } else { DI = DI - 1 SI = SI - 1 } String másolás egyszerűen Az adatok másolása esetén általában nem kell azzal foglalkozni, hogy az index regisztereket növeljük vagy csökkentjük a másolás során. A következő assembly programrészletben növekvő címek mellett 160

161 (a) (b) (c) (d) (e) (f) ábra: A MOVSB utasítás egyszerű működése másoljuk át a tömböt: cld lea si, array1 lea di, array2 mov cx, 321 rep movsb... array1: repb 321 array2: repb 321 A másolás folyamatát a ábra mutatja be. String másolás átlapolással Ezek után nézzük meg mi történik a következő programrészletben? cld lea si, array1 161

162 (a) (b) (c) (d) (e) (f) ábra: A MOVSB utasítás működése átlapolással és növekvő címekkel lea di, array2 mov cx, 321 rep movsb... array1: db X array2: repb 321 A változás abban van, hogy Úgy tűnik mintha az array1 nevű változó most csak 1 elemű lenne. Akkor mégis hogyan fog ez a programrészlet 321 byte-ot átmásolni? Azt kell figyelembe venni, hogy az assembler az egymás után írt adat vagy kódrészleteket közvetlenül egymás után fogja elhelyezni a memóriában a fordítás során. A jelen esetben ez azt jelenti, hogy először az array1 cím által jelölt egy byte-ot teszi a memóriába, majd közvetlenül utána másik 321 byte-ot helyez el. Ez az elrendezés a 12.2.a. ábrán látható. Ennek az a következménye, hogy így a két tömb (array1 és array2) tulajdonképpen át van lapolva, más szóval egymásba érnek. Ebben az esetben nem mindegy, hogy növekvő vagy csökkenő címek mellett másoljuk át az adatokat. A ábra azt a helyzetet mutatja, amikor a Direction státusz bit zérus és az SI illetve DI regiszterek növekednek a MOVSB utasítás ismételt végrehajtása során. Az ábrában a szagggatott vonallal jelölt nyíl jelöli, hogy melyik adat hova lesz átmásolva. Amint látható az ábrából, ebben az esetben lényegében azt kapjuk, hogy az array1 címen lévő byte értékével felülírjuk az array2 tömb minden elemét. Egy tömb adott byte-al való felülírására ugyanakkor jobb a STOS utasítása (lásd bekezdés). String másolás nagyobb átlapolással Esetleg valakiben felmerülhet, hogy ezek után mi van akkor, ha nem csak egy byte-ot definiálunk az array1 címen? A helyzet az, hogy a működés nem változik, csak most, az array1 címen definiált több értékkel, mint mintával írjuk felül az array2 címen található tömböt. Például az alábbi kód működését: cld lea si, array1 lea di, array2 mov cx, 321 rep movsb 162

163 (a) (b) (c) (d) (e) (f) ábra: A MOVSB utasítás működése nagyob átlapolással és növekvő címekkel. Végeredményben ismétlődő mintát kapunk.... array1: db X, Y, Z array2: repb 321 a ábra mutatja be. Az ábra azt mutatja, hogy a programrészletben megadott három byte fog ismétlődni az array2 címtől is. Erre már nem lenne képes a STOSB utasítás, csak akkor ha az ismétlődő minta byte, word vagy double word méretű. String másolás átlapolással fordított forrás és cél címmel A bekezdésben bemutatott másolást egy kicsit másképpen is felírhatjuk, vagyis például a forrás és cél címet felcserélhetjük. Nezzük meg mi történik ilyenkor: cld lea si, array2 lea di, array1 mov cx, 321 rep movsb... array1: db X array2: repb 321 Amint a ábrán is látható, ebben az esetben az array2 címen található tömböt eggyel előre másoljuk. Ha csak az array2 tömböt tekintjük, akkor ez lényegében azt jelenti, hogy a tömb első elemét töröljük, hiszen a programrészlet végrehajtása után az array2 tömbben már nem található az első elem, illetve az utolsó elem a tömb végén kétszer szerepel. Ha a legutolsó elemre nincs szükség duplán akkor az array2 tömb lényegében egy elemmel kevesebbet tartalmaz. Ez helyzet látható a ábrán Stringek összehasonlítása Az összehasonlítás alapja a CMPS utasítás melynek működését a következő pseudo-kód írja le: 163

164 (a) (b) (c) (d) (e) (f) ábra: A MOVSB utasítás működése átlapolással és fordított forrás és cél címmel. (a) ábra: A tömbök állapota a MOVSB utasítás végrehajtása utána, amikor a tömbök át vannak lapolva és fordított forrás és cél címmel. [ES:DI] összehasonlítása [DS:SI] if(df == 0) { DI = DI + 1 SI = SI + 1 } else { DI = DI - 1 SI = SI - 1 } Fontos azt tudni, hogy az összehasonlítás az assembly nyelvben úgy történik, hogy tulajdonképpen a két értéket kivonjuk egymásból és az eredmény alapján a státusz biteket beállítjuk. Például ha a két érték egyenlő, akkor a különbségük zérus lesz. Ezért van, hogy a zérus státusz bit jelzi az értékek egyenlőségét vagy nem egyenlőségét. A CMPS utasítás nagyon jól kombinálható a REPZ, REPE, REPNZ és REPNE prefixekkel. Stringek egyenlősége A következő programrészlet két szöveget hasonlít össze: string1: db abcxef,0 strlen EQU $ - string1 string2: db abcdef,

165 mov CX, strlen mov SI,string1 mov DI,string2 cld repe cmpsb Az összehasonlítás addig folytatódik ameddig a karakterek megegyeznek, illetve a CX regiszter értéke nem zérus. A fenti példában a program addig fog futni, amíg meg nem találja a string1 szövegben az x és a string2 szövegben a d karaktereket, melyek nem egyenlőek. Ugyanakkor fontos, hogy amikor az összehasonlítás véget ér az SI és DI regiszterek az utoljára összehasonlított karakterek utáni karakterre mutatnak, vagyis az e karakterre. Tehát ha a szövegekben az első eltérő karakterre vagyunk kiváncsiak, akkor az SI és DI regisztereket csökkenteni kell eggyel Keresés stringek-ben A szövegben való keresésre a SCAS utasítás használható. Az utasítás pseudo kódja: [ES:DI] összehasonlítása AL-el if(df == 0) { DI = DI + 1 } else { DI = DI - 1 } Ez az utasítás is az ismétlő prefixekkel használható a legjobban. Egy karakter keresése Nézzünk egy egyszerű példát, amikor azt szeretnénk ellenőrizni, hogy egy karakter megtalálható-e a szövegben és ha igen, akkor a címére is kiváncsiak vagyunk, tehát, hogy hol található a karakter a szövegben: 1 string1: db abcxef,0 2 strlen EQU $ - string mov CX, strlen 5 mov DI, string1 6 mov AL, x 7 cld 8 repne scasb 9 jnz nincs_meg 10 dec di jmp vege 13 nincs_meg: vege: A 8. sorban a kereső parancsot addig ismételjük, amig az AL regiszter értéke nem egyenlő az ES:DI regiszterpár által mutatott értékkel, vagy a CX regiszter értéke zérus nem lesz. A 9. sorban azt ellenőrizzük, hogy miért ért véget a keresés. Ha a zérus státusz bit értéke nulla, akkor nem találtuk 165

166 meg a karaktert ezért ugrunk a 9. sorban. Ha valóban megtaláltuk a karaktert, akkor a zérus státusz bit értéke 1 lesz és átesünk a 10. sorba. A 10. sorban azért csökkentjük a DI regiszter értékét, hogy a regiszter valóban arra a karakterre mutasson, amelyiket megtaláltunk. lényegében a keresést meg is fordíthatjuk. Ebben az esetben amíg a memória értékei megegyeznek (egyenlőek) a megadott karakterrel, addig folyamatosan továbblépünk a memóriában, míg ha a memóriában található érték különbözik az AL regiszter értékétől akkor leáll a keresés. Például egy szöveg elején ugorjunk át minden SPACE karaktert: 1 string1: db abc,0 2 strlen EQU $ - string mov CX, strlen 5 mov DI, string1 6 mov AL, 7 cld 8 repe scasb 9 dec di A program végén a DI regiszter az első nem SPACE karakterre fog mutatni. Így a programban a 8. sorban a REPE prefix azt jelenti, hogy ismételjük addig a SCASB utasítást, amíg egyenlő az AL regiszterrel LODSB és STOSB utasítások használata Elsőre furcsának tűnhetnek a LODSB és STOSB utasítások. Főleg azért mert nem igazán használhatók a REP prefixekkel. (Azért nincs értelme használni a REP prefixet például a LODSB utasítással mivel ez csak azt jelentené, hogy n-szer betöltünk értéket az AL regiszterbe.) Ugyanakkor abban az esetben, ha nem csak át kell másolni az adatokat, hanem közben valamilyen módon módosítani is kell, akkor a LODSB utasítással be tudjuk tölteni az adatot, majd a módosítás után a STOSB utasítással tárolni tudjuk. Erre nézzünk egy példát, amelyben minden karakter helyett az utána következő karaktert szeretnénk tárolni: 1 cld 2 lea si, forras 3 lea di, cel 4 mov cx, [hossz] 5 ujra: 6 lodsb 7 inc al 8 stosb 9 loop ujra 12.2 String utasítások előnyei és hátrányai Két fő előnye van ezeknek az utasításoknak: Az index regiszterek automatikusan módosulnak a Direction státusz bit szerint. Képesek egyszerre két, a memóriában levő operandussal dolgozni, vagyis például képesek memóriából memóriába másolni. Az utasítások nem csak, hogy egyszerűek és elegánsak, de nagyon hatékonyak is. Az utasíáts annál gyorsabb minnél nagyobb méretű adatokat másolunk egyszerre, így a MOVSB utasításnál gyorsabb 166

167 a MOVSW és ennél gyorsabb a MOVSD utasítás. Így például ha 4099 byte-ot szeretnénk a lehető leggyrosabban átmásolni egyik címről egy másikra, akkor a következő kódrészlet használható: 1 cld 2 lea si, forras 3 lea di, cel 4 mov cx, 1024 ; 4096 byte 5 rep movsd 6 movsw ; 2 byte 7 movsb ; 1 byte 8... A 4. sorban azért adunk meg 1024-et, mivel a MOVSD utasítások 4 byte-ot mozgatnak és így = 4096 byte-ot másolunk át, majd a 6. és 7. sorban a maradék kettő és egy byte-ot mozgatjuk át. Ebben a kódrészletben a szöveg mérete előre ismert volt, de lehetőség van hasonlóan gyors adat másolásra akkor is, ha a szöveg méretét nem ismerjük előre. Erre mutat példát a következő programrészlet: 1 cld 2 lea si, forras 3 lea di, cel 4 mov cx, [meret] 5 shr cx, 2 ; osztás 4-el 6 jz kevesebb_mint_4 7 rep movsd 8 kevesebb_mint_4: 9 mov cx, [meret] 10 and cx, 11b ; maszkolás, 0-3 lehet 11 jz vege 12 rep movsb 13 vege: A 4. sorban betöltjük a másolandó adat méretét, amit az 5. sorban elosztunk 4-el. Azért 4-el, mert megpróbáljuk a MOVSD utasítást használni és ez az utasítás 4 byte-ot mozgat egyszerre, így a CX regiszterbe a meret negyedét kell tárolni. Ugyanakkor a 6. sorban ellenőrizni kell, hogy a meret negyede az nagyobb-e mint zérus. Ha kisebb, például csak 3 byte-ot kell átmásolni, akkor nem szabad használni a MOVSD utasítást és ezért átugorjuk azt. Ha nagyobb, akkor 4 byte-onként átmásoljuk az adatokat. Ekkor még mindig előfordulhat az, hogy amikor a meret értékét eloszottuk 4-el, akkor volt valamennyi maradék. Ezt a maradékot az 5. sorban eldobtuk, vagyis nem vettük figyelembe. A 4-el való osztásnak a maradéka lehet: 0, 1, 2 és 3. Ahhoz, hogy megállapítsuk, hogy mennyi a maradék a 10. sorban maszkoljuk a meret értékét úgy, hogy a CX értéke csal 0, 1, 2 és 3 lehet. Itt is megvizsgáljuk, hogy volt-e maradék. Ha nem volt maradék, akkor a 11. sorból a 13. sorba ugrunk. Ha volt maradék, akkor a 12. sorban a REP MOVSB utasítással másoljuk át a byte-okat. 167

168 12.3 Ellenőrző kérdések 1. Mik az előnyei és hátrányai a fix méretű szövegeknek? 2. Mik az előnyei és hátrányai a változó méretű szövegeknek? 3. Hasonlítsa össze a különböző szöveg tárolási módokat, amikor lezáró karaktert tárolunk illetve amikor közvetlenül tároljuk a szöveg hosszát. 4. Mik az előnyei a string kezelő utasítások használatának? 5. Miért nincs értelme a REP prefixnek a LODSB utasítás esetén? 6. Adjon meg olyan esetet, amikor fontos a Direction státusz bit értéke! 7. Hasonlítsa össze a következő két szöveg definíciót. Mik az előnyök és hátrányok: szoveg: db Hello vilag hossz: dw $-szoveg illetve szoveg: db Hello vilag hossz: dw

169 13. Fejezet Példák függvényekre és szöveg kezelésre 13.1 Szöveg hosszának megállapítása 169

170 1 org 100h 2 push szoveg 3 call strlen 4 add sp, 2 5 int 20h 6 7 szoveg: db abcd,0 8 9 strlen: 10 push bp 11 mov bp, sp 12 push cx 13 push di 14 push es 15 les di, [bp+4] ; szöveg pointer ES:DI-be 16 mov cx, 0ffffh ; lehetséges maximális hossz 17 cld 18 mov al,0 ; NULL karakter 19 repne scasb 20 jcxz sl_no_string ; if ECX = 0, not a string 21 inc di ; növeljük 1-el, így 0-ra mutat 22 mov ax, di 23 sub ax, [bp+4] ; szöveg hossz AX-ben 24 clc ; nem volt hiba 25 jmp sl_done 26 sl_no_string: 27 stc ; carry 1 => nem szöveg 28 sl_done: 29 pop es 30 pop di 31 pop cx 32 pop bp 33 ret tábla: Egy szöveg hosszát megállapító függvény 170

171 14. Fejezet C és assembly programok kapcsolata 14.1 Függvény hívási konvenciók bites mód Egy függvénynek a paramétereket a vermen keresztül adjuk át. A legelső paraméter kerül a legalacsonyabb memória címre. Akár 8 vagy 16 bite értéket akarunk feltölteni a veremre minden alkalommal egy 16 bites, word értéket kell tárolni a vermen. Ha 16 bitnél több bites értéket akarunk tárolni, akkor is word értékekeket kell feltölteni a veremre little-endiam módon. Ez azt jelenti, hogy a legkisebb memória címre kerül a legkisebb helyiértékű word. Mindez azt jelenti, hogy minden érték a vermen 2 byte-ra van illesztve (2 byte aligned). A függvények a visszatérési értékükett regiszteren keresztül adjuk át. 8 bites egész szám esetén az AL regisztert használjuk, 16 bites egész szám esetén az AX regisztert használjuk, 32 bites egész szám esetén a DX:AX regisztereket használjuk, boolean érték esetén az AX regisztert használjuk és valós szám esetén az ST0 FPU regisztert használjuk bites mód A cdecl hívási konvenció az alap eset Linux-on. Konvenció Paraméter sorrend a vermen Paraméter eltávolító cdecl Első paraméter az alacsony címen a hívó stdcall Első paraméter az alacsony címen a függvény fastcall, Mi- Az első két paraméter ECX és EDX regiszterekben, a a függvény crosoft és GNU fastcall, Borland többi paraméter stdcall szerint Az első három paraméter EAX, ECX és EDX regiszterekben, a többi paraméter stdcall szerint tábla: Függvény hívási konvenciók 32 bites módban a függvény 171

172 172

173 15. Fejezet Optimalizálás A fejezetben tárgyalt eljárások és módszerek egy része csak a modern x86-os processzorok esetén használható, mivel egy részük még nem is létezett az 8086-os processzor idejében Optimalizálás sebességre Az első és legfontosabb dolog, hogy azonosítsuk azt a kód részletet, ahol a programunk legtöbb időt tölti. 1 Ez az egyik legfontosabb alapelv, mivel a mai programok egy jelentős része gyakran sokkal több időt tölt modulok, erőforrások betöltésével, adatbázisok elérésével mint valamilyen számítással a programban. Így ha csak a számítást optimalizáljuk akkor a program által felhasznált időnek csak az 1%-át javítjuk, míg a többi idő változatlan marad. Az assembly kód használata egy programban csak akkor hasznos, ha a program CPU intenzív, például: kép és hang feldolgozás, titkosítás, rendezés, adat tömörítés és komplikált matematikai számítás. A CPU intenzív programokra általában az jellemző, hogy van egy olyan ciklus amit a program nagyon sokszor végrehajt. Ez általában az úgynevezett legbelső ciklus (innermost loop). Ezt a program részletet kell megtalálni és optimalizálni. Ha bármilyen más program részt optimalizálunk, akkor tulajdonképpen csak az időnket pazaroljuk, mivel nem tudunk gyorsítást elérni és a programunkat csak átláthatatlanná tesszük az optimalizálással. A másik fontos alapelv, hogy érdemes magát az optimalizálandó algoritmust tanulmányozni, mivel sokszor egy másik algoritmus választásával már jobb eredményt érhetünk el Sorrenden kívüli végrehajtás Lényegében minden modern x86-os processzor képes a sorrenden kívüli végrehajtásra (out-of-order execution). 1 Premature optimization is the root of the evil. 173

174 1 mov ax, [mem1] 2 imul 6 3 mov [mem2], ax 4 mov bx, [mem3] 5 add bx, 2 6 mov [mem4], bx tábla: Sorrenden kívüli végrehajtás Utasítás betöltés és dekódolás Utasítás késleltetés és átbocsátási képesség Függőségi lánc megtörése Ugrások és függvény hívások Függvényhívások eltüntetése Feltétel nélküli ugrások eltüntetése 15.2 Optimalizálás méretre 15.3 Memória hozzáférés optimalizálása 15.4 Ciklusok optimalizálása 15.5 Vector programozás 15.6 Problémás utasítások 174

175 16. Fejezet Optimalizált példák 16.1 ASCII tábla nyomtatása rövidebben Ez a program a fejezetben bemutatott program rövidebb változata. Ebben a programban két dolgot használunk ki. Az egyik az, hogy a DL regiszter egy 8 bites regiszter és így 0 és 255 közötti számokat tud tárolni, illetve az ASCII karaktereket is 0 és 255 közötti számok reprezentálnak. Így a regiszter és az ASCII karakterek számai között egy az egyes megfeleltetést tudunk létrehozni. Az igazi trükk a 6. sorban van, itt növeljük meg mindig a DL regiszter tartalmát. Igen ám de amikor a DL regiszter tartalma 255, majd megnöveljük az értékét, akkor bár 256-ot kellene kapni, de ezt nem képes a regiszter tárolni, így átfordul. Ez azt jelenti, hogy a 255 után a 0 következik. Ez fog történni 6. sorban és az INC utasítás be is állítja a ZF státusz bitet megfelelően, vagyis nincs szükség CMP utasításra, elegendő a feltételes utasítást használni. Ezeknek a trükköknek a segítségével 15 byte-ról 12 byte-ra lehet csökkenteni a lefordított program méretét. 1 org 100h 2 MOV DL, 0 3 MOV AH, 2 4 ujra: 5 INT 21h 6 INC DL 7 JNZ ujra 8 INT 20h tábla: Az ASCII tábla kinyomtatására szolgáló optimalizált program 175

176 176

177 17. Fejezet Megjegyzések 17.1 Szokásos hibák Az alábbi lista a leggyakrabban elkövetett hibákat tartalmazza: Elfelejtjük a regisztereket elmenteni! Minden művelet vagy függvény elején mentsük el a regisztereket, illetve a művelet vagy függvény végén állítsuk helyre a regisztereket. Erre azért lehet szükség, mert a regiszter értékére később szükségünk van, vagy nem szeretnénk, hogy a művelet vagy függvény az általa megváltoztatott regiszterekkel a végrehajtásban az utána következő műveleteket befolyáolja. Ne felejtsük el, hogy a POP és PUSH utasítások sorrendje különböző kell legyen (lásd??. fejezet). A PUSH vagy POP utasításnak nincs megfelelő párja. Ez lényegében azt jelenti, hogy bármilyen végrehajtási útvonalon fut le a program a PUSH és POP műveletek számának meg kell egyeznie. Vegyük a következő példát: push bx test cx, cx jz vege... pop bx vege: ret Ha a program a vege címre ugrik, akkor a pop bx utasítás nem hajtódik végre. Ez azt jelenti, hogy a ret utasítás a BX regiszter korábbi értékét fogja levenni a veremről és így rossz címre fog ugrani a program. Egy speciális célra foglalt regisztert nem rendeletésszerűen használunk. Például a BP regiszternek speciális rendeltetése van amikor függvényeket használunk Stack-relatív címzés használata PUSH műveletek után. A stack-relatív címzés itt azt jelenti, hogy az SP regisztert használjuk a címzésben. Például: mov [sp+4], di push ax push bx push bp cmp si, [sp+4] 177

178 A fenti kódrészletben a programozó eredeti célja valószínűleg az volt, hogy az SI és DI regisztereket összehasonlítsa, de a két PUSH utasítás megváltoztatja az SP regiszter értékét. Gyakorlásképpen gondoljuk végig, hogy az SI regiszter mivel lesz összehasonlítva? A válasz lábjegyzetben található. 1 Egy változó értékének és címének összekeverése! valtozo: dw 0... mov bx, valtozo ; valtozo címe kerül BX-be mov ax, [valtozo] ; valtozo értéke kerül AX-be mov cx, [bx] ; valtozo értéke kerül CX-be A függvény hívási könvenciókat nem tartjuk be. Fontos, hogy a programokban a függvényeknek a megfelelő sorrendben adjuk át a paramétereket. Elfelejtjük a RET utasítást a függvény végéről. Ha a RET utasítást nem tesszük a függvény végére, akkor a program a függvény utolsó utasítása utáni művelettel fog folytatódni anélkül, hogy visszatérne a hívási ponthoz. Elfelejtjük kiüríteni a Floating-Point Unit vermét. Mielőtt az FPU verem bármelyik regiszterébe értéket írhatnánk a regisztert törölni kell. (Lásd??. fejezet.) Az irány státusz bitet (direction flag) elfelejtjük megfelelően beállítani. Előjeles és előjel néküli egész számok összekeverése a műveletek során. Rossz indexelést használunk egy tömb adatszerkezet elérése során. A tömb indexet meg kell szorozni a tömb egy elemének méretével: tomb: dw 0, 0, 0, 0, 0, 0, 0, 0, 0... mov bx, tomb... mov si, 2 lea di, [bx+si*2] Egy tömb címzésénél a nem megfelelő indexet használjuk. Figyeljünk arra, hogy egy n elemű tömbben az elemek indexe nullától n 1-ig tart. A LOOP műveletet úgy használjuk, hogy a CX regiszter értéke zérus. Ne feledjük, hogy a LOOP művelet először csökkenti a CX regiszter értékét és csak utána ellenőrzi, hogy a regiszter zérus lett-e. Vegyük például a következő kódrézletet: mov cx, 0 ujra:... loop ujra Gyakorlásképpen határozzuk meg, hogy a fenti kódrészlet hányszor fut le? A válasz lábjegyzetben található. 2 1 A fenti kódrészletben valójában az SI és az AX regiszterek tartalma lesz összehasonlítva. 2 A fenti kódrészlet szor fog lefutni. 178

179 A. Függelék ASCII táblázat Elvileg 256 ASCII karakter van. Az A.1. táblán látható ASCII táblázat csak a fontosabb karaktereket tartalmazza és direkt ilyen módon van ábrázolva. dec hex NULL SPC P p 1 1! 1 A Q a q B R b r 3 3 # 3 C S c s 4 4 $ 4 D T d t 5 5 % 5 E U e u 6 6 & 6 F V f v 7 7 BELL 7 G W g w 8 8 BS ( 8 H X h x 9 9 HT ) 9 I Y i y 10 A LF * : J Z j z 11 B VT + ; K [ k { 12 C FF, < L \ l 13 D CR - = M ] m } 14 E. > N ˆ n 15 F /? O _ o DEL A.1. tábla: Egyszerűsített ASCII táblázat A táblázatban szereplő speciális szimbólumok magyarázata: BELL - Alarm BS - Backspace HT - Horizontal TAB LF - Linefeed VT - Vertical TAB FF - Formfeed CR - Carriage return 179

180 SPC - Space DEL - Delete 180

181 B. Függelék Felhasznált irodalom 1. Agner Fog: Optimizing subroutines in assembly language An optimization guide for x86 platforms, Copenhagen University College of Engineering, Sivarama P. Dandamudi: Introduction to Assembly Language Programming, For Pentium and RISC Processors, Springer,

182 Példa programok listája Öt karakter beolvasása és kinyomtatása fordított sorrendben, 100 Érték szerinti paraméter átadás a vermen keresztül, 136 ASCII tábla kinyomtatása, 111 ASCII tábla nyomtatása rövidebben, 175 Cím szerinti paraméter átadás a vermen keresztül, 136 CAPS LOCK állapotának nyomtatása, 114 Egy byte bináris kinyomtatása, 91 Egy byte hexadecimális kinyomtatása, 95 Egy hexadecimális szám kinyomtatása, 93 Egy karakter beolvasása és a köv. kinyomtatása (a), 98 Egy karakter beolvasása és az utána köv. kinyomtatása (b), 98 Egy karakter kinyomtatása, 48 Egy karakter n-szeri kinyomtatása, 104 Egy sakktábla kinyomtatása, 108 Egy sakktábla kinyomtatása XOR-al, 109 Egy szöveg kinyomtatása, 49 Egy számjegy beolvasása és kinyomtatása, 97 Egy téglalap kinyomtatása, első rész, 105 Egy téglalap kinyomtatása, második rész, 105 Egymásba ágyazott függvények, 126 Első program, 47 Két szám összeadása, 101 Két szám összeadása ciklussal, 101 Második példa a paraméter átadásra memórián keresztül, 128 Példa a paraméter átadásra a vermen keresztül, 132 Paraméter átadás memórián keresztül, 128 Paraméter átadás regiszteren keresztül, 128 Szöveg hosszát megállapító függvény, 169 Szöveg nyomtatása függvénnyel, 136 Szám kiírása decimális formában, 112 Számjegyek nyomtatása rekurzív függvénnyel, 144 Változó számú szám összeadása és annyi pont nyomtatása,

183 Tárgymutató ASCII, 48 AT&T, 15 big-endian, 24 branching, 21 C programozási nyelv, 124, 135, 136 CALL, 130 Carry bit, 92 CISC, 11 COM, 87 data alignment, 25 EXE, 88 rekurzív függvény, 141, 143 rendszer busz, 17 RISC, 11 seg kulcsszó, 39 stack, 121 stack frame, 131 times kulcsszó, 39 TOS, 121 ugró utasítás, 21 wrt kulcsszó, 40 függvény, 124 frame pointer, 131, 141 gépi kód, 12 I/O kontroller, 26 időleges tárolás, 123 Intel, 15 LIFO, 121, 134 Little endian, 38, 40 Little-endian, 125 little-endian, 24 lokális változók, 141 LSB, 24 maszkolás, 95 megszakítás, 27 memory management, 87 moduláris programozás, 124, 144 MSB, 24 nasm, 125 overhead, 144 paraméter átadás, 126 paraméter átadás memóriával, 126, 128 paraméter átadás regiszterrel, 126, 128 paraméter átadás veremmel, 126, 130 POP, 121, 123, 124, 130 POPA, 135 Program Segment Prefix, 87 PSP, 87 PUSH, 121, 123, 130 PUSHA,

Assembly programozás. szerkesztette: Iványi Péter. September 22, 2009

Assembly programozás. szerkesztette: Iványi Péter. September 22, 2009 Assembly programozás szerkesztette: Iványi Péter September 22, 2009 2 Tartalomjegyzék 1 Bevezetés 9 1.1 Assembly elsőre...................................... 9 1.2 Miért tanuljunk assembly nyelvet?............................

Részletesebben

Assembly utasítások listája

Assembly utasítások listája Assembly utasítások listája Bevezetés: Ebben a segédanyagban a fontosabb assembly utasításokat szedtem össze. Az utasítások csoportosítva vannak. A fontos kategóriába azok az utasítások tartoznak, amiknek

Részletesebben

Számítógépek felépítése, alapfogalmak

Számítógépek felépítése, alapfogalmak 2. előadás Számítógépek felépítése, alapfogalmak Lovas Szilárd, Krankovits Melinda SZE MTK MSZT [email protected] B607 szoba Nem reprezentatív felmérés kinek van ilyen számítógépe? 2 Nem reprezentatív felmérés

Részletesebben

Számítógépek felépítése

Számítógépek felépítése Számítógépek felépítése Emil Vatai 2014-2015 Emil Vatai Számítógépek felépítése 2014-2015 1 / 14 Outline 1 Alap fogalmak Bit, Byte, Word 2 Számítógép részei A processzor részei Processzor architektúrák

Részletesebben

Digitális rendszerek. Utasításarchitektúra szintje

Digitális rendszerek. Utasításarchitektúra szintje Digitális rendszerek Utasításarchitektúra szintje Utasításarchitektúra Jellemzők Mikroarchitektúra és az operációs rendszer közötti réteg Eredetileg ez jelent meg először Sokszor az assembly nyelvvel keverik

Részletesebben

Számítógépek felépítése, alapfogalmak

Számítógépek felépítése, alapfogalmak 2. előadás Számítógépek felépítése, alapfogalmak Lovas Szilárd SZE MTK MSZT [email protected] B607 szoba Nem reprezentatív felmérés kinek van ilyen számítógépe? Nem reprezentatív felmérés kinek van

Részletesebben

Assembly. Iványi Péter

Assembly. Iványi Péter Assembly Iványi Péter További Op. rsz. funkcionalitások PSP címének lekérdezése mov ah, 62h int 21h Eredmény: BX = PSP szegmens címe További Op. rsz. funkcionalitások Paraméterek kimásolása mov di, parameter

Részletesebben

Számítógép felépítése

Számítógép felépítése Alaplap, processzor Számítógép felépítése Az alaplap A számítógép teljesítményét alapvetően a CPU és belső busz sebessége (a belső kommunikáció sebessége), a memória mérete és típusa, a merevlemez sebessége

Részletesebben

elektronikus adattárolást memóriacím

elektronikus adattárolást memóriacím MEMÓRIA Feladata A memória elektronikus adattárolást valósít meg. A számítógép csak olyan műveletek elvégzésére és csak olyan adatok feldolgozására képes, melyek a memóriájában vannak. Az információ tárolása

Részletesebben

SZÁMÍTÓGÉPEK BELSŐ FELÉPÍTÉSE - 1

SZÁMÍTÓGÉPEK BELSŐ FELÉPÍTÉSE - 1 INFORMATIKAI RENDSZEREK ALAPJAI (INFORMATIKA I.) 1 NEUMANN ARCHITEKTÚRÁJÚ GÉPEK MŰKÖDÉSE SZÁMÍTÓGÉPEK BELSŐ FELÉPÍTÉSE - 1 Ebben a feladatban a következőket fogjuk áttekinteni: Neumann rendszerű számítógép

Részletesebben

Assembly Utasítások, programok. Iványi Péter

Assembly Utasítások, programok. Iványi Péter Assembly Utasítások, programok Iványi Péter Assembly programozás Egyszerű logikán alapul Egy utasítás CSAK egy dolgot csinál Magas szintű nyelven: x = 5 * z + y; /* 3 darab művelet */ Assembly: Szorozzuk

Részletesebben

1. Az utasítás beolvasása a processzorba

1. Az utasítás beolvasása a processzorba A MIKROPROCESSZOR A mikroprocesszor olyan nagy bonyolultságú félvezető eszköz, amely a digitális számítógép központi egységének a feladatait végzi el. Dekódolja az uatasításokat, vezérli a műveletek elvégzéséhez

Részletesebben

Ismerkedjünk tovább a számítógéppel. Alaplap és a processzeor

Ismerkedjünk tovább a számítógéppel. Alaplap és a processzeor Ismerkedjünk tovább a számítógéppel Alaplap és a processzeor Neumann-elvű számítógépek főbb egységei A részek feladatai: Központi egység: Feladata a számítógép vezérlése, és a számítások elvégzése. Operatív

Részletesebben

A számok kiírása is alapvetően karakterek kiírásán alapul, azonban figyelembe kell venni, hogy a számjegyeket, mint karaktereket kell kiírni.

A számok kiírása is alapvetően karakterek kiírásán alapul, azonban figyelembe kell venni, hogy a számjegyeket, mint karaktereket kell kiírni. Példák számok kiírására A számok kiírása is alapvetően karakterek kiírásán alapul, azonban figyelembe kell venni, hogy a számjegyeket, mint karaktereket kell kiírni. Decimális számok kiírása Az alábbi

Részletesebben

A regiszterek az assembly programozás változói. A processzor az egyes mőveleteket kizárólag regiszterek közremőködésével tudja végrehajtani.

A regiszterek az assembly programozás változói. A processzor az egyes mőveleteket kizárólag regiszterek közremőködésével tudja végrehajtani. 1. Regiszterek A regiszterek az assembly programozás változói. A processzor az egyes mőveleteket kizárólag regiszterek közremőködésével tudja végrehajtani. Általános célú regiszterek AX akkumulátor: aritmetikai

Részletesebben

Digitális technika VIMIAA01 9. hét Fehér Béla BME MIT

Digitális technika VIMIAA01 9. hét Fehér Béla BME MIT BUDAPESTI MŰSZAKI ÉS GAZDASÁGTUDOMÁNYI EGYETEM VILLAMOSMÉRNÖKI ÉS INFORMATIKAI KAR MÉRÉSTECHNIKA ÉS INFORMÁCIÓS RENDSZEREK TANSZÉK Digitális technika VIMIAA01 9. hét Fehér Béla BME MIT Eddig Tetszőleges

Részletesebben

Digitális technika VIMIAA01 9. hét

Digitális technika VIMIAA01 9. hét BUDAPESTI MŰSZAKI ÉS GAZDASÁGTUDOMÁNYI EGYETEM VILLAMOSMÉRNÖKI ÉS INFORMATIKAI KAR MÉRÉSTECHNIKA ÉS INFORMÁCIÓS RENDSZEREK TANSZÉK Digitális technika VIMIAA01 9. hét Fehér Béla BME MIT Eddig Tetszőleges

Részletesebben

Bevezetés az informatikába

Bevezetés az informatikába Bevezetés az informatikába 3. előadás Dr. Istenes Zoltán Eötvös Loránd Tudományegyetem Informatikai Kar Programozáselmélet és Szoftvertechnológiai Tanszék Matematikus BSc - I. félév / 2008 / Budapest Dr.

Részletesebben

Balaton Marcell Balázs. Assembly jegyzet. Az Assembly egy alacsony szintű nyelv, mely a gépi kódú programozás egyszerűsítésére született.

Balaton Marcell Balázs. Assembly jegyzet. Az Assembly egy alacsony szintű nyelv, mely a gépi kódú programozás egyszerűsítésére született. Balaton Marcell Balázs Assembly jegyzet Az Assembly egy alacsony szintű nyelv, mely a gépi kódú programozás egyszerűsítésére született. 1. Regiszterek Regiszterek fajtái a. Szegmensregiszterek cs (code):

Részletesebben

A számítógép egységei

A számítógép egységei A számítógép egységei A számítógépes rendszer két alapvető részből áll: Hardver (a fizikai eszközök összessége) Szoftver (a fizikai eszközöket működtető programok összessége) 1.) Hardver a) Alaplap: Kommunikációt

Részletesebben

Memóriák - tárak. Memória. Kapacitás Ár. Sebesség. Háttértár. (felejtő) (nem felejtő)

Memóriák - tárak. Memória. Kapacitás Ár. Sebesség. Háttértár. (felejtő) (nem felejtő) Memóriák (felejtő) Memória Kapacitás Ár Sebesség Memóriák - tárak Háttértár (nem felejtő) Memória Vezérlő egység Központi memória Aritmetikai Logikai Egység (ALU) Regiszterek Programok Adatok Ez nélkül

Részletesebben

Operandus típusok Bevezetés: Az utasítás-feldolgozás menete

Operandus típusok Bevezetés: Az utasítás-feldolgozás menete Operandus típusok Bevezetés: Az utasítás-feldolgozás menete Egy gépi kódú utasítás általános formája: MK Címrész MK = műveleti kód Mit? Mivel? Az utasítás-feldolgozás általános folyamatábrája: Megszakítás?

Részletesebben

Informatika érettségi vizsga

Informatika érettségi vizsga Informatika 11/L/BJ Informatika érettségi vizsga ÍRÁSBELI GYAKORLATI VIZSGA (180 PERC - 120 PONT) SZÓBELI SZÓBELI VIZSGA (30 PERC FELKÉSZÜLÉS 10 PERC FELELET - 30 PONT) Szövegszerkesztés (40 pont) Prezentáció-készítés

Részletesebben

5-6. ea Created by mrjrm & Pogácsa, frissítette: Félix

5-6. ea Created by mrjrm & Pogácsa, frissítette: Félix 2. Adattípusonként különböző regisztertér Célja: az adatfeldolgozás gyorsítása - különös tekintettel a lebegőpontos adatábrázolásra. Szorzás esetén karakterisztika összeadódik, mantissza összeszorzódik.

Részletesebben

Adatok ábrázolása, adattípusok

Adatok ábrázolása, adattípusok Adatok ábrázolása, adattípusok Összefoglalás Adatok ábrázolása, adattípusok Számítógépes rendszerek működés: információfeldolgozás IPO: input-process-output modell információ tárolása adatok formájában

Részletesebben

Adatszerkezetek Tömb, sor, verem. Dr. Iványi Péter

Adatszerkezetek Tömb, sor, verem. Dr. Iványi Péter Adatszerkezetek Tömb, sor, verem Dr. Iványi Péter 1 Adat Adat minden, amit a számítógépünkben tárolunk és a külvilágból jön Az adatnak két fontos tulajdonsága van: Értéke Típusa 2 Adat típusa Az adatot

Részletesebben

5. tétel. A számítógép sematikus felépítése. (Ábra, buszok, CPU, Memória, IT, DMA, Periféria vezérlő)

5. tétel. A számítógép sematikus felépítése. (Ábra, buszok, CPU, Memória, IT, DMA, Periféria vezérlő) 5. tétel 12a.05. A számítógép sematikus felépítése (Ábra, buszok, CPU, Memória, IT, DMA, Periféria vezérlő) Készítette: Bandur Ádám és Antal Dominik Tartalomjegyzék I. Neumann János ajánlása II. A számítógép

Részletesebben

Bepillantás a gépházba

Bepillantás a gépházba Bepillantás a gépházba Neumann-elvű számítógépek főbb egységei A részek feladatai: Központi egység: Feladata a számítógép vezérlése, és a számítások elvégzése. Operatív memória: A számítógép bekapcsolt

Részletesebben

8. Fejezet Processzor (CPU) és memória: tervezés, implementáció, modern megoldások

8. Fejezet Processzor (CPU) és memória: tervezés, implementáció, modern megoldások 8. Fejezet Processzor (CPU) és memória: The Architecture of Computer Hardware and Systems Software: An Information Technology Approach 3rd Edition, Irv Englander John Wiley and Sons 2003 Wilson Wong, Bentley

Részletesebben

Rekurzió. Dr. Iványi Péter

Rekurzió. Dr. Iványi Péter Rekurzió Dr. Iványi Péter 1 Függvényhívás void f3(int a3) { printf( %d,a3); } void f2(int a2) { f3(a2); a2 = (a2+1); } void f1() { int a1 = 1; int b1; b1 = f2(a1); } 2 Függvényhívás void f3(int a3) { printf(

Részletesebben

GPU Lab. 4. fejezet. Fordítók felépítése. Grafikus Processzorok Tudományos Célú Programozása. Berényi Dániel Nagy-Egri Máté Ferenc

GPU Lab. 4. fejezet. Fordítók felépítése. Grafikus Processzorok Tudományos Célú Programozása. Berényi Dániel Nagy-Egri Máté Ferenc 4. fejezet Fordítók felépítése Grafikus Processzorok Tudományos Célú Programozása Fordítók Kézzel assembly kódot írni nem érdemes, mert: Egyszerűen nem skálázik nagy problémákhoz arányosan sok kódot kell

Részletesebben

Assembly programozás levelező tagozat

Assembly programozás levelező tagozat Assembly programozás levelező tagozat Szegedi Tudományegyetem Képfeldolgozás és Számítógépes Grafika Tanszék 2011-2012-2 Tematika Assembly nyelvi szint. Az Intel 8086/88 regiszter készlete, társzervezése,

Részletesebben

Kinek szól a könyv? A könyv témája A könyv felépítése Mire van szükség a könyv használatához? A könyvben használt jelölések. 1. Mi a programozás?

Kinek szól a könyv? A könyv témája A könyv felépítése Mire van szükség a könyv használatához? A könyvben használt jelölések. 1. Mi a programozás? Bevezetés Kinek szól a könyv? A könyv témája A könyv felépítése Mire van szükség a könyv használatához? A könyvben használt jelölések Forráskód Hibajegyzék p2p.wrox.com xiii xiii xiv xiv xvi xvii xviii

Részletesebben

A processzor hajtja végre a műveleteket. összeadás, szorzás, logikai műveletek (és, vagy, nem)

A processzor hajtja végre a műveleteket. összeadás, szorzás, logikai műveletek (és, vagy, nem) 65-67 A processzor hajtja végre a műveleteket. összeadás, szorzás, logikai műveletek (és, vagy, nem) Két fő része: a vezérlőegység, ami a memóriában tárolt program dekódolását és végrehajtását végzi, az

Részletesebben

8. Fejezet Processzor (CPU) és memória: tervezés, implementáció, modern megoldások

8. Fejezet Processzor (CPU) és memória: tervezés, implementáció, modern megoldások 8. Fejezet Processzor (CPU) és memória: The Architecture of Computer Hardware and Systems Software: An Information Technology Approach 3rd Edition, Irv Englander John Wiley and Sons 2003 Wilson Wong, Bentley

Részletesebben

Programozás alapjai. 10. előadás

Programozás alapjai. 10. előadás 10. előadás Wagner György Általános Informatikai Tanszék Pointerek, dinamikus memóriakezelés A PC-s Pascal (is) az IBM PC memóriáját 4 fő részre osztja: kódszegmens adatszegmens stackszegmens heap Alapja:

Részletesebben

Mintavételes szabályozás mikrovezérlő segítségével

Mintavételes szabályozás mikrovezérlő segítségével Automatizálási Tanszék Mintavételes szabályozás mikrovezérlő segítségével Budai Tamás [email protected] http://maxwell.sze.hu/~budait Tartalom Mikrovezérlőkről röviden Programozási alapismeretek ismétlés

Részletesebben

2. Számítógépek működési elve. Bevezetés az informatikába. Vezérlés elve. Külső programvezérlés... Memória. Belső programvezérlés

2. Számítógépek működési elve. Bevezetés az informatikába. Vezérlés elve. Külső programvezérlés... Memória. Belső programvezérlés . Számítógépek működési elve Bevezetés az informatikába. előadás Dudásné Nagy Marianna Az általánosan használt számítógépek a belső programvezérlés elvén működnek Külső programvezérlés... Vezérlés elve

Részletesebben

The Architecture of Computer Hardware and Systems Software: An InformationTechnology Approach 3. kiadás, Irv Englander John Wiley and Sons 2003

The Architecture of Computer Hardware and Systems Software: An InformationTechnology Approach 3. kiadás, Irv Englander John Wiley and Sons 2003 . Fejezet : Számrendszerek The Architecture of Computer Hardware and Systems Software: An InformationTechnology Approach. kiadás, Irv Englander John Wiley and Sons Wilson Wong, Bentley College Linda Senne,

Részletesebben

Számítógép Architektúrák

Számítógép Architektúrák Számítógép Architektúrák Utasításkészlet architektúrák 2015. április 11. Budapest Horváth Gábor docens BME Hálózati Rendszerek és Szolgáltatások Tsz. [email protected] Számítógép Architektúrák Horváth

Részletesebben

Assembly Címzési módok. Iványi Péter

Assembly Címzési módok. Iványi Péter Assembly Címzési módok Iványi Péter Gépi kód Gépi kód = amit a CPU megért 1-13 byte hosszúak lehetnek az utasítások Kb. 20 000 variációja van a gépi kódú utasításoknak Számítógép architektúrától függ Feszültség

Részletesebben

Bevezetés a számítástechnikába

Bevezetés a számítástechnikába Bevezetés a számítástechnikába, Címzési módok, Assembly Fodor Attila Pannon Egyetem Műszaki Informatikai Kar Villamosmérnöki és Információs Rendszerek Tanszék [email protected] 2010. november 2/9. ú utasítás

Részletesebben

Gyakorló feladatok. /2 Maradék /16 Maradék /8 Maradék

Gyakorló feladatok. /2 Maradék /16 Maradék /8 Maradék Gyakorló feladatok Számrendszerek: Feladat: Ábrázold kettes számrendszerbe a 639 10, 16-os számrendszerbe a 311 10, 8-as számrendszerbe a 483 10 számot! /2 Maradék /16 Maradék /8 Maradék 639 1 311 7 483

Részletesebben

Architektúra, megszakítási rendszerek

Architektúra, megszakítási rendszerek Architektúra, megszakítási ek Mirıl lesz szó? Megszakítás fogalma Megszakítás folyamata Többszintű megszakítási ek Koschek Vilmos Példa: Intel Pentium vkoschek@vonalkodhu Koschek Vilmos Fogalom A számítógép

Részletesebben

A mikroprocesszor egy RISC felépítésű (LOAD/STORE), Neumann architektúrájú 32 bites soft processzor, amelyet FPGA val valósítunk meg.

A mikroprocesszor egy RISC felépítésű (LOAD/STORE), Neumann architektúrájú 32 bites soft processzor, amelyet FPGA val valósítunk meg. Mikroprocesszor A mikroprocesszor egy RISC felépítésű (LOAD/STORE), Neumann architektúrájú 32 bites soft processzor, amelyet FPGA val valósítunk meg. A mikroprocesszor részei A mikroprocesszor a szokásos

Részletesebben

A mikroszámítógép felépítése.

A mikroszámítógép felépítése. 1. Processzoros rendszerek fő elemei mikroszámítógépek alapja a mikroprocesszor. Elemei a mikroprocesszor, memória, és input/output eszközök. komponenseket valamilyen buszrendszer köti össze, amelyen az

Részletesebben

2. Fejezet : Számrendszerek

2. Fejezet : Számrendszerek 2. Fejezet : Számrendszerek The Architecture of Computer Hardware and Systems Software: An Information Technology Approach 3. kiadás, Irv Englander John Wiley and Sons 2003 Wilson Wong, Bentley College

Részletesebben

VI. SZOFTVERES PROGRAMOZÁSÚ VLSI ÁRAMKÖRÖK

VI. SZOFTVERES PROGRAMOZÁSÚ VLSI ÁRAMKÖRÖK VI. SZOFTVERES PROGRAMOZÁSÚ VLSI ÁRAMKÖRÖK 1 Az adatok feldolgozását végezhetjük olyan általános rendeltetésű digitális eszközökkel, amelyeket megfelelő szoftverrel (programmal) vezérelünk. A mai digitális

Részletesebben

találhatók. A memória-szervezési modell mondja meg azt, hogy miként

találhatók. A memória-szervezési modell mondja meg azt, hogy miként Memória címzési módok Egy program futása során (legyen szó a program vezérléséről vagy adatkezelésről) a program utasításai illetve egy utasítás argumentumai a memóriában találhatók. A memória-szervezési

Részletesebben

A 32 bites x86-os architektúra regiszterei

A 32 bites x86-os architektúra regiszterei Memória címzési módok Jelen nayagrészben az Intel x86-os architektúrára alapuló 32 bites processzorok programozását tekintjük. Egy program futása során (legyen szó a program vezérléséről vagy adatkezelésről)

Részletesebben

ELŐADÁS 2016-01-05 SZÁMÍTÓGÉP MŰKÖDÉSE FIZIKA ÉS INFORMATIKA

ELŐADÁS 2016-01-05 SZÁMÍTÓGÉP MŰKÖDÉSE FIZIKA ÉS INFORMATIKA ELŐADÁS 2016-01-05 SZÁMÍTÓGÉP MŰKÖDÉSE FIZIKA ÉS INFORMATIKA A PC FIZIKAI KIÉPÍTÉSÉNEK ALAPELEMEI Chip (lapka) Mikroprocesszor (CPU) Integrált áramköri lapok: alaplap, bővítőkártyák SZÁMÍTÓGÉP FELÉPÍTÉSE

Részletesebben

6. óra Mi van a számítógépházban? A számítógép: elektronikus berendezés. Tárolja az adatokat, feldolgozza és az adatok ki és bevitelére is képes.

6. óra Mi van a számítógépházban? A számítógép: elektronikus berendezés. Tárolja az adatokat, feldolgozza és az adatok ki és bevitelére is képes. 6. óra Mi van a számítógépházban? A számítógép: elektronikus berendezés. Tárolja az adatokat, feldolgozza és az adatok ki és bevitelére is képes. Neumann elv: Külön vezérlő és végrehajtó egység van Kettes

Részletesebben

Mi az assembly? Gyakorlatias assembly bevezető. Sokféle assembly van... Mit fogunk mi használni? A NASM fordítóprogramja. Assembly programok fordítása

Mi az assembly? Gyakorlatias assembly bevezető. Sokféle assembly van... Mit fogunk mi használni? A NASM fordítóprogramja. Assembly programok fordítása Mi az assembly Gyakorlatias assembly bevezető Fordítóprogramok előadás (A, C, T szakirány) programozási nyelvek egy csoportja gépközeli: az adott processzor utasításai használhatóak általában nincsenek

Részletesebben

Egyszerű RISC CPU tervezése

Egyszerű RISC CPU tervezése IC és MEMS tervezés laboratórium BMEVIEEM314 Budapesti Műszaki és Gazdaságtudományi Egyetem Egyszerű RISC CPU tervezése Nagy Gergely Elektronikus Eszközök Tanszéke (BME) 2013. február 14. Nagy Gergely

Részletesebben

Algoritmusok és adatszerkezetek gyakorlat 06 Adatszerkezetek

Algoritmusok és adatszerkezetek gyakorlat 06 Adatszerkezetek Algoritmusok és adatszerkezetek gyakorlat 06 Adatszerkezetek Tömb Ugyanolyan típusú elemeket tárol A mérete előre definiált kell legyen és nem lehet megváltoztatni futás során Legyen n a tömb mérete. Ekkor:

Részletesebben

Programozás II. 2. Dr. Iványi Péter

Programozás II. 2. Dr. Iványi Péter Programozás II. 2. Dr. Iványi Péter 1 C++ Bjarne Stroustrup, Bell Laboratórium Első implementáció, 1983 Kezdetben csak precompiler volt C++ konstrukciót C-re fordította A kiterjesztés alapján ismerte fel:.cpp.cc.c

Részletesebben

Adatszerkezetek 1. Dr. Iványi Péter

Adatszerkezetek 1. Dr. Iványi Péter Adatszerkezetek 1. Dr. Iványi Péter 1 Adat Adat minden, amit a számítógépünkben tárolunk és a külvilágból jön Az adatnak két fontos tulajdonsága van: Értéke Típusa 2 Adat típusa Az adatot kódoltan tároljuk

Részletesebben

Programozás alapjai. 6. gyakorlat Futásidő, rekurzió, feladatmegoldás

Programozás alapjai. 6. gyakorlat Futásidő, rekurzió, feladatmegoldás Programozás alapjai 6. gyakorlat Futásidő, rekurzió, feladatmegoldás Háziellenőrzés Egészítsd ki úgy a simplemaths.c programot, hogy megfelelően működjön. A program feladata az inputon soronként megadott

Részletesebben

VIRTUALIZÁCIÓ KÉSZÍTETTE: NAGY ZOLTÁN MÁRK EHA: NAZKABF.SZE I. ÉVES PROGRAMTERVEZŐ-INFORMATIKUS, BSC

VIRTUALIZÁCIÓ KÉSZÍTETTE: NAGY ZOLTÁN MÁRK EHA: NAZKABF.SZE I. ÉVES PROGRAMTERVEZŐ-INFORMATIKUS, BSC VIRTUALIZÁCIÓ KÉSZÍTETTE: NAGY ZOLTÁN MÁRK EHA: NAZKABF.SZE I. ÉVES PROGRAMTERVEZŐ-INFORMATIKUS, BSC A man should look for what is, and not for what he thinks should be. Albert Einstein A számítógépek

Részletesebben

A C programozási nyelv I. Bevezetés

A C programozási nyelv I. Bevezetés A C programozási nyelv I. Bevezetés Miskolci Egyetem Általános Informatikai Tanszék A C programozási nyelv I. (bevezetés) CBEV1 / 1 A C nyelv története Dennis M. Ritchie AT&T Lab., 1972 rendszerprogramozás,

Részletesebben

sallang avagy Fordítótervezés dióhéjban Sallai Gyula

sallang avagy Fordítótervezés dióhéjban Sallai Gyula sallang avagy Fordítótervezés dióhéjban Sallai Gyula Az előadás egy kis példaprogramon keresztül mutatja be fordítók belső lelki világát De mit is jelent, az hogy fordítóprogram? Mit csinál egy fordító?

Részletesebben

A C programozási nyelv I. Bevezetés

A C programozási nyelv I. Bevezetés A C programozási nyelv I. Bevezetés Miskolci Egyetem Általános Informatikai Tanszék A C programozási nyelv I. (bevezetés) CBEV1 / 1 A C nyelv története Dennis M. Ritchie AT&T Lab., 1972 rendszerprogramozás,

Részletesebben

A MiniRISC processzor

A MiniRISC processzor BUDAPESTI MŰSZAKI ÉS GAZDASÁGTUDOMÁNYI EGYETEM VILLAMOSMÉRNÖKI ÉS INFORMATIKAI KAR MÉRÉSTECHNIKA ÉS INFORMÁCIÓS RENDSZEREK TANSZÉK A MiniRISC processzor Fehér Béla, Raikovich Tamás, Fejér Attila BME MIT

Részletesebben

Párhuzamos programozási platformok

Párhuzamos programozási platformok Párhuzamos programozási platformok Parallel számítógép részei Hardver Több processzor Több memória Kapcsolatot biztosító hálózat Rendszer szoftver Párhuzamos operációs rendszer Konkurenciát biztosító programozási

Részletesebben

Számítógép architektúra

Számítógép architektúra Budapesti Műszaki Főiskola Regionális Oktatási és Innovációs Központ Székesfehérvár Számítógép architektúra Dr. Seebauer Márta főiskolai tanár [email protected] Irodalmi források Cserny L.: Számítógépek

Részletesebben

A Számítógépek felépítése, mőködési módjai

A Számítógépek felépítése, mőködési módjai Mechatronika, Optika és Gépészeti Informatika Tanszék Kovács Endre tud. Mts. A Számítógépek felépítése, mőködési módjai Mikroprocesszoros Rendszerek Felépítése Buszrendszer CPU OPERATÍV TÁR µ processzor

Részletesebben

Labor gyakorlat Mikrovezérlők

Labor gyakorlat Mikrovezérlők Labor gyakorlat Mikrovezérlők ATMEL AVR ARDUINO 1. ELŐADÁS BUDAI TAMÁS Tartalom Labor 2 mikrovezérlők modul 2 alkalom 1 mikrovezérlők felépítése, elmélet 2 programozás, mintaprogramok Értékelés: a 2. alkalom

Részletesebben

IT - Alapismeretek. Feladatgyűjtemény

IT - Alapismeretek. Feladatgyűjtemény IT - Alapismeretek Feladatgyűjtemény Feladatok PowerPoint 2000 1. FELADAT TÖRTÉNETI ÁTTEKINTÉS Pótolja a hiányzó neveket, kifejezéseket! Az első négyműveletes számológépet... készítette. A tárolt program

Részletesebben

1. ábra: Perifériára való írás idődiagramja

1. ábra: Perifériára való írás idődiagramja BELÉPTETŐ RENDSZER TERVEZÉSE A tárgy első részében tanult ismeretek részbeni összefoglalására tervezzük meg egy egyszerű mikroprocesszoros rendszer hardverét, és írjuk meg működtető szoftverét! A feladat

Részletesebben

A számítógépek felépítése. A számítógép felépítése

A számítógépek felépítése. A számítógép felépítése A számítógépek felépítése A számítógépek felépítése A számítógépek felépítése a mai napig is megfelel a Neumann elvnek, vagyis rendelkezik számoló egységgel, tárolóval, perifériákkal. Tápegység 1. Tápegység:

Részletesebben

1. Digitális írástudás: a kőtáblától a számítógépig 2. Szedjük szét a számítógépet 1. örök 3. Szedjük szét a számítógépet 2.

1. Digitális írástudás: a kőtáblától a számítógépig 2. Szedjük szét a számítógépet 1. örök 3. Szedjük szét a számítógépet 2. Témakörök 1. Digitális írástudás: a kőtáblától a számítógépig ( a kommunikáció fejlődése napjainkig) 2. Szedjük szét a számítógépet 1. ( a hardver architektúra elemei) 3. Szedjük szét a számítógépet 2.

Részletesebben

Processzor (CPU - Central Processing Unit)

Processzor (CPU - Central Processing Unit) Készíts saját kódolású WEBOLDALT az alábbi ismeretanyag felhasználásával! A lap alján lábjegyzetben hivatkozz a fenti oldalra! Processzor (CPU - Central Processing Unit) A központi feldolgozó egység a

Részletesebben

A Számítógépek hardver elemei

A Számítógépek hardver elemei Mechatronika, Optika és Gépészeti Informatika Tanszék Kovács Endre tud. Mts. A Számítógépek hardver elemei Korszerű perifériák és rendszercsatolásuk A µ processzoros rendszer regiszter modellje A µp gépi

Részletesebben

Párhuzamos programozási platformok

Párhuzamos programozási platformok Párhuzamos programozási platformok Parallel számítógép részei Hardver Több processzor Több memória Kapcsolatot biztosító hálózat Rendszer szoftver Párhuzamos operációs rendszer Konkurenciát biztosító programozási

Részletesebben

Assembly. Iványi Péter

Assembly. Iványi Péter Assembly Iványi Péter Miért? Ma már ritkán készül program csak assembly-ben Általában bizonyos kritikus rutinoknál használják Miért nem használjuk? Magas szintű nyelven könnyebb programozni Nehéz más gépre

Részletesebben

SzA19. Az elágazások vizsgálata

SzA19. Az elágazások vizsgálata SzA19. Az elágazások vizsgálata (Az elágazások csoportosítása, a feltételes utasítások használata, a műveletek eredményének vizsgálata az állapottér módszerrel és közvetlen adatvizsgálattal, az elágazási

Részletesebben

Megoldás. Feladat 1. Statikus teszt Specifikáció felülvizsgálat

Megoldás. Feladat 1. Statikus teszt Specifikáció felülvizsgálat Megoldás Feladat 1. Statikus teszt Specifikáció felülvizsgálat A feladatban szereplő specifikáció eredeti, angol nyelvű változata egy létező eszköz leírása. Nem állítjuk, hogy az eredeti dokumentum jól

Részletesebben

Az interrupt Benesóczky Zoltán 2004

Az interrupt Benesóczky Zoltán 2004 Az interrupt Benesóczky Zoltán 2004 1 Az interrupt (program megszakítás) órajel generátor cím busz környezet RESET áramkör CPU ROM RAM PERIF. adat busz vezérlõ busz A periféria kezelés során információt

Részletesebben

C programozási nyelv Pointerek, tömbök, pointer aritmetika

C programozási nyelv Pointerek, tömbök, pointer aritmetika C programozási nyelv Pointerek, tömbök, pointer aritmetika Dr. Schuster György 2011. június 16. C programozási nyelv Pointerek, tömbök, pointer aritmetika 2011. június 16. 1 / 15 Pointerek (mutatók) Pointerek

Részletesebben

Hardverközeli programozás 1 1. gyakorlat. Kocsis Gergely 2015.02.17.

Hardverközeli programozás 1 1. gyakorlat. Kocsis Gergely 2015.02.17. Hardverközeli programozás 1 1. gyakorlat Kocsis Gergely 2015.02.17. Információk Kocsis Gergely http://irh.inf.unideb.hu/user/kocsisg 2 zh + 1 javító (a gyengébbikre) A zh sikeres, ha az elért eredmény

Részletesebben

Aritmetikai utasítások I.

Aritmetikai utasítások I. Aritmetikai utasítások I. Az értékadó és aritmetikai utasítások során a címzési módok különböző típusaira látunk példákat. A 8086/8088-as mikroprocesszor memóriája és regiszterei a little endian tárolást

Részletesebben

Adatelérés és memóriakezelés

Adatelérés és memóriakezelés Adatelérés és memóriakezelés Jelen nayagrészben az Intel x86-os architektúrára alapuló 32 bites processzorok programozását tekintjük. Egy program futása során (legyen szó a program vezérléséről vagy adatkezelésről)

Részletesebben

Járműfedélzeti rendszerek I. 3. előadás Dr. Bécsi Tamás

Járműfedélzeti rendszerek I. 3. előadás Dr. Bécsi Tamás Járműfedélzeti rendszerek I. 3. előadás Dr. Bécsi Tamás ATmega128 CPU Single-level pipelining Egyciklusú ALU működés Reg. reg., reg. konst. közötti műveletek 32 x 8 bit általános célú regiszter Egyciklusú

Részletesebben

Nyíregyházi Egyetem Matematika és Informatika Intézete. Input/Output

Nyíregyházi Egyetem Matematika és Informatika Intézete. Input/Output 1 Input/Output 1. I/O műveletek hardveres háttere 2. I/O műveletek szoftveres háttere 3. Diszkek (lemezek) ------------------------------------------------ 4. Órák, Szöveges terminálok 5. GUI - Graphical

Részletesebben

Mutatók és mutató-aritmetika C-ben március 19.

Mutatók és mutató-aritmetika C-ben március 19. Mutatók és mutató-aritmetika C-ben 2018 március 19 Memória a Neumann-architektúrában Neumann-architektúra: a memória egységes a címzéshez a természetes számokat használjuk Ugyanabban a memóriában van:

Részletesebben

Labor gyakorlat Mikrovezérlők

Labor gyakorlat Mikrovezérlők Labor gyakorlat Mikrovezérlők ATMEL AVR ARDUINO 1. ELŐADÁS BUDAI TAMÁS 2015. 09. 06. Tartalom Labor 2 mikrovezérlők modul 2 alkalom 1 mikrovezérlők felépítése, elmélet 2 programozás, mintaprogramok Értékelés:

Részletesebben

4. Fejezet : Az egész számok (integer) ábrázolása

4. Fejezet : Az egész számok (integer) ábrázolása 4. Fejezet : Az egész számok (integer) ábrázolása The Architecture of Computer Hardware and Systems Software: An Information Technology Approach 3. kiadás, Irv Englander John Wiley and Sons 2003 Wilson

Részletesebben

Programozási nyelvek (ADA)

Programozási nyelvek (ADA) Programozási nyelvek (ADA) Kozsik Tamás előadása alapján Készítette: Nagy Krisztián 1. előadás Hasznos weboldal http://kto.web.elte.hu Program felépítése Programegységek (program unit) eljárások (procedure)

Részletesebben

Első sor az érdekes, IBM PC. 8088 ra alapul: 16 bites feldolgozás, 8 bites I/O (olcsóbb megoldás). 16 kbyte RAM. Nem volt háttértár, 5 db ISA foglalat

Első sor az érdekes, IBM PC. 8088 ra alapul: 16 bites feldolgozás, 8 bites I/O (olcsóbb megoldás). 16 kbyte RAM. Nem volt háttértár, 5 db ISA foglalat 1 2 3 Első sor az érdekes, IBM PC. 8088 ra alapul: 16 bites feldolgozás, 8 bites I/O (olcsóbb megoldás). 16 kbyte RAM. Nem volt háttértár, 5 db ISA foglalat XT: 83. CPU ugyanaz, nagyobb RAM, elsőként jelent

Részletesebben

Alapismeretek. Tanmenet

Alapismeretek. Tanmenet Alapismeretek Tanmenet Alapismeretek TANMENET-Alapismeretek Témakörök Javasolt óraszám 1. Történeti áttekintés 2. Számítógépes alapfogalmak 3. A számítógép felépítése, hardver A központi egység 4. Hardver

Részletesebben

IT - Alapismeretek. Megoldások

IT - Alapismeretek. Megoldások IT - Alapismeretek Megoldások 1. Az első négyműveletes számológépet Leibniz és Schickard készítette. A tárolt program elve Neumann János nevéhez fűződik. Az első generációs számítógépek működése a/az

Részletesebben

A mikroprocesszor felépítése és működése

A mikroprocesszor felépítése és működése A mikroprocesszor felépítése és működése + az egyes részegységek feladata! Információtartalom vázlata A mikroprocesszor feladatai A mikroprocesszor részegységei A mikroprocesszor működése A mikroprocesszor

Részletesebben

Orvosi készülékekben használható modern fejlesztési technológiák lehetőségeinek vizsgálata

Orvosi készülékekben használható modern fejlesztési technológiák lehetőségeinek vizsgálata Kutatási beszámoló a Pro Progressio Alapítvány számára Budapesti Műszaki és Gazdaságtudományi Egyetem Villamosmérnöki és Informatikai Kar Mérnök informatika szak Orvosi készülékekben használható modern

Részletesebben

Programozás alapjai gyakorlat. 4. gyakorlat Konstansok, tömbök, stringek

Programozás alapjai gyakorlat. 4. gyakorlat Konstansok, tömbök, stringek Programozás alapjai gyakorlat 4. gyakorlat Konstansok, tömbök, stringek Házi ellenőrzés (f0069) Valósítsd meg a linuxos seq parancs egy egyszerűbb változatát, ami beolvas két egész számot, majd a kettő

Részletesebben

A fordítóprogramok szerkezete. Kódoptimalizálás. A kódoptimalizálás célja. A szintézis menete valójában. Kódoptimalizálási lépések osztályozása

A fordítóprogramok szerkezete. Kódoptimalizálás. A kódoptimalizálás célja. A szintézis menete valójában. Kódoptimalizálási lépések osztályozása A fordítóprogramok szerkezete Forrásprogram Forrás-kezelő (source handler) Kódoptimalizálás Fordítóprogramok előadás (A,C,T szakirány) Lexikális elemző (scanner) Szintaktikus elemző (parser) Szemantikus

Részletesebben

Dr. Oniga István DIGITÁLIS TECHNIKA 8

Dr. Oniga István DIGITÁLIS TECHNIKA 8 Dr. Oniga István DIGITÁLIS TECHNIA 8 Szekvenciális (sorrendi) hálózatok Szekvenciális hálózatok fogalma Tárolók RS tárolók tárolók T és D típusú tárolók Számlálók Szinkron számlálók Aszinkron számlálók

Részletesebben

Fordító részei. Fordító részei. Kód visszafejtés. Izsó Tamás szeptember 29. Izsó Tamás Fordító részei / 1

Fordító részei. Fordító részei. Kód visszafejtés. Izsó Tamás szeptember 29. Izsó Tamás Fordító részei / 1 Fordító részei Kód visszafejtés. Izsó Tamás 2016. szeptember 29. Izsó Tamás Fordító részei / 1 Section 1 Fordító részei Izsó Tamás Fordító részei / 2 Irodalom Izsó Tamás Fordító részei / 3 Irodalom Izsó

Részletesebben

Hardver Ismeretek IA32 -> IA64

Hardver Ismeretek IA32 -> IA64 Hardver Ismeretek IA32 -> IA64 Problémák az IA-32-vel Bonyolult architektúra CISC ISA (RISC jobb a párhuzamos feldolgozás szempontjából) Változó utasításhossz és forma nehéz dekódolni és párhuzamosítani

Részletesebben

Bevezetés a számítástechnikába

Bevezetés a számítástechnikába Bevezetés a számítástechnikába Megszakítások Fodor Attila Pannon Egyetem Műszaki Informatikai Kar Villamosmérnöki és Információs Rendszerek Tanszék [email protected] 2010. november 9. Bevezetés Megszakítások

Részletesebben