TIES448 luento 12
Luettavaa
Viralliset dokumentaatiot (laajoja, pitkälti TMI):
- AMD64 Architecture Programmer’s Manual
Konekieliset esimerkkikoodit
Konekielten yleispiirteitä
- erittäin yksinkertainen rakenne
- tyypittömyys (”kaikki on sanoja”)
- rekisterit (ei nimettyjä paikallisia muuttujia)
- alkeistoimituksiin rajoittuminen
- abstraktiokeinojen puute
Konekielen rakenne
- Konekielinen ohjelma on jono käskyjä
- Kukin käsky koostuu (tavallisesti) operaatiokoodista eli opkoodista (engl. opcode) sekä 0–3 operandista
- Käskyjen koodaustapa riippuu ISA:sta. Esimerkiksi
- IA32:ssa kolmitavuinen käsky 80 F1 12 käskee XORraamaan luvun 18 ja erään rekisterin arvon ja tallettamaan tuloksen ko. rekisteriin.
Konekielen tyypit
- Konekielessä kaikki data on eri mittaisia bittijonoja.
- Kohdedatan pituus ja tulkinta riippuvat täysin opkoodista.
- Tyyppitarkastusta ei ole.
- tyypillisiä konekielen tyyppejä:
- \(n\)-bittinen etumerkitön kokonaisluku (aritmetiikka modulo \(2^n\))
- \(n\)-bittinen etumerkillinen kokonaisluku (kahden komplementti)
- \(n\) bitin bittijono
- \(n\) bitin IEEE-liukuluku
- \(n = 8, 16, 32, 64(, 128)\) (paitsi liukuluvuilla \(n = (16,) 32, 64\))
Symbolinen konekieli
- Tunnetaan myös nimellä assembly.
- Ihmisen luettavaksi tarkoitettu konekielen esitystapa.
- Joka ISA:lla on oma assemblynsä, joillakin on jopa useampia.
- Yksi käsky esitetään yhdellä rivillä.
- Kullakin opkoodilla on lyhyt nimi eli muistikas (engl. mnemonic).
- Operandit esitetään havainnollisella tavalla.
Assemblerit
- Yksinkertainen kääntäjä, ns. assembler, kääntää symbolisen konekielen konekieleksi.
- Monilla ISA:illa on monta eri assembleria
- Osa assemblereista tukee monta ISA:ta
- tällä luennolla käytämme GNU:n assembleria ja AMD64:n Intel-syntaksia (melkein sama kuin nasmissa)
Ensimmäinen ohjelma
# Lasketaan 1+1 (kokonaisluvuilla), tulos mainin paluuarvona
.intel_syntax noprefix
.global main
.text
main: mov rax, 1
add rax, 1
ret
Käännös ja suoritus Linuxin komentorivillä (AMD64-koneessa, 64-bittinen käyttöjärjestelmä):
$ gcc -o minimal-amd64 minimal-amd64.s
$ ./minimal-amd64
$ echo $?
Sivuhuomautus
Käännämme gcc:llä, koska
gcctajuaa tiedostopäätteestä.s, että syöte on assemblya- kutsuu automaattisesti assembleria
gcchoitaa linkityksen puolestammegcclinkittää mukaan C-standardikirjaston- mukaan lukien C:n alustuskirjaston, joka kutsuu
main-funktiota
- mukaan lukien C:n alustuskirjaston, joka kutsuu
Ensimmäiset rivit
- Kommenttirivi alussa; kommentti päättyy rivin loppuun ja alkaa
#(AMD64)
- Seuraava epätyhjä rivi valitsee käytetyn syntaksin
- Kolmas epätyhjä rivi
.global mainilmoittaa, että nimimainon julkinen
Assemblerin toimintaperiaate
- pääsääntöisesti jokainen epätyhjä rivi muuttuu jonoksi tavuja ohjelmassa
- myös käskyrivit koodataan jonoiksi tavuja
- kommenttirivit lasketaan tyhjiksi
- poikkeuksena osa riveistä, joilla esiintyy ns. "pseudokäskyjä", jotka alkavat pisteellä
- ohjelma jaetaan osiin (section):
.text-osaan ohjelmakoodi ja vakiot.data-osaan alustetut globaalit muuttujat.bss-osaan nollaksi alustettavat globaalit muuttujat
- osaan tulee kaikki osan ilmoittavan pseudokäskyn jälkeen tulevat tavut
Ensimmäinen koodirivi
# AMD64
main: mov rax, 1
- Rivin alussa on label
mainmaintarkoittaa tästä lähtien labelia seuraavan tavun osoitetta
- Siirretään rekisteriin kokonaislukuvakio
1 - Käsky alkaa muistikkaalla (engl. mnemonic), joka kertoo käskyn operaation
- tässä sattumalta molemmissa assemblyissä sama muistikas, aina näin ei ole
- Käskyn operandit erotetaan toisistaan pilkulla
- eka operandi on operaation kohde
- Vakio kirjoitetaan AMD64:ssä sellaisenaan
Rekisterit
- keskusyksikön sisällä olevia nimettyjä muistiyksiköitä
- rekisterin käyttö olennaisesti muistin käyttöä nopeampaa
- joissakin ISA:issa laskenta mahdollista vain rekistereissä
- erittäin rajattu määrä, kourallisesta muutamaan kymmeneen
- AMD64:ssä
- 64-bittisiä kokonaislukurekistereitä 16 kpl (RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP, R8–R15)
- 2×64-bittisiä liukulukurekistereitä 16 kpl (XMM0–XMM15)
AMD64:n alirekisterit
- Jokaisella AMD64:n kokonaislukurekisterillä on nimettyjä alirekistereitä
- RAX, RBX, RCX, RDX:
- 32 alinta bittiä EAX, EBX, ECX, EDX
- 16 alinta bittiä AX, BX, CX, DX
- jonka 8 ylintä bittiä AH, BH, CH, DH
- 8 alinta bittiä AL, BL, CL, DL
- RSI, RDI, RBP, RSP, R8–R15:
- 32 alinta bittiä ESI, EDI, EBP, ESP, R8D–R15D
- 16 alinta bittiä SI, DI, BP, SP, R8W–R15W
- 8 alinta bittiä SIL, DIL, BPL, SPL, R8B–R15B
Toinen koodirivi
# AMD64
add rax, 1
- AMD64:n
addlaskee operandit yhteen ja tallettaa tuloksen ensimmäiseen operandiin addtekee kokonaislukuyhteenlaskun!- liukulukulaskentaan on eri käskyt
Kolmas koodirivi
# AMD64
ret
- käsky tekee "pellin alla" aika lailla, mutta lopputulos on selkeä:
- palataan aliohjelmasta
mainsen kutsujaan
- palataan aliohjelmasta
Toinen ohjelma
# Kopioidaan syöte tulosteeseen
.intel_syntax noprefix
.global main
.text
main: push rbp
0: call getchar
cmp eax, 0
jl 1f
mov rdi, rax
call putchar
jmp 0b
1: mov rax, 0
pop rbp
ret
Suuruusvertailujen koodaaminen
# AMD64
cmp eax, 0
cmpsuorittaa vähennyslaskun, jonka tulosta ei tallenneta mihinkään- tuloksen ominaisuuksia tallennetaan lippuihin, mm.
- SF: oliko negatiivinen?
- ZF: oliko nolla?
- OF: tuottiko ylivuodon?
- CF: tuottiko carry-tilanteen?
Valintojen tekeminen
# AMD64
jl 1f
...
1:
- lippujen perusteella voidaan tehdä ehdollisia hyppyjä
- hyppykäskyn nimi alkaa AMD64:ssä
j - nimi jatkuu ehdon kuvauksella
- hyppykäskyn nimi alkaa AMD64:ssä
- hypyn kohteena jokin lähellä oleva käsky
1fviittaa käskyä seuraavaan1:-labeliin1bviittaa käskyä edeltävään1:-labeliin2f/2:jne myös mahdollisia- voi myös käyttää tavallisia nimettyä labeleita
Ehdollisia hyppykäskyjä AMD64:ssä
| Käsky | Ehto | cmp \(a\),\(b\) |
\(a\) |
|---|---|---|---|
je, jz |
\(ZF = 1\) | \(a = b\) | \(a = 0\) |
jne, jnz |
\(ZF = 0\) | \(a \neq b\) | \(a \neq 0\) |
js |
\(SF = 1\) | \(a < 0\) | |
jns |
\(SF = 0\) | \(a \geq 0\) | |
jb |
\(CF=1\) | \(a < b\) (u) | |
jnb |
\(CF=0\) | \(a \geq b\) (u) | |
jge |
\(SF = OF\) | \(a \geq b\) (s) | |
jl |
\(SF \neq OF\) | \(a < b\) (s) | |
jg |
\(ZF=0\land SF=OF\) | \(a > b\) (s) | |
jle |
\(ZF=1\lor SF\neq OF\) | \(a \leq b\) (s) |
- u tarkoittaa etumerkitöntä tulkintaa
- s tarkoittaa kahden komplementti -tulkintaa
Silmukat
# AMD64
0:
...
jl 1f
...
jmp 0b
1:
- silmukoita ei ole, ne on koodattava hyppykäskyistä
while (e) Skääntyy muotoone- jos epätosi, hyppää ulos
S- hyppää silmukan alkuun
- ehdoton hyppy AMD64:ssä
jmp
Application Binary Interface (ABI)
- ISA-kohtainen
- yleensä myös käyttöjärjestelmäkohtainen
- voi olla myös kääntäjäkohtainen
- määrittelee mm.
- tietotyyppien esitystavat
- aliohjelmien kutsurajapinnan
- jos käännetty ohjelma ja käännetty kirjasto noudattavat samaa ABIa, ne voi linkittää yhteen
ABI-vaihtoehtoja
- System V Application Binary Interface, AMD64 Architecture Processor Supplement, Draft Version 0.99.8
- jatkossa AMD64 SysV ABI
- käytössä mm. Linuxissa
- ei käytössä Windowsissa!
- Visual C++ x64 Software Conventions
- 64-bittinen Windows
- jatkossa tarkastellaan vain AMD64 SysV ABIa
Aliohjelmat AMD64:ssä (SysV ABI)
main: push rbp
call getchar # int getchar(void)
cmp eax, 0
jl 1f
mov rdi, rax
call putchar # int putchar(int)
...
1: mov rax, 0
pop rbp
ret
- aliohjelmakutsukäsky on
callja paluukäskyret- paluuosoite kulkee automaattisesti pinossa
- paluuosoite on 8 tavua pitkä, mutta pinon osoitteen tulee olla 16:lla jaollinen kutsun tapahduttua, joten laitetaan jotain muuta myös pinoon
- 6 ensimmäistä kokonaislukuparametria välitetään rekistereissä
rdi,rsi,rdx,rcx,r8jar9 - kokonaislukupaluuarvo välitetään rekisterissä
rax- mutta
raxon 64-bittinen,inton 32-bittinen, joten vertailu tehdään yllärax:n 32-bittisellä osarekisterilläeax
- mutta
Caller save ja callee save
- rekisterit jaetaan kahteen luokkaan:
- caller save: rekisteri, jota aliohjelma saa käyttää vapaasti aliohjelmakutsujen välissä, mutta jonka arvo ei välttämättä säily aliohjelmakutsun yli
- callee save: rekisteri, jota aliohjelma saa käyttää vain, jos se tallettaa sen alkuperäisen arvon ja palauttaa sen ennen paluutaan
- AMD64:ssä (SysV ABI)
rbx,rbp,r12,r13,r14,r15
- AMD64:ssä (SysV ABI)
Rekisterien arvojen talletus pinoon AMD64:ssä
push rekisterilaitaa pinoon- huolehdi, että aliohjelmakutsun laitettua paluuosoitteen pinoon pino-osoitin on jaollinen 16:lla
pop rekisteripoistaa pinosta- poista käänteisessä järjestyksessä (LIFO!)
Liukuluvut AMD64:ssä
- yksinkertaisuuden vuoksi vain kaksoistarkkuus (
double) - Samalla myös esimerkki
- globaaleista muuttujista
- merkkijonovakioista
Keskiarvoaliohjelma
avg: addsd xmm0, xmm1
mov rax, 2
cvtsi2sd xmm1, rax
divsd xmm0, xmm1
ret
- 8 ensimmäistä liukulukuparametria välitetään rekistereissä
xmm0,...,xmmm7 - liukulukupaluuarvo välitetään rekisterissä
xmm0 addsdlaskee kaksi liukulukua yhteencvtsi2sdmuuttaa kokonaisluvun liukuluvuksidivsdon jakolasku
Liukulukujen lukeminen scanf:llä
lea rdi, [rfmt]
mov al, 0
lea rsi, [tmp1]
lea rdx, [tmp2]
call scanf
lealaskee ensimmäiseen operandiinsa toisen operandinsa muistiosoitteen- hakasulkeisiin kirjoitetaan muistiosoitteen lähde
- voi olla vakio, rekisteri tai vähän monimutkaisempaa
- hakasulkeisiin kirjoitetaan muistiosoitteen lähde
scanf-funktion tyyppisille aliohjelmille (stdarg) on kerrottavaal-rekisterissä, montako liukulukuparametria on enintään rekistereissä
Formaattimerkkijono
lea rdi, [rfmt]
...
.balign 8
rfmt: .asciz "%lf %lf\n"
- Merkkijonovakio (ei Unicode) kirjoitetaan
.text-osaan.asciz-pseudokäskyllä.- Sille annetaan nimi kuten aliohjelmalla.
- Ennen merkkijonovakiota varmistetaan, että osoite on kahdeksalla jaollinen, pseudokäskyllä
.balign 8
Luettujen lukujen tallennusmuuttujat
lea rsi, [tmp1]
lea rdx, [tmp2]
...
.bss
.balign 8
.comm tmp1, 8
.comm tmp2, 8
- Jokaista formaattimerkkijonossa olevaa
%lf-osajonoa kohti pitää olla yksi tallennusmuuttuja, jonka osoite viedään parametina. - Globaalit muuttujat voidaan luoda
.bss-osaan- Muuttujalle annetaan nimi
tmp1ja 8 tavua tilaa pseudokäskyllä.comm tmp1, 8 - Muuttujat alustuvat ohjelman käynnistyessä nollabittijonoiksi
- Muuttujalle annetaan nimi
Lukemisen onnistumisen tarkastaminen ja lukujen käyttöönotto
call scanf
cmp rax, 2
jne 1f
movsd xmm0, [tmp1]
movsd xmm1, [tmp2]
scanfpalauttaa onnistuneesti luettujen muuttujien lukumäärän- luetut luvut siirretään liukulukurekistereihin
movsd-käskyllä
Lukujen tulostaminen printf:llä
movsd xmm0, [tmp1]
movsd xmm1, [tmp2]
lea rdi, [wfmt]
mov al, 1
call printf
- printf:n formaattimerkkijonossa esiintyvä %lf tarkoittaa, että printf:lle pitää antaa lisäksi double-tyyppinen parametri
movsd-käskyä voi myös käyttää liukuluvun muistista lataamiseen- liukulukuparametrit välitetään myös
stdarg-funktioille liukulukurekistereissäal:ssä kerrottava liukulukurekistereissä olevien parametrien enimmäismäärä- ei tarvitse olla tarkka tieto, mutta max 8
C-kirjastofunktioiden yhteenveto
Vapaaehtoisia harjoitustehtäviä
Kirjoita assembly-ohjelma:
- joka lukee käyttäjältä rivin ja tulostaa saman rivin niin, että pienet kirjaimet on muutettu isoiksi kirjaimiksi (vain ASCII, eli ei ääkkösiä ym).
- joka laskee syötteessä olevien sanojen lukumäärän.
- joka tulostaa Fibonaccin lukujonon (siihen asti kunnes 64-bittisestä kokonaisluvusta loppuu esitystarkkuus tai kunnes käyttäjä ohjelman keskeyttää Ctrl-C:llä)
Näitä voidaan käsitellä ohjauksissa.
These are the current permissions for this document; please modify if needed. You can always modify these permissions from the manage page.