   #Start Prev Contents

   Jak pisac programy w jezyku asembler pod Linuksem?

Czesc 17 - Pobieranie i wyswietlanie, czyli jak komunikowac sie ze swiatem

   O ile wyswietlanie i pobieranie od uzytkownika tekstow jest latwe do
   wykonania - wystarczy uruchomic tylko jedna funkcje systemowa (eax=3
   lub 4 przerwania 80h) - to pobieranie i wyswietlanie na przyklad liczb
   wcale nie jest takie proste i kazdemu moze przysporzyc problemow. W
   tej czesci podam pare algorytmow, dzieki ktorym kazdy powinien sobie z
   tym poradzic.
     _________________________________________________________________

Wyswietlanie tekstu

   (przeskocz wyswietlanie tekstu)

   Co prawda wszyscy juz to umieja, ale dla porzadku tez o tym wspomne.
   Wszyscy znaja funkcje EAX=4 przerwania Linuksa - w EBX podajemy
   deskryptor, na ktory wyswietlamy (1 oznacza standardowe wyjscie,
   najczesciej ekran), w ECX - adres bufora z napisem do wyswietlenia, a
   w EDX - liczba bajtow do wyswietlenia. Po wywolaniu int 80h w EAX
   dostajemy liczba zapisanych bajtow (jesli EAX jest ujemny, to wystapil
   blad).

   Zawsze mozna tez wyswietlac tekst recznie.
     _________________________________________________________________

Pobieranie tekstu

   (przeskocz pobieranie tekstu)

   Do pobierania tekstow od uzytkownika sluzy funkcja EAX=3 przerwania
   Linuksa - w EBX podajemy deskryptor, z ktorego czytamy (0 oznacza
   standardowe wejscie, najczesciej klawiature), w ECX - adres bufora na
   dane, a w EDX - liczba bajtow do przeczytania. Po wywolaniu int 80h w
   buforze dostajemy dane, a w EAX - liczba przeczytanych bajtow (jesli
   EAX jest ujemny, to wystapil blad).
     _________________________________________________________________

Wyswietlanie liczb

   (przeskocz wyswietlanie liczb)

   Sa generalnie dwa podejscia do tego problemu:
    1. dzielenie przez coraz mniejsze potegi liczby 10 (zaczynajac od
       najwyzszej odpowiedniej) i wyswietlanie ilorazow
    2. dzielenie przez 10 i wyswietlanie reszt wspak

   Podejscie pierwsze jest zilustrowane takim kodem dla liczb 16-bitowych
   (0-65535):
        mov     ax, [liczba]
        xor     dx, dx
        mov     cx, 10000
        div     cx
        or      al, '0'
        ; wyswietl AL jako znak
        mov     ax, dx
        xor     dx, dx
        mov     cx, 1000
        div     cx
        or      al, '0'
        ; wyswietl AL jako znak
        mov     ax, dx
        mov     cl, 100
        div     cl
        or      al, '0'
        ; wyswietl AL jako znak
        mov     al, ah
        xor     ah, ah
        mov     cl, 10
        div     cl
        or      ax, '00'
        ; wyswietl AL jako znak
        ; potem wyswietl AH jako znak

   Jak widac, im wiecej cyfr moze miec liczba, tym wiecej bedzie takich
   blokow. Trzeba zaczac od najwyzszej mozliwej potegi liczby 10, bo
   inaczej moze dojsc do przepelnienia. W kazdym kroku dzielnik musi miec
   o jedno zero mniej, gdyz inaczej nie uda sie wyswietlic prawidlowego
   wyniku (moze byc dwucyfrowy i wyswietli sie tylko jakis znaczek).
   Ponadto, jesli liczba wynosi na przyklad 9, to wyswietli sie 00009,
   czyli wiodace zera nie beda skasowane. Mozna to oczywiscie ominac.

   Podejscie drugie jest o tyle wygodniejsze, ze mozna je zapisac za
   pomoca petli. Jest to zilustrowane procedura _pisz_ld z czesci
   czwartej oraz kodem z mojej biblioteki:
        mov     ax, [liczba]
        xor     si, si                  ; indeks do bufora
        mov     cx, 10                  ; dzielnik
_pisz_l_petla:                          ; wpisujemy do bufora reszty z
                                        ; dzielenia liczby przez 10,
        xor     dx, dx                  ; czyli cyfry wspak
        div     cx                      ; dziel przez 10
        or      dl, '0'                 ; dodaj kod ASCII cyfry zero
        mov     [_pisz_bufor+si], dl    ; zapisz cyfre do bufora
        inc     si                      ; zwieksz indeks
        test    ax, ax                  ; dopoki liczba jest rozna od 0
        jnz     _pisz_l_petla

_pisz_l_wypis:
        mov     al, [_pisz_bufor+si-1]  ; pobierz znak z bufora
        call    far _pisz_z             ; wyswietla znak
        dec     si                      ; przejdz na poprzedni znak
        jnz     _pisz_l_wypis

   Zmienna _pisz_bufor to bufor odpowiedniej liczby bajtow.
     _________________________________________________________________

Pobieranie liczb

   (przeskocz pobieranie liczb)

   Do tego zagadnienia algorytm jest nastepujacy:
    1. wczytaj lancuch znakow od razu w calosci lub wczytuj znak po znaku
       w kroku 3
    2. wstepnie ustaw wynik na 0
    3. wez kolejny znak z wczytanego lancucha znakow (jesli juz nie ma,
       to koniec)
    4. zamien go na jego wartosc binarna. Jesli znak wczytales do AL, to
       wystarczy:
       sub al, '0'
    5. przemnoz biezacy wynik przez 10
    6. dodaj do niego wartosc AL otrzymana z kroku 4
    7. skacz do 3

   Przykladowa ilustracje mozna znalezc takze w mojej bibliotece:
        xor     bx, bx          ; miejsce na liczbe
l_petla:
        call    far _we_z       ; pobierz znak z klawiatury

        cmp     al, lf          ; czy Enter?
        je      l_juz           ; jesli tak, to wychodzimy
        cmp     al, cr
        je      l_juz
                                ; przepuszczamy Spacje:
        cmp     al, spc
        je      l_petla

        cmp     al, '0'         ; jesli nie cyfra, to blad
        jb      l_blad
        cmp     al, '9'
        ja      l_blad

        and     al, 0fh         ; izolujemy wartosc (sub al, '0')
        mov     cl, al
        mov     ax, bx

        shl     bx, 1           ; zrobimy miejsce na nowa cyfre
        jc      l_blad

        shl     ax, 1
        jc      l_blad
        shl     ax, 1
        jc      l_blad
        shl     ax, 1
        jc      l_blad

        add     bx, ax          ; BX=BX*10 - biezaca liczbe mnozymy przez 10
        jc      l_blad

        add     bl, cl          ; dodajemy cyfre
        adc     bh, 0
        jc      l_blad          ; jesli przekroczony limit, to blad

        jmp     short l_petla
l_juz:
        ; wynik w AX
     _________________________________________________________________

Sprawdzanie rodzaju znaku

   (przeskocz sprawdzanie rodzaju znaku)

   Powiedzmy, ze uzytkownik naszego programu wpisal nam jakies znaki
   (tekst, liczby). Jak teraz sprawdzic, co dokladnie otrzymalismy?
   Sprawa nie jest trudna, lecz wymaga czasem zastanowienia i tablicy
   ASCII pod reka.
    1. Cyfry
       Cyfry w kodzie ASCII zajmuja miejsca od 30h (zero) do 39h
       (dziewiatka). Wystarczy wiec sprawdzic, czy wczytany znak miesci
       sie w tym zakresie:
                cmp     al, '0'
                jb      nie_cyfra
                cmp     al, '9'
                ja      nie_cyfra
                ; tu wiemy, ze AL reprezentuje cyfre.
                ; Pobranie wartosci tej cyfry:
                and     al, 0fh ; skasuj wysokie 4 bity, zostaw 0-9
    2. Litery
       Litery, podobnie jak cyfry, sa uporzadkowane w kolejnosci w dwoch
       osobnych grupach (najpierw wielkie, potem male). Aby sprawdzic,
       czy znak w AL jest litera, wystarczy kod
                        cmp     al, 'A'
                        jb      nie_litera      ; na pewno nie litera
                        cmp     al, 'Z'
                        ja      sprawdz_male    ; na pewno nie wielka,
                                                ; sprawdz male
                        ; tu wiemy, ze AL reprezentuje wielka litere.
                        ; ...
                sprawdz_male:
                        cmp     al, 'a'
                        jb      nie_litera      ; na pewno nie litera
                        cmp     al, 'z'
                        ja      nie_litera
                        ; tu wiemy, ze AL reprezentuje mala litere.
    3. Cyfry szesnastkowe
       Tu sprawa jest latwa: nalezy najpierw sprawdzic, czy dany znak
       jest cyfra. Jesli nie, to sprawdzamy, czy jest wielka litera z
       zakresu od A do F. Jesli nie, to sprawdzamy, czy jest mala litera
       z zakresu od a do f. Wystarczy polaczyc powyzsze fragmenty kodu.
       Wyciagniecie wartosci wymaga jednak wiecej krokow:
                ; jesli AL to cyfra '0'-'9'
                and     al, 0fh
                ; jesli AL to litera 'A'-'F'
                sub     al, 'A' - 10
                ; jesli AL to litera 'a'-'f'
                sub     al, 'a' - 10
       Jesli AL jest litera, to najpierw odejmujemy od niego kod
       odpowiedniej (malej lub wielkiej) litery A. Dostajemy wtedy
       wartosc od 0 do 5. Aby dostac realna wartosc danej litery w kodzie
       szesnastkowym, wystarczy teraz dodac 10. A skoro AL-'A'+10 to to
       samo, co AL-('A'-10), to juz wiecie, skad sie wziely powyzsze
       instrukcje.
    4. Przerabianie wielkich liter na male i odwrotnie
       Oczywistym sposobem jest odjecie od litery kodu odpowiedniej
       litery A (malej lub wielkiej), po czym dodanie kodu tej drugiej,
       czyli:
                ; z malej na wielka
                sub     al, 'a'
                add     al, 'A'
                ; z wielkiej na mala
                sub     al, 'A'
                add     al, 'a'
       lub nieco szybciej:
                ; z malej na wielka
                sub     al, 'a' - 'A'
                ; z wielkiej na mala
                sub     al, 'A' - 'a'
       Ale jest lepszy sposob: patrzac w tabele kodow ASCII widac, ze
       litery male od wielkich roznia sie tylko jednym bitem - bitem
       numer 5. Teraz widac, ze wystarczy
                ; z malej na wielka
                and     al, 5fh
                ; z wielkiej na mala
                or      al, 20h
     _________________________________________________________________

Wyswietlanie liczb niecalkowitych

   (przeskocz wyswietlanie liczb niecalkowitych)

   To zagadnienie mozna rozbic na dwa etapy:
    1. wyswietlenie czesci calkowitej liczby
    2. wyswietlenie czesci ulamkowej liczby

   Do wyswietlenia czesci calkowitej moze nam posluzyc procedura
   wyswietlania liczb calkowitych, wystarczy z danej liczby wyciagnac
   czesc calkowita. W tym celu najpierw ustawiamy tryb zaokraglania na
   obcinanie (gdyz inaczej na przyklad czesc calkowita z liczby 1,9
   wynioslaby 2):
        fnstcw  [status]                          ; status to 16-bitowe slowo
        or      word [status], (0Ch << 8)  ; zaokraglanie: obcinaj
        ;or     word [status], (0Ch shl 8) ; dla FASMa
        fldcw   [status]

   W trakcie calej procedury wyswietlania bedziemy korzystac z tego
   wlasnie trybu zaokraglania. Pamietajcie, aby przy wyjsciu z procedury
   przywrocic poprzedni stan slowa kontrolnego koprocesora (na przyklad
   poprzez skopiowanie wartosci zmiennej status przed jej zmiana do innej
   zmiennej, po czym zaladowanie slowa kontrolnego z tej drugiej
   zmiennej).

   Teraz wyciagamy czesc calkowita liczby nastepujacym kodem:
        frndint                         ; jesli liczba byla w ST0
        fistp   qword [cz_calkowita]

   Pojawia sie jednak problem, gdy czesc calkowita nie zmiesci sie nawet
   w 64 bitach. Wtedy trzeba skorzystac z tego samego sposobu, ktory byl
   podany dla liczb calkowitych: ciagle dzielenie przez 10 i wyswietlenie
   reszt z dzielenia wspak.
   W tym celu ladujemy na stos FPU czesc calkowita z naszej liczby oraz
   liczbe 10:
        frndint                         ; jesli liczba byla w ST0
        fild    word [dziesiec]         ; zmienna zawierajaca wartosc 10
        fxch    st1                     ; stos: ST0=czesc calkowita, ST1=10

   Stos koprocesora zawiera teraz czesc calkowita naszej liczby w ST0 i
   wartosc 10 w ST1. Po wykonaniu
        fprem                           ; stos: ST0=mod (czesc calkowita,10), S
T1=10

   w ST0 dostajemy reszte z dzielenia naszej liczby przez 10 (czyli cyfre
   jednosci, do wyswietlenia jako ostatnia). Reszte te zachowujemy do
   bufora na cyfry. Teraz dzielimy liczbe przez 10:
                                        ; ST0=czesc calkowita, ST1=10
        fdiv    st0, st1                ; ST0=czesc calkowita/10, ST1=10
        frndint                         ; ST0=czesc calkowita z poprzedniej
                                        ; podzielonej przez 10, ST1=10

   i powtarzamy cala procedure do chwili, w ktorej czesc calkowita stanie
   sie zerem, co sprawdzamy takim na przyklad kodem:
        ftst                            ; zbadaj liczbe w ST0 i ustaw flagi FPU
        fstsw   [status]                ; zachowaj flagi FPU do zmiennej
        mov     ax, [status]
        sahf                            ; zapisz AH do flag procesora
        jnz     powtarzamy_dzielenie

   Po wyswietleniu czesci calkowitej nalezy wyswietlic separator (czyli
   przecinek), po czym zabrac sie do wyswietlania czesci ulamkowej. To
   jest o tyle prostsze, ze uzyskane cyfry mozna od razu wyswietlic, bez
   korzystania z zadnego bufora.

   Algorytm jest podobny jak dla liczb calkowitych, z ta roznica, ze
   teraz liczba jest na kazdym kroku mnozona przez 10:
                                        ; ST0=czesc ulamkowa, ST1=10
        fmul    st0, st1                ; ST0=czesc ulamkowa * 10, ST1=10
        fist    word [liczba]           ; cyfra (czesc ulamkowa*10) do zmiennej

   Po wyswietleniu wartosci znajdujacej sie we wskazanej zmiennej, nalezy
   odjac ja od biezacej liczby, dzieki czemu na stosie znow bedzie liczba
   mniejsza od jeden i bedzie mozna powtorzyc procedure:
        fild    word [liczba]           ; ST0=czesc calkowita,
                                        ; ST1=czesc calkowita + czesc ulamkowa,
                                        ; ST2=10
        fsubp   st1, st0                ; ST0=nowa czesc ulamkowa, ST1=10

   Po kazdej iteracji sprawdzamy, czy liczba jeszcze nie jest zerem
   (podobnie jak powyzej).
     _________________________________________________________________

Pobieranie liczb niecalkowitych

   Procedure wczytywania liczb niecalkowitych mozna podzielic na dwa
   etapy:
    1. wczytanie czesci calkowitej
    2. wczytanie czesci ulamkowej

   Wczytywanie czesci calkowitej odbywa sie podobnie, jak dla liczb
   calkowitych: biezaca liczbe pomnoz przez 10, po czym dodaj aktualnie
   wprowadzona cyfre. Kluczowa czesc kodu wygladac moze wiec podobnie do
   tego fragmentu:
        ; kod wczytujacy cyfre laduje ja do zmiennej WORD [cyfra]
                                        ; ST0=10, ST1=aktualna liczba
        fmul    st1, st0                ; ST0=10, ST1=liczba*10
        fild    word [cyfra]            ; ladujemy ostatnia cyfre,
                                        ; ST0=cyfra, ST1=10, ST2=10 * liczba
        faddp   st2, st0                ; ST0=10, ST1=liczba*10 + cyfra

   Procedure te powtarza sie do chwili napotkania separatora czesci
   ulamkowej (czyli przecinka, ale mozna akceptowac tez kropke). Od
   chwili napotkania separatora nastepuje przejscie do wczytywania czesci
   ulamkowej.

   Aby wczytac czesc ulamkowa, najlepiej powrocic do algorytmu z
   dzieleniem. Wszystkie wprowadzane cyfry najpierw ladujemy do bufora,
   potem odczytujemy wspak, dodajemy do naszej liczby i dzielimy ja przez
   10. Zasadnicza czesc petli moglaby wygladac podobnie do tego:
        fild    word [cyfra]    ; ST0=cyfra, ST0=biezaca czesc ulamkowa, ST2=10
        faddp   st1, st0        ; ST0=cyfra+biezaca czesc ulamkowa, ST1=10
        fdiv    st0, st1        ; ST0=nowa liczba/10 = nowy ulamek, ST1=10

   Po wczytaniu calej czesci ulamkowej pozostaje tylko dodac ja do
   uprzednio wczytanej czesci calkowitej i wynik gotowy.

   Pamietajcie o dobrym wykorzystaniu stosu koprocesora: nigdy nie
   przekraczajcie osmiu elementow i nie zostawiajcie wiecej, niz
   otrzymaliscie jako parametry.

   Poprzednia czesc kursu (klawisz dostepu 3)
   Spis tresci off-line (klawisz dostepu 1)
   Spis tresci on-line (klawisz dostepu 2)
   Ulatwienia dla niepelnosprawnych (klawisz dostepu 0)
     _________________________________________________________________

Cwiczenia

    1. Korzystajac z przedstawionych tu algorytmow, napisz algorytmy
       wczytujace i wyswietlajace liczby dziesietne 8-bitowe.
    2. Korzystajac z przedstawionych tu algorytmow, napisz algorytmy
       wczytujace i wyswietlajace liczby szesnastkowe 16-bitowe
       (wystarczy zmienic liczby, przez ktore mnozysz i dzielisz oraz to,
       jakie znaki sa dozwolone i wyswietlane - dochodza litery od A do
       F).
