   #Start Prev Next Contents

   Jak pisac programy w jezyku asembler pod Linuksem?

Czesc 3 - Podstawowe instrukcje, czyli poznajemy dialekt procesora

   Poznalismy juz rejestry, omowilismy pamiec. Pora zaczac na nich
   operowac.
   Zanim jednak zaczniemy, prosze Was o to, abyscie tej listy tez NIE
   uczyli sie na pamiec. Instrukcji jest duzo, a proba zrozumienia ich
   wszystkich na raz moze spowodowac niezly chaos. Co najwyzej
   przejrzyjcie te liste kilka razy, aby wiedziec mniej-wiecej, co kazda
   instrukcja robi.

   Instrukcje procesora mozna podzielic na kilka grup:
     * instrukcje przemieszczania danych
     * instrukcje arytmetyki binarnej
     * instrukcje arytmetyki dziesietnej
     * instrukcje logiczne
     * operacje na bitach i bajtach
     * instrukcje przekazujace kontrole innej czesci programu (sterujace
       wykonywaniem programu)
     * instrukcje operujace na lancuchach znakow
     * instrukcje kontroli flag
     * instrukcje rejestrow segmentowych
     * inne

   Zacznijmy je po kolei omawiac (nie omowie wszystkich).
    1. instrukcje przemieszczania danych.
       Tutaj zaliczymy juz wielokrotnie uzywane MOV oraz kilka innych:
       XCHG  , PUSH i POP.
    2. arytmetyka binarna.
       add do_czego,co  - dodaj
       sub od_czego,co  - odejmij
       inc cos / dec cos  - zwieksz/zmniejsz cos o 1
       cmp co, z_czym  - porownaj. Wykonuje dzialanie odejmowania "co
       minus z_czym", ale nie zachowuje wyniku, tylko ustawia flagi.
       Wynikiem moze byc ustawienie lub wyzerowanie jednej lub wiecej
       flag - zaznaczenie wystapienia jednego z warunkow. Glowne warunki
       to:
          + A - above - "ponad" (dla liczb traktowanych jako liczby bez
            znaku): "co > z_czym"
            (przeskocz przyklad uzycia warunku A)
                cmp al,bl
                ja al_wieksze_od_bl     ; ja -  jump if above
          + B - below - "ponizej" (bez znaku): co < z_czym
          + G - greater - "wiecej niz" (ze znakiem): co > z_czym
          + L - lower - "mniej niz" (ze znakiem): co < z_czym
          + O - overflow - przepelnienie (ze znakiem, na przyklad
            przebicie 32767 w gore) ostatniej operacji. Niekoniecznie
            uzywane przy cmp.
          + C - carry - przepelnienie (bez znaku, czyli przebicie na
            przyklad 65535 w gore)
            (przeskocz przyklad uzycia warunku C)
                add al,bl
                jc blad_przepelnienia   ; jc -  jump if carry
          + E lub Z - equal (rowny) lub zero. Te dwa warunki sa
            rownowazne.
            (przeskocz przyklady uzycia warunkow rownosci)
                cmp ax,cx
                je ax_rowne_cx
                ...
                sub bx,dx
                jz bx_rowne_dx
          + NE/NZ - przeciwienstwo poprzedniego: not equal/not zero.
          + NA - not above, czyli "nie ponad" - mniejsze lub rowne (ale
            dla liczb bez znaku)
          + NB - not below, czyli "nie ponizej" - wieksze lub rowne (dla
            liczb bez znaku)
          + NG - not greater, czyli "nie wiecej" - mniejsze lub rowne
            (ale dla liczb ze znakiem)
          + NL - not lower, czyli "nie mniej" - wieksze lub rowne (dla
            liczb ze znakiem)
          + NC - no carry, czyli brak przepelnienia
          + AE/BE - above or equal ("ponad lub rowne"), below or equal
            ("ponizej lub rowne")
          + NO - no overflow
    3. arytmetyka dziesietna
          + NEG - zmienia znak.
          + MUL, IMUL - mnozenie, mnozenie ze znakiem (ktore uwzglednia
            liczby ujemne)
            (przeskocz przyklady instrukcji mnozenia)
                mul cl          ; AX = AL*CL
                mul bx          ; DX:AX = AX*BX
                mul esi         ; EDX:EAX = EAX*ESI
                mul rdi         ; RDX:RAX = RAX*RDI

                imul eax        ; EDX:EAX = EAX*EAX
                imul ebx,ecx,2  ; EBX = ECX*2
                imul ebx,ecx    ; EBX = EBX*ECX
                imul si,5       ; SI = SI*5
            Zapis "rej1 : rej2" oznacza, ze starsza czesc wyniku znajdzie
            sie w pierwszym rejestrze podanej pary (DX, EDX, RDX), a
            mlodsza - w drugim (AX, EAX, RAX), gdyz wynik mnozenia dwoch
            liczb o dlugosci n bitow kazda wymaga 2n bitow.
          + DIV, IDIV - dzielenie, dzielenie ze znakiem (i jednoczesne
            obliczanie reszty z dzielenia).
            (przeskocz przyklady instrukcji dzielenia)
                div cl  ; AL = (AX div CL), AH = (AX mod CL)
                div bx  ; AX = (DX:AX div BX),
                        ; DX = (DX:AX mod BX)
                div edi ; EAX = (EDX:EAX div EDI),
                        ; EDX = (EDX:EAX mod EDI)
                div rsi ; RAX = (RDX:RAX div RSI),
                        ; RDX = (RDX:RAX mod RSI)
            Zapis "rej1 : rej2" oznacza, ze starsza czesc dzielnej jest
            oczekiwana w pierwszym rejestrze podanej pary (DX, EDX, RDX),
            a mlodsza - w drugim (AX, EAX, RAX). Jesli liczba miesci sie
            w rejestrze dla mlodszej czesci, rejestr starszy nalezy
            wyzerowac. Slowo "div" w powyzszych zapisach oznacza iloraz,
            a mod - reszte z dzielenia (modulo).
    4. Instrukcje bitowe (logiczne).
          + AND
          + OR
          + XOR
          + NOT
          + TEST
       Instrukcja TEST dziala tak samo jak AND z tym, ze nie zachowuje
       nigdzie wyniku, tylko ustawia flagi. Pokrotce wytlumacze te
       instrukcje:
       (przeskocz dzialanie instrukcji logicznych)
                0 AND 0 = 0     0 OR 0 = 0      0 XOR 0 = 0
                0 AND 1 = 0     0 OR 1 = 1      0 XOR 1 = 1
                1 AND 0 = 0     1 OR 0 = 1      1 XOR 0 = 1
                1 AND 1 = 1     1 OR 1 = 1      1 XOR 1 = 0
                NOT 0 = 1
                NOT 1 = 0
       Przyklady zastosowania:
       (przeskocz przyklady instrukcji logicznych)
                and ax,1        ; wyzeruje wszystkie bity z
                                ; wyjatkiem bitu numer 0.
                or ebx,1111b    ; ustawia (wlacza) 4 dolne bity.
                                ; Reszta bez zmian.
                xor cx,cx       ; CX = 0
                not dh          ; DH ma 0 tam, gdzie mial 1
                                ; i na odwrot
    5. Instrukcje przesuniecia bitow.
         1. SAL, SHL - shift left - przesuniecie w lewo
            bit7 = bit6, bit6 = bit5, ... , bit1 = bit0, bit0 = 0.
         2. SHR - shift logical right - przesuniecie logiczne w prawo
            bit0 = bit1, bit1 = bit2, ... , bit6 = bit7, bit7 = 0
         3. SAR - shift arithmetic right - przesuniecie arytmetyczne (z
            zachowaniem znaku) w prawo
            bit0 = bit1, bit1 = bit2, ... , bit6 = bit7, bit7 = bit7 (bit
            znaku zachowany!)
            Najstarszy bit w rejestrze nazywa sie czasem wlasnie bitem
            znaku.
         4. ROL - rotate left - obrot w lewo
            bit7 = bit6, ... , bit1 = bit0, bit0 = stary bit7
         5. RCL - rotate through carry left - obrot przez flage CF (flage
            przepelnienia) w lewo
            CF = bit7, bit7 = bit6, ... , bit1 = bit0, bit0 = stara CF
         6. ROR - rotate right - obrot w prawo
            bit0 = bit1, ... , bit6 = bit7, bit7 = stary bit0
         7. RCR - rotate through carry right - obrot przez flage CF w
            prawo
            CF = bit0, bit0 = bit1, ... , bit6 = bit7, bit7 = stara CF
       Oczywiscie, te instrukcje moga dzialac na wartosciach wiekszych
       niz bajt - bit7 jako najstarszy jest tutaj tylko dla ilustracji.
       Przy pomocy SHL mozna przeprowadzac szybkie mnozenie, a dzieki SHR
       - szybkie dzielenie. Na przyklad, SHL AX,1 jest rownowazne
       przemnozeniu AX przez 2, SHL AX,5 - przez 2^5, czyli 32. SHR BX,4
       dzieli BX przez 16.
    6. Instrukcje sterujace wykonywaniem programu.
          + Skoki warunkowe (patrz: warunki powyzej): JA=JNBE, JAE=JNB,
            JNA=JBE, JNAE=JB, JG=JNLE (jump if greater - dla liczb ze
            znakiem) = jump if not lower or equal, JNG=JLE, JGE=JNL,
            JNGE=JL, JO, JNO, JC, JNC, JS (jump if sign czyli bit7 wyniku
            jest rowny 1), JNS, JP=JPE (jump if parity equal = liczba
            bitow rownych jeden jest parzysta), JNP=JPO.
          + Skoki bezwarunkowe: JMP, JMP SHORT, JMP FAR
          + Uruchomienia procedur: CALL [NEAR/FAR]
          + Powrot z procedury: RET, RETF.
          + Przerwania: INT, INTO (wywoluje przerwanie INT4 w razie
            przepelnienia), BOUND (int 5)
          + Instrukcje petli: LOOP. Skladnia: "LOOP gdzies". Jesli CX
            jest rozny od 0, to skacz "gdzies".
    7. Operacje na lancuchach znakow.
         1. LODS[B/W/D/Q] - Load Byte/Word/Dword/Qword - pobierz
            bajt/slowo/podwojne slowo/poczworne slowo
            MOV AL/AX/EAX/RAX , DS:[SI/ESI/RSI]
            ADD SI,1/2/4/8 ; ADD, gdy flaga kierunku DF = 0, SUB gdy DF =
            1
         2. STOS[B/W/D/Q] - Store Byte/Word/Dword/Qword - zapisz
            bajt/slowo/podwojne slowo/poczworne slowo
            MOV ES:[DI/EDI/RDI], AL/AX/EAX/RAX
            ADD DI,1/2/4/8 ; ADD/SUB jak wyzej
         3. MOVS[B/W/D/Q] - Move Byte/Word/Dword/Qword - przesun
            (skopiuj) bajt/slowo/podwojne slowo/poczworne slowo
            MOV ES:[DI/EDI/RDI], DS:[SI/ESI/RSI] ; to nie jest
            instrukcja!
            ADD DI,1/2/4/8 ; ADD/SUB jak wyzej
            ADD SI,1/2/4/8
         4. CMPS[B/W/D/Q] - Compare Byte/Word/Dword/Qword - porownaj
            bajt/slowo/podwojne slowo/poczworne slowo
            CMP DS:[SI/ESI/RSI], ES:[DI/EDI/RDI] ; to nie jest
            instrukcja!
            ADD SI,1/2/4/8 ; ADD/SUB jak wyzej
            ADD DI,1/2/4/8
         5. SCAS[B/W/D/Q] - Scan Byte/Word/Dword/Qword - szukaj
            bajtu/slowa/podwojnego slowa/poczwornego slowa
            skanuje lancuch bajtow/slow/podwojnych slow/poczwornych slow
            pod ES:[DI/EDI/RDI] w poszukiwaniu, czy jest tam wartosc
            wskazana przez AL/AX/EAX/RAX.
       Do kazdej z powyzszych instrukcji mozna z przodu dodac przedrostek
       REP (repeat), co spowoduje, ze bedzie ona wykonywana, az CX stanie
       sie zerem, albo REPE/REPZ albo REPNE/REPNZ co spowoduje, ze bedzie
       ona wykonywana, dopoty CX nie jest zerem i jednoczesnie flaga ZF
       (flaga zera) bedzie rowna odpowiednio 1 lub 0.
    8. Instrukcje wejscia/wyjscia do portow.
       Sa one bardziej szczegolowo opisane w czesci poswieconej portom,
       ale podam tu skrot:
          + IN
            IN AL/AX/EAX, port/DX
            Pobierz z portu 1/2/4 bajty i wloz do AL/AX/EAX (od
            najmlodszego). Jesli numer portu jest mniejszy lub rowny 255,
            mozna go podac bezposrednio. Jesli wiekszy - trzeba uzyc DX.
          + OUT
            OUT port/DX, AL/AX/EAX
            Uwagi jak przy instrukcji IN.
    9. Instrukcje flag
          + STC/CLC - set carry / clear carry. Do flagi przepelnienia CF
            wstaw 1 lub 0, odpowiednio.
          + STD/CLD. Ustaw flage kierunku DF na 1 lub 0, odpowiednio.
          + STI/CLI. Interrupt Flag (flaga wlaczenia przerwan) IF = 1 lub
            IF = 0, odpowiednio. Gdy IF=0, przerwania sprzetowe sa
            blokowane.
          + Przenoszenie flag
            PUSHF / PUSHFD / PUSHFQ - umiesc flagi na stosie (16, 32 i 64
            bity flag, odpowiednio)
            POPF / POPFD / POPFQ - zdejmij flagi ze stosu (16/32/64 bity
            flag)
            SAHF / LAHF - zapisz AH w pierwszych 8 bitach flag / zapisz
            pierwsze 8 bitow flag w AH.
   10. Instrukcja LEA - Load Effective Address - zaladuj wyliczony adres.
       Wykonanie:
                lea     rej, [pamiec]
       jest rownowazne:
       (przeskocz pseudo-kod LEA)
                mov     rej, pamiec             ; NASM/FASM
       Po co wiec osobna instrukcja? Otoz, LEA przydaje sie w wielu
       sytuacjach do obliczania zlozonych adresow. Kilka przykladow:
         1. Jak w 1 instrukcji sprawic, ze EAX = EBP-12 ?
            Odpowiedz: lea eax, [ebp-12]
         2. Niech EBX wskazuje na tablice o 20 elementach o rozmiarze 8
            kazdy. Jak do ECX zapisac adres jedenastego elementu, a do
            EDX elementu o numerze EDI?
            Odpowiedz: lea ecx, [ebx + 11*8] oraz lea edx,[ebx+edi*8]
         3. Jak w 1 instrukcji sprawic, ze ESI = EAX*9?
            Odpowiedz: lea esi, [eax + eax*8]

   Pominalem mniej wazne instrukcje operujace na rejestrach segmentowych
   i klika innych instrukcji. Te, ktore tu podalem, wystarczaja
   absolutnie na napisanie wiekszosci programow, ktore mozna zrobic.
   Wszystkie informacje przedstawione w tej czesci pochodza z tego samego
   zrodla: Intel i AMD

     Byle glupiec potrafi napisac kod, ktory zrozumie komputer. Dobry
     programista pisze taki kod, ktory zrozumie czlowiek.

   Poprzednia czesc kursu (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. Zapisz instrukcje: do rejestru AX dodaj 5, od rejestru SI odejmij
       178.
    2. Nie uzywajac cyfry jeden napisz jedna instrukcje, ktora zmniejszy
       rejestr DX o jeden.
    3. Przemnoz wartosc rejestru EDI przez 2 na przynajmniej dwa rozne
       sposoby po jednej instrukcji. Postaraj sie nie uzywac instrukcji
       (I)MUL.
    4. W jednej instrukcji podziel wartosc rejestru BP przez 8.
    5. Nie uzywajac instrukcji MOV spraw, by DX mial wartosc 0 (na
       przynajmniej 3 sposoby, kazdy po jednej instrukcji).
    6. Nie uzywajac instrukcji przesuwania bitow SH* ani mnozenia *MUL
       przemnoz EBX przez 8. Mozesz uzyc wiecej niz 1 instrukcji.
    7. W dwoch instrukcjach spraw, by EDI rownal sie 7*ECX. Postaraj sie
       nie uzywac instrukcji (I)MUL.
