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 80h
Eikö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.yksi
Antamassani 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.