Assembly

A Wikipédiából, a szabad enciklopédiából

Az assembly (angol: összerakás, összegyűjtés, összeépítés) a gépi kódhoz (a számítógép „anyanyelvéhez”) legközelebb álló, és így helykihasználás és futási idő szempontjából a leghatékonyabb általános célú programozási nyelv.

Az assembly nyelv nem keverendő össze a gépi kóddal: egy assembly nyelvű program végrehajtható utasításai általában egy gépi kódú utasításnak felelnek meg, tehát az assembly egy programozási nyelv, a gépi kód az a tárgykód, amit csaknem minden programozási nyelv előállít végeredményként.

Keletkezés[szerkesztés | forrásszöveg szerkesztése]

Az első számítógépek programozása úgy történt, hogy a számításokat végző elemek huzalozását változtatták meg.

A számítógépeket a kezdetekben a processzorok utasításaihoz rendelt számok bevitelével (gépi kóddal) lehetett programozni, melyek ábrázolása eleinte bináris majd később oktális (nyolcas) vagy hexadecimális (tizenhatos) számrendszerben (vagy röviden csak „hexában”) történt.

Állítólag az atombomba-számításokat végző dr. Glennie 1954-ben saját szakállára elkészítette a MARK I első assembler fordítóját.

Az assembly kódhoz tartozó fordítóprogramot assemblernek nevezik. Ez készít a szöveges forrásprogramból egy olyan állományt, amely csaknem teljes egészében megfelel annak a memória képnek, amelyet a processzor végrehajtható programként értelmezni fog.

Az assembler "párja" a disassembler ami a lefordított bináris kódot értelmezi és assembly forráskódú listává alakítja. Az alacsony szintű programozás további kellékei:

  • memóriatartalom vizsgáló, "dump" program
  • debugger – hibakereső program
  • hexa editor – állományok hexadecimális (16-os számrendszerű), ha szükséges utasításszintű módosítását teszi lehetővé
  • processzor szimulátor – adott processzorra írt programot "szoftveresen" futtat egy másik számítógépen, esetleg a célgép további hardver elemeit is emulálja

Formai jegyek[szerkesztés | forrásszöveg szerkesztése]

Az assembly nyelvet utasítások (tényleges kódot hoznak létre) és direktívák, vagy pszeudó utasítások (a fordítás vagy kódgenerálás vezérlése) alkotják.

A különböző processzorcsaládok utasításkészlete bár sokszor jelentősen eltér, de a gyártók által megadott assembly nyelvű szintaxis általában hasonló irányelvekre épül. Az utasítások általában néhány betűs rövidítések, azoknak a gépi utasításoknak a mnemonikjai, amelyek a processzor utasításkészletét alkotják.

A direktívákkal vezérelhető a változók és a program elhelyezése, igazítása, a program belépési pontjának meghatározása. A direktívák hatására létrejövő információk egy részét a fordító szintaktikai ellenőrzéshez használja, más részük a szerkesztő és/vagy a betöltő program számára ad információt. Ez a két program teszi lehetővé, hogy az assembler által készített kódból futtatható program jöjjön létre.

Az assembly forráskód soronként logikailag egy műveletet tartalmaz, egészében nézve a forrás felépítése a következő:

  • Deklarációs rész: változók, konstansok, makrók definiálása.
  • Végrehajtható, ill kód-rész: utasítások egymásutánja. Egy utasítássorhoz klasszikus esetben egy gépi kódú utasítás (és annak esetleges paraméterei) tartozik, az újabb, úgynevezett makró assemblerekkel definiálhatunk nevesített kódrészleteket is, mintegy „magasabb szintre emelve” ezáltal a nyelvet.
    1. Címke: „megcímkézhetünk” egy utasítást, melyet ugró utasítások célpontjaként, esetleg változók illetve konstansok azonosítására használhatunk.
    2. Az elvégzendő művelet (operátor) megnevezése (mnemonikja)
    3. Egy szóköz után az esetlegesen szükséges operandus(ok) (paraméterek), több operandust hagyományosan vesszővel választunk el. Az operandus(ok) előtt vagy után speciális jelölések mutatják a címzési módot.
    4. Megjegyzések, ezeket szintaxistól függő elválasztó karakter után írva rögzíthetjük.
    • Utasítástípusok

Egy processzornak vagy műveletvégzőnek általában 50-80 mnemonikkal megkülönböztetett végrehajtható utasítása van, de ez nagyon eszközfüggő.

Jellemző utasítások típusok[szerkesztés | forrásszöveg szerkesztése]

Memóriakezelő utasítások[szerkesztés | forrásszöveg szerkesztése]

Ezek az utasítások az operatív memóriával közvetlenül kapcsolatos olvasó és/vagy író utasításokat valósítják meg. Minden esetben legalább egy operandusuk van (egy címes gépek esetén), ami az adott memóriacímre hivatkozás (bár ez a cím lehet implicit operandus is, vagyis már adott regiszterben előkészített; nem pedig a műveleti kód adott mezőjében megadott). Az olvasási utasítások a kijelölt memóriacím tartalmat egy kijelölt regiszterbe töltik, míg az írási utasítások a kijelölt regiszter tartalmát tárolják el a kijelölt memóriacímen; továbbá léteznek memória-memória utasítások is, melyekkel egy memóriarekeszt vagy tartományt mozgatnak át (egyes helyeken string-kezelő utasításokként említik őket). Külön utasítások létezhetnek a byte, szó (2 byte) duplaszó (2 szó) stb. tartományok elérésére, de lehetséges, hogy ezt előtéttagok – más néven prefixum – segítségével kell elérni. Ezek – formailag – az utasítás előtt helyezkednek el, és egy utasítás előtt egyszerre több is lehet belőlük (a maximális szám processzorfüggő). Tehát a memóriareferens utasítások kimenetelét illetve lezajlását különböző előtéttagok befolyásolhatják:

  • ismétlő prefix – ismétlés adott feltétel fennállásáig
  • operandusméret prefix – az elérni kívánt memóriarekesz nagyságát befolyásolja
  • címmódosító prefix – az elérni kívánt memóriarekesz címének értelmezését befolyásolja
  • szinkronizációs prefix – a CPU és az FPU közötti szinkronizáció céljára (a 386-os PC-nél már hardveresen működik eme mechanizmus)
  • buszlezárás prefix – az adatmódosítás biztonságát (konzisztencia) növeli, például többprocesszoros rendszerekben

Regiszterkezelő utasítások[szerkesztés | forrásszöveg szerkesztése]

A regiszterek között végezhető műveleteket (regiszterek közötti csere, regiszter jobb-bal oldalának cseréje, speciális regiszterekhez való hozzáférés stb.) valósítják meg.

Aritmetikai és logikai utasítások[szerkesztés | forrásszöveg szerkesztése]

Ezek az utasítások aritmetikai műveletek (összeadás, kivonás, szorzás, osztás, esetleg ugyanezek lebegőpontos változatai, kiegészítve az un. normalizáló utasítással), illetve elemi logikai műveletek (és, vagy, nem, kizáró-vagy) végrehajtására szolgálnak.

Általában ide sorolják az un. léptetési utasításokat is, mivel ezek – a bináris számrendszer speciális esete miatt – épp 2 egész számú hatványaival való szorzás, osztás műveletet valósítanak meg.

Ezen kívül az inkrementáló és dekrementátó utasításokat is ide soroljuk.

Ugró utasítások[szerkesztés | forrásszöveg szerkesztése]

Az ugró utasításokkal a program végrehajtásának folyamata vezérelhető. Általában feltétel nélküli és feltételes ugrási utasításokról beszélünk. A feltételek egy regiszter, vagy az úgynevezett program- vagy processzorjelző (flag) adott bitjének/bitjeinek állapotához köthetőek (nulla, nem nulla, pozítív, negatív, kisebb, nagyobb, van túlcsordulás, van átvitel stb). Az egyes utasítások leírásánál pontosan meg van adva, hogy a processzorjelzőket hogyan módosítják a végrehajtás során. (A módosító hatásúak legtöbbször aritmetikai-logikai műveletek vagy kifejezetten erre a célra szánt utasítások.)

Az utasítások operandusa az a hely (vagyis egy cím), ahol a programot folytatni kell, a feltételtől függően. Ha a feltétel nem teljesül, akkor a program a következő utasítást hajtja végre, tehát a végrehajtás folyamata nem módosul. Maga a címoperandus lehet literális – vagyis címke vagy címke kifejezéssel megadott cím – vagy regiszterben kijelölt, esetleg valamilyen indirekció. Továbbá a feltételes ugrásoknál előfordul implicit operandusú megoldás, vagyis amikor az ugrási címet nem adjuk meg konkrétan. Ilyen elágazó utasítás esetén a végrehajtás mindig a (program)sorban következő vagy a következő utáni utasításra helyeződik át – az elágazástól függően.

Speciális utasítások[szerkesztés | forrásszöveg szerkesztése]

Speciális utasítások a megállító (halt, sleep), az üres (nop) utasítás és egyes processzor állapot kezelő utasítások.

Megállító utasítás[szerkesztés | forrásszöveg szerkesztése]

Megállítja a program futását, és csak "külső beavatkozás" (pontosabban interrupt vagy reset) hatására lép tovább. Egyes processzorok ekkor automatikusan energiatakarékos üzemmódba lépnek.

Üres utasítás[szerkesztés | forrásszöveg szerkesztése]

Az üres utasítás "nem tesz semmit". Ezt általában időzítésre használják (valamilyen hardver időzítés miatt, elágazott programszálak futási idejének kiegyenlítésére esetleg hardver diagnosztikai célra → címsín vizsgálatához).

Processzor állapot kezelő utasítások[szerkesztés | forrásszöveg szerkesztése]

A megszakítások kezelésére szolgáló, valamint a ki- és beviteli műveletek, illetve egyéb, a számítógép és/vagy a processzor működését vezérlő utasítások tartoznak ebbe a csoporba.

Direktívák[szerkesztés | forrásszöveg szerkesztése]

A direktívák az assemblernek szóló utasítások, amelyekből nem keletkezik gépi kód. Néhány fontosabb csoportjuk

  • listakészítés engedélyezése/tiltása, pl
PRINT ON           ; /360
  • Program tulajdonságainak megadása, pl
LOAD cím           ; Z80: program (betöltési) címe
ORG cím            ; Z80: program (futáskori) címe
SEGMENT AT cím     ; x86: memóriaterület címe
END címke          ; Program vége, egyben belépési pont megadása
  • Program szakaszokra bontása, szakaszok tulajdonságainak megadása, pl
snév SEGMENT CODE  ; x86:  kódszegmens
snév DSECT         ; /360: 'dummy' adatszegmens
snév CSECT READ    ; /360: kódszegmens, írásvédett
snév AMODE 31      ; /370: csak 31 bites módban futtatható
  • Címzéshez használandó regiszterek definiálása, pl
ASSUME CS:code,DS:data,ES:video ; 8086: definíció
ASSUME ES:nothing               ; 8086: visszavonás
USING code,R12                  ; /360: definíció
DROP R12                        ; /360: visszavonás
  • Szimbólumok exporja/importja, pl
ENTRY rutin   ; /360
EXTRN valtozo ; /360
  • Változók definíciója
var EQU érték ; általános
var = érték   ; /8086
  • Makrók definíciója és meghívása, illetve a makrókban használható speciális utasítások (pl elágazás)

Assembly nyelvjárások[szerkesztés | forrásszöveg szerkesztése]

A különféle architektúráknak, platformoknak, processzoroknak eltérő, egymással általában semmilyen kompatibilitást nem biztosító assembly nyelvei vannak, bár ezen nyelvek alapszerkezete nagyon hasonló.

Intel x86 (8086 – 80486 – Pentium)[szerkesztés | forrásszöveg szerkesztése]

  • Intel szintaxis
    • Microsoft Macro Assembler (MASM, Microsoft) – Ingyenesen hozzáférhető a MASM32 projekt részeként [1]
    • Turbo Assembler (TASM, Borland) [2]
    • Netwide Assembler (NASM, GNU) [3]
    • High Level Assembler (HLA, Public Domain) – Randall Hyde-nak, a The Art of Assembly Language című könyv szerzőjének saját assemblere [4]
    • Flat Assembler (FASM, GNU GPL) [5]

Példa (C eredeti: *p = n):

MOV  EDX,[EBP-16]
MOV  EAX,[EBP-20]
MOV  [EDX],EAX
  • AT&T szintaxis
    • GNU Assembler (GAS, GNU)

Példa (ugyanaz, mint az előbbi):

mov    -16(%ebp),%edx
mov    -20(%ebp),%eax
mov    %eax,(%edx)

MOS-6510[szerkesztés | forrásszöveg szerkesztése]

A MOS Technology 6510 (pontosabban, a 65xx, 75xx, 85xx processzorcsalád) a Commodore 64 és társai (mint amilyen a C16, VIC-20 vagy a Commodore 128) processzora. Három általános célú nyolcbites regiszterrel rendelkezik: az A akkumulátorral, valamint az X és Y indexregiszterekkel. Címtartománya 64 kilobyte, de külön címzési módok támogatják az első 256-byte (00xxH címek, az ugynevezett zero page) elérését, a második 256-byte (01xxH címek) pedig rögzítetten a stack helye (ezért az S stack-pointer regiszter is csak nyolc bites).

Motorola 68xxx[szerkesztés | forrásszöveg szerkesztése]

Ezek a processzorok főként a Commodore Amiga és a korai Apple Macintosh gépekben váltak ismertté.

Z80[szerkesztés | forrásszöveg szerkesztése]

Az Intel 8080 adaptációjaként a Zilog cég által gyártott, és korának legelterjedtebb processzorának nyelve. Számos összetettebb utasítással is rendelkezik. A Z80 assembly nyelve az Intel 8080-asénak egy kibővített és egyben egyszerűsített változata, például az összes adatmozgató utasítás mnemonikja egységesen LD, szemben a 8080 különféle változataival, például:

8080       Z80

MOV  A,B   LD   A,B
MOV  A,M   LD   A,(HL)
MOV  M,A   LD   (HL),A
LDAX B     LD   A,(BC)
STAX B     LD   (BC),A 
MVI  A,n   LD   A,n
LXI  H,nn  LD   HL,nn
LHLD cím   LD   HL,(cím)

IBM System/360[szerkesztés | forrásszöveg szerkesztése]

Az IBM mainframe-ei processzorainak nyelve. Tizenhat általános célú 32 bites regisztere van (R0-R15), a használható címtartomány eredetileg 24 bites volt (16 MB címtartomány), a /370-es sorozattól ez 31 bitesre bővült (2 GB címtartomány). A gépi utasítások 2, 4 vagy 6 byte hosszúak, a használható adattípusok: byte, félszó (16 bit), szó (32 bit), duplaszó (64 bit), pakolt és 'zónázott' decimális szám (1-16 byte), általános memóriaterület (1-256 byte). Kétcímű gép, azaz egy utasításban két memóriaterület is szerepelhet.

Példaprogram (egy eljárás tipikus kezdete/vége/hívása):

RUTIN STM   R14,R12,12(R13) ; regiszterek mentése a hívó
                            ; regisztermentési területére
      BASR  R12,R0          ; bázisregisztrer beállítása
      USING *,R12           ; assembly direktíva:
                            ; használd a bázisregiszert
      LA    R14,SAVEA       ; saját mentési területünk
      ST    R13,4(R14)      ; elmentjük a hívóét
      LR    R13,R14         ; használjuk a sajátunkat

      …

KILÉP L     R15,visszatérési_érték
      L     R13,4(R13)      ; vissza a hívó mentési területére
      L     R14,12(R13)     ; visszatérési cím
      LM    R0,R12,20(R13)  ; hívó regisztereinek visszatöltése
                            ; kivéve az R15-öt
      BR    R14             ; visszatérés
SAVEA DS    18F             ; regisztermentési terület: 72 byte

      …

HÍVÁS L     R15,=A(RUTIN)   ; rutin címe R15-be
      LA    R1,paraméterek_címe
      BASR  R14,R15         ; ugrás R15-re,
                            ; visszatérési cím R14-be
UTÁNA LTR   R15,R15         ; visszaadott érték vizsgálata
      BZ    SIKER           ; tipikusan 0=siker

A nyelv előnyei és hátrányai[szerkesztés | forrásszöveg szerkesztése]

Az összehasonlítást a magas(abb) szintű programozási nyelvekkel szemben kell érteni.

Előnyei[szerkesztés | forrásszöveg szerkesztése]

  • Kisebb méretű kód, mert a programozó maga dönti el mely utasítások szükségesek az adott probléma megoldásához.
  • Az esetek többségében gyorsabb futás jellemzi.
  • Könnyebben lehet optimalizálni mind memória használatra, mind futási sebességre.
  • Vannak olyan eszközök (pl. mikrokontrollerek), amelyek nagyon kis méretű operatív tárral és memóriával rendelkeznek, és feldolgozási sebességük is igen lassú. A magas szintű nyelvek pedig az assemblyvel szemben többnyire lényegesen nagyobb programkódot állítanak elő, és sem a memória, sem pedig a futási sebességre nem igazán lehet velük optimalizálni.
  • Bitműveletek használata egyszerűbb.
  • Az általunk megírt eljárások pontos működését ismerjük, könnyebben lehet további speciális célokra úgy módosítani, hogy az ne vonjon maga után aránytalanul nagyobb kódot, memória használatot, vagy hosszabb futási időt, sőt az esetek többségében még javíthatunk is rajta!
  • A nyelv határai a rendelkezésre álló hardver(ek) tényleges határáig terjednek, más szóval: a programozó a rendelkezésre álló hardver(ek) összes elérhető funkcióját 100%-ig ki tudja használni.

Hátrányai[szerkesztés | forrásszöveg szerkesztése]

  • A komoly számítást igénylő feladatok elvégzése sok munkát és hozzáértést igényel.
  • Vannak olyan eszközök, amelyek olyan alapvető matematikai műveleteket sem tartalmaznak, mint a szorzás vagy az osztás. Assembly nyelven ezeket kézzel kell megvalósítani, ezzel szemben a magas szintű programnyelvek beépített támogatást nyújthatnak hozzájuk.
  • Maga a forrás nehezebben átlátható annak strukturálatlansága és hossza miatt.
  • Platform-függő, azaz szorosan kötődik az adott processzorhoz és/vagy futtató szoftver(ek)hez. Emiatt vagy a szoftver forrásának átírása, vagy emulátorban való futtatása jelenthet megoldást más környezetre való áttéréskor.
  • Az optimalizálási előny csekély olyan rendszerekben, ahol kimagaslóan sok regiszter áll rendelkezésre a futtató processzorban, vagy sok processzor áll rendelkezésre a feladatok megosztására.

Használata napjainkban[szerkesztés | forrásszöveg szerkesztése]

Manapság legelterjedtebben mikrokontroller alapú rendszerekben alkamazzák, mivel ott sokszor maga a futtató eszköz és/vagy annak környezete nem teszi lehetővé magas szintű nyelvek alkalmazását vagy akár fordítóprogramok létrehozását (túl kevés támogatott utasítás, kis méretű memória illetve stack, stb.). Megjegyzendő azonban, hogy a mikrokontroller architektúrák robbanásszerű fejlődése eredményeképp erősen terjed a C, C++ nyelv használta is e téren.

"Nagyszámítógépes" környezetben (PC-k, munkaállomások stb.) a hardverillesztő szoftvert és az operációs rendszert programozók írnak assembly nyelven, más esetekben használata ritka, csak a nagyon méret – vagy időkritikus feladatoknál alkalmazzák.

Egy példa[szerkesztés | forrásszöveg szerkesztése]

Ez a példa (rutin) a Z80-as (Zilog Z80) mikroprocesszor assembly-jében íródott. Bájtok blokkját másolja át a memória (RAM) egyik helyéről (címéről) a másikra. Értelme a Sinclair ZX Spectrum számítógépen az, hogy a másolás a videomemória célterületére történik (a gyakorlatban arra használták, hogy a például egy sok számítást igénylő kép előállítása ezen a számítógépen sokáig tartott, s ha annak folyamatát a felhasználó elől el akarták rejteni, akkor először a memória egy használatlan területén állították elő a képet, majd ez – nagyon rövidke idő alatt – be lett másolva a videomemória területére. A megjegyzések pontosvesszővel vannak elválasztva a programkódtól.

       ld    hl, 16384      ; a hl regiszterpárba a videomemória első bajtjának címe kerül
       ld    bc, 6912       ; a bc regiszterpárba kerül az átmozgatott blokk hossza (a videomemória hossza) bájtban 
       ld    de, 40000      ; a de regiszterpárba a forráscím
loop   ld    a, (de)        ; az a regiszterbe a forrás értéke kerül
       ld    (hl), a        ; a hl regiszterpár értékének a címére a kiolvasott érték kerül
       inc   hl             ; célcím növelése eggyel
       inc   de             ; forráscím növelése eggyel
       dec   bc             ; a hátralévő hossz csökkentése
       ld    a, b           ; ez és a következő sor a vizsgálja, hogy a bc regiszterpár
       or    c              ; értéke 0
       jr    nz, loop       ; ha nem, ugrás vissza (a loop fejlécű sorra)
       ret                  ; visszatérés

Érdekes megfigyelni a tizenhat bites bc regiszterpár nullás értékének vizsgálátát egy nyolcbites regiszterrel, ami két lépésben történik. Először az ld a, b utasítással a b regiszter értéke (mely a bc regiszterpár egyik regisztere) az a regiszterbe töltődik, majd az or c utasítással az a és a c regiszter értékei közt or logikai utasítás hajtódik végre, melynek értéke az a regiszterbe kerül. Ha az eredmény éppen 0, az azt jelenti, hogy a bc regiszterpár értéke is éppen 0 és a jr nz, loop feltételes ugrás nem hajtódik végre (de egyébként igen).

Az Intel 8080-as processzorhoz képest fejlettebb utasításkészletnek köszönhetően az előző példát egyszerűbben is megírhatjuk (és gyorsabb lesz a végrehajtási sebesség is: előző esetben 52 órajel/bájt, következő esetben 21 órajel/bájt):

       ld    bc,6912     ;bc-ben a hossz bájtokban
       ld    de,40000    ;de-ben a forrásterület címe
       ld    hl,16384    ;hl-ben a célterület címe
       ldir              ;blokkmozgató utasítás automata ismétléssel (21 órajel átvitt bájtonkánt)
       ret

A játékokhoz korábban a végsőkig igénybevették a processzorok számítási teljesítményét. Pl a következő trükkel újabb 25%-nyi időt nyerhetünk (16,5 órajel/bájt):

       ld    b,0         ;számláló (0=256)
       ld    de,40000  
       ld    hl,16384
loop   ldi               ;blokkmozgató utasítás, nem ismétel (16 órajel)
       ldi
       .
       .
       .
       ldi               ;összesen 27 db ldi utasítás egymás után (27*256=6912)
       djnz  loop        ;b-t csökkenti és visszaugrik, ha b<>0 (13 órajel)
       ret

Hello World[szerkesztés | forrásszöveg szerkesztése]

Példa "hello.asm" állomány tartalma :

 ORG  100H             ; ez minden COM allomany elejen jelen kell legyen egy
                       ; COM allomany maximum 64 KB meretu, es egyetlen 64 KB-os
                       ; teruleten van jelen az adat, kod es verem szegmens is;
                       ; az elso 256 byte (100h) a DOS szamara van fenntartva;
                       ; mind a negy szegmensregiszter erteke azonos, igy nem
                       ; kell ezeket a legtobb esetben valtoztatnunk

start:

 mov  dx, uzenet       ; DS:DX-be helyezzuk a kiirando uzenet kezdocimet; a 
                       ; DS-t mar nem kell beallitani, mert COM allomanyban vagyunk
                       ; az uzenet DOS-os formatumu, azaz $ jellel vegzodik (0x24)
                       ; NASM-ban egy valtozo cimere a nevevel (pl. uzenet) hivatkozunk
                       ; a cimen levo ertekre pedig a [nevevel] (pl. [uzenet]) hivatkozunk
 mov  ah, 09h          ; DOS 21h / 09h - DOS-os string kiirasa
 int  21h
 xor  ah, ah           ; egy BIOS megszakitas hivasa: varakozas egy billentyu lenyomasara
 int  16h              ; leirasert lasd Tech Help! 6.0
 int  20h              ; a COM programok vegen a 0x20 megszakitas meghivasaval fejezzuk
                       ; be a program futasat es adjuk vissza a vezerlest az operacios
                       ; rendszernek
 

adatok:

 uzenet db "Hello, World!", 0x0D, 0x0A, "$"

Lásd még[szerkesztés | forrásszöveg szerkesztése]