Kirjautuminen

Haku

Tehtävät

Keskustelu: Koodit: Assembly: Suurin yhteinen tekijä ja C-funktioiden käyttöä

kinnala [03.03.2009 03:12:37]

#

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

jalski [06.03.2009 16:58:28]

#

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

Metabolix [06.03.2009 17:43:32]

#

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.

kinnala [06.03.2009 19:48:11]

#

Muokkasin koodia hieman liittyen Metabolixin huomautuksiin. Kiitoksia palautteesta!

Jaska [09.03.2009 21:26:00]

#

Eräs assemblyllä tehty gcd-algoritmi on tehty osoitteessa ftp://mersenne.org/gimps/source258.zip tiedostossa factor64.asm.

ErroR++ [30.10.2011 17:39:50]

#

Mitäs on nuo RAX, RBX, RDI, RSI, RDX, RCX, R8, R9, XMM0-7 ?

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta