Koska syscalleilla tulostelu on hankalaa, päätin etsiä käsiini miten käyttää C:n funktioita esim. juuri tulosteluun ja syötteiden lukemiseen. Tämä tieto tuntui olevan hyvin jemmattu ja 64-bittisyys teki etsimisestä vieläkin hankalempaa.
NASMilla kääntyy 64-bittisessä Linuxissa komennoilla:
nasm -f elf64 syt.asm
ld --dynamic-linker /lib/ld-linux-x86-64.so.2 -lc -o syt syt.o
./syt
Olen aloittelija assemblyssä -> vinkkejä ja parannusehdotuksia otetaan vastaan mieluusti.
syt.asm
[section .data]
txt1: db 'This program calculates the greatest common divisor of two numbers.',10,'Give first number: ',0
txt2: db 'Give second number: ',0
txtf: db '%s',0
readf: db '%d',0
answ: db 'Answer: %u',10,0
[section .bss]
num_a: resq 16
num_b: resq 16
gcm: resq 16
[section .text]
extern printf,scanf ; Käytettävät C-funktiot
global _start
_start:
; Tulostetaan txt1
xor rax,rax ; RAX sisältää C-funktiolle syötettävien float-argumenttien määrän, eli tässä 0.
; C-funktion kutsuminen pukkaa segfaulttia jossei RAXia nollaa ennen jokaista kutsua,
; sillä funktion ensimmäinen palautusarvo menee aina RAXiin (muuttuu joka kerta)
mov rdi,txtf ; RDI sisältää ensimmäisen funktiolle menevän argumentin.
mov rsi,txt1 ; RSI toisen, tulostettaessa merkkejä pelkkä osoite riittää.
; Jos argumenttejä on enemmän, menevät ne järjestyksessä rekistereihin
; RDI, RSI, RDX, RCX, R8, R9, XMM0-7.
call printf ; kutsutaan funktiota printf
; Pitkälti sama juttu scanf:n kanssa. Luetaan num_a.
xor rax,rax
mov rdi,readf
mov rsi,num_a
call scanf
; Tulostetaan txt2
xor rax,rax
mov rdi,txtf
mov rsi,txt2
call printf
; Luetaan num_b
xor rax,rax
mov rdi,readf
mov rsi,num_b
call scanf
; Siirretään luetut numerot rekistereihin sytin laskua varten
mov rax,[num_a]
mov rbx,[num_b]
; Sytti lasketaan silmukassa käyttäen Eukleideen algoritmia:
; # while b != 0
; # t := b
; # b := a mod b
; # a := t
.loop:
mov rcx,rbx ; Laitetaan RBX:n arvo, eli luku b talteen. (t := b)
xor rdx,rdx ; Nollataan RDX, koska käytettäessä DIViä jaettava
; saadaan yhdistämällä rekisterien RDX ja RAX arvot.
; Tässä riittää vain RAX.
div rbx ; Kuten sanottu, RAX/RDX. Tulos löytyy rekisteristä RAX
; ja jakojäännös rekisteristä RDX.
mov rbx,rdx ; RBX = jakojäännös (b := a mod b)
mov rax,rcx ; Otetaan talteen laitettu b:n arvo jemmasta ja laitetaan se RAXiin (a := t)
cmp rbx,0 ; Jos RCX<=0, ...
ja .loop ; ... niin hyppää kohtaan loop.
mov [gcm],rax ; Tallennetaan saatu syt sille varattuun tilaan.
; Tulostetaan gcm (eli haluttu syt)
xor rax,rax
mov rdi,answ
mov rsi,[gcm] ; Tulostettaessa integeriä, on RSI:ssä oltava luvun arvo.
call printf
; Linuxin sulkemisproseduurit
mov rax,1
mov rbx,0
int 80hEikös linkitsemisen voi jättää myös gcc:n huoleksi muuttamalla ohjelman aloitus kohdan labelin: _start: -> main:
Eli kääntö ja linkitys:
nasm -f elf64 syt.asm
gcc syt.o -o syt
Sisällöltään hyvä vinkki. Muutama kommentti silti:
Käyttämäsi "jump" ei ole kaikkein kuvaavin nimi. Lisäksi paikalliset nimet aloitetaan pisteellä:
palauta:
.nolla:
xor eax, eax
ret
.yksi:
xor eax, eax
inc eax
ret
huono_funktio:
call palauta.yksi
jnz .yksi ; huono_funktio.yksi
.nolla:
jmp palauta.nolla
.yksi:
jmp palauta.yksiAntamassani koodissa sinänsä ei ole mitään järkeä, eikä se myöskään noudata juuri mitään hyviä ohjelmointitapoja — paitsi pistettä paikallisissa nimissä. Opetus siis on, että pisteen kanssa kummallakin funktiolla voi olla omat .nolla ja .yksi, kun taas ilman pistettä tämä ei onnistu vaan nimien täytyy olla eri.
Käytät aivan turhaan RCX-rekisteriä silmukassa. Nythän kuljetat samaa arvoa kahdessa paikassa. Lisäksi 64-bittisiä rekistereitä on niin paljon, että on minusta jokseenkin tyhmää tallennella arvoja silmukassa pinoon. Operaatiot on nopeampaa suorittaa rekistereissä.
Voi olla opettavaista katsoa, miten GCC tekee vastaavan silmukan:
// GCC:n optimoitu assembly-tuloste:
// gcc koe.c -O2 -S -masm=intel -o-
typedef unsigned int I;
I gcd(I a, I b) {
if (!(a % b)) return b;
return gcd(b, a % b);
}Monenlaiset kysymykset ratkeavat Googlea helpommin C-kääntäjän voimin: parametri -S saa GCC:n tulostamaan tuottamansa assemblyn, josta esimerkiksi 64-bittinen kutsukäytäntö selviää näppärästi. Toinen hyvä lähtökohta ovat vaikka Wikipedian artikkeli aiheesta x86 calling conventions ja sen sisältämät linkit.
Itse tykkään myös käyttää gcc:tä linkitykseen, kuten jalski yllä esittää, koska se on hieman tutumpi komento kuin ld itse.
Kiersiö :D, toisinaan myös silmukaksi väitetään.
Muokkasin koodia hieman liittyen Metabolixin huomautuksiin. Kiitoksia palautteesta!
Eräs assemblyllä tehty gcd-algoritmi on tehty osoitteessa ftp://mersenne.org/gimps/source258.zip tiedostossa factor64.asm.
Mitäs on nuo RAX, RBX, RDI, RSI, RDX, RCX, R8, R9, XMM0-7 ?
Aihe on jo aika vanha, joten et voi enää vastata siihen.