   #Start Prev Next Contents

   Jak pisac programy w jezyku asembler?

Czesc 1 - Podstawy, czyli czym to sie je

   Wyobrazcie sobie, jakby to bylo moc programowac maszyne bezposrednio,
   czyli "rozmawiac" z procesorem bez posrednictwa struktur wysokiego
   poziomu, na przyklad takich jak spotykamy w jezyku C. Bezposrednie
   operowanie na procesorze umozliwia przeciez pelna kontrole jego
   dzialan! Bez zbednych instrukcji i innych "smieci" spowalniajacych
   nasze programy.

   Czy juz czujecie chec pisania najkrotszych i najszybszych programow na
   swiecie?
   Programow, ktorych czasem w ogole NIE MOZNA napisac w innych jezykach?
   Brzmi wspaniale, prawda?

   Tylko pomyslcie o tym, co powiedzieliby znajomi, gdybyscie sie im
   pochwalili. Widzicie juz te ich zdumione miny?

   Mila perspektywa, prawda? No, ale dosc juz gadania. Zabierajmy sie do
   rzeczy!
   Zacznijmy od krotkiego wprowadzenia:
     _________________________________________________________________

Niedziesietne systemy liczenia

    1. Dwojkowy (binarny)
       Najprostszy dla komputera, gdzie cos jest albo wlaczone, albo
       wylaczone. System ten operuje na liczbach zwanych bitami (bit =
       binary digit = cyfra dwojkowa). Bit przyjmuje jedna z dwoch
       wartosci: 0 lub 1.
       Na bajt sklada sie 8 bitow. Jednym bajtem mozna przedstawic wiec
       2^8=256 mozliwosci.
       Przeliczenie liczby zapisanej w systemie dwojkowym na dziesietny
       jest proste. Podobnie jak w systemie dziesietnym, kazda cyfre
       mnozymy przez odpowiednia potege podstawy (podstawa wynosi 2 w
       systemie dwojkowym, 10 w systemie dziesietnym).
       Oto przyklad (niech daszek ^ oznacza potegowanie):
       1010 1001 dwojkowo =
       1*(2^7) + 0*(2^6) + 1*(2^5) + 0*(2^4) + 1*(2^3) + 0*(2^2) +
       0*(2^1) + 1*(2^0) =
       128 + 32 + 8 + 1 =
       169 dziesietnie (lub "dec", od "decimal").
       Dzialanie odwrotne tez nie jest trudne: nasza liczbe dzielimy
       ciagle (do chwili uzyskania ilorazu rownego 0) przez 2, po czym
       zapisujemy reszty z dzielenia wspak:
       (przeskocz konwersje liczby dziesietnej na dwojkowa)
        169     |
         84     | 1
         42     | 0
         21     | 0
         10     | 1
          5     | 0
          2     | 1
          1     | 0
          0     | 1
       Wspak dostajemy: 1010 1001, czyli wyjsciowa liczbe.
    2. Szesnastkowy (heksadecymalny, w skrocie hex)
       Jako ze system dwojkowy ma mniej cyfr niz dziesietny, do
       przedstawienia wzglednie malych liczb trzeba uzyc duzo zer i
       jedynek. Jako ze bajt ma 8 bitow, podzielono go na dwie rowne,
       czterobitowe czesci. Teraz bajt mozna juz reprezentowac dwoma
       znakami, a nie osmioma. Na kazdy taki znak sklada sie 2^4=16
       mozliwosci. Stad wziela sie nazwa "szesnastkowy".
       Powstal jednak problem: cyfr jest tylko 10, a trzeba miec 16. Co
       zrobic?
       Postanowiono liczbom 10-15 przyporzadkowac odpowiednio znaki A-F.
       Na przyklad
       Liczba 255 dziesietnie = 1111 1111 binarnie = FF szesnastkowo
       (1111 bin = 15 dec = F hex)
       Liczba 150 dziesietnie = 1001 0110 binarnie = 96 szesnastkowo.
       Nalezy zauwazyc scisly zwiazek miedzy systemem dwojkowym i
       szesnastkowym: 1 cyfra szesnastkowa to 4 bity, co umozliwia
       blyskawiczne przeliczanie miedzy obydwoma systemami: wystarczy
       "tlumaczyc" po 4 bity (1 cyfre hex) na raz i zrobione.
       Przeliczenie liczby zapisanej w systemie szesnastkowym na
       dziesietny jest rownie proste, jak tlumaczenie z dwojkowego na
       dziesietny. Kazda cyfre mnozymy przez odpowiednia potege podstawy
       (podstawa wynosi 16 w systemie szesnastkowym).
       Oto przyklad:
       10A szesnastkowo =
       1*16^2 + 0*16^1 + A*16^0 =
       256 + 0 + 10 =
       266 dziesietnie.
       Dzialanie odwrotne tez nie jest trudne: nasza liczbe dzielimy
       ciagle (do chwili uzyskania ilorazu=0) przez 16, po czym
       zapisujemy reszty z dzielenia wspak:
       (przeskocz konwersje liczby dziesietnej na szesnastkowa)
         266    |
         16     | 10
          1     | 0
          0     | 1
       Wspak dostajemy kolejno: 1, 0 i 10, czyli 10A, czyli wyjsciowa
       liczbe.
       Podczas pisania programow, liczby w systemie szesnastkowym oznacza
       sie przez dodanie na koncu litery h (lub z przodu 0x), a liczby w
       systemie dwojkowym - przez dodanie litery b.
       Tak wiec, 101 oznacza dziesietna liczbe o wartosci 101, 101b
       oznacza liczbe 101 w systemie dwojkowym (czyli 5 w systemie
       dziesietnym), a 101h lub 0x101 oznacza liczbe 101 w systemie
       szesnastkowym (czyli 257 dziesietnie).
     _________________________________________________________________

Jezyk asembler i rejestry procesora

   Co to w ogole jest asembler?

   Asembler jest to jezyk programowania, nalezacy do jezykow niskiego
   poziomu. Znaczy to tyle, ze jednej komendzie asemblera odpowiada
   dokladnie jeden rozkaz procesora. Asembler operuje na rejestrach
   procesora.

   A co to jest rejestr procesora?

   Rejestr procesora to zespol ukladow elektronicznych, mogacy
   przechowywac informacje (taka wlasna pamiec wewnetrzna procesora).

   Zaraz podam Wam podstawowe rejestry, na ktorych bedziemy operowac.
   Wiem, ze ich liczba moze przerazic, ale od razu mowie, abyscie NIE
   uczyli sie tego wszystkiego na pamiec! Najlepiej zrobicie, czytajac
   ponizsza liste tylko 2 razy, a potem wracali do niej, gdy jakikolwiek
   rejestr pojawi sie w programach, ktore bede pozniej prezentowal w
   ramach tego kursu.
   Oto lista interesujacych nas rejestrow:
    1. ogolnego uzytku:
          + akumulator:
            RAX (64 bity) = EAX (mlodsze 32 bity) + starsze 32 bity,
            EAX (32 bity) = AX (mlodsze 16 bitow) + starsze 16 bitow,
            AX = AH (starsze 8 bitow) + AL (mlodsze 8 bitow)
            Rejestr ten najczesciej sluzy do wykonywania dzialan
            matematycznych, ale czesto w tym rejestrze lub jego czesci
            (AX lub AH) bedziemy mowic systemowi operacyjnemu i BIOS-owi,
            co od niego chcemy.
          + rejestr bazowy:
            RBX (64 bity) = EBX (mlodsze 32 bity) + starsze 32 bity,
            EBX (32 bity) = BX (mlodsze 16 bitow) + starsze 16 bitow,
            BX = BH (starsze 8 bitow) + BL (mlodsze 8 bitow)
            Ten rejestr jest uzywany na przyklad przy dostepie do tablic.
          + licznik:
            RCX (64 bity) = ECX (mlodsze 32 bity) + starsze 32 bity,
            ECX (32 bity) = CX (mlodsze 16 bitow) + starsze 16 bitow,
            CX = CH (starsze 8 bitow) + CL (mlodsze 8 bitow)
            Tego rejestru uzywamy na przyklad do okreslania ilosci
            powtorzen petli.
          + rejestr danych:
            RDX (64 bity) = EDX (mlodsze 32 bity) + starsze 32 bity,
            EDX (32 bity) = DX (mlodsze 16 bitow) + starsze 16 bitow,
            DX = DH (starsze 8 bitow) + DL (mlodsze 8 bitow)
            W tym rejestrze przechowujemy adresy roznych zmiennych. Jak
            wkrotce zobaczymy, do tego rejestru bedziemy wpisywac adres
            napisu, ktory bedziemy chcieli wyswietlic.
          + rejestry dostepne tylko w trybie 64-bitowym:
               o 8 rejestrow 8-bitowych: R8B, R9B, ..., R15B (lub R8L,
                 R9L, ..., R15L)
               o 8 rejestrow 16-bitowych: R8W, R9W, ..., R15W
               o 8 rejestrow 32-bitowych: R8D, R9D, ..., R15D
               o 8 rejestrow 64-bitowych: R8, R9, ..., R15
          + rejestry indeksowe:
               o indeks zrodlowy:
                 RSI (64 bity) = ESI (mlodsze 32 bity) + starsze 32 bity,
                 ESI (32 bity) = SI (mlodsze 16 bitow) + starsze 16
                 bitow,
                 SI (16 bitow) = SIL (mlodsze 8 bitow) + starsze 8 bitow
                 (tylko tryb 64-bit)
               o indeks docelowy:
                 RDI (64 bity) = EDI (mlodsze 32 bity) + starsze 32 bity,
                 EDI (32 bity) = DI (mlodsze 16 bitow) + starsze 16
                 bitow,
                 DI (16 bitow) = DIL (mlodsze 8 bitow) + starsze 8 bitow
                 (tylko tryb 64-bit)
            Rejestry indeksowe najczesciej sluza do operacji na dlugich
            lancuchach danych, w tym napisach i tablicach.
          + rejestry wskaznikowe:
               o wskaznik bazowy:
                 RBP (64 bity) = EBP (mlodsze 32 bity) + starsze 32 bity,
                 EBP (32 bity) = BP (mlodsze 16 bitow) + starsze 16
                 bitow.
                 BP (16 bitow) = BPL (mlodsze 8 bitow) + starsze 8 bitow
                 (tylko tryb 64-bit)
                 Najczesciej sluzy do dostepu do zmiennych lokalnych.
               o wskaznik stosu:
                 RSP (64 bity) = ESP (mlodsze 32 bity) + starsze 32 bity,
                 ESP (32 bity) = SP (mlodsze 16 bitow) + starsze 16
                 bitow.
                 SP (16 bitow) = SPL (mlodsze 8 bitow) + starsze 8 bitow
                 (tylko tryb 64-bit)
                 Sluzy do dostepu do stosu (o tym nieco pozniej).
               o wskaznik instrukcji:
                 RIP (64 bity) = EIP (mlodsze 32 bity) + starsze 32 bity,
                 EIP (32 bity) = IP (mlodsze 16 bitow) + starsze 16
                 bitow.
                 Mowi procesorowi, skad ma pobierac instrukcje do
                 wykonywania.
    2. rejestry segmentowe (wszystkie 16-bitowe):
          + segment kodu CS - mowi procesorowi, gdzie znajduja sie dla
            niego instrukcje.
          + segment danych DS - ten najczesciej pokazuje na miejsce,
            gdzie trzymamy nasze zmienne.
          + segment stosu SS - dzieki niemu wiemy, w ktorym segmencie
            jest nasz stos. O tym, czym w ogole jest stos, powiem w
            nastepnej czesci.
          + segment dodatkowy ES - czesto uzywany, gdy chcemy cos napisac
            lub narysowac na ekranie bez pomocy Windows, DOSa czy nawet
            BIOSu.
          + FS i GS (dostepne dopiero od 80386) - nie maja specjalnego
            przeznaczenia. Sa tu na wypadek, gdyby zabraklo nam innych
            rejestrow segmentowych.
    3. rejestr stanu procesora: FLAGI (16-bitowe), E-FLAGI (32-bitowe)
       lub R-FLAGI (64-bitowe).
       Sluza one przede wszystkim do badania wyniku ostatniej operacji
       (na przyklad czy nie wystapilo przepelnienie, czy wynik jest
       zerem, itp.). Najwazniejsze flagi to CF (carry flag - flaga
       przeniesienia), OF (overflow flag - flaga przepelnienia), SF (sign
       flag - flaga znaku), ZF (zero flag - flaga zera), IF (interrupt
       flag - flaga przerwan), PF (parity flag - flaga parzystosci), DF
       (direction flag - flaga kierunku).

   Uzycie litery "R" przed symbolem rejestru, na przyklad RCX, oznacza
   rejestr 64-bitowy, dostepny tylko na procesorach 64-bitowych.
   Uzycie litery "E" przed symbolem rejestru, na przyklad EAX, oznacza
   rejestr 32-bitowy, dostepny tylko na procesorach rodziny 80386 lub
   lepszych. Nie dotyczy to rejestru ES.
   Napisy

     RAX = EAX+starsze 32 bity; EAX=AX + starsze 16 bitow; AX=AH+AL

   oznaczaja takie zaleznosci miedzy tymi rejestrami:
   (przeskocz rozwiniecie rejestru RAX)
                                RAX (64 bity)
                                    |               EAX (32b)
   00000000000000000000000000000000 | 0000000000000000 | 00000000 | 00000000
                  32b               |        16b       |        AX(16b)
                                    |                  |   AH(8b) |   AL(8b)

   Napisy

     RSI = ESI + starsze 32 bity; ESI = SI + starsze 16 bitow; SI =
     SIL+starsze 8 bitow

   oznaczaja:
   (przeskocz rozwiniecie rejestru RSI)
                                RSI (64 bity)
                                    |               ESI (32b)
   00000000000000000000000000000000 | 0000000000000000 | 00000000 | 00000000
                  32b               |        16b       |        SI(16b)
                                    |                  |    8b    |  SIL(8b)

   Tak, w DOSie mozna uzywac rejestrow 32-bitowych (o ile posiada sie
   80386 lub nowszy). Mozna nawet 64-bitowych, jesli tylko posiada sie
   wlasciwy procesor.

   Jedna wazna uwaga - miedzy nazwami rejestrow moze pojawic sie
   dwukropek w dwoch roznych znaczeniach:
     * zapis "DX : AX" (lub 2 dowolne zwykle rejestry) bedzie oznaczac
       liczbe, ktorej starsza czesc znajduje sie w rejestrze po lewej
       stronie (DX), a mlodsza - w tym z prawej (AX). Wartosc liczby
       wynosi DX*65536 + AX.
     * zapis "CS : SI" (rejestr segmentowy + dowolny zwykly) bedzie
       najczesciej oznaczac wskaznik do jakiegos obiektu w pamieci (o
       pamieci opowiem nastepnym razem). Rejestr segmentowy zawiera
       oczywiscie segment, w ktorym znajduje sie ow obiekt, a rejestr
       zwykly - offset (przesuniecie, adres w tym segmencie) tegoz
       obiektu.

   Na razie nie musicie sie przejmowac tymi dwukropkami. Mowie to tylko
   dlatego, zebyscie nie byli zaskoczeni, gdyz w przyszlosci sie pojawia.

   Programista moze odnosic sie bezposrednio do wszystkich wymienionych
   rejestrow, z wyjatkiem *IP oraz flag procesora (z wyjatkami).

   Jak widac po ich rozmiarach, do rejestrow 8-bitowych mozna wpisac
   liczbe z przedzialu 0-255 (lub od -128 do 127, gdy najwyzszy, siodmy
   bit sluzy nam jako bit oznaczajacy znak liczby), w 16-bitowych
   zmieszcza sie liczby 0-65535 (od -32768 do 32767), a w 32-bitowych -
   liczby od 0 do 4.294.967.295 (od -2.147.483.648 do 2.147.483.647)
   Dobrym, choc trudnym w odbiorze zrodlem informacji sa: Intel
   Architecture Software Developer's Manual (IASDM) dostepny ZA DARMO ze
   stron Intela oraz DARMOWE podreczniki AMD64 Architecture Programmer's
   Manual firmy AMD
     _________________________________________________________________

Pisanie i kompilowanie (asemblowanie) swoich programow

   Jak pisac programy w asemblerze?

   Nalezy zaopatrzyc sie w:
     * Edytor tekstu, mogacy zapisywac pliki formatu TXT (bez
       formatowania), na przyklad Programmer's File Editor, Quick Editor,
       The Gun (wszystkie sa na www.movsd.com) czy zwykly Notatnik
     * Kompilator jezyka asembler (patrz dalej)
     * Odpowiedni program laczacy (kosolidator, ang. linker), chyba ze
       kompilator ma juz taki wbudowany, jak na przyklad A86, NASM lub
       FASM (patrz dalej)

   Wtedy wystarczy napisac w edytorze tekstu plik zawierajacy komendy
   procesora (o tym pozniej), zapisac go z rozszerzeniem .ASM, po czym
   uzyc kompilatora, aby przetworzyc program na kod rozumiany przez
   procesor.

   Jakiego kompilatora uzyc?

   Istnieje wiele kompilatorow jezyka asembler. Do najpopularniejszych
   naleza Turbo asembler firmy Borland, Microsoft Macro asembler (MASM),
   Netwide asembler Project (NASM), A86/A386, NBASM, FASM, HLA.

   Mozna je sciagnac z internetu:
   (przeskocz adresy stron kompilatorow)
     * Strona glowna NASMa: sf.net/projects/nasm
     * A86 z eji.com
     * Flat asembler (FASM): flatasembler.net
     * MASM z Webster.cs.ucr.edu lub z www.movsd.com (wersje 32-bitowe)
     * HLA Webster.cs.ucr.edu

   Po skompilowaniu pliku z kodem zrodlowym nalezy uzyc programu
   laczacego, dostepnego zwykle z odpowiednim kompilatorem (na przyklad
   tlink z tasm, link z masm).

   Mamy wiec juz wszystko, co potrzeba. Zaczynamy pisac. Bede tutaj
   uzywal skladni Turbo asemblera zgodnego z MASMem oraz FASMa i NASMa.
   (przeskocz program w wersji TASM)
        ; wersja TASM
        .model tiny
        .code
        org 100h

        start:
                mov     ah, 9
                mov     dx, offset info
                int     21h

                mov     ah, 0
                int     16h

                mov     ax, 4C00h
                int     21h

        info    db      "Czesc.$"

        end start
     _________________________________________________________________

   Teraz wersja NASM:
   (przeskocz program w wersji NASM)
        ; wersja NASM

        ; nie ma ".model" ani ".code"
        ; tu mozna wstawic:
        ; section .text
        ; aby dac znac NASMowi, ze to bedzie w sekcji kodu.
        ; Nie jest to jednak wymagane, bo to jest sekcja domyslna.

        org 100h

        start:                          ; nawet tego NASM nie wymaga
                mov     ah, 9
                mov     dx, info        ; nie ma slowa "offset"
                int     21h

                mov     ah, 0
                int     16h

                mov     ax, 4C00h
                int     21h

        info    db      "Czesc.$"

        ; nie ma "end start"
     _________________________________________________________________

   Teraz wersja FASM
   (przeskocz program w wersji FASM)
        ; wersja FASM

        format binary

        ; nie ma ".model" ani ".code"
        org 100h

        start:                          ; nawet tego FASM nie wymaga
                mov     ah, 9
                mov     dx, info        ; nie ma slowa "offset"
                int     21h

                mov     ah, 0
                int     16h

                mov     ax, 4C00h
                int     21h

        info    db      "Czesc.$"

        ; nie ma "end start"
     _________________________________________________________________

   Bez paniki! Teraz omowimy dokladnie, co kazda linia robi.
     * linie lub napisy zaczynajace sie srednikiem
       Traktowane sa jako komentarze i sa calkowicie ignorowane przy
       kompilacji. Rozmiar skompilowanego programu wynikowego nie zalezy
       od ilosci komentarzy. Dlatego najlepiej wstawiac tyle komentarzy,
       aby inni (rowniez my) mogli pozniej zrozumiec nasz kod.
     * .model tiny (pamietajcie o kropce) lub format binary (w FASMie)
       Wskazuje kompilatorowi rodzaj programu. Jest kilka takich
       dyrektyw:
          + tiny: kod i dane mieszcza sie w jednym 64kB segmencie. Typowy
            dla programow typu .com
          + small: kod i dane sa w roznych segmentach, ale obydwa sa
            mniejsze od 64kB
          + medium: kod moze byc > 64kB, ale dane musza byc < 64kB
          + compact: kod musi byc < 64kB, dane moga miec wiecej niz 64kB
          + large: kod i dane moga byc > 64kB, ale tablice musza byc <
            64kB
          + huge: kod, dane i tablice moga byc > 64kB
     * .code (tez z kropka)
       Wskazuje poczatek segmentu, gdzie znajduje sie kod programu. Mozna
       jednak w tym segmencie umieszczac dane, ale nalezy to robic tak,
       aby nie staly sie one czescia programu. Zwykle wpisuje sie je za
       ostatnia komenda konczaca program. Procesor przeciez nie wie, co
       jest pod danym adresem i z mila checia potraktuje to cos jako
       instrukcje, co moze prowadzic do przykrych konsekwencji. Swoje
       dane umieszczajcie tak, aby w zaden sposob strumien wykonywanych
       instrukcji nie wszedl na nie.
       Sa tez inne dyrektywy: .data, deklarujaca poczatek segmentu z
       danymi oraz .stack, deklarujaca segment stosu (o tym pozniej),
       ktorej nie mozna uzywac w programach typu ".com", gdzie stos jest
       automatycznie ustawiany.
     * org 100h (bez kropki)
       Ta linia mowi kompilatorowi, ze nasz kod bedzie (dopiero po
       uruchomieniu!) znajdowal sie pod adresem 100h (256 dziesietnie) w
       swoim segmencie. To jest typowe dla programow .com. DOS,
       uruchamiajac taki program, szuka wolnego segmentu i kod programu
       umieszcza dopiero pod adresem (czasami zwanym offsetem -
       przesunieciem) 100h.
       Co jest wiec wczesniej? Wiele ciekawych informacji, z ktorych
       chyba najczesciej uzywana jest linia polecen programu (parametry
       uruchomienia, na przyklad rozne opcje itd.).
       Dyrektywa org podana na poczatku kodu NIE wplywa na rozmiar
       programu, ulatwia kompilatorowi okreslenie adresow roznych etykiet
       (w tym danych) znajdujacych sie w programie.
       Jesli chcemy tworzyc programy typu .com, nalezy zawsze podac "org
       100h" i opcje /t dla Turbo Linkera.
     * "start:" (z dwukropkiem) i "end start" (bez dwukropka)
       Mowia kompilatorowi, gdzie sa odpowiednio: poczatek i koniec
       programu.
     * mov ah,9
       Do 8-bitowego rejestru AH (gornej czesci 16-bitowego AX) wstaw
       (MOV = move, przesun) wartosc 9. Po co i czemu akurat 9? Zaraz
       zobaczymy.
       Najpierw powiem o czyms innym: komenda MOV ma wazne ograniczenia:
         1. nie mozna skopiowac jedna komenda MOV komorki pamieci do
            innej komorki pamieci, czyli takie cos:
                mov     [a], [b]
            (gdzie a i b - dwie zmienne w pamieci) jest zabronione.
            O tym, co oznaczaja nawiasy kwadratowe, czyli o adresowaniu
            zmiennych w pamieci - nastepnym razem.
         2. nie mozna skopiowac jedna komenda MOV jednego rejestru
            segmentowego (cs, ds, es, ss, fs, gs) do innego rejestru
            segmentowego, czyli dzialanie
                mov     es, ds
            jest zabronione.
         3. Nie mozna do rejestru segmentowego bezposrednio wpisac jakies
            wartosci, czyli nie mozna
                mov     ds, 0
            ale mozna:
                mov     bx, 0
                mov     ds, bx
     * mov dx,offset info
       Do rejestru danych (DX, 16-bitowy) wstaw offset (adres wzgledem
       poczatku segmentu) etykiety "info". Mozna obliczac adresy nie
       tylko danych, ale etykiet znajdujacych sie w kodzie programu.
     * int 21h
       INT = interrupt = przerwanie. Nie jest to jednak znane na przyklad
       z kart dzwiekowych przerwanie typu IRQ. Wywolujac przerwanie 21h
       (33 dziesietnie) uruchamiamy jedna z funkcji DOSa. Ktora? O tym
       zazwyczaj mowi rejestr AX. W spisie przerwan Ralfa Brown'a (RBIL)
       patrzymy:
       (przeskocz opis int 21h, ah=9)
                INT 21 - DOS 1+ - WRITE STRING TO STANDARD OUTPUT
                AH = 09h
                DS:DX -> $-terminated string
       Juz widzimy, czemu do AH poszla wartosc 9. Chcielismy uruchomic
       funkcje, ktora wypisuje na na ekran ciag znakow zakonczony znakiem
       dolara. Adres tego ciagu musi sie znajdowac w parze rejestrow: DS
       wskazuje segment, w ktorym znajduje sie ten ciag, a DX - jego
       adres w tym segmencie. Dlatego bylo mov dx,offset info.
       Zaraz, zaraz! Ale przeciez my nic nie robilismy z DS, a dane
       znajduja sie przeciez w segmencie kodu! I to dziala?
       Oczywiscie! Programy typu .com sa male. Tak male, ze mieszcza sie
       w jednym segmencie pamieci. Dlatego przy ich uruchamianiu DOS
       ustawia nam CS=DS=ES=SS. Nie musimy sie wiec o to martwic.
       Opis podstawowych funkcji kilku przerwan znajdziecie na jednej z
       moich podstron, poswieconej najczesciej stosowanym funkcjom
       przerwan, gdzie znajdziecie takze instrukcje budowania RBIL.
     * mov ah,0
       Do rejestru AH wpisz 0. Czemu? Zaraz zobaczymy. Ale najpierw
       wspomne o czyms innym. Otoz,
                mov     rejestr, 0
       nie jest najlepszym sposobem na wyzerowanie danego rejestru.
       Szybsze lub krotsze sa dwa inne:
                xor     rej1, rej1 ; 1 xor 1 = 0 oraz 0 xor 0 = 0.
                                   ; Stad   "cos XOR to_samo_cos"
                                   ; zawsze daje 0.

                sub     rej1, rej1      ; sub=subtract=odejmij.
                                        ; rej1 - rej1 = 0
       Ja zwykle uzywam XOR.
     * int 16h
       Kolejne przerwanie, wiec znowu do listy Ralfa Brown'a:
       (przeskocz opis int 16h, ah=0)
                INT 16 - KEYBOARD - GET KEYSTROKE
                AH = 00h
                Return: AH = BIOS scan code
                        AL = ASCII character
       Ta funkcja pobiera znak z klawiatury i zwraca go w rejestrze AL.
       Jesli nie nacisnieto nic, poczeka, az uzytkownik nacisnie.
     * mov ax,4c00h
       Do rejestru AX wpisujemy wartosc 4c00 szesnastkowo.
     * int 21h
       Znow przerwanie DOSa, funkcja 4ch. Patrzymy do RBIL:
       (przeskocz opis int 21h, ah=4ch)
          INT 21 - DOS 2+ - "EXIT" - TERMINATE WITH RETURN CODE
                AH = 4Ch
                AL = return code
                Return: never returns
       Jak widzimy, ta funkcja powoduje wyjscie z powrotem do DOSa, z
       numerem bledu (errorlevel) w AL rownym 0. Przyjmuje sie, ze 0
       oznacza, iz program zakonczyl sie bez bledow. Jak widac po
       rozmiarze rejestru AL (8 bitow), program moze wyjsc z 2^8=256
       roznymi numerami bledu.
     * info db "Czesc.$"
       Etykieta "info" opisujemy kilka bajtow, w tym przypadku zapisanych
       jako ciag znakow.
       A po co znak dolara $? Jak sobie przypomnimy, funkcja 9.
       przerwania DOSa wypisuje ciag znakow zakonczony wlasnie na znak
       dolara $. Gdyby tego znaczka nie bylo, DOS wypisywalby rozne
       smieci z pamieci, az trafilby na przypadkowy znak dolara $ nie
       wiadomo gdzie. O deklarowaniu zmiennych bedzie w nastepnej czesci.
     * end start
       Koniec programu.

   Programik kompilujemy poleceniem:
        tasm naszplik.asm
        tlink naszplik.obj /t

   (opcja /t - utworzy plik typu .com).
   Jesli otrzymujecie komunikaty o braku pamieci, mozecie wyprobowac
   nastepujacy sposob:
        tasmx naszplik.asm
        tlink naszplik.obj /t /3

   Dla NASMa kompilacja wyglada tak:
        nasm -o naszplik.com -f bin naszplik.asm

   (-o = nazwa pliku wyjsciowego
   -f = format pliku. Bin = binarny = na przyklad COM lub SYS).

   A dla FASMa:
        fasm naszplik.asm naszplik.com

   Kompilacja, nawet programu w asemblerze (zwana czasem asemblacja), ma
   kilka etapow:
     * pre-processing - w tej fazie preprocesor przetwarza dyrektywy
       takie jak definicje stalych, dolaczanie innych plikow do kodu,
       rozwiniecia makr i inne, zanim poda program kompilatorowi do
       kompilacji
     * kompilacja - na tym etapie tworzony jest binarny kod programu
       wynikowego. Podprogram kompilatora sprawdza skladnie instrukcji,
       zmienia je na ich binarne odpowiedniki, przetwarza zmienne na ich
       binarne odpowiedniki, sprawdza, czy wszystkie wykorzystane symbole
       (na przyklad u nas zmienna info) sa zadeklarowane, sprawdza, czy
       skoki mieszcza sie w granicach i wykonuje inne niezbedne
       czynnosci, w tym optymalizacje. Pozostawia jednak adresy symboli
       nieuzupelnione.
     * linkowanie (konsolidowanie) - na tym etapie nastepuje sprawdzenie,
       czy wszystkie symbole pozostawione przez kompilator do
       uzupelnienia sa dostepne w samym programie lub innych plikach
       podanych linkerowi do polaczenia. Jesli wszystkie symbole sa
       obecne, nastepuje wstawianie ich adresow do programu wynikowego i
       wygenerowanie samego programu.

   Jesli do programu nie dolaczamy innych juz skompilowanych plikow ani
   bibliotek, to niektore kompilatory nie wymagaja osobnego linkera i
   moga same sobie poradzic z wygenerowaniem programu wyjsciowego. Widac
   to na przyklad w wywolaniach NASMa i FASMa powyzej.

   Teraz uruchamiamy naszplik.com i cieszymy sie swoim dzielem.

   Milego eksperymentowania.

     Na swiecie jest 10 rodzajow ludzi:
     ci, ktorzy rozumieja liczby binarne i ci, ktorzy nie.

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

Cwiczenia

    1. Poeksperymentujcie sobie, wstawiajac z rozne znaki do napisu. Na
       przyklad, znaki o kodach ASCII 10 (Line Feed), 13 (Carriage
       Return), 7 (Bell). Pamietajcie tylko, ze znak dolara $ musi byc
       ostatni, dlatego robcie cos w stylu:
                info db "Czesc.", 00, 01, 02, 07, 10, 13, 10, 13, "$"
