Ultrakevyttä Assembler-ohjelmointia
ITKA203 Käyttöjärjestelmät -kurssin Demo 4b keväällä 2021 ja 2022.
Tehdään itse ultrapieni aliohjelma konekielellä.
Paavo Nieminen, paavo.j.nieminen@jyu.fi
Tämä tehtävä syntyi keväällä 2021 vastaukseksi aiemmissa
kurssipalautteissa olleisiin toiveisiin: Lisää demoja, ja etenkin
konekielestä joku kevyempi välitehtävä kuin syvempään päätyyn heittävä
demo 6. No tässä on ensimmäinen yritys tarjota moinen.
Päätavoite:
- Tulee ultrakevyt ensiaskel ohjelmointiin symbolisella konekielellä
eli assemblerilla.
- Eli pehmeä lasku edellisistä seuraavia demoja kohti.
Edelleen käytetään nykyaikaisia työkaluja, joita kurssilla on tähän
asti käytetty. Tehdään AMD64 -prosessorin (ts. "x86-64") konekieltä
GNU:n assemblersyntaksilla, jota on tähänkin asti nähty debuggerin
tulosteissa. Ohjelma käännetään ja linkitetään komentoriviltä GNU:n
työkaluilla Jyväskylän yliopiston suorakäyttökoneella (jalava tai
halava), jossa sitä myös kokeillaan. Jotta työskentely olisi
näppärää, screen-ohjelman, kuoren ja tekstieditorin käyttö on hyvä
olla hanskassa.
Aiemmin opittua soveltaen valmistele hakemisto tätä demoa varten ja
hae pieni koodipaketti seuraavin komennoin:
wget https://gitlab.jyu.fi/itka203-kurssimateriaali/itka203-kurssimateriaali-avoin/-/raw/master/2015/demot/mallikoodia/d04b/d04b_paketti.zip
unzip d04b_paketti.zip
Tutki paketista avautunutta runkoa aiemmin opituilla keinoilla
(mm. komennot ls, less, tekstieditori). Mukana on Makefile,
joka automatisoi käytännössä kaiken paitsi koodin kirjoittamisen
vastauksena palautettavaan tiedostoon. Seuraava komento kääntää,
linkittää ja ajaa kokonaan C:llä tehdyn mallikoodin:
make esimerkki
Seuraava komento kääntää, linkittää ja ajaa version, jossa yksi
aliohjelmista on toteutettu suoraan konekielellä. Voit todeta, että se
ei vielä tee toimenpidettä, joka sen pitäisi. Sen näkee siitä, että
tuloste on erilainen kuin mitä make esimerkki tulostaa:
make oma
Kyseinen kohde oma on määritelty Makefilessä ensimmäisenä, joten
se on oletus ja voit sen sijaan komentaa pelkästään:
make
Kokeile vaikka, niin se on tässä nyt sama asia kuin make oma.
Tehtäväsi on muokata konekielisen aliohjelman tynkä asmvaihtaja.s
sellaiseksi, että ohjelma toimii sen kanssa täysin samoin kuin
malliesimerkki. Tiedät olevasi perillä, kun komennot make
esimerkki ja make oma antavat täsmälleen saman tulosteen.
Täsmennyksiä:
- Mitään muita tiedostoja ei ole tarpeen muuttaa palautustehtävän
tekemiseksi kuin konekielistä tynkää.
- Asian ymmärtämiseksi on syytä tutkia, miten C-malli toimii
- Muista kääntää ja kokeilla aina pienten muutosten jälkeen, ennen
kuin koodi menee hallitsemattomaan tilaan liian monen muutoksen
kautta!
Esimerkkikoodi ja sen Makefile on tehty tukemaan ymmärrystä tähän asti
käsitellyistä asioista.
Tällaista on tavoiteltu:
- C-koodi on suomeksi kirjoitettu ja kommentoitu, jotta sen
ymmärtäisi. (Vuonna 2021 väsätty versio on nopeasti ja väsyneenä
tehty, mistä voi tulla epäselkeyttä yrityksestä huolimatta;
korjataan, jos/kun ilmenee jotain dramaattista ymmärtämisen estettä!
Tähän asti ei ole havaittu isoja ongelmia (tilanne 29.4.2022))
- Kaikki muuttujat ja data ovat int64_t -tyyppisiä eli
64-bittisiä kokonaislukuja.
- Niinpä kaiken pitäisi näyttää mahdollisimman nätisti ja
yksinkertaisesti 64-bittisten rekisterien (RAX, RBX, RCX, ...)
kokoisilta möhkäleiltä, jotka ovat muistissa 8:lla jaollisissa
osoitteissa. Mist sen näkee? Niiden osoitteet heksana ilmoitettuna
päättyvät heksanumeroon 8 tai 0.
- Muistiosoitteet ja kokonaislukumuuttujat ovat saman kokoisia, mikä
helpottanee muistiosoitteiden ajattelemista "ihan tavallisina
lukuina, jotka kertovat data-alkion ensimmäisen tavun
sijaintipaikan"
- Esimerkki demonstroi sitä, että .h -otsikkotiedosto ei ole sama
asia kuin otsikon mukaisen toiminnallisuuden toteuttava aliohjelma.
- Esimerkki demonstroi ohjelman koostamista erikseen käännetyistä
objektitiedostoista.
- Esimerkissä on käytelty aiemmista C-demoista tuttuja asioita, joten
jospa tulisi uuden ohella vahvistusta ja kertausta edellisiin.
- Noin 130 rivin mittainen ja muutamasta tiedostosta koostuva C-koodi
toivottavasti on ymmärrettävissä kokonaan, joten se olisi sopiva
väliaskel tähän kohtaan oppimisen kaarta.
- Itse palautustehtävä on aivan minimaalinen, mutta siitä on
mahdollista oppia jo aika paljon asioita. Mukana on väistämättä
epäsuoraa muistin käyttöä osoitteen kautta, rekisterien käyttämistä,
suoritusjärjestystä, GNU AT&T -syntaksin kirjoittamista, ABIn
ymmärtämistä ...
- Sovellus eli "arvontasimulaatio" toivottavasti on hauska ja jättää
mielenkiintoisen muistijäljen siitä, että pienillä detaljeilla on
yllättävän paljon vaikutusta. Ei ainoastaan konekielen ja bittien
tasolla vaan ihan algoritmivalinnoissakin.
Nämä ovat oletuksia. On mielenkiintoista kuulla palautetta kevään 2022
opiskelijoilta, kun demo on vasta toista vuotta käytössä!
Tehtävä on yksinkertainen, mutta vasta sitten, kun palaset yhdistyvät
kohdalleen oppimisen ja kertaamisen kautta.
Mallivastauksen näkeminen pilaisi sen aika totaalisesti, samoin kuin
liian tarkat ohjeet.
Näin ollen on syytä antaa enemmän tai vähemmän lennokkaita ja
tarkoituksellakin yleistasoisia vinkkejä:
- Tässä varmaankin pitää yhdistellä muutama tähän asti erillisinä
luettu tiedonmurunen toisiinsa.
- C-kielisen esimerkin ymmärtäminen olisi hyvä lähtökohta. Eli pääsee
kertaamaan aiempaa samalla.
- Olisi hyvä ymmärtää C:n osoittimen ja tietokoneen muistiosoitteen
yhteys, koska nyt suoraan konekielellä pitää toteuttaa jotakin
osoittimien päässä oleviin arvoihin.
- Assembler-syntaksi muistin ja rekisterien käyttämiseen pitää olla
tiedossa, koska jotenkinhan näillä käytettävillä palikoilla tässä
pitää jonglöörata. Luentomonisteessa on kattavat esimerkit, joista
syntaksin periaate ja kummallisuudet pitäisi olla
selvitettävissä. Se vaatii omaa ajattelua ja soveltamista
esimerkkien pohjalta! Tässä tehtävässä selviää todellä pienellä
määrällä keskenään samankaltaisia käskyjä.
- Debuggeri auttaa oppimaan ja hahmottamaan käytännön
toiminnallisuuksia sen lisäksi että selvittämään virheitä, joita
tosi helposti tulee konekielellä (ja C-kielellä) ohjelmoidessa. Eli
käyttele debuggeria vaan - on suositeltavaa olla utelias ja lisäksi
näppärä selvitystyökalujen kanssa!
- Muista kokeilla kääntää ja suorittaa ohjelmaa usein, niin
syntaksivirheitä ei pääse kasautumaan.
- Kommentoi assembler-koodisi tarvittaessa vaikka rivi riviltä! Muuten
on itsellekin tosi vaikea pysyä kärryillä, mitä koodi tekee, ja
muille lukijoille se voi olla aivan mahdotonta.
- Huomaa, että tässä demossa ei vielä tarvita kokonaista
"pinokehystä", jota käsitellään tarkemmin seuraavassa demossa ja
joka pitää sitten olla mukana viimeisessä demossa. Tässä ei vielä
tarvita, eikä kannata turhan päiten tehdäkään, koska toteutettava
aliohjelma ei kutsu muita aliohjelmia vaan se on aina kutsupinon
päällimmäisenä silloin, kun se ylipäätään tulee kutsutuksi.
Tämä asia pitää opetella alustavasti, vaikka se onkin vasta seuraavan
demon pääteema:
Miten parametrit välittyvät, kun C-kielinen ohjelma kutsuu
konekieliohjelmaasi?
Tämän määrää ABI-dokumentaatio. Pilaisi oppimisen kertoa vastaus
suoraan tässä tehtäväohjeessa.
Virallinen ABI löytyy esimerkiksi osoitteesta
http://refspecs.linuxbase.org/elf/x86_64-SysV-psABI.pdf ja sitäkin
kannattaa silmäillä yhtenä esimerkkinä reaalimaailman
dokumentaatiosta.
Aliohjelman (eli C-terminologialla funktion) kutsuminen löytyy
dokumentista aliotsikon "Function calling sequence" alta.
Luentomonisteessa on kiteytetty suomen kielellä tähän tarpeeseen
olennaisimmat kohdat poislukien seuraava:
- ABI lupaa, että rekisterit RBX, R12, R13, R14 ja R15 ovat
aliohjelmakutsun jälkeen samat kuin ennen sitä. Kutsuja voi
pitää niissä jotakin tarvitsemaansa sisältöä. Sinunkin oman
aliohjelmasi pitää toteuttaa tätä ABI-lupausta / sopimusta! Ota
tämä huomioon. Muille rekistereille ABIssa ei ole samanlaista
sääntöä. Tämä tieto on esimerkiksi ABI-dokumentin
rekisteritaulukossa sarakkeessa "Callee-saved" yes/no.
Parametrien ja paluuarvon välityksestä sekä pinokehyksestä tulee
lisää seuraavassa demossa. Tässä demossa ei tosiaan tarvita vielä
muuta tietoa kuin tuo, missä parametrit tulevat sisään.
Miksi?
- Otsikkonsa mukaisesti aliohjelma ei tässä palauta mitään,
koska paluuarvon tyypiksi on kirjoitettu C:llä void.
- Vaikutus ulkopuoliseen maailmaan on tarkoitus tehdä suoraan
muistiin, mistä syystä molemmat parametrit ovat tyypiltään
muistiosoitteita eli int64_t*.
- Tuotettavan aliohjelman ei tarvitse kutsua muita aliohjelmia,
joten sen ei tarvitse luoda omaa kehystä. Tähän liittyy
ABI-dokumentissa selostettu "Red zone".
- Tuotettavan aliohjelman tehtävä on niin pienimuotoinen, että sen
tarvitsemat väliaikaiset tiedot mahtuvat rekistereihin. Pystyy
valitsemaan sellaiset, jotka eivät ole edes "callee saved".
Tästä demosta palautetaan tasan yksi tiedosto nimeltään
asmvaihtaja.s jossa on tekemäsi assembler-aliohjelma. Sen pitää
toimia yllä olevan ohjeen mukaisesti. Eli tehtäväpaketin Makefile
kääntää kokonaisuuden, ja simulaattori tulostaa täysin samoin
komennolla make oma kuin komennolla make esimerkki.