Maniacen Assembly-opas

Mureakuha

Loikkaa: valikkoon, hakuun

Sisällysluettelo

Levitys

Opasta saa siis levittää ja muokata miten haluaa, kunhan mukana on maininta tekijästä, joka on Maniace Tai Joni Hietala.

Kirjoitusasu

Pari sanaa oppaan kirjoitusasusta. Esimerkkiohjelmat on sijoitettu väliviivastojen väliin, joten itse ohjelmaan ei rivejä kirjoiteta, vaan niiden tehtävä on vain erottaa itse ohjelma muusta tekstistä. Tärkeät asiat ovat erikseen niille varatussa viivoin erotetussa tilassa, jotta niihin kiinnittyisi tarkemmin huomio. Ohjelmissa on käytetty heksadesimaalilukuja. Ne erottaa kirjoitusasusta, joka käsitellään myöhemmin. Oppaassa ei käsitellä liukulukulaskuyksikön komentoja ja käskykanta ei muutenkaan ole täydellinen(Voiko sitä edes koota johonkin listaan ja todeta: "Tässä on kaikki assembly-käskyt."?).


Tehdyt ja tulevat muutokset

Muutokset versiosta 1.0

  • Mittayksiköihin lisätty peta ja eksa, teratavu ei ole enää 1024 megatavua :)
  • Tämähän on assembly-ohjelmointia, eikä assembler-ohjelmointia. Perfektionisti kun olen niin jokainen väärä assembler-sana on korvattu assembly-sanalla...
  • Lisätty seuraavat kappaleet:
  • 1.3 Tehdyt ja tulevat muutokset
  • 2.5 PC-koneen piirit
  • 2.6 Loogiset operaatiot
  • Lisätty kappale makrojen luomisesta ja käytöstä
  • Korjattu virhe luvussa "kymmenjärjestelmästä binäärijärjestelmään", suuret kiitokset virheen oikaisusta Jussi Toivolalle.

Muutokset ja lisäykset tulevissa versioissa

  • Windows-ohjelmointi assemblylla
  • Oman käyttöjärjestelmän ohjelmoinnista oma kappale ja esimerkkikoodeja
  • Linux-ohjelmointi assemblylla

Tällä hetkellä tapahtuvat muutokset

Niistä huolehtii joku muu kuin minä ;)

Esimerkkiohjelmat

Esimerkkiohjelmien lähdekoodeja voi käyttää omiin tarkoituksiinsa miten ikinä haluaa, minulle siitä ei tarvitse ilmoittaa. Esimerkki- ohjelmat on tarkoitettu käännettäväksi NASM-kääntäjällä, joka on komentorivipohjainen assembleri(assembler = assembly-kääntäjä). Se käyttää Intel-syntaksia.


Miksi kirjoittaa ohjelmia assemblyllä?

Koska assembly on symbolinen konekieli, se on hyvin laiteläheinen, joka tarkoittaa käytännössä sitä, että sillä on vaikea ohjelmoida, mutta se on erittäin käytännöllinen kun ohjelmalta vaaditaan nopeutta, pientä kokoa tai käyttöjärjestelmäriippumattomuutta. Käyttöjärjestelmä- riippumattomuudella tässä yhteydessä tarkoitan sitä, että koska ohjelma on puhdasta konekieltä eikä tietyn käyttöjärjestelmän oma ohjelma, sen voi ajaa missä vain käyttöjärjestelmässä tai vaikka ilman käyttöjärjestelmää, riippuen luonnollisesti siitä, kuinka ohjelma on koodattu. Lähinnä assemblya kuitenkin käyttöjärjestelmäriippumattomuutensa takia käytetään vain ohjelmoitaessa omaa käyttöjärjestelmää. Peleissä on monia nopeuskriittisiä kohtia, kuten näytölle piirto. ym. Ohjelmasta saadaan monta kertaa nopeampi, kun tällaiset kohdat koodataan assemblylla. Assembly-kieltä voi liittää korkean tason ohjelmointikieliin ns. inline-assemblyna, jolloin assembly-koodi kirjoitetaan korkeamman tason ohjelmointikielen sisään tai ulkoisena objektitiedostona, jolloin kääntäjä kääntää ensin assembly-kielen objektitiedostoksi, korkeantason ohjelmointikielen kääntäjä kääntää korkeantason kielen objektitiedostoksi ja nämä linkitetään yhteen. Harvoin nykyään enää tarvitsee ohjelmissa pientä kokoa, mutta tämä on yksi assemblyn eduista. Tämän takia mm. useimmat virukset on koodattu assemblylla. Assemblyn etuna ja hyötynä on siis se, että sillä pääsee ja joutuu käymään suoraan käsiksi suorittimeen ja portteihin.

PC-tietokoneen toiminta

Johdanto

Kuten jo edellä mainitsin, on assembly erittäin laiteläheinen kieli, joten sen käyttö edellyttää hyvää laitteiston tuntemusta. Tämän takia käyn nyt pääpiirteittäin PC-tietokoneen toiminnan lävitse. Oletan, että osaat käyttää sen verran DOSia, että hallitset NASM-kääntäjän käytön komentoriviltä.


Prosessorin toiminta

Koska assembly-kieli toimii siten, että kaikki komennot syötetään suoraan prosessorille nk. symbolisina komentoina, joissa kaikki binääriset komennot on korvattu lyhyellä sanalla, joka on helpompi muistaa kuin binäärikoodit, on erittäin tärkeää ymmärtää prosessorin toiminta hyvin. Mikroprosessori on tietokoneen suoritin ja ydin. Se on koko koneen ydin, joka suorittaa ohjelmoijan koodin ja hoitaa tärkeimmät tehtävät, kuten muistin käsittelyn, keskeytysten kutsut ja laskuoperaatiot. Tässä oppaassa käsitellään Intelin 80486-mikroprosessorin ja sitä uudempien prosessoreiden ohjelmointia. Tutustutaan ensin lyhyesti Intelin prosessorien historiaan.

Vuonna 1981 tuli markkinoille ensimmäinen PC-tietokone(PC = Personal Computer, henkilökohtainen tietokone), IBM-PC. Siinä oli Intelin valmistama 8086-prosessori. Intelin prosessoriarkkitehtuuri sai ns. x86-muodon, jonka mukaan 8086-prosessorin jälkeen tulevat prosessorit saivat nimet 80286, 80386 ja 80486. Yleensä kun prosessoreista puhutaan, käytetään vain kolmea viimeistä numeroa, eli 286, 386 ja 486. Sitä seuraava prosessori ei ollutkaan 80586, vaan markkinointisyistä se sai nimen Pentium, vaikkakin joskus Pentium-suoritinperhettä kutsutaan 586:ksi. Sitä seurasivat prosessorit Pentium Pro, Pentium II, Pentium III ja Pentium 4. Kaikilla x86- prosessoreilla oli myös matematiikkaprosessori x87, eli 8087, 80287, jne. Matematiikkaprosessori on prosessorin erillinen yksikkö, jonka tehtävä on laskea liukulukuja. Tavallinen mikroprosessori laskee liukuluvut hitaasti, sillä se joutuu rikkomaan ne kokonaisluvuiksi, suorittamaan laskutoimituksen ja muuttamaan luvut takaisin liukuluvuksi. Matematiikkaprosessori taasen on suunniteltu juuri liukuluvuilla laskemiseen, joten sillä laskutoimitukset käyvät monia kertoja nopeammin kuin tavallisella prosessorilla. Useimmista Pentiumeja vanhemmista prosessoreista on olemassa myös halvempi versio, SX. Normaali versio on DX. DD-versiossa taas on kaksi kertaan niin paljon välimuistia kun DX-versiossa. Prosessoreita merkitään merkein 386DX, 486SX, jne.

286-prosessorista lähtien on ollut olemassa uusi asia, suojattu tila. Suojatusta tilasta saatiin lopullinen hyöty vasta 386-prosessorissa, jonka pystyy siirtämään 32-bittiseen suojattuun tilaan. Tässä oppaassa käsitellään vain 32-bittistä suojattua tilaa. Käynnistyessään prosessori toimii 16-bittisenä. Joten sillä ei voi parhaimmiillaankaan(Käyttämällä nk. A20-linjaa, jonka avulla voidaan käyttää muutamaa lisäbittiä muistin osoitukseen) osoittaa kuin yhden megatavun muistia. Lähes joka tietokoneessa on nykyään yli megatavu muistia, joten sitä täytyy pystyä käsittelemään jotenkin. Tähän on vastaus 32-bittinen tila, jolloin voidaan osoittaa 4 gigatavua muistia RAM-muistia(Random Access Memory) ja sen lisäksi 60 gigatavua levyllä olevaa virtuaalimuistia(swap). Suojattu tila antaa myös mahdollisuuden käyttää virtuaalimuistia, joka on kiintolevyllä sijaitsevaa muistia, mutta ohjelma huijataan luulemaan, että se on keskusmuistia. Näin tavallinen ohjelma luulee, että sillä on käytössään kokonaista 64 gigatavua keskusmuistia.

Moniajo on myös yksi suojatun tilan tuomista eduista. Nimi suojattu tila tulee siitä, että suojatussa tilassa käyttöjärjestelmä voi valvoa mitä prosessit tekevät, ne eivät voi kirjoittaa mielivaltaisesti muistiin ja kaataa koko järjestelmää, esimerkiksi kirjoittamalla keskusmuistiin sille kohtaa, minne käyttöjärjestelmä on ladattu. Tätä kutsutaan muistinsuojaukseksi, siitä siis nimi suojattu tila. Ikävä kyllä kun prosessori on suojatussa tilassa, ei voida käyttää BIOS-keskeytyksiä(käsitellään myöhemmin oppaassa) vaan pitää luoda itse omat keskeytyksensä. Mutta miksi sitten esimerkiksi Windowsissa, joka on 32-bittinen voi ajaa 16-bittisiä DOS-ohjelmia? Vastaus tähän on ns. emulaatiotila, virtuaalinen 8086-tila jossa prosessori on suojatussa tilassa, mutta emuloi(matkii) alkuperäistä 16-bittistä 8086-prosessoria. Tällöin ei taas voida osoittaa muistia yhden megatavun yläpuolelle.

Ohessa taulukko Intelin prosessoreista:

--------------------------------------------------------------------------------
8086                           Ensimmäisessä PC-tietokoneessa. 16-bittinen prosessori.
80286                          Toi mukanaan uuden toimintatilan, suojatun tilan.
                               Keskusmuistia suojatussa tilassa voitiin osoittaa 16 
                               megatavua, sillä 286-prosessorissa on 24-bittinen 
                               osoiteväylä. Suojattu tila mahdollisti myös moniajon, 
                               eli monen prosessin(ohjelman) suorituksen näennäisesti
                               samaan aikaan.
80386                          Muistia voitiin osoittaa suojatussa tilassa nyt 
                               kokonaiset 4 gigatavua ja jälleen uusi toimintatila, 
                               virtuaalinen 8086-tila, sekä virtuaalimuisti, eli 
                               heittovaihto.
80486                          Periaatteessa samanlainen kuin 386-prosessori. 
                               Nopeus perustuu lähinnä sisäänrakennettuun 
                               matematiikkaprosessoriin ja 8 kilotavun välimuistiin.
Pentium: I, PRO, II, III ja 4  Vuosien varrella Pentium-prosessorin arkkitehtuuri 
                               ei ole muuttunut käytännössä ollenkaan. Siinä on 
                               ikäänkuin kaksi prosessoria rinnakkain, uusia 
                               multimediakäskyjä, nk. liukuhihna ja uusi toimintatila, 
                               nimeltään järjestelmänhallintatila.
--------------------------------------------------------------------------------

Tästälähin kun mainitsen prosessorin, tarkoitan 486- tai sitä uudempaa prosessoria. Tutustutaan vähän lähemmin prosessorin toimintaan. Prosessori sisältää nk. rekistereitä, jotka ovat tiedon väliaikaisia tallennuspaikkoja, niiden välityksellä voi ilmoittaa parametreja keskeytyksille tai muuten ohjata tietokoneen toimintaa. Niitä on olemassa eri kokoisia ja niillä on monia eri tarkoituksia. Tutustutaan prosessorin rekistereihin ryhmittäin:

Yleisrekisterit

Yleisrekistereitä käytetään lähinnä tiedon väliaikaiseen tallennukseen ja parametrien välittämiseen keskeytyksille. Rekisterillä CX on myös toinen merkitys. Se on nk. laskurirekisteri ja sitä käytetään tavallisesti laskureissa ja silmukoissa. Reaalitilassa, eli 16-bittisessä toimintatilassa yleisrekistereiden nimet ovat AX, BX, CX ja DX. Ne ovat kaikki 16-bittisiä. Kun prosessori toimii suojatussa tilassa eli 32-bittisenä, rekistereitä kutsutaan nimellä EAX, EBX, ECX ja EDX. Kuten huomaat, on kaikkien rekistereiden nimen eteen tullut kirjain E, joka johtuu sanasta Extended(laajennettu). 32-bittisessä tilassa rekistereitä voi käsitellä myös nimillä AX, BX, CX ja DX. Tällöin käsitellään 32-bittisten rekisterien 16 alinta bittiä. 16-bittiset yleisrekisterit voidaan myös jakaa vielä 8-bittisiksi tavurekistereiksi, AH ja AL, BH ja BL, jne. Kirjaimet tulevat sanoista High(H), eli korkea ja Low(L), eli matala. AH on siis AX-rekisterin 8 ylintä bittiä ja CL CX-rekisterin 8 alinta bittiä.


Segmenttirekisterit

Segmenttirekisterit sisältävät tietoja prosessorin muistinkäsittelyyn liittyen. Segmenttirekistereitä ovat CS, DS, SS, ES, FS ja GS ja ne ovat kaikki 16-bittisiä riippumatta prosessorin toimintatilasta. CS on koodisegmenttirekisteri, eli se sisältää segmentin, jossa suoritettava koodi sijaitsee. DS on tietosegmenttirekisteri. SS on pinosegmentti- rekisteri ja se sisältää segmentin, jossa pino sijaitsee. ES, FS ja GS ovat ylimääräisiä segmenttirekistereitä, niitä voi käyttää esim. jono-operaatioihin. FS ja GS-rekistereitä ei ole 386:ta vanhemmissa prosessoreissa.

Osoitinrekisterit

Osoitinrekistereitä ovat SP ja IP. SP osoittaa sitä kohtaa muistissa, missä pinon päällimmäinen luku sillä hetkellä on. Kun pinosta poimitaan luku, pino-osoitinta(SP = Stack Pointer) kasvatetaan yhdellä ja kun pinoon tallennetaan luku, pino-osoitinta pienennetään yhdellä. IP(Instruction Pointer), joka tunnetaan myös nimellä PC(Program Counter) osoittaa seuraavaan käsiteltävään käskyyn. Tämän rekisterin sisältöä ei voi muuttaa suoraan(käytä JMP komentoa). Kun tarkoitetaan 32-bittistä IP-rekisteriä, on sen nimi EIP, 16-bittistä tarkoitettaessa IP. Prosessori siis suorittaa käskyn ja kasvattaa IP:tä käskyn viemän tavumäärän verran, tarkistaa IP:stä seuraavan suoritettavan käskyn osoitteen, suorittaa käskyn, kasvattaa IP:tä jne.

Indeksirekisterit

Indeksirekistereitä on kaksi, SI (Source Index) ja DI (Destination Index). Ne ovat molemmat 32-bittisiä, eli 16-bittisessä tilassa niiden nimet siis ovat SI ja DI, sekä 32-bittisessä tilassa ESI ja EDI. Niitä käytetään lähinnä rakenteellisten tietojen käsittelyssä.

Ohjausrekisterit

Ohjausrekistereillä ohjataan prosessorin toimintaa. Niistä ohjelmoijan käytettävissä on vain CR0. Muut on varattu Intelin käyttöön. Ne ovat kaikki 32-bittisiä riippumatta siitä, missä toimintatilassa prosessori on. CR0 on laitteiston tilarekisteri. 16 alinta bittiä vastaavat 286-prosessorin MSW:tä(Machine Status Word). Sitä käytetään, mm. toimintatilan vaihtamiseen. MSW käsitellään osiossa "Muut rekisterit". CR2 sisältää osoitteen, joka aiheutti viimeisimmän sivuvirheen. CR3 sisältää fyysisen osoiteen sivutauluun, sen 12 alinta bittiä jätetään huomiotta.

Järjestelmän osoiterekisterit

Järjestelmän osoiterekistereitä käytetään toimintatilojen hallitsemiseen, niitä ovat GDTR, IDTR, LDTR ja TR. GDTR sisältää osoitteen Globaaliin tauluun(GDT = Global Description Table), IDTR keskeytystauluun(IDT = Interrupt Description Table) ja LDTR Lokaaliin tauluun(LDT = Local Description Table). TR on työtehtävän segmenttirekisteri(käytetään moniajossa). GDTR ja IDTR ovat 32-bittisiä, LDTR ja TR 16-bittisiä.

Virheenetsintä- ja testausrekisterit

Nimensä mukaisesti näitä rekistereitä käytetään prosessorin virheenetsintään ja testaukseen. Näitä rekistereitä ovat DR0, DR1, DR2, DR3, DR6, DR7, TR6 ja TR7. Ensimmäiset neljä rekisteriä(DR0 - DR3) sisältävät lineaaristen katkokohtien osoitteita. DR6 sisältää katkokohdan tilan, DR7 on katkokohdan osoiterekisteri, TR6 testin ohjausrekisteri ja TR7 testin osoiterekisteri.

FLAGS ja MSW

FLAGS on 16-bittinen rekisteri, joka sisältää nk. lippuja, joita käytetään järjestelmän tilan tutkimiseen ja muuttamiseen. FLAGS tunnetaan nimellä lippurekisteri. Lippurekisterissä jokainen bitti vastaa yhtä lippua, eli lippu on joko ylhäällä tai ei, riippuen onko bitti asetettu ylätilaan vai ei (mieleen tulee pilkkiminen monella avannolla ylösnouseviin lippuihin kiinnitetyillä syöteillä :). Lippurekisterin koko on reaalitilassa 16 bittiä ja suojatussa tilassa 32-bittiä, jolloin rekisterin nimeen jälleen tulee etuliite E, eli rekisterin nimi prosessorin toimiessa 32-bittisenä on EFLAGS. Käydäänpä lippurekisterin jokainen lippu erikseen lävitse.

Bitti:  Lyhenne:            Kuvaus:
--------------------------------------------------------------------------------
0       CF                  Carry-lippu. Mikäli tämä lippu on ykkönen, 
                            on edellisen aritmeettisen operaation tulos 
                            ollut liian suuri mahtuakseen ilmoitettuun 
                            kohteeseen. Mikäli aritmeettinen operaatio on 
                            ollut laillinen, tämä lippu asetetaan nollaksi.

2       PF                  Pariteettilippu. Mikäli edellisen operaation 
                            tuloksen kahdeksassa alimmassa bitissä on 
                            parillinen määrä ykkösiä, tämä lippu asetetaan 
                            ykköseksi. Muutoin se on nolla.

4       AF                  Lisäyslippu. Toimii samoin kuin Carry-lippu, 
                            mutta käytetään silloin, kun suoritetaan 
                            operaatioita pakatuilla BCD-luvuilla
                            (Binary Coded Decimal). Mikäli tulos ei mahdu 
                            neljään bittiin, lippu asetetaan ykköseksi, 
                            muutoin lippu on nolla.

6       ZF                  Nollalippu. Mikäli kahden luvun välillä 
                            suoritetun operaation tulos on nolla, on 
                            tämä lippu asetettu ykköseksi, mikäli 
                            tulos on muu, tämä lippu on nolla.

7       SF                  Merkkilippu. Ilmaisee, onko operaation tulos 
                            positiivinen (tällöin lippu on nolla) vai 
                            negatiivinen(jolloin lippu on yksi). 

8       TF                  Askellippu. Mikäli tämä lippu on ykkönen, 
                            prosessori siirtyy tilaan, jossa se 
                            suoritettuaan käskyn jää odottamaan, 
                            kunnes sen sallitaan suorittaa seuraava 
                            käsky. Tätä lippua ei voi muuttaa suoraan.

9       IF                  Keskeytyslippu. Mikäli tämä lippu on nolla, 
                            ei ulkoisia keskeytyksiä suoriteta.

10      DF                  Suuntalippu. Käytetään silmukoissa ja 
                            laskureissa. Mikäli tämä lippu on ykkönen, 
                            jokaisen käskyn jälkeen vähennetään 
                            rekistereitä, mikäli tämä taasen on nolla, 
                            rekistereitä kasvatetaan jokaisen komennon 
                            jälkeen.

11      OF                  Ylivuotolippu. Käytetään kahden komplementti-
                            operaatioissa ilmoittamaan, onko eniten 
                            merkitsevä bitti muuttunut sanan sisäisen 
                            muistinnumeron siirtymisen johdosta.

12-13   IOPL                I/O Oikeuslippu. Käytetään prosessorin 
                            toimiessa suojatussa tilassa. Ilmoitetaan 
                            tietyn työtehtävän oikeudet ulkoiseen 
                            syöttöön ja tulostukseen.

14      NT8                 Sisäkkäisyyslippu. Käytetään myös prosessorin 
                            toimiessa suojatussa tilassa. Mikäli tämä 
                            lippu on ykkönen, on nyt suoritettavalla 
                            työtehtävällä linkki edelliseen työtehtävään. 
                            Tällöin tehtävät ovat sisäkkäisiä.

16      RF                  Uudelleenaloituslippu. Mikäli tämä lippu on 
                            ykkönen, ei seuraavan käskyn puhdistusvirheitä 
                            huomioida. Käytetään yhdessä 
                            puhdistusrekisterien kanssa kun toimitaan 
                            vianetsintärutiinissa(Katso TF, bitti 8). 
                            Kun suoritetaan onnistunut käsky, prosessori 
                            muuttaa tämän lipun nollaksi.

17      VM                  Emulaatiotilan lippu. Mikäli tämä lippu on 
                            ykkönen, on prosessori emulaatiotilassa. Voidaan
                            käyttää mm. tarkistamiseen, onko prosessori 
                            emulaatiotilassa.
-------------------------------------------------------------------------


MSW taasen ei ole rekisteri(sehän on osa CR0-rekisteriä), mutta oppaan rakenteen selvyyden vuoksi käyn sen sisällön tässä läpi. Kyseessä on siis ns. konetilasana(MSW = Machine Status Word), jota käytetään prosessorin tilan tutkimiseen. Sen koko on 16 bittiä ja käyn tässä lävitse sen tärkeimmät bitit:

Bitti:	Nimi:		Merkitys:
-----------------------------------------------------------------------------
0       PE              Prosessori siirretään suojattuun tilaan asettamalla
                        tämä bitti ykköseksi.

1       MP              Käytetään TS-bitin kanssa osoittamaan käytetäänkö
                        matemaatiikkaprosessoria työtehtävää vaihdettaessa.

2       EM              Jos tämä bitti on ykkönen, aiheuttavat kaikki 
                        matematiikkaprosessorin komennot virheen.

3       TS              Kun suoritetaan työtehtävän vaihto, TS asetetaan 
                        ykköseksi.

4       ET              Osoittaa prosessorin käyttämän laajennustyypin.
-----------------------------------------------------------------------------

Ikävä kyllä MSW:n sisällöstä on vaikea löytää tietoa, joten enempää en voi sitä käsitellä.

Keskeytykset

PC-tietokoneen toiminta perustuu pitkälti nk. keskeytyksiin. Ne ovat ohjelmapätkiä, jotka nimensä mukaan keskeyttävän koneen muun toiminnan oman suorituksensa ajaksi. Keskeytyksillä voi tehdä periaatteessa mitä vain, piirtää näytölle tai kirjoittaa kiintolevylle. Keskeytyksiä on tarjolla koneen BIOSin(Basic Input/Output System) ja käyttöjärjestelmän puolesta. BIOSin toimintaan tässä oppaassa ei perehdytä, sinulle riittää tällä hetkellä tietää, että se on nk. ROM-piireillä oleva ohjelmisto (Read Only Memory), joka huolehtii koneen perustoiminnoista, kuten keskusmuistin toiminnan tarkastus ja käyttöjärjestelmän lataaminen. ROM-piirien sisältöä ei voi muuttaa, BIOS on tallennettu kemiallisesti muistipiireihin jo tehtaalla. Luonnollisesti myös käyttöjärjestelmällä on omia keskeytyksiä, jotka voivat hoitaa muutamia toimintoja, kuten tiedostojen käsittely, ym. Mikäli kirjoitat paljon ohjelmia assemblyllä, tulet varmasti jossain vaiheessa käyttämään keskeytyksiä (ainakin tämän oppaan esimerkkiohjelmissa käytetään keskeytyksiä). Keskeytysten kutsuminen ohjelmasta käydään myöhemmin lävitse.

Toimittaessa suojatussa tilassa, ei voi kutsua 16-bittisiä keskeytyksiä, joita kaikki BIOSin ja DOSin keskeytykset ovat. Tällöin myös keskeytysten täytyy olla 32-bittisiä, ennenkuin niitä voidaan käyttää. Mikäli suojatussa tilassa suoritetaan 16-bittinen keskeytys, saadaan aikaan lähes varmasti nk. "triple fault", jolloin suoritin nollaa itsensä ja käynnistää järjestelmän uudestaan. Keskeytysten käyttö ja osoitteet ilmoitetaan prosessorille IDT-taulukossa.

Keskusmuisti

PC-tietokoneen muisti jaetaan kahteen osaan, ROM ja RAM.

Koska massamuistien(levykkeet, kiintolevyt, kasetit) käsittely on hidasta, on koneessa nk. keskusmuisti eli RAM, jonne suoritin ja käyttöjärjestelmä tallentaa tietoja, jonne ohjelmat ladataan ja jossa sijaitsee myös kaikkien ohjelmien tarvitsema data(taulukot, muuttujat, ym). Keskusmuistin on väliaikaismuisti, joka tarkoittaa, että tiedot eivät säily siellä kovinkaan kauaa, joten keskusmuisti virkistetään monia kertoja sekunnissa. Kun tietokoneesta katkaistaan virta, kaikki keskusmuistin sisältö pyyhkiytyy pois. Keskusmuisti jaetaan moneen osaan, jotka alla käyn läpi. Keskusmuisti jaetaan kahteen pääosaan, ROM ja RAM.

ROM - Read Only Memory - Lukumuisti:

ROM-muistin sisältöä ei voi muuttaa. Yleensä ROM-muistia on PC-yhteensopivassa tietokoneessa 32 kilotavua tai enemmän. ROM-piirit sijaitsevat tietokoneen emolevyllä ja sisältävät BIOSin ja keskeytykset.

RAM - Random Access Memory - Työmuisti:

Tämä muisti nimensä mukaan toimii muistina, jota tietokone käyttää suorittamissaan töissä. Tähän väliaikaismuistiin tallennetaan ohjelmien data, ym. väliaikainen tieto.

Keskusmuistin osat:

--------------------------------------------------------------------
BIOS-tietoalue:  Sisältää suorittimen keskeytysvektorit, ym. tietoa.
Perusmuisti:     Tämä on n. 640 kilotavun kokoinen lohko, joka on
                 jaettu MSDOSin ohjelmien, laiteohjaimien, ja
                 käyttöjärjestelmän ytimen käyttöön. Sen alaosassa
                 ovat myös suorittimen keskeytysvektorit ja BIOSin
                 tietoalue.
Näyttömuisti:    Keskusmuisti väliltä A000:0 - B800:7FFF on 
                 näyttömuistia, johon arvoja sijoittamalla
                 voi hallita, mitä näytölle piirtyy.
Extended-        Sitä osaa, mikä sijoittuu RAM-muistin ensimmäisen
muisti:          megatavun yläpuolelle, kutsutaan Extended-muistiksi.
                 Se ei sisällä mitään erityisiä tietoalueita tai
                 kiinteitä muisteja(esim. grafiikkamuisti), vaan
                 se on omistettu täysin ohjelmien datalle.
Expanded-        Vuonna miekka ja kilpi, kun ei ollut suojattua tilaa,
muisti:          oli tämä ainoa tapa saada lisää muistia perusmuistin
                 lisäksi. Perustuu systeemiin, jossa muistia käsitellään
                 nk. sivuina, jolloin vain osa muistista on kerrallaan
                 suorittimen käytettävissä.
Upper Memory     Ylämuistipalikoilla(UMB, Upper Memory Blocks) hallitaan
Blocks:          muistia grafiikkamuistin ja BIOSin välillä.
BIOS-ROM:        ROM-muisti sijaitsee tällä kohtaa, välillä 0F0000 - 
                 0FFFFF.
High Memory      Tämä on myös perua vuosilta miekka ja kilpi. Extended-
Area:            muistin ensimmäiset 64 kilotavua, jotka voitiin ottaa
                 käyttöön ilman siirtymistä suojattuun tilaan.
--------------------------------------------------------------------



PC-koneen piirit

PC-koneissa on paljon erilaisia piirejä, jotka suorittavat eri tehtäviä. Joka laitteella on oma ohjauspiirinsä ja laitetta voi käyttää suoraan käymällä käsiksi laitteen piireihin. Esimerkiksi omaa suojatun tilan järjestelmää ohjelmoidessa et voi käyttää BIOSin tarjoamia keskeytyksiä, joten laitteiden käyttö tapahtuu kertomalla laitteen piirille mitä sen pitää tehdä. Piirien suorasta käytöstä sinun ei tarvitse huolehtia, etkä edes voi, mikäli teet assemblyllä ohjelmaa johonkin suojatun tilan järjestelmään(Windows, Linux). Suoritin käyttää nk. portteja piirien suoraan käyttöön. Portteihin voi kirjoittaa(komennolla OUT) ja niistä voidaan lukea(komennolla IN) tietoja. Portit ovat 8-bittisiä ja niitä on 65536 kappaletta. Jokaista porttia voidaan käyttää myös 16- ja 32-bittisenä. Kun käytät jotain porttia 16-bittisenä käytät itse asiassa kahta 8-bittistä porttia ja 32-bittisenä käyttäessäsi käytät neljää 8-bittistä porttia. Luonnollisesti 16-bittisenä voit käyttää vain 32768 porttia ja 32-bittisenä 16384 porttia (7.1 - Lukujärjestelmät).

Alla on taulukko joistakin PC-koneen piireistä:

(Täällä mitään taulukkoo viä oo, mee kotios siitä)

Loogiset operaatiot

Loogiset operaatiot ovat erittäin lähellä bittioperaatioita(luku 7.3) ja prosessori käyttää pääasiallisesti niitä ratkaistessaan tehtäviä. Lukijan kannattaa perehtyä ensin lukuun 7.3, vaikka tämä luku onkin sitä ennen oppaan rakenteen takia. Koko prosessorin toiminta perustuu pääosin nk. loogisiin portteihin joita ovat mm. AND(ja), OR(tai) ja NOT(ei), NAND(käänteinen and), NOR(käänteinen or) ja XOR(poissulkeva tai).

Piirit toimivat luonnollisesti sähkövirralla ja sisään menevä virta tarkoittaa ykköstä, jos sisään ei mene virtaa, se merkitsee nollaa. Suorittimen sisässä on myös nk. kiikkuja, jotka säilyttävät sähkövirtaa(voi käyttää muistina) ja virtaa vahvistavia komponentteja, sillä sähkövirta heikkenee kulkiessaan johdinta pitkin, kunnes jännite on liian heikko tunnistettavaksi ykköseksi tai nollaksi. Esimerkiksi jotkin RAM-muistit(elleivät periaatteessa kaikki) muodostuvat pelkästään kiikuista.

Hyödyllinen apuväline suunniteltaessa piirejä on totuustaulu, joka ilmoittaa piirin sisäänmenot ja ulostulot. NAND-portti on kaikkein yksinkertaisin portti ja sillä voidaan muodostaa kaikki muut portit(NOT-porttia lukuunottamatta, joka on oikeastaan nk. muuntaja). Se toimii AND-porttiin nähden käänteisesti. Mikäli sisään menee 1 ja 1, ulostulo on 0, muussa tapauksessa ulostulo on 1. NAND-portin totuustaulu on siis:

IN1     IN2     OUT1
------------------------
0       0       1
0       1       1
1       0       1
1       1       0
------------------------

Portteja yhdistelemällä saadaan aikaiseksi muita portteja ja hyvin monimutkaisiakin rakenteita. Alla muutamia esimerkkejä:

NAND-portilla ja NOT-portilla saadaan aikaan AND-portti:

|   |
NAND
   |
NOT
 |

Totuustaulu:

IN1     IN2     OUT1
------------------------
0       0       0
0       1       0
1       0       0
1       1       1
------------------------

(jatkuu...)

Kannattaa tietää

Lukujärjestelmät

Ohjelmoijan pelastukseksi(ja osittain harmiksi) on olemassa kolme lukujärjestelmää, jotka on hyvä tuntea. Ne ovat heksadesimaalijärjestelmä, binäärijärjestelmä ja kymmenjärjestelmä. Tässä esitellyt järjestelmät ovat heksadesimaali- ja binäärijärjestelmä, kymmenjärjestelmän luultavasti jo tunnet, sillä käytät sitä joka päivä. Kymmenjärjestelmä on siis kymmenkantainen järjestelmä, aina kun lukusarjaan tulee yksi luku lisää, kymmenkertaistuu lukusarjalla ilmoitettavien vaihtoehtojen määrä. Esimerkkinä tästä, yhdellä kymmenjärjestelmän luvulla voi ilmoittaa luvut väliltä 0-9, eli kymmenen eri lukua. Kahdella numerolla taas voi ilmoittaa luvut väliltä 0-99, eli sata eri lukua. Alkuperäinen 10 eri lukua kertautui siis kymmenellä ja muuttui sadaksi, kun lisäsimme yhden luvun. Binääri- ja heksadesimaalijärjestelmän käyn läpi alla:

Binäärijärjestelmä

Olet varmasti kuullut jonkun sanovan, että tietokone toimii ykkösillä ja nollilla. Sillä on tarkoitettu juuri tätä. Tietokoneen muisti koostuu biteistä, jotka ovat periaatteessa kytkimiä. Näillä kytkimillä voi olla kaksi tilaa, päällä tai pois. Tätä tilaa on alettu merkitä numeroilla 1 ja 0, sen mukaan onko kytkin päällä vai ei. Koska tietokoneen muisti koostuu biteistä, on sille ominaisin lukujärjestelmä binäärijärjestelmä, joka on nk. kaksikantainen järjestelmä, eli se on periaatteessa samanlainen kuin kymmenjärjestelmä, mutta kymmenen eri numeron sijasta käytetäänkin vain kahta numeroa. Nämä numerot ovat 1 ja 0. Tarkastellaan asiaa lähemmin 8-bittisillä luvuilla. Binäärijärjestelmä etenee loogisessa järjestyksessä, jonka näet alta:

0 = 00000000 1 = 00000001 2 = 00000010 3 = 00000011 4 = 00000100 5 = 00000101 6 = 00000110 7 = 00000111


Eli luvut kasvavat periaatteessa kymmenjärjestelmän lukuina, käyttäen vain numeroita 1 ja 0. Eli ensimmäinen luku, jonka kymmerjärjestelmässä voi muodostaa luvuilla 1 ja 0, on 0, seuraava on 1, sitä seuraava on 10 ja sitä seuraava 11. Jos meillä on yksi bitti ja sillä voi olla siis kaksi tilaa, 0 ja 1, voi sillä ilmoittaa kaksi lukua, binäärisenä 1 ja 0, sekä kymmenjärjestelmässä 0 ja 1. Jos meillä onkin kaksi bittiä, kaksinkertaistuu vaihtoehtojen määrä, eli voimme ilmoittaa neljä eri lukua. binäärisenä 00, 01, 10, 11 ja kymmerjärjestelmässä 1, 2, 3, 4. Kun tiedämme, että vaihtoehtojen määrä kaksinkertaistuu aina kun lukujonoon tulee yksi bitti lisää, voimme laatia pienen taulukon siitä, montako eri arvoa tietyllä bittimäärällä voi olla:

Bittien määrä:		Montako eri lukua voi muodostaa:
---------------------------------------------------------------------------
1			2
2			4
3			8
4			16
5			32
6			64
7			128
8			256
9			512
10			1024
11			2048
12			4096
13			8192
14			16384
15			32768
16			65536
---------------------------------------------------------------------------


Tästä voimme kätevästi päätellä, että esimerkiksi rekistereihin AH ja AL mahtuu molempiin jokin arvo väliltä 0-255 ja rekisteriin AX jokin arvo väliltä 0-65535. Mikäli taas rekisterissä AX on ensimmäinen bitti varattu etumerkkibitiksi, eli se ilmoittaa, onko luku positiivinen vai negatiivinen, on itse luvun ilmoittamiseen käytössä vai 15-bittiä, jolloin AX voi saada arvon väliltä -32768 - +32768.

Heksadesimaalijärjestelmä

Tämäkin lukujärjestelmä on samanlainen kuin kaikki muut lukujärjestelmät, erona vain se, että se on 16-kantainen. Eli mikäli kaksikantaisessa binäärijärjestelmässä jokainen uusi bitti kaksinkertaistaa eri vaihto- ehtojen määrän ja kymmenkantaisessa binäärijärjestelmässä jokainen uusi luku kymmenkertaistaa vaihtoehtojen määrän, on luonnollista, että 16-kantaisessa heksadesimaalijärjestelmässä jokainen uusi luku 16-kertaistaa vaihtoehtojen määrän. Mutta koska lukuja on 16, ei niitä kaikkia voikaan ilmoittaa kymmenjärjestelmän lukuina, vaan numeroiksi on täytynyt ottaa aakkosten kuusi ensimmäistä kirjainta, A-F. Alla olevasta taulukosta näet, kuinka heksadesimaalijärjestelmä kasvaa:

Heksadesimaaliluku:	Kymmenjärjestelmän luku:
---------------------------------------------------------------------------
0			0
1			1
2			2
3			3
4			4
5			5
6			6
7			7
8			8
9			9
A			10
B			11
C			12
D			13
E			14
F			15
10			16
11			17
12			18
13			19
14			20
15			21
16			22
17			23
18			24
19			25
1A			26
1B			27
1C			28
1D			29
1E			30
1F			31
---------------------------------------------------------------------------


Eli heksadesimaalijärjestelmä toimii samoin kuin mikä tahansa lukujärjestelmä, mutta se on 16-kantainen. Tästä voimme kätevästi päätellä, että rekistereihin AH ja AL mahtuu jokin arvo väliltä 0-FF ja rekisteriin AX jokin arvo väliltä 0-FFFF.

Lukujärjestelmien muunto

Mikäli käytössäsi ei ole laskinta, jolla eri lukujärjestelmät voi muuttaa toisiin lukujärjestelmiin, neuvon nyt, kuinka se tehdään.

Binäärijärjestelmästä kymmenjärjestelmään

Otetaan esimerkiksi luku 10010100. Se on kahdeksan bittinen. Kahdeksalla bitillä voi siis muodostaa 256 eri vaihtoehtoa. Mikäli luku olisi 255, se olisi 11111111, eli kaikki bitit olisivat ykkösiä. Kun tarkastelemme edellä olevaa taulukkoa, voimme päätellä, että luvun vasemmanpuoleisin bitti, jota kutsutaan eniten merkitseväksi, on arvoltaan 128. Seuraava 64, sitä seuraava 32, sitten 16, 8, 4, 2 ja 1. Tarkastakaamme siis, onko eniten merkitsevä bitti 1. Se on, joten luku on arvoltaan vähintään 128. Pistäkäämme luku muistiin. Seuraavat kaksi lukua ovat nollia, niihin emme kiinnitä huomiota. Kiinnitämme huomiota vasta neljänteen bittiin, joka on 1. Se on arvoltaan 16. Pistäkäämme sekin muistiin. Seuraava bitti on nolla ja sitä seuraava bitti jälleen yksi, arvoltaan 4. Pistäkäämme sekin muistiin. Loput bitit ovat nollia, niihin emme kiinnitä huomiota. Nyt laskemme muistiin pistämämme numerot yhteen: 128 + 16 + 4 = 148. Binäärijärjestelmän luku 10010100 on siis kymmenjärjestelmässä 148. Otetaan lyhyemmäksi esimerkiksi luku 00011010. Saamme siitä laskun: 16 + 8 + 2 = 26.

Kymmenjärjestelmästä binäärijärjestelmään

Tämä on yllättävän yksinkertainen laskutoimitus. Otetaan esimerkiksi kymmenjärjestelmän luku 79. Laskutoimituksessa käytetään jakojäännöstä bitin laskemiseen. Toimitus on seuraavanlainen: Jaetaan 79 kahdella. Saamme tulokseksi 39, jää 1. Luvun oikeanpuoleinen, eli vähiten merkitsevä bitti on siis 1. Jaetaan 39 kahdella. Tulos on 19, jää 1. Oikealta laskien seuraava bitti on siis 1. Jaetaan 19 kahdella. Saamme 9, jää 1. Seuraava bitti on siis 1. Jaetaan 9 kahdella. Tulos on 4, jää 1. Seuraava bitti on siis 1. Jaetaan neljä kahdella, tulos on 2, jää 0, eli seuraava bitti on 0. Jaetaan kaksi kahdella, tulos on 1, jää 0, eli seuraava bitti on 0. Jaetaan 1 kahdella, tulos on 0, jää 1, seuraava bitti on siis 1. Näin voimme päätellä, että luku 79 on binäärijärjestelmässä 1001111, se on seitsemän bittinen, mikäli haluamme siitä 8- tai 16- bittisen, lisäämme eteen vain tarvittavan määrän nollia.

Heksadesimaalijärjestelmästä kymmenjärjestelmään

Otetaan esimerkiksi heksadesimaalijärjestelmän luku A5FA. Jokaisen luvun arvo pitää kertoa yhtä monta kertaa luvulla 16, kuin kuinka monentena se jonossa on - 1. Luku A pitää siis kertoa kolme kertaa luvulla 16(se on neljäs numero, siitä vähennetään yksi), luku 5 kaksi kertaa luvulla 16 ja luku F kerran luvulla 16. Luvun A arvo on siis 10, luvun 5 on 5, luvun F on 15 ja luvun A jälleen kymmenen. Tästä saamme laskutoimituksen: (10 * 16 * 16 * 16) + (5 * 16 * 16) + (15 * 16) + 10 = 42490.

Kymmenjärjestelmästä heksadesimaalijärjestelmään

Tämä on hyvin pitkälle samanlainen laskutoimitus kuin kymmenjärjestelmästä binäärijärjestelmään, nyt kaikki luvut jaetaan luvulla 16. Otetaan esimerkiksi luku 347. Jaetaan se ensin luvulla 16. Saamme tulokseksi 21, jää 11. Oikeanpuoleisin numero on siis B(kymmenjärjestelmässä 11). Jaetaan 21 luvulla 16. Saamme 1, jää 5. Seuraava luku oikealta on siis 5. Viimeiseksi numeroksi jää luku 1. Luku 347 on siis heksadesimaalijärjestelmässä 15B.

Mikäli haluat muuttaa binäärijärjestelmästä heksadesimaalijärjestelmään tai toisinpäin, muuta luku ensin kymmenjärjestelmään ja sen jälkeen toiseen lukujärjestelmään. Lukujärjestelmien erottaminen toisistaan tapahtuu NASM-kääntäjällä siten, että mikäli luvun edessä on 0x, tai luvun lopussa on h-kirjain, se tulkitaan heksadesimaaliluvuksi, muuten se tulkitaan kymmenjärjestelmän luvuksi. Eli 0x100 ja 15h ovat heksa- desimaalilukuja, 10 ja 879 kymmenjärjestelmän lukuja, sekä 100b binääriluku.

Binäärijärjestelmästä hexadesimaalijärjestelmään muuttaminen onnistuu myös niin, että jaat binääriluvun oikealta neljän bitin osiin ja lasket niille niitä vastaavan heksadesimaaliarvon käsin tai tarkistat arvon jostain taulukosta. Esimerkiksi luku 10011010110 jakaantuu osiin 100 1101 0110. Lasketaan kunkin neljän bitin jonon alle sitä vastaava heksadesimaaliluku:

0100 1101 0110
  4    D    6

Tulos on siis 4D6 heksadesimaaleina. Operaatio toimii hyvin myös käänteisenä:

  B    1    7
1011 0001 0111

Mittayksiköt

Assembly-ohjelmoijan on välttämätöntä tuntea mittayksiköt, joilla kuvataan koneen muistia. Käyn seuraavaksi lävitse käsitteet kilo, mega, giga, tera, peta, eksa, sana, kaksoissana, tavu, kymmentavu ja bitti.

Muistin osat: Bitti, tavu, sana, kaksoissana ja kymmentavu.

------------------------------------------------------------------------
Bitti:		Tämä on kuvattu tarkasti jo osassa lukujärjestelmät.
		Tietokoneen informaation pienin yksikkö, jota voi
		verrata kytkimeen, joka on päällä tai pois päältä,
		toisin sanoen 1 tai 0.
Tavu:		Tavun koko on nykyään 8-bittiä, vanhemmissa
		järjestelmissä on käytössä myös muita bittimääriä.
		Sillä voi siis olla 256 eri arvoa. Tämän takia
		PC-tietokoneen merkistössä on 256 eri kirjoitusmerkkiä.
		Tavuilla myös mitataan muistia. Esimerkiksi rekisterit
		AH ja AL ovat yhden tavun kokoisia, siitä nimi tavurekisteri.
Sana:		Sanan koko on kaksi tavua. Se on siis 16-bittiä.
		Esimerkiksi yleisrekisterit(AX - DX) ovat 16-bittisiä.
		Sanalla voi siis olla 65536 eri arvoa.
Kaksoissana:	Nimensä mukaan kaksi sanaa, eli 32 bittiä. Esimerkkinä
		yleisrekisterit(EAX - EDX) ovat suojatussa tilassa 
		toimittaessa kaksoissanan mittaisia, eli 32-bittisiä.
Kymmentavu:	Nimensä mukaisesti kymmentavu on kymmenen tavua tai
		80 bittiä.
------------------------------------------------------------------------


Joskus kuuluu puhuttavan myös käsitteestä puolitavu, joka on siis neljä bittiä.

Etuliitteet: Kilo, mega, giga, tera, peta, eksa.

-------------------------------------------------------------------------
Kilo:           Normaalisti tarkoittaa tuhatta, esimerkiksi kilogramma
                on tuhat grammaa. Tietokoneista puhuttaessa se on
                1024. Eli kilotavu onkin 1024 tavua. Tämä johtuu siitä,
                että tietokone toimii binäärijärjestelmällä ja 1024
                on numeron kaksi kerrannainen(kaksi potenssiin 10 on 1024).
Mega:           Mega tarkoittaa normaalisti miljoonaa, mutta tietokoneista 
                puhuttessa se onkin 1024 kilotavua, eli 1048576 tavua.
                Tämä johtuu samasta syystä, minkä takia kilotavukin on 
                1024 tavua.
Giga:           Samalla tavalla kuin kaikki edellisetkin, on gigatavu 1024
                megatavua.
Tera:           Teratavu on 1024 gigatavua. Sana tera tulee
                latinasta(hirviö). Kyseessä todella on hirviötavu,
                käytännössä ohjelmoija ei tarvitse enää tämänkokoisia
                mittayksiköitä eikä kahta seuraavaakaan mittayksikköä.
Peta:           1024 Teratavua.
Eksa:           1024 Petatavua.
-------------------------------------------------------------------------

Bittioperaatiot

Assembly-ohjelmoinnissa täytyy usein päästä käsittelemään jonkin luvun yksittäisiä bittejä. Näissä toimenpiteissä käytetään nk. bittioperaatioita. Ensin käyn läpi loogiset operaatiot, joita ovat AND(ja), OR(tai), NOT(ei) ja XOR(poissulkeva tai). On olemassa myös operaatiot NAND ja NOR, mutta niitä en kuvaile erikseen, sillä ne ovat hyvin samanlaisia kuin AND ja OR, käyn niiden toiminnan vain lyhyesti läpi. Muita bittioperaatioita ovat siirto ja kierto. Käyn alla jokaisen bittioperaation lyhyesti läpi.

AND

AND on looginen JA. Nimensä se on saanut siitä, että molempien lausekkeiden pitää olla tosia, jotta tulos olisi tosi. Eli bittitasolla molempien bittien pitää olla ykkösiä, jotta tuloksen vastaava bitti on 1. Jos siis suoritamme AND operaation lausekkeiden (1 = 1) AND (2 > 1) on tulos tosi, sillä sekä ensimmäinen lause, että toinen lause ovat molemmat tosia(yksi on samanarvoinen kuin yksi ja kaksi on suurempi kuin yksi). Jos taas suoritamme operaation (1 = 1) AND (2 < 1) on tuloksena epätosi, sillä toinen lause ei ole tosi(kaksi ei ole pienempi kuin yksi). Mutta miten kummassa tällä tavalla voi käsitellä jonkin luvun yksittäisiä bittejä? Vastaus on, että suoritamme AND operaation kahden luvun välillä, joista ensimmäinen on rekisteri. Tulos tallennetaan rekisteriin. Jos esimerkiksi suoritamme AND-operaation binääriluvuille 00110110 ja 01011001 saamme laskutoimituksen:

00110110	<- Ensimmäinen luku
01011001	<- Toinen luku
--------
00010000	<- Tulos


Kuten huomaat, mikäli lukujen molemmat vastaavat bitit ovat olleen ykkösiä, on tuloksenkin vastaava bitti 1. Jos taas jompikumpi tai molemmat ovat nollia, on tuloksenkin vastaava bitti 0. Assembly-kielellä tämä operaatio siis suoritetaan komennoilla:

----------------------------------------------------------------------------
mov     ah, 36h    ; 36h on heksadesimaalinumero, kymmenjärjestelmässä
                   ; 54 ja binäärijärjestelmässä 00110110.
and     ah, 1Ah    ; 1Ah on heksadesimaalinumero, kymmenjärjestelmässä
                   ; 26 ja binäärijärjestelmässä 01011001.
----------------------------------------------------------------------------


Ensimmäisellä rivillä on komento mov, jonka käsittelemme myöhemmin. Nyt riittää tietää, että se siirtää rekisteriin AH luvun 36h. Kolmannella rivillä suoritetaan itse AND-operaatio siten, että toinen luku on rekisterissä AH ja toinen on luku 1Ah. Tulos tallennetaan rekisteriin AH.

AND-operaatio on kätevä, jos halutaan esim. muutaa tietyn luvun kaikki muut bitit nolliksi ja säilyttää jokin tietty bitti ennallaan. Jos haluamme muuttaa luvun 01010001 seitsemän ensimmäistä bittiä nolliksi ja säilyttää viimeisen ennallaan, meidän täytyy suorittaa AND-operaatio sen ja luvun 00000001 välillä. Saamme siis seuraavanlaisen laskutoimituksen:

01010001	<- Ensimmäinen luku
00000001	<- Toinen luku
--------
00000001	<- Tulos


Eli koska kaikki muut bitit, paitsi viimeinen ovat nollia, on tuloksenkin kaikki vastaavat bitit nollia. Koska viimeinen bitti on yksi, on tuloksenkin viimeinen bitti yksi, muutoin se olisi ollut nolla, eli se siis säilyy ennallaan.

OR

OR on looginen TAI. Eli mikäli toinen tai molemmat ehdoista ovat tosia, on tuloskin tosi. Otetaan esimerkiksi edellisen esimerkin luvut, saamme siis laskutoimituksen:

01010001	<- Ensimmäinen luku
00000001	<- Toinen luku
--------
01010001	<- Tulos


OR on käytännöllinen, jos halutaan muuttaa jokin luvun yksittäinen bitti ykköseksi. Otetaan esimerkki: Meillä on luku 00101000 ja haluamme muuttaa sen kolme ensimmäistä bittiä ykkösiksi, saamme seuraavanlaisen laskutoimituksen:

00101000	<- Ensimmäinen luku
11100000	<- Toinen luku
--------
11101000	<- Tulos


Mikäli siis jompikumpi biteistä on ollut ykkönen, on tuloksenkin vastaava bitti ykkönen. Jos haluamme muuttaa jonkun bitin ykköseksi, täytyy siis toisen luvun vastaava bitti olla ykkönen ja jos haluamme säilyttää bitin ennallaan, pitää vastaavan bitin olla nolla.

NOT

NOT on EI. Käsittelen sen tässä lyhyesti, siitä tarvitsee tietää vain, että sille annetaan yksi parametri ja se muuttaa kaikki annetun luvun ykköset nolliksi ja toisinpäin.


Nyt kun ymmärrät loogisten operaatioiden toiminnan, on sinun helpompi ymmärtää operaattoreiden NAND ja NOR toiminta. NAND on muuten samanlainen kuin AND, mutta jos molemmat bitit ovat ykkösiä, on tuloksen vastaava bitti nolla. NOR on muuten samanlainen kuin OR, mutta jos jompikumpi biteistä on ykkönen, on tuloksen vastaava bitti nolla.

Muita bittioperaatioita ovat siirto ja kierto. Siirto merkitsee bittien siirtämistä jompaan kumpaan suuntaan. Jos siis siirtäisimme luvun 00011001 bittejä kahden bitin verran vasemmalle, saisimme tulokseksi 01100100. Kuten huomaat, luku täyttyy oikealtapäin nollilla. Koska binäärijärjestelmä on kaksikantainen järjestelmä, jokainen siirto vasemmalle vastaa luvun kertomista kahdella ja oikealle jakamista kahdella. Se on hyvä tietää, sillä kerto ja jakolasku, joka on suoritettu tällä tavalla, on paljon nopeampi kuin jako- ja kertolaskukomentojen DIV ja MUL käyttö. Kierto on samanlainen operaation kuin siirto, mutta luvun yli siirtyvät bitit siirretään luvun toiselle puolelle. Jos siis kierrämme luvun 00101000 bittejä kolmen bitin verran vasemmalle, on tuloksena 01000001, sillä luvun kolmas bitti(arvoltaan yksi) on siis siirtynyt vasemmalle, ja luvun viides bitti on siirtynyt normaalisti kolmen bitin verran vasemmalle.

XOR

XOR on looginen poissulkeva TAI. Bittitasolla siis lauseke on tosi jos toinen on tosi ja toinen epätosi. Koska assemblylla voi tehdä nopeita ohjelmia, kerron tässä, että jos jokin rekisteri pitää nollata, se kannattaa tehdä XOR-operaatiolla. Se on nopeampi tapa kuin siirtää kyseiseen rekisteriin nolla. Eli jos suoritamme kahden samanarvoisen luvun välillä XOR-operaation, on tulos nolla. Esimerkiksi:

01011101
01011101
--------
00000000


Koska luvut ovat samat, ei yksikään biteistä voi olla tosi ja toinen epätosi, joten jokainen bitti nollautuu. Jos haluamme nollata esimerkiksi AH-rekisterin, ei kannata tehdä:

------------------
mov	ah, 0
------------------

Vaan:

------------------
xor	ah, ah
------------------

Otetaan toiseksi esimerkiksi erilaiset luvut:

00100100
10100101
--------
10000001


Eli biteistä vain ensimmäinen ja viimeinen ovat eriarvoiset, joten ensimmäinen ja viimeinen bitti muuttuvat ykkösiksi, muut nolliksi.


Assembly-kielen syntaksi

Käyn tässä läpi assembly-kielen syntaksin, eli tavan jolla assembly- kieltä kirjoitetaan. Toisin kuin esimerkiksi C- tai C++-kielessä, ei assembly-kielessä erotella isoja ja pieniä kirjaimia. Yleisenä tapana on ollut, että ensimmäinen sarkainkohta on varattu nimiöille(nimiö siis on kohta, johon voi hypätä muualta ohjelmasta, käsitellään seuraavassa luvussa), seuraava sarkainkohta itse komennolle ja sitä seuraava sarkainkohta komennon operandeille. Jos esimerkiksi määrittelemme nimiön "nollaa", joka nollaa AX-rekisterin, kirjoittaisimme sen näin:

nollaa: xor     ax, ax
|       |       |    
|       |       |     Kolmas sarkainkohta operandeille
|       |       |-------------------------------------------------------
|       | 
|       |             Toinen sarkainkohta itse komennolle
|       |---------------------------------------------------------------
|	
|                     Ensimmäinen sarkainkohta nimiölle
|-----------------------------------------------------------------------


Tämä on siis niin kutsuttua Intel-syntaksia, joka on pääosin tämän näköistä toinen yleinen syntaksi on AT&T-syntaksi, jonka toimintaan tässä emme perehdy, se on kohtalaisen vaikealukuista, sillä rekistereiden nimien eteen pitää kirjoittaa erikoismerkkejä, kuten prosenttimerkkejä(%). Muuttujien ja nimiöiden nimien pitää alkaa alaviivalla(_) tai kirjaimella väliltä A-Z. Nimissä saa muuten käyttää kirjaimia(väliltä A-Z), alaviivoja ja numeroita. Alla on esimerkkejä sallituista nimistä:

Nayton_Leveys _NaytonLeveys Muuttuja1


Ja esimerkkejä kielletyistä muuttujista:

Näytön-leveys Näytön_Leveys 1Muuttuja

Ohjelmoinnin perusmallit

Johdanto

Sinun ei tarvitse syventyä tähän lukuun, jos olet ohjelmoinut jo jollain ohjelmointikielellä. Tässä luvussa käydään läpi joitakin ohjelmoinnin perusmalleja, kuten mikä on kiersiö, muuttuja ja aliohjelma. Mikäli nämä asiat ovat sinulle entuudestaan tuttuja ja luet tätä dokumenttia teksti- editorilla, jossa on Etsi-toiminto, etsi numeroa 5.1 ja hyppää tämän luvun yli.


Muuttujat ja vakiot

Ohjelmoinnissa käytetään paljon muuttujia ja vakioita. Vakio voi olla numero, merkki tai merkkijono. Oletetaan, että meidän täytyy muistaa jokin luku, esimerkiksi näytön leveys. Jos pelkäämme, että emme muista, että tekstitilassa näytön leveys on 80, voimme tehdä siitä vakion. Vakion luominen tapahtuu siten, että kääntäjälle(kääntäjä kääntää ohjelmoijan koodin konekielelle) ilmoitetaan vakion nimi ja arvo. Jos siis tekisimme luvusta 80 LEVEYS-nimisen vakion, korvaisi kääntäjä siitä lähtien jokaisen LEVEYS-sanan arvolla 80. Onhan helpompi muistaa sana LEVEYS kuin luku 80. Vakiot osoittautuvat käytännöllisiksi varsinkin bittioperaatioissa, jotka on käsitelty luvussa 3. Nimensä mukaan muuttujan arvo voi muuttua ohjelman suorituksen aikana. Jos näytön leveys esimerkiksi riippuu käyttäjän laitteistosta, voimme ensin kysyä käyttäjältä näytön leveyden ja sen mukaan hallita ohjelman tulostusta. Näytön leveys tallennetaan vaikkapa leveys-nimiseen muuttujaan. Karkeasti ajatellen siis ainoa ero muuttujan ja vakion välillä on, että muuttujan arvo voi muuttua, vakion ei.


Aliohjelmat ja makrot

Jos jokin operaatio tai operaatioden sarja pitää suorittaa useasti ohjelman suorituksen aikana, siitä kannattaa tehdä aliohjelma tai makro. Aliohjelmat ja makrot ovat rakenteita, jotka kuuluvat ohjelmaan, mutta niitä ei suoriteta muuta kuin erillisen kutsun avulla. Makron ja aliohjelman (ts. funktion) ero on, että makro on kääntäjän tarjoama palvelu ja funktio on osa ohjelmaa. Jos meidän esimerkiksi pitäisi monessa kohdassa ohjelmaa tyhjentää näyttö ja asettaa kursori vasempaan yläkulmaan, ei meidän joka kohtaan kannata kirjoittaa koodia, joka tämän tekee, vaan kirjoittaa se kerran ja joka kerta kun operaatiot pitää suorittaa, meidän vain tarvitsee kutsua makroa tai aliohjelmaa, joka tekee kaiken. Käyn alla jokaisen tyypin erikseen lävitse: Aliohjelmille ja makroille voi välittää myös parametreja. Jos meidän pitää esimerkiksi laskea ympyrän kehän pituus * 8, ei meidän joka kerta kannata kirjoittaa:

keha = halkaisija * pii * 8

Kannattavampaa on kirjoittaa aliohjelma tai makro, jolle annetaan parametriksi vain ympyrän halkaisija, ja se palauttaa kehan pituuden. Riippuen ohjelmointikielestä me voisimme kirjoittaa sen näin(olettaen siis, että laske on aliohjelman tai makron nimi nimi):

keha = laske(halkaisija)


Makro

Makro on kääntäjän tarjoama palvelu. Kun olemme määritelleet makron, kääntäjä kääntäessään ohjelmaa kopioi makron sisältämän koodin siihen kohtaan ohjelmaa, josta makroa on kutsuttu. Makrojen käyttö siis isontaa ohjelman kokoa ja on käytännöllisempää käyttää funktioita, mutta makrot ovat hitusen nopeampia.

Aliohjelma eli funktio

Funktio on osa ohjelmaa, johon hypätään, sen sisältämä koodi suoritetaan ja palataan suorittamaan edellistä koodia sen seuraavasta käskystä. Funktio siis käännetään ohjelmaan vain kerran, sen takia sen käyttö tekee ohjelmasta pienemmän, kuin jos käytettäisiin makroa, mutta aliohjelma on hitaampi. Kaikkea kun ei voi saada kerralla.

Laskurit ja kiersiöt

Jos ohjelmassa täytyy suorittaa yksi tai useampi käsky monta kertaa peräkkäin, kannattaa käyttää laskuria tai kiersiötä. Laskurin ja kiersiö ero on, että laskuri suoritetaan tietty määrä kertoja ja kiersiön loppuminen päätellään ehdolla. Jos esimerkiksi haluaisimme piirtää näytölle koko näytön pituisen viivan, voisimme tehdä sen seuraavalla tavalla(pseudokoodina):

Laskuri

Suorita seuraava lause 80 kertaa

  Tulosta näytölle merkki "-"

Kiersiö

(Tee mikäli ei olla rivin lopussa)

  Tulosta näytölle merkki "-"

Luonnollisesti kiersiössä ja laskurissa voisi suorittaa useampiakin lauseita, tai vaikka kutsua aliohjelmia, mutta yhdellä lauseella asia on helpompi ymmärtää.

Ohjausrakenteet

Ohjausrakenteet ovat lauseita, joissa vertaillaan kahta tai useampaa vaihtoehtoa ja ohjelman suorituksen eteneminen jatkuu riippuen vertailujen tuloksesta. Ohjausrakenteita on periaatteessa kolmenlaisia, ehtorakenne, valintarakenne ja hyppy. Nimensä mukaan ohjausrakenteilla ohjataan ohjelman suoritusta. Valintarakenteessa annetaan jokin arvo ja eri vaihtoehtoja, joista valita. Ohjelman suoritus tai tiettyjen komentojen suoritus riippuvat valinnan tuloksesta.

Otetaan esimerkki ehtorakenteesta:

-------------------------------------
Jos ikä > 80: Tulosta "Olet kyllä ihan kalkkis..."
-------------------------------------

Sitten esimerkki valintarakenteesta:

--------------------------------------
Valitse ikä:

Tapaus < 15: Tulosta "Oletpas sinä nuori!"
             Lopeta ohjelman suoritus.
Tapaus < 35: Tulosta "Lähestyt keski-ikää."
             Lopeta ohjelman suoritus.
Tapaus < 50: Tulosta "Olet keski-iässä."
             Lopeta lauseen suoritus.
Tapaus < 75: Tulosta "Olet pian eläkkeellä."
             Lopeta ohjelman suoritus.
Tapaus > 74: Tulosta "Oletpas sinä vanha..."
             Lopeta ohjelman suoritus.
Tapaus muu:  Tulosta "Tässä ohjelmassa on nyt kyllä jotain vikaa..."
             Lopeta ohjelman suoritus.
--------------------------------------

Yllä on ikärasistinen esimerkki ehto- ja valintarakenteesta. Hyppykäskyn esimerkki on alla:

-------------------------------------
Hyppää kohtaan pomppu.
Tulosta "Moi!"
Pomppu:
-------------------------------------

Ohjelma ei koskaan tulosta ruudulle sanaa "Moi!", sillä tulostuskomennon edellä on hyppykäsky, jossa ohjelman suorituksen käsketään siirtyä kohtaa pomppu, joka on tulostuskomennon jälkeen.


Assembly-ohjelmointi

Johdanto

Tässä luvussa käydään lävitse itse assembly-ohjelmointi ja kuinka edellisessä luvussa kerrotut mallit toteutetaan assembly-kielellä. Assemblyhan on symbolinen konekieli, eli se on kaikkea muuta kuin käyttäjäläheinen. Sen takia assemblyssa ei ole valmiita komentoja toisto- ja valintarakenteille, tulostukselle, ym. vaan ne kaikki täytyy tehdä itse alusta loppuun asti. Esittelen kuinka nämä ohjelmonnin kannalta välttämättömät perusrakenteet tehdään assembly- kielellä, sekä sen, kuinka muuttujat, aliohjelmat, ym. esitellään assembly-kielellä. Luvun lopussa on lyhyt assembly-ohjelma.


Pino

Pino on muistista varattu alue, jonne voi tallentaa tietoa. Pinoon tallentaminen tapahtuu komennolla "push" ja tietojen pinosta palauttaminen komennolla "pop". Nimensä pino on saanut siitä, että se toimii kuin mikä tahansa pino. Sääntö kuuluu: "Minkä viimeiseksi sinne tallennat, sen ensimmäiseksi saat takaisin.", eli vertailuna voisi toimia paperipino, jonka päälle laitamme papereita. Se paperi, mikä viimeiseksi pinoon on laitettu nousee sieltä myös ensimmäisenä pois. Tutustutaan komentojen "push" ja "pop" käyttöön.

Push

-----------------------------------------------------------------
Käyttö: push    lähde
Kuvaus: Tallentaa lähteen pinoon. Lähde voi olla suora lukuarvo, rekisteri tai muistiosoite.
-----------------------------------------------------------------

Pop

-----------------------------------------------------------------
Käyttö: pop     kohde
Kuvaus: Poimii luvun pinosta ja tallentaa sen kohteeseen. Kohde voi olla rekisteri tai muistiosoite.
-----------------------------------------------------------------


Otetaan esimerkki pinon käytöstä:

	push    15
	push    10
	pop     ax
	pop     bx

Nyt rekisterissä AX on arvo 10 ja rekisterissä BX on arvo 15. Ensin siis pinoon tallennettiin luku 15 ja sitten luku 10. Sitten pinon päällimmäinen luku(siis viimeiseksi laitettu luku, eli luku 10) poimittiin rekisteriin AX. Sen jälkeen pinon päällimmäinen luku poimittiin rekisteriin BX(pinon päällimmäinen luku on siis nyt 15, koska luku 10 on jo poimittu pinosta). Pinon kontrollointiin käytetään rekistereitä SS ja SP. On hyvä tietää, että aina kun pinosta poimitaan luku, SP:n arvoa lisätään yhdellä ja kun pinoon tallennetaan luku, SP:n arvoa pienennetään yhdellä. Pino siis sijaitsee muistissa periaatteessa väärinpäin.


Muuttujat ja vakiot

Luvussa 4.2 on kuvattu muuttujien ja vakioiden toiminta, joten niiden toimintaan en sen syvemmin pureudu, vaan selitän vain ne komennot, joilla muuttujat ja vakiot NASM-kääntäjälle esitellään. Vakion esitellyyn NASM-kääntäjälle käytetään komentoja EQU ja %define. EQU-komentoa käytettäessä ilmoitetaan ensin vakion nimi, esimerkissä NAYTON_LEVEYS, sitten komento EQU, joka kertoo kääntäjälle, että kyseessä on vakio ja itse vakion arvo: NAYTON_LEVEYS equ 80 Esiteltäessä vakio %define komennolla, alkuun laitetaan komento %define, sen jälkeen vakion nimi ja lopuksi vakion arvo: %define NAYTON_LEVEYS 80 Muuttuja esitellään assembly-kielessä seuraavaan tapaan(arvot ovat samat kuin edellisessä esimerkissä: Nayton_Leveys db 80 Ensin kääntäjälle ilmoitetaan muuttujan nimi, sitten arvo, kuinka paljon muuttujalle varataan muistia ja itse muuttujan arvo. Mikäli arvoa ei muuttujan esitelyssä haluta ilmoittaa, korvataan muuttujan arvo(tässä tapauksessa 80) dollarimerkillä($): Nayton_Leveys db $ Useimmissa kääntäjissä dollarimerkin tilalla pitää olla kysymysmerkki(?). Muuttujan muistinvaraus ilmoitetaan sanoilla db, dw ja dd. Näin ollen muuttujalle varataan muistia joko tavuina, sanoina tai kaksoissanoina.

db = Tavuja(b = byte)
dw = Sanoja(w = word)
dd = Kaksoissanoja(d = double word)

Aliohjelmat

Esittelen tässä vain yhden tavan tehdä aliohjelma NASM-kääntäjällä. Aliohjelman esittely suoritetaan seuraavasti:

AliohjelmanNimi:
(Koodia)
        ret

Ensimmäisellä rivillä on aina nimiö, joka on aliohjelman nimi. Viimeisellä rivillä on paluukomento "ret", joka kertoo prosessorille, että aliohjelman suoritus on loppunut ja sen pitää palata suorittamaan koodia, josta aliohjelma kutsuttiin. Aliohjelman kutsuminen muualta ohjelmasta tapahtuu seuraavasti: call AliohjelmanNimi Eli ensin on komento "call", joka kertoo prosessorille, että kyseessä on aliohjelmakutsu ja sen jälkeen itse aliohjelman nimi, jonka kääntäjä tiedostoa konekielelle kääntäessään muuttaa muistiosoitteeksi. Assembly-kielessä ei ole mitään erityistä toimintoa parametrien välittämiseksi aliohjelmille, useimmiten parametrit välitetään yleisrekistereiden tai pinon välityksellä. Mikäli välität parametreja pinon välityksellä, ole varovainen, sillä aliohjelmaa kutsuttaessa prosessori siirtää paluuosoitteen pinon päällimmäiseksi. Jos olet aliohjelmassasi siirtänyt pinoon eri määrän dataa kuin olet ennen ret-komentoa ottanut pois, paluu tapahtuu väärään osoitteeseen ja ohjelma mitä todennäköisimmin kaatuu, tai sen toiminta ainakin muuttuu ennalta-arvaamattomaksi.


Makrot

Makrojen toiminta on kuvattu luvussa 4.3, joten tässä luvussa neuvon vain, miten makroja luodaan NASM-kääntäjällä. Syntaksi on seuraava:

%macro MAKRONNIMI PARAMETRIEN_MAARA
        (koodia)
%endmacro

Parametreja käsitellään syntaksilla %1, %2, %3, jne., jossa %1 on siis ensimmäinen parametri. Nopein tapa nollata rekisteri on suorittaa sille xor-operaatio. Voimme luoda sitä varten makron:

%macro ZERO    1
        xor     %1, %1
%endmacro

Makron nimi on siis ZERO ja sille välitetään yksi parametri, kuten ensimmäiseltä riviltä käy ilmi. Sen jälkeen suoritetaan xor-operaatio annetulle parametrille itsensä kanssa, jonka jälkeen makro loppuu. Voimme koodissa kirjoittaa esimerkiksi: ZERO ax Esikääntäjän käsittelyn jälkeen koodi on siis: xor ax, ax Mikäli makrolle ei välitetä ollenkaan parametreja, laitetaan parametrien määräksi 0, kuten seuraavassa makrossa, joka tallettaa kaikki rekisterit pinoon:

%macro PUSHALL 0
        pushf                                            ; Talletetaan liput pinoon
        pusha                                            ; Talletetaan kaikki rekisterit pinoon
%endmacro

Koodi ei tarkalleen ottaen tallenna kaikkia rekistereitä pinoon, mutta monimutkaisempia ohjelmointirakenteita löytyy luvusta 8. Koodia voimme siis käytää seuraavalla tavalla:

PUSHALL

(jatkuu)


Laskurit ja kiersiöt

Et varmaan ylläty, kun kerron, että assembly-kieli ei symbolisena konekielenä sisällä mitää omaa komentoa laskurin ja kiersiön luomiseen. Nämä rakenteet täytyy luoda itse, joten kerron nyt kuinka se tehdään.

Laskuri:

Vaikka assembly-kieli ei sisällä varsinaista lausetta laskurin luomiseen, on kuitenkin olemassa yksi hyvä komento, "loop", jota voi käyttää laskurin luomiseen. Seuraavassa on lyhyt esimerkki laskurin luomisesta.

        mov           cx, 65535
hyppy:  (koodia)
        loop	hyppy

Ensimmäisellä rivillä siirrämme rekisteriin CX(nk. laskurirekisteri) arvon 65535. Rekisterin CX arvo siis määrää, kuinka monta kertaa laskuri suoritetaan. Toisella rivillä on nimiö "hyppy" ja teksti "(koodia)", joka tarkoittaa, että siihen kohtaan voit laittaa omaa koodiasi, tai vaikka jättää sen tyhjäksi. Viimeisellä rivillä on komento "loop", jolle on annettu parametriksi nimiö "hyppy". "loop"-käsky toimii siten, että se pienentää rekisterin CX-arvoa yhdellä ja jos arvo on suurempi kuin nolla, se aiheuttaa hypyn osoitteeseen "hyppy". Jos luku taas on nolla tai pienempi, ohjelman suoritus jatkuu seuraavasta komennosta. Itse koodi siis tulee nimiön ja loop-käskyn väliin.

Kiersiö:

Toteutetaan edellinen laskuri kiersiönä:

        mov           cx, 65535
hyppy:  (koodia)
        dec           cx
        cmp           cx, 0
        ja            hyppy

Ensimmäisellä rivillä asetetaan jälleen rekisterin CX arvoksi 65535. Seuraavalla rivillä on jällen nimiö "hyppy" ja teksti "(koodia)" joka tarkoittaa, että voit laittaa omaa koodiasi tähän. Kolmannella rivillä on komento "dec", jolle on annettu parametriksi rekisteri CX. Komento "dec" pienentää kohteen arvoa yhdellä, tässä tapauksessa kohde siis on rekisteri CX. Neljännellä rivillä on komento "cmp"(CoMPare), joka vertailee annettuja kohteita ja asettaa lippuja lippurekisterissä sen mukaan. Vertailun tuloksiin pääsee käsiksi ehdollisilla hyppykäskyillä, jotka on esitelty Kuudennessa luvussa. Viimeisellä rivillä on yksi ehdollisista hyppykäskyistä. Se on "ja"(Jump if Above), joka suorittaa hypyn annettuun osoitteeseen, mikäli vertailusta saatiin tulokseksi, että ensimmäinen kohde(tässä tapauksessa CX) on ollut toista kohdetta(tässä tapauksessa nolla) suurempi, hypätään annettuun osoitteeseen(tässä tapauksessa "hyppy"), muuten ohjelman suoritus jatkuu seuraavasta käskystä. Eli rekisterin CX arvoa pienennetään yhdellä ja jos se on suurempi kuin nolla, hypätään nimiöön "hyppy".

Ensimmäinen assembly-ohjelma

Esittelen tässä nyt oppaan ensimmäisen toimivan assembly-ohjelman, jonka nimi on hello.asm ja se kääntyy NASM-kääntäjällä komennolla:

C:\> nasm -f bin -o hello.com hello.asm [enter]

Merkintä "C:\>" siis edustaa tietokoneen komentokehoitetta ja "[enter]" sitä, että sinun pitää painaa enter-näppäintä. Eli ensin parametrina on "-f" joka ilmoittaa missä formaatissa konekielinen tiedosto tuotetaan, tässä tapauksessa "bin", eli puhdas binääritiedosto eli COM-ohjelma. Sen jälkeen on parametri "-o", joka ilmoittaa minkä niminen käännetty ohjelma on, tässä tapauksessa ohjelman nimeksi tulee "hello.com". Viimeisenä parametrina on itse lähdetiedoston(missä kirjoitettu koodi on) nimi. Ohjelma ajetaan siis komennolla:

C:\> hello [enter]

Itse ohjelma on tämän näköinen(älä kirjoita väliviivastoja):

--------------------------------------------------------------------------

[org 100h]                      ; Ilmoitetaan kääntäjälle, että on kyse
                                ; DOSin COM-ohjelmasta.
        mov     si, viesti      ; Ilmoitetaan aliohjelmalle, mikä viesti
                                ; pitää tulostaa. Parametri siis välitetään
                                ; aliohjelmalle rekisterin SI kautta.
        call    tulosta         ; Kutsutaan aliohjelmaa tulosta.
        mov     ah, 4Ch         ; Siirretään rekisteriin AH 
                                ; arvo 4Ch (heksadesimaaliluku).
        int     21h             ; Kutsutaan keskeytystä 21h,
                                ; joka lopettaa ohjelman, jos rekisterissä
                                ; AH on arvo 4Ch

; -- Aliohjelmat -- ;

; Aliohjelma "laita". Laittaa yhden merkin näytölle

laita:  push    bx              ; Laitetaan pinoon rekisteri BX
        push    ax              ; ja rekisteri AX.
        mov     ah,0Eh          ; Asetetaan rekisteriin AH arvo Eh.   
        xor     bx,bx           ; Nollataan BX käyttämällä komentoa "XOR".
        int     10h             ; Kutsutaan keskeytystä 10h, joka tulostaa
                                ; näytölle rekisterissä AL olevan merkin,
                                ; jos rekisterissä AH on arvo Eh. Kiinnitä
                                ; huomiota, miten heksadesimaaliluku kir-
                                ; joitetaan muodossa 0Eh, jos lukuja on
                                ; vain yksi. Muutenhan esimerkiksi
                                ; luvusta Ah ei tiedä, onko se rekisteri
                                ; AH vai heksadesimaaliluku AH(10).
        pop     ax              ; Palautetaan rekistereiden AX ja BX
        pop     bx              ; arvot pinosta. Huomaa käänteinen jär-
                                ; jestys: "Minkä viimeisenä laitat, sen
                                ; ensimmäisenä saat pois.".
        ret                     ; Paluukäsky ret, palataan suorittamaan
                                ; aliohjelmaa kutsunutta koodia sen
                                ; seuraavasta käskystä. Tässä tapauksessa
                                ; nimiöstä "tulosta1".

; Aliohjelma "tulosta" suorittaa merkkijonon tulostuksen.

tulosta: push    si             ; Siirretään rekisterin SI 
        push    ax              ; ja rekisterin AX arvo pinoon.
        cld                     ; Asetetaan DF(Direction Flag), eli
                                ; suuntalippu nollaksi.
        jmp     tulosta2        ; hypätään nimiöön "tulosta2"
tulosta1:                       ; Nimiö "tulosta1"
        call    laita           ; Kutsutaan aliohjelmaa "laita", joka laittaa
                                ; yhden merkin näytölle.
tulosta2: lodsb                 ; Kopioidaan tavu osoitteesta DS:SI akkuun.
                                ; SI-rekisteri päivitetään kasvattamalla
                                ; sitä yhdellä.
        or      al,al           ; Suoritetaan OR-operaatio AL-rekisterissä.
        jne     tulosta1        ; Jos OR-operaation tulos oli erisuuri
                                ; kuin nolla, hypätään nimiöön "tulosta1".
        pop     ax              ; Palautetaan jälleen rekistereiden AX
        pop     si              ; ja SI arvot pinosta. Huomaa käänteinen
                                ; järjestys.
        ret                     ; Palataan suorittamaan edellistä koodia.

viesti:         db       'Hello, World!', 10, 0

; Yllä on nimiö viesti, sille siis varataan muistista joukko tavuja(db).
; Itse merkkijono on heittomerkkien välissä ja lopussa on tavut 10 ja 0.
; Kymmenen on rivinvaihto ja nolla on merkkijonon lopetusmerkki, josta
; aliohjelma "tulosta" tunnistaa, että merkkijono on loppunut.

--------------------------------------------------------------------------

Käskykanta

Johdanto

Tässä luvussa käydään lyhyesti lävitse 486-prosessorin yleisimmät käskyt. Olen jakanut ne kolmeen ryhmään, matemaattiset operaatiot, suorituksen- ja järjestelmän ohjaus.

Matemaattiset operaatiot

AAA
-------------------
Käyttö:	AAA
Kuvaus:	Muuntaa AL-rekisterin sisällön pakkaamattomaksi BCD-luvuksi.
		Jos AL on suurempi kuin 9, kasvatetaan AH-rekisterin arvoa
		yhdellä, AF ja CF liput asetetaan ykköseksi, AL-rekisteriin
		lisätään 6 ja AL-rekisterin neljä ylintä bittiä nollataan.

AAD
-------------------
Käyttö:	AAD
Kuvaus:	Muuntaa AX-rekisterissä olevan pakkaamattoman BCD-luvun
		binääriluvuksi.

AAM
-------------------
Käyttö:	AAM
Kuvaus:	Muuntaa AX-rekisterissä olevan binääriluvun pakkaamattomaksi 
		BCD-luvuksi. Ylempi osa	sijoitetaan AH- ja alempi AL-rekisteriin.

AAS
-------------------
Käyttö:	AAS
Kuvaus:	Muuntaa AL-rekisterin sisällön pakkaamattomaksi BCD-luvuksi
        Käytetään luvun muuntamiseksi vähennyslaskun suorituksen
        jälkeen.

ADD
-------------------
Käyttö:	ADD	kohde, lähde
Kuvaus:	Lisää lähteen kohteeseen. Tulos tallennetaan kohteeseen. Jos
        tulos on liian iso kohteeseen, asetetaan CF-ykköseksi.

ADC
-------------------
Käyttö:	ADC	kohde, lähde
Kuvaus:	Lisää lähteen kohteeseen. Tulos tallennetaan kohteeseen. Jos
        CF-lippu on ykkönen, kohdetta kasvatetaan yhdellä, jos luku
        on liian suuri mahtuakseen kohteeseen, CF-lippu asetetaan
        ykköseksi.

AND
-------------------
Käyttö:	AND	kohde, lähde
Kuvaus:	Suorittaa AND-operaation kohteen ja lähteen välillä ja tallentaa
        tuloksen kohteeseen. AND-operaatio on kuvattu luvussa 3.3 - Bitti-
        operaatiot.

BSF
-------------------
Käyttö:	BSF	kohde, lähde
Kuvaus:	Kirjoittaa kohteeseen arvon, joka ilmaisee lähteen ensimmäisen
        ykkösbitin aseman. Esim. Jos lähteen arvo on 00101110,
        asetetaan kohteen arvoksi 2(Ensimmäinen bitti on nolla, seuraava 1,
        jne.).

BSR
------------------
Käyttö:	BSR	kohde, lähde
Kuvaus:	Toimii samoin kuin BSF(yllä), mutta haku suoritetaan lopusta päin.

DAA
-------------------
Käyttö:	DAA
Kuvaus:	Muuntaa pakatun BCD-luvun rekisterissä AL.
        Käytetään luvun muuntamiseksi yhteenlaskun suorituksen
        jälkeen. Jos AL-rekisterin neljä alinta bittiä muodostavat suuremman
        luvun kuin 9, asetetaan AF-lippu ykköseksi ja AL-rekisteriin lisätään
        6. Muutoin AF-lippu nollataan. Jos neljä ylintä bittiä muodostavat
        suuremman luvun kuin 9 tai jos CF-lippu on ykkönen, lisätään AL-rekis-
        teriin 96 ja CF-lippu asetetaan ykköseksi. Muutoin CF-lippu nollataan.

DAS
-------------------
Käyttö:	DAS
Kuvaus:	Muuntaa pakatun BCD-luvun rekisterissä AL vähennyslaskun jälkeen.
        Jos AL-rekisterin neljä alinta bittiä muodostavat suuremman luvun
        kuin 9, tai jos AF-lippu on ykkönen, vähennetään
        AL-rekisteristä 6 ja AF-lippu asetetaan ykköseksi. Muussa tapauk-
        sessa AF-lippu nollataan. Jos AL-rekisterin neljä ylintä bittiä
        muodostavat suuremman luvun kuin 9 tai jos CF-lippu on ykkönen,
        vähennetään AL-rekisteristä 60h, eli kymmenjärjestelmässä 96 ja
        CF-lippu asetetaan ykköseksi. Muussa tapauksessa CF-lippu asetetaan
        nollaksi

DEC
-------------------
Käyttö:	DEC 	kohde
Kuvaus:	Pienentää kohdetta yhdellä.

DIV
-------------------
Käyttö:	DIV	lähde
Kuvaus:	Jakaa lähteellä rekisterin AX sisällön. Molempien lukujen pitää
        olla positiivisia kokonaislukuja. Jos lähteen koko on:
        Tavu:            Jaetaan rekisterin AX sisältö sillä, osamäärä sijoitetaan
                         rekisteriin AL ja jakojäännös rekisteriin AH.
        Sana:            Rekistereiden AX(vähiten merkitsevä sana) ja DX(eniten
                         merkitsevä sana) muodostava 32-bittinen luku jaetaan
                         lähteellä ja osamäärä sijoitetaan rekisteriin AX ja
                         jakojäännös rekisteriin DX.
        Kaksoissana:     Rekistereiden EAX(vähiten merkitsevä sana)
                         ja EDX(eniten merkitsevä sana) muodostama
                         64-bittinen luku jaetaan lähteellä ja osamäärä
                         sijoitetaan rekisteriin EAX(Extended AX)
                         ja jakojäännös rekisteriin EDX(Extended DX)

INC
-------------------
Käyttö:	INC	kohde
Kuvaus:	Kasvattaa kohdetta yhdellä.

MUL
-------------------
Käyttö:	MUL	lähde
Kuvaus:	Kertoo rekisterin AX sisällön lähteellä. Toimii kuten DIV, mutta
        suorittaa kertolaskun. Toinen ero on se, että jos AH, DH tai EDX
        (riippuen moniko bittinen lähde on) on operaation jälkeen nolla,
        asetetaan CF- ja OF-lippu nollaksi. Muussa tapauksessa ne asetetaan
        ykkösiksi.

NEG
-------------------
Käyttö:	NEG	kohde
Kuvaus:	Vaihtaa kohteen etumerkin, tulos tallennetaan kohteeseen. Mikäli
kohteen arvo on nolla, asetetaan CF-lippu nollaksi, muussa
        tapauksessa se asetetaan ykköseksi.

NOT
-------------------
Käyttö:	NOT	kohde, lähde
Kuvaus:	Suorittaa NOT-operaation kohteen ja lähteen välillä. Tulos tallenne-
        taan kohteeseen. NOT-operaatio on kuvattu luvussa 3.3 - Bittioperaa-
        tiot.

OR
-------------------
Käyttö:	OR	kohde, lähde
Kuvaus:	Suorittaa OR-operaation kohteen ja lähteen välillä ja tallentaa
        tuloksen kohteeseen. OR-operaatio on kuvattu luvussa 3.3 - Bitti-
        operaatiot.

RCL
-------------------
Käyttö:	RCL	kohde, bittien määrä
Kuvaus:	Suorittaa kohteen biteille kierto-operaation vasemmalle, joka on 
        kuvattu luvussa 3.3 - Bittioperaatiot.

RCR
-------------------
Käyttö:	RCR	kohde, bittien määrä
Kuvaus:	Toimii samoin kuin RCL(yllä), mutta operaatio suoritetaan oikealle.

SUB
-------------------
Käyttö:	SUB	kohde, lähde
Kuvaus:	Vähentää lähteen kohteesta ja tallentaa tuloksen lähteeseen.

SBB
-------------------
Käyttö:	SBB	kohde, lähde
Kuvaus:	Vähentää kohteen lähteestä ja tallentaa tuloksen kohteeseen. Jos CF
        lippu on asetettu ykköseksi ennen käskyn suoritusta, vähennetään koh-
        teesta 1.

SHL
-------------------
Käyttö:	SHL	kohde, bittien määrä
Kuvaus:	Siirtää kohteen bittejä bittien määrän verran vasemmalle. 
        CF-lippu ilmaisee ulos "putoavan" bitin tilan(1/0). Jokainen 
        siirto vasemmalle vastaa luvun kertomista kahdella.

SHR
-------------------
Käyttö:	SHR	kohde, bittien määrä
Kuvaus:	Toimii samoin kuin SHL(yllä), mutta siirtää bittejä oikealle.
        Tällöin jokainen siirto oikealle vastaa luvu jakamista kahdella.

XOR
-------------------
Käyttö:	XOR	kohde, lähde
Kuvaus:	Suorittaa XOR-operaation kohteen ja lähteen välillä. Tulos tallennetaan
        kohteeseen. XOR-operaatio on kuvattu luvussa 3.3 - Bittioperaatiot.

Suorituksen ohjaus

Kaikkien hyppykäskyjen kohdassa "nimiö" voi olla myös muistiosoite.

CALL
-------------------
Käyttö:	CALL 	aliohjelmannimi
Kuvaus:	Kutsuu aliohjelmaa nimeltä aliohjelmannimi

CMP
-------------------
Käyttö:	CMP	lähde1, lähde2
Kuvaus:	Vertaa lähteitä ja asettaa lippuja lippurekisterissä sen mukaan.
        Tämän käskyn jälkeen voi suoritusta jatkaa ehdollisella hyppykäskyllä,
        jotka kaikkia alkavat J-kirjaimella ja joiden kuvaus löytyy alta.

CLC
-------------------
Käyttö:	CLC
Kuvaus:	Asettaa CF-lipun nollaksi.

CMC
-------------------
Käyttö:	CMC
Kuvaus:	Mikäli CF-lippu on nolla, se asetetaan ykköseksi ja
        mikäli se on ykkönen, se asetetaan nollaksi.

CLD
-------------------
Käyttö:	CLD
Kuvaus:	Asettaa DF-lipun nollaksi.

CLI
-------------------
Käyttö:	CLI
Kuvaus:	Asettaa IF-lipun nollaksi.

INT
-------------------
Käyttö:	INT	numero
Kuvaus:	Kutsuu keskeytystä. Kutsuttava keskeytys välitetään numerona.

IRET
-------------------
Käyttö:	IRET
Kuvaus:	Palaa suorittaamaan ohjelmaa, joka kutsui keskeytysrutiinia.

JA
-------------------
Käyttö:	JA	nimiö
Kuvaus:	Hyppää nimiöön "nimiö", mikäli CMP-komennolla vertailluista arvoista
        ensimmäinen oli suurempi tai jos CF- ja ZF-lippu on nolla.

JAE
-------------------
Käyttö:	JAE 	nimiö
Kuvaus:	Hyppää nimiöön "nimiö", mikäli CMP-komennolla vertailluista arvoista
        ensimmäinen oli yhtä suuri tai suurempi tai jos CF-lippu on nolla.

JB
-------------------
Käyttö:	JB	nimiö
Kuvaus:	Hyppää nimiöön "nimiö", mikäli CMP-komennolla vertailluista arvoista
        ensimmäinen oli pienempi tai jos CF-lippu on yksi.

JBE
-------------------
Käyttö:	JBE	nimiö
Kuvaus:	Hyppää nimiöön "nimiö", mikäli CMP-komennolla vertailluista arvoista
        ensimmäinen oli yhtäsuuri tai pienempi tai jompikumpi, CF- tai ZF-
        lippu on yksi.

JCXZ
-------------------
Käyttö:	JCXZ	nimiö
Kuvaus:	Hyppää nimiöön "nimiö", jos CX-rekisterin arvo on nolla.

JECXZ
-------------------
Käyttö:	JECXZ	nimiö
Kuvaus:	Hyppää nimiöön "nimiö", jos ECX-rekisterin arvo on nolla.

JE
-------------------
Käyttö:	JE	nimiö
Kuvaus:	Hyppää nimiöön "nimiö", mikäli CMP-komennolla vertaillut arvot olivat
        yhtäsuuret tai jos ZF-lippu on nolla.

JMP
-------------------
Käyttö:	JMP	nimiö
Kuvaus:	Ehdoton hyppy. Hyppää nimiöön "nimiö".

JNE
-------------------
Käyttö:	JNE	nimiö
Kuvaus:	Hyppää nimiöön "nimiö", mikäli CMP-komennolla vertaillut arvot olivat 
        erisuuret tai ZF-lippu on nolla.

JNO
-------------------
Käyttö:	JNO	nimiö
Kuvaus:	Hyppää nimiöön "nimiö", mikäli OF-lippu on asetettu nollaksi.

JNP
-------------------
Käyttö: JNP     nimiö
Kuvaus:	Hyppää nimiöön "nimiö", jos PF-lippu on nolla.

JNS
-------------------
Käyttö:	JNS	nimiö
Kuvaus:	Hyppää nimiöön "nimiö", jos SF-lippu on nolla.

LAHF
-------------------
Käyttö:	LAHF
Kuvaus:	Kopioi lippurekisterin alemman puolen AH-rekisteriin.

LOOP
-------------------
Käyttö:	LOOP	nimiö
Kuvaus:	Toiminta kuvattu luvussa 5.5 - Laskurit ja kiersiöt.

POPF
-------------------
Käyttö:	POPF
Kuvaus:	Poimii pinosta lippurekisterin sisällön.

PUSHF
-------------------
Käyttö:	PUSHF
Kuvaus: Kopioi lippurekisterin sisällön pinon päälle.

RET
-------------------
Käyttö:	RET
Kuvaus:	Aliohjelmasta paluu. Palaa suorittamaan CALL-komennon jälkeen
        tulevaa koodia.

SAHF
-------------------
Käyttö:	SAHF
Kuvaus:	Kopioi AH-rekisterin sisällön lippurekisterin alempaan tavuun.

STC
-------------------
Käyttö:	STC
Kuvaus:	Asettaa CF-lipun ykköseksi.

STD
-------------------
Käyttö:	STD
Kuvaus:	Asettaa DF-lipun ykköseksi.

STI
-------------------
Käyttö:	STI
Kuvaus:	Asettaa IF-lipun ykköseksi. Komento sallii keskeysten käytön.

TEST
-------------------
Käyttö:	TEST	lähde, lähde
Kuvaus:	Suorittaa lähteiden välillä AND-operaation, muttei vaikuta lähteiden
        sisältöön. Operaatio vaikuttaa vaikuttaa lippurekisteriin.

Järjestelmän ohjaus

HLT
-------------------
Käyttö:	HLT
Kuvaus:	Pysäyttää suorittimen toiminnan.

LGDT
-------------------
Käyttö:	LGDT	lähde
Kuvaus:	Kopioi lähteen sisällön rekisteriin GDTR.

LIDT
-------------------
Käyttö:	LIDT	lähde
Kuvaus:	Kopioi lähteen sisällön rekisteriin IDTR.

LLDT
-------------------
Käyttö:	LLDT	lähde
Kuvaus:	Kopioi lähteen sisällön rekisteriin LDTR.

LMSW
-------------------
Käyttö:	LMSW	lähde
Kuvaus:	Kopioi lähteen sisällön konetilasanan paikalle.

LTR
-------------------
Käyttö:	LTR	lähde
Kuvaus:	Kopioi lähteen sisällön rekisteriin TR.

MOV
-------------------
Käyttö:	MOV	kohde, lähde
Kuvaus:	Kopioi lähteen kohteeseen. Käsky ei siis siirrä(MOV = MOVe)
        lähdettä, vaan lähteen sisältö säilyy ennallaan.

NOP
-------------------
Käyttö:	NOP
Kuvaus:	Tätä käskyä on käytetty erittäin paljon koodattaessa vakaita
        ja nopeita käyttöjärjestelmiä kuten Windowsia ja DOSia.
        Se ei tee mitään. Kätevä esimerkiksi silloin kun pitää odottaa
        lyhyt hetki.

SGDT
-------------------
Käyttö:	SGDT	osoite
Kuvaus:	Kopioi rekisterin GDTR sisällön kohteen 48 ensimmäiseen bittiin.

SIDT
-------------------
Käyttö:	SIDT	osoite
Kuvaus:	Kopioi rekisterin IDTR sisällön kohteen 48 ensimmäiseen bittiin.

SLDT
-------------------
Käyttö:	SLDT	kohde
Kuvaus:	Kopioi rekisterin LDTR sisällön kohteeseen.

SMSW
-------------------
Käyttö:	SMSW	kohde
Kuvaus:	Kopioi konetilasanan kohteeseen.

STR
-------------------
Käyttö:	STR	kohde
Kuvaus:	Kopioi rekisterin TR sisällön kohteeseen.

WAIT
-------------------
Käyttö:	WAIT
Kuvaus:	Komento saa suorittimen odottamaan, kunnes matematiikkasuoritin
		on saanut työnsä loppuun.


Muuta

Loppusanat

Toivottavasti oppaasta on ollut jonkinlaista apua assembly-kielen opettelussa. Opas ei vielä ole kasvanut täysiin mittoihinsa ja uusissa versiossa käydään lävitse mm. suojattu tila ja matematiikkasuorittimen käyttö. Uusia komentoja esitellään ja esimerkkiohjelmien kirjo kasvaa nykyisestä yhdestä ylöspäin. Tämän oppaan tarkoitus ei ollut(vielä) opettaa täydellistä assembly-ohjelmointia, vaan saattaa sinut alkuun ja opettaa assembly-kielen perusteen ja ohjelmoinnin perusmallit. Luonnollisesti oppaassa on virheitä, koska se ei vielä ole täysin valmis.

Apua?

Tällä hetkellä kirjoitan itse omaa käyttöjärjestelmää. Se tulee olemaan suojatun tilan moniajokäyttöjärjestelmä. Jos olet Guru tai Hakkeri ja tiedät suojatun tilan ohjelmoinnista, keskeytysten ohjelmoinnista ja moniajosta kaiken (ja sinua huvittaa auttaa minunlaistani tumpeloa) ota ihmeessä yhteyttä! Sähköpostiosoitteeni oli jo oppaan alussa, mutta tässä se on uudelleen:

maniance@daug.net

Henkilökohtaiset työkalut