   #Start Prev Contents

   Jak pisac programy w jezyku asembler pod Linuksem?

Czesc 16 - Operacje na lancuchach znakow. Wyrazenia regularne

   Jak wiemy, lancuch znakow to nic innego jak jednowymiarowa tablica
   bajtow. Dlatego podane tutaj informacje tak samo dzialaja dla tablic.

   W zestawie instrukcji procesora przeznaczonych jest klika rozkazow
   przeznaczonych specjalnie do operacji na lancuchach znakow: MOVS,
   CMPS, SCAS, LODS, STOS. To nimi wlasnie teraz sie zajmiemy.

   Rozkazy te operuja na lancuchach spod DS:[ESI/RSI] lub ES:[EDI/RDI]
   lub obydwu. Rejestry segmentowe nie beda tutaj grac duzej roli bo
   pokazuja zawsze na ten sam segment, wiec bedziemy je pomijac. Oraz,
   zajmiemy sie omowieniem instrukcji tylko na ESI oraz EDI, pomijajac
   rejestry 64-bitowe, dla ktorych wszystko wyglada analogicznie.

   Instrukcje wystepuja w 4 formach: *B, *W, *D (dla 32-bitowych) i *Q
   (dla 64-bitowych). Operuja one odpowiednio na bajtach, slowach,
   podwojnych slowach i danych 64-bitowych. Po kazdym wykonaniu
   pojedynczej operacji zwiekszaja rejestry SI/ESI/RSI i DI/EDI/RDI o 1,
   2, 4 lub 8, przechodzac tym samym na nastepne elementy.

   UWAGA: Zwiekszaniem rejestrow *SI i *DI steruje flaga kierunku DF:
   jesli rowna 0, oba rejestry sa zwiekszane, jesli 1 - sa zmniejszane o
   odpowiednia liczbe (co pozwala na przyklad na przeszukiwanie lancuchow
   wspak). Flage DF mozna wyczyscic instrukcja CLD, a ustawic instrukcja
   STD.
     _________________________________________________________________

MOVS

   (przeskocz MOVS)

   Zasada dzialania tej instrukcji jest przeniesienie odpowiedniej ilosci
   bajtow spod [ESI] i umieszczenie ich pod [EDI]. Ale przeniesienie co
   najwyzej 4 bajtow to przeciez zaden wysilek:
        mov     eax, [esi]
        mov     [edi], eax

   Dlatego wymyslono prefiks REP (powtarzaj). Jest on wazny tylko dla
   instrukcji operujacych na lancuchach znakow oraz instrukcji INS i
   OUTS. Powoduje on powtorzenie dzialania instrukcji ECX razy. Teraz juz
   widac mozliwosci tej instrukcji. Chcemy przeniesc 128 bajtow? Prosze
   bardzo:
        mov     esi, zrodlo
        mov     edi, cel
        cld                             ; idz do przodu
        mov     ecx, 128
        rep     movsb

   Oczywiscie, dwie ostatnie linijki mozna bylo zastapic czyms takim i
   tez by podzialalo:
        mov     ecx, 32
        rep     movsd

   Sposob drugi oczywiscie jest lepszy, bo ma mniej operacji (choc
   najwiecej czasu i tak zajmuje samo rozpedzenie sie instrukcji REP).

   Instrukcji REP MOVS* mozna uzywac do przenoszenia malej ilosci danych.
   Gdy ilosci danych rosna, lepiej sprawuja sie MMX i SSE (patrz: czesc
   6).
     _________________________________________________________________

CMPS

   (przeskocz CMPS)

   Ta instrukcja porownuje odpowiednia liczbe bajtow spod [ESI] i [EDI].
   Ale nas oczywiscie nie interesuje porownywanie pojedynczych ilosci.
   Myslimy wiec o prefiksie REP, ale po chwili zastanowienia dochodzimy
   do wniosku, ze w ten sposob otrzymamy tylko wynik ostatniego
   porownania, wszystkie wczesniejsze zostana zaniedbane. Dlatego
   wymyslono prefiksy REPE/REPZ (powtarzaj, dopoki rowne/flaga ZF
   ustawiona) oraz REPNE/REPNZ (powtarzaj, dopoki nie rowne/flaga ZF =
   0).
   Na przyklad, aby sprawdzic rownosc dwoch lancuchow, zrobimy tak:
        mov     esi, lancuch1
        mov     edi, lancuch2

        mov     ecx, 256        ; tyle bajtow maksymalnie chcemy porownac
        cld
        repe    cmpsb           ; dopoki sa rowne, porownuj dalej
        jnz     lancuchy_nie_rowne

   REPE przestanie dzialac na pierwszych rozniacych sie bajtach. W ECX
   otrzymamy pewna liczbe. Roznica liczby 256 i tej liczby mowi o ilosci
   identycznych znakow i jednoczesnie o tym, na ktorej pozycji znajduja
   sie rozniace sie znaki.
   Oczywiscie, jesli po ukonczeniu REPE rejestr ECX=0, to znaczy ze
   sprawdzono wszystkie znaki (i wszystkie dotychczas byly rowne). Wtedy
   flagi mowia o ostatnim porownaniu.
   REPE CMPS ustawia flagi jak normalna instrukcja CMP.
     _________________________________________________________________

SCAS

   (przeskocz SCAS)

   Ta instrukcja przeszukuje lancuch znakow pod [EDI] w poszukiwaniu
   bajtu z AL, slowa z AX lub podwojnego slowa z EAX. Sluzy to do
   szybkiego znalezienia pierwszego wystapienia danego elementu w
   lancuchu.
   Przyklad: znalezc pozycje pierwszego wystapienia litery Z w lancuchu
   "lancuch1":
        mov     al, "Z"         ; poszukiwany element
        mov     edi, lancuch1
        mov     ecx, 256
        cld
        repne   scasb           ; dopoki sprawdzany znak rozny
                                ; od "Z", szukaj dalej

        je      znaleziono

        mov     edi, -1         ; gdy nie znaleziono, zwracamy -1
        jmp     koniec

    znaleziono:
        sub     edi, lancuch1   ; EDI = pozycja znalezionego znaku w lancuchu

   REPNE przestanie dzialac w dwoch przypadkach: ECX=0 (wtedy nie
   znaleziono szukanego elementu) oraz wtedy, gdy ZF=1 (gdy po drodze
   natrafila na szukany element, wynik porownania ustawil flage ZF).
     _________________________________________________________________

LODS

   (przeskocz LODS)

   Instrukcje LODS* pobieraja do AL/AX/EAX odpowiednia liczbe bajtow spod
   [ESI]. Jak widac, prefiksy REP* nie maja tutaj sensu, bo w rejestrze
   docelowym i tak zawsze wyladuje ostatni element.
   Ale za to tej instrukcji mozna uzywac do pobierania poszczegolnych
   znakow do dalszego sprawdzania, na przyklad
        cld
    petla:
        lodsb                           ; pobierz kolejny znak

        cmp     al, 13
        jne     nie_enter

        cmp     al, "0"
        je      al_to_zero

        ....

        loop    petla
     _________________________________________________________________

STOS

   (przeskocz STOS)

   Instrukcja ta umieszcza AL/AX/EAX pod [EDI]. Poza oczywistym
   zastosowaniem, jakim jest na przyklad zapisywanie kolejnych podawanych
   przez uzytkownika znakow gdzies do tablicy, STOS mozna tez uzyc do
   szybkiej inicjalizacji tablicy w czasie dzialania programu lub do
   wyzerowania pamieci:
        mov     edi, tablica

        mov     eax, 11223344h
        mov     ecx, 1000
        cld
        rep     stosd
        ...

        tablica: times 1000 dd 0
     _________________________________________________________________

Wyrazenia regularne

   Wyrazenia regularne (regular expressions, regex) to po prostu ciagi
   znaczkow, przy uzyciu ktorych mozemy opisywac dowolne lancuchy znakow
   (adresy e-mail, WWW, nazwy plikow z pelnymi sciezkami, ...).
   Na wyrazenie regularne skladaja sie rozne symbole. Postaram sie je
   teraz po krotce omowic.
     * "aaa" (dowolny ciag znakow) - reprezentuje wszystkie lancuchy,
       ktore go zawieraja, na przyklad "laaaaaaaaaato".
     * ^ - oznacza poczatek linii (wiersza). Na przyklad wyrazenie
       "^asembler" reprezentuje wszystkie linie, ktore zaczynaja sie od
       ciagu znakow "asembler". Innymi slowy, kazda linia zaczynajaca sie
       od "asembler" pasuje do tego wyrazenia.
     * $ - oznacza koniec linii. Na przyklad wyrazenie "asm$"
       reprezentuje wszystkie linie, ktore koncza sie na "asm".
     * . (kropka) - dowolny znak (z wyjatkiem znaku nowej linii). Na
       przyklad wyrazenie "^a.m$" reprezentuje linie, ktore zawieraja w
       sobie tylko a*m, gdzie gwiazdka to dowolny znak (w tym cyfry). Do
       tego wzorca pasuja "asm", "aim", "a0m" i wiele innych.
     * | (Shift+BackSlash)- oznacza alternatywe. Na przyklad wyrazenie
       "a|b|z" reprezentuje dowolna z tych trzech liter i zadna inna.
     * (,) - nawiasy sluza do grupowania wyrazow. Na przyklad
       "^(aa)|(bb)|(asm)" reprezentuje linie, ktore zaczynaja sie od
       "aa", "bb" lub "asm".
     * [,] - wyznaczaja klase znakow. Na przyklad wszystkie wyrazy
       zaczynajace sie od "k", "a" lub "j" pasuja do wzorca "[ajk].*".
       Mozna tutaj podawac przedzialy znakow - wtedy 2 skrajne znaki
       oddzielamy myslnikiem, na przyklad "[a-z]". Umieszczenie w srodku
       znaku daszka ^ oznacza przeciwienstwo, na przyklad "[^0-9]"
       reprezentuje znaki, ktore nie sa cyfra (a tym samym wszystkie
       ciagi nie zawierajace cyfr).
     * ? - oznacza co najwyzej jedno wystapienie poprzedzajacego znaku
       lub grupy. Na przyklad, "ko?t" reprezentuje wyrazy "kot" i "kt",
       ale juz nie "koot".
     * * - oznacza dowolna liczbe wystapien poprzedzajacego znaku/grupy.
       Wyrazenie "ko*t" reprezentuje wiec wyrazy "kt", "kot", "koot",
       "kooot", itd.
     * + - oznacza co najmniej jedno wystapienie poprzedzajacego
       znaku/grupy. Na przyklad "al(fa)+" reprezentuje "alfa", "alfafa",
       "alfafafa" itd, ale nie "al".
     * {n} - oznacza dokladnie n wystapien poprzedzajacego znaku/grupy.
       Wyrazenie "[0-9]{7}" reprezentuje wiec dowolny ciag skladajacy sie
       dokladnie z 7 cyfr.
     * {n,} - oznacza co najmniej n wystapien poprzedzajacego
       znaku/grupy. Wyrazenie "[a-z]{2,}" reprezentuje wiec dowolny ciag
       znakow skladajacy sie co najmniej z 2 malych liter.
     * {n,m} - oznacza co najmniej n i co najwyzej m wystapien
       poprzedzajacego znaku/grupy. Wiec wyrazenie "[A-M]{3,7}"
       reprezentuje dowolny ciag znakow skladajacy sie z co najmniej 3 i
       co najwyzej 7 wielkich liter z przedzialu od A do M.
     * Jesli w lancuchu moze wystapic ktorys ze znakow specjalnych,
       nalezy go w wyrazeniu poprzedzic odwrotnym ukosnikiem "\".

   Dalsze przyklady:
     * ([a-zA-Z0-9]+\.?)+[a-zA-Z]+@([a-zA-Z0-9]+\.)+[a-zA-Z]{2,4} - adres
       e-mail (zapisany tak, by login ani domena nie konczyly sie kropka)
     * ([a-zA-Z]{3,6}://)?([a-zA-Z0-9/\-]+\.?)+[a-zA-Z0-9]+(#[a-zA-Z0-9\-
       ]+)? - adres (z protokolem lub bez) zasobu na serwerze (zapisany
       tak, by nie konczyl sie kropka, moze zawierac myslniki a w
       ostatnim czlonie takze znak #)

   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. Napisac program zawierajacy 2 tablice DWORDow o wymiarach 17 na
       31, po czym w trakcie dzialania programu wypelnic kazde pole
       pierwszej wartoscia FFEEDDCCh. Potem, 8 pierwszych elementow
       skopiowac do drugiej tablicy, a reszte drugiej wypelnic wartoscia
       BA098765h. Wtedy porownac zawartosc obu tablic i wyliczyc pierwsza
       pozycje, na ktorej sie roznia (powinna oczywiscie wynosic 9)
    2. Napisac wyrazenie regularne, ktore opisze:
          + wszystkie wyrazenia deklaracji zmiennych: DB, DW, DP, DQ, DT
          + znacznik HTML bez atrybutow, czyli cos wygladajace tak: < PRE
            > lub tak: < /LI > (bez spacji).
          + liczbe szesnastkowa dowolnej niezerowej dlugosci z
            ewentualnym przedrostkiem "0x" albo (do wyboru) przyrostkiem
            "H" lub "h".
