Demo 6 - juonipaljastuksia

Spoiler alert! Tämän dokumentin lukeminen saattaa vähentää omakohtaisen löytämisen iloa Käyttöjärjestelmät -kurssin demossa 6. Sinua on varoitettu!

Contents

Spoiler alert! Tämän dokumentin lukeminen saattaa vähentää omakohtaisen löytämisen iloa Käyttöjärjestelmät -kurssin demossa 6. Sinua on varoitettu (kahdesti)!

Miten pinokehys luodaan?

Demo 5:ssä nähty juttu kiteytyy uuden aliohjelman tekemisen osalta siihen, minkä voi aina laittaa aliohjelman koodiin heti ensimmäiseksi. Eli tämän seuraavan pätkän kirjoittamisesta pitäisi olla helppo aloittaa minkä tahansa (AMD64 Sysv ABIn mukaisen) aliohjelman koodaaminen:

useimmiten:
        # Alkutoimet:
        pushq   %rbp                    ;# Aiempi kanta pinon päälle
        movq    %rsp, %rbp              ;# Kiinnitetään uusi kanta
        subq    $2064, %rsp             ;# Varataan tarvittava tila

        movq    %rdi, -8(%rbp)          ;# Parametri talteen
        movq    %rsi, -16(%rbp)         ;# Parametri talteen

        # Aliohjelman varsinainen toiminnallisuus tulee tähän

        # Lopputoimet:
        movq    $0, %rax                ;# Paluuarvon asetus
        leaveq                          ;# Puretaan kehys
        ret                             ;# Paluu kutsujan koodiin

Jos jouduit katsomaan tämän juonipaljastuksen, se on merkki siitä, että olisi syytä kerrata aliohjelma-aktivaation tapahtumista ja sisäistää sen vaiheet!

Mitä tarkoittaa "pysy kartalla pinokehyksesi rakenteesta"

"Pysy kartalla pinokehyksesi rakenteesta" tarkoittaa, että selvität itsellesi ja koodisi lukijalle, luultavasti kommentin muodossa, miten pinokehyksessäsi oleva data sijoittuu suhteessa kehyksen kantaosoitteeseen:

.text
.global useimmiten
# Aliohjelma, jota voi kutsua C-kielestä, kuten sen otsikkorivi olisi:
# "int useimmiten(FILE *fp, int eofmarker);"
#
# Tähdätään mahdollisimman selkeään koodiin. Päätetään käyttää
# 64-bittisiä lukuja kaikkeen, vaikka C-rajapinnassa on 32-bittiset!
# Ei ole tässä ongelma. Jätetään myös virhetilanteiden tarkistukset
# ja muu sinänsä tärkeä nyt huomioimatta.
#
# Sommitellaan pinokehys (muuten on vaikea pysyä kartalla):
#
# RBP+8    paluuosoite (kutsussa laitettu)
# RBP+0    edellisen kehyksen kanta (täytyy hoitaa täällä)
# RBP-8    ensimmäinen parametri eli "FILE* fp", joka saadaan RDI:ssä
# RBP-16   toinen parametri eli "int eofmarker", joka saadaan RSI:ssä
# RBP-24   taulukko[255] merkin 255 esiintymismäärä lasketaan tähän
# RBP-32   taulukko[254] merkin 254 esiintymismäärä lasketaan tähän
# ...
# RBP-2064 taulukko[0] (laskin kait oikein.. selviää debugatessa jos en)

useimmiten:
        ...

Jos tämä oli yllättävää, se tarkoittaa, että kannattaa kerrata Ohjelmointi 1 -kurssin perusperiaatteita koodin dokumentoinnista ja toiminnallisuuden miettimisestä ennen kuin kajoaa näppikseen toteutuksen tekemiseksi.

Jos et ymmärrä, mitä tarkoittaa "kehyksen kantaosoite" tai mikä on kommentissa oleva RBP tai miksi RBP:n perässä on +8, 0, -8, -16 jne., on syytä kerrata kurssin alkupuoli ja ajatella demo 5 tarkemmin läpi.

Miten taulukon nollaaminen tehdään?

Suoraan mallivastauksesta:

# Nollataan taulukko.
        movq    $0, %rax                ;# käytetään vaikka RAXia indeksinä
nollaus:
        movq    $0, -2064(%rbp,%rax,8)  ;# nolla taulukkoon indeksin kohdalle
        addq    $1, %rax                ;# indeksi++
        cmpq    $256, %rax              ;# Onko indeksi...
        jl      nollaus                 ;# ... pienempää (less) kuin 256? Ni uudestaan.

Jos jouduit lukemaan tämän juonipaljastuksen, se tarkoittaa (mahdollisesti), että et ole vielä aivan sisäistänyt konekielen toimintaa siinä määrin, että pystyisit muokkaamaan ja yhdistelemään erillisinä annettuja käskyjä luovasti uuden toiminnallisuuden toteuttamiseksi. Tai sitten et ole onnistunut palastelemaan ongelmaa riittävän pieniksi osaongelmiksi (mikä onkin aloittelijalle usein vaikeaa). Vain riittävän pieni osaongelma kerrallaan on mahdollista toteuttaa. Avainkysymys kaikessa ohjelmoinnissa on hallittavan kokoisten osien löytäminen kokonaisuudesta.

Mitä käskyjä tarvitaan

Mallivastauksessa on jossakin järjestyksessä seuraavien kaltaisia käskyjä, eikä mitään muita. Se ei tarkoita, etteikö muitakin käskyjä voisi käyttää hyödyksi. Mutta täsmälleen seuraavan mallisilla käskyillä ilmeisesti on mahdollista pärjätä. Tietenkin käskyjen yksityiskohdat, kuten rekisterien nimet ja vakioluvut ja luvut muistiosoitteiden laskemisessa riippuvat itse tekemistäsi ratkaisuista!

Siirtokäskyt (tarkkaan ottaen kopiointikäskyt):

movq    %rsp, %rbp              ;# Siirrä (==kopioi) luku rekisteristä toiseen
movq    $0, %rax                ;# Siirrä luku rekisteriin
movq    %rdi, -8(%rbp)          ;# Siirrä rekisteristä muistiin
movq    -8(%rbp), %rdi          ;# Siirrä muistista rekisteriin
movq    $0, -2064(%rbp,%rax,8)  ;# Siirrä luku muistiin, indeksoituun taulukkoon
movq    -2064(%rbp,%rcx,8),%rdx ;# Siirrä muistista indeksin perusteella rekisteriin

Aritmetiikkakäskyt:

subq    $2064, %rsp             ;# Vähennä luku rekisterin arvosta
addq    $1, %rax                ;# Lisää luku rekisterin arvoon
addq    $1, -2064(%rbp,%rax,8)  ;# Lisää luku muistiin indeksin perusteella

Vertailukäskyt (GNUn syntaksissa "jälkimmäistä operandia verrataan ensimmäiseen"):

cmpq    $256, %rax              ;# Vertaa rekisterin arvoa vakiolukuun
cmpq    %rax, -16(%rbp)         ;# Vertaa muistissa olevaa lukua rekisterin arvoon
cmpq    -2064(%rbp,%rcx,8),%rdx ;# Vertaa rekisterin arvoa muistissa olevaan

Hyppykäskyt:

jl      hypyn_kohde             ;# Hyppää, jos vertailun tulos "<"  (l=less)
je      hypyn_kohde             ;# Hyppää, jos vertailun tulos "==" (e=equal)
jg      hypyn_kohde             ;# Hyppää, jos vertailun tulos ">"  (g=greater)
jmp     hypyn_kohde             ;# Hyppää aina

Muut käskyt:

pushq   %rbp                    ;# Rekisterin sisältö pinoon
call    aliohjelman_osoite      ;# Kutsu aliohjelmaa
leaveq                          ;# Pura kehys (ikään kuin "mov %rbp,%rsp; pop %rbp")
ret                             ;# Palaa aliohjelmasta

Jos jouduit lukemaan tämän juonipaljastuksen, kurssin alkupuolella olleista assembler-esimerkeistä ei ole vielä hahmottunut, kuinka erilaisia muistinosoitustapoja ja käskyjä voi yhdistellä toisiinsa.

Miten löydän lisätietoa Internetistä?

Hieman hämmentäviä avunhuutoja on tullut muodossa "kehotuksista ja yrityksistäni huolimatta en löydä Internetistä lisätietoa". Tätä on hieman vaikea uskoa, kun kokeilee vaikkapa seuraavaa Google-hakua:

Löytyy aika paljon aika hyvää kamaa. Luettavaa tosin on aika paljon, eikä se ole suomenkielistä. Lisähaasteen tuo se, että assembler-syntakseja on pari erilaista. Hakusana "x86-64" näyttäisi tuovan GNU-syntaksilla tehtyjä dokkareita. Silti täytyy muistaa varmistaa jokaisen löydöksen kohdalta, mitä työkaluja siinä luvataan käyttää. Tämän kurssin puitteissa lukemisen voi lopettaa heti, jos sanotaan, että esimerkeissä tullaan käyttämään Intel-syntaksia tai esim. NASM-kääntäjää. Eivät toimisi GNUn työkaluissa.

Jos tiedonhaku ei ole tuottanut tulosta ilman tätä juonipaljastusta, suosittelen lämpimästi kokeilemaan useammin hakusanoja "AIHEPIIRI introduction", jossa AIHEPIIRI on aihe, josta haluat lisätietoa. Hakusanaa voi joutua tarkentamaan, kunnes juuri sopiva sana tai sanayhdistelmä löytyy. "Introduction" tai "tutorial" ovat aika hyviä ensimmäisiin hakuihin. Myös "for beginners" tai "example" voivat toimia joskus. Ja aina on hieman 'tuoksuteltava' löytyneen materiaalin alkua tietääkseen, onko se lukemisen väärti vai ohitettavaa osastoa.

Eikö vieläkään onnistu?

Kysy apuja kaverilta, joka on pidemmällä.

Jos jouduit lukemaan tämän juonipaljastuksen, suosittelen esimerkiksi hengaamaan aiempaa enemmän ainejärjestötilassa, jossa on useita kurssin aiempina vuosina suorittaneita tyyppejä. Heillä on vielä tuoreessa muistissa, mikä asiassa oli vaikeinta ensimmäisellä kerralla. Opettajilta se meinaa joskus vähän unohtua.

Ei edelleenkään onnistu?

Kysy apuja opettajalta, jos kaveritkaan ei osanneet auttaa (tai jos opastivat huonosti:)).

Jos jouduit lukemaan tämän juonipaljastuksen, kannattaa kuin salamaniskusta oppia, että tyhmiä kysymyksiä ei ole, paitsi ehkä se kysymys, joka jätetään kysymättä, vaikka olisi pitänyt!