Jak pisać programy w języku asembler pod Linuksem?

Część 16 - Operacje na łańcuchach znaków. Wyrażenia regularne

Jak wiemy, łańcuch znaków to nic innego jak jednowymiarowa tablica bajtów. Dlatego podane tutaj informacje tak samo działają dla tablic.

W zestawie instrukcji procesora przeznaczonych jest klika rozkazów przeznaczonych specjalnie do operacji na łańcuchach znaków: MOVS, CMPS, SCAS, LODS, STOS. To nimi właśnie teraz się zajmiemy.

Rozkazy te operują na łańcuchach spod DS:[ESI/RSI] lub ES:[EDI/RDI] lub obydwu. Rejestry segmentowe nie będą tutaj grać dużej roli bo pokazują zawsze na ten sam segment, więc będziemy je pomijać. Oraz, zajmiemy się omówieniem instrukcji tylko na ESI oraz EDI, pomijając rejestry 64-bitowe, dla których wszystko wygląda analogicznie.

Instrukcje występują w 4 formach: *B, *W, *D (dla 32-bitowych) i *Q (dla 64-bitowych). Operują one odpowiednio na bajtach, słowach, podwójnych słowach i danych 64-bitowych. Po każdym wykonaniu pojedynczej operacji zwiększają rejestry SI/ESI/RSI i DI/EDI/RDI o 1, 2, 4 lub 8, przechodząc tym samym na następne elementy.

UWAGA: Zwiększaniem rejestrów *SI i *DI steruje flaga kierunku DF: jeśli równa 0, oba rejestry są zwiększane, jeśli 1 - są zmniejszane o odpowiednią liczbę (co pozwala na przykład na przeszukiwanie łańcuchów wspak). Flagę DF można wyczyścić instrukcją CLD, a ustawić instrukcją STD.




MOVS


(przeskocz MOVS)

Zasadą działania tej instrukcji jest przeniesienie odpowiedniej ilości bajtów spod [ESI] i umieszczenie ich pod [EDI]. Ale przeniesienie co najwyżej 4 bajtów to przecież żaden wysiłek:

	mov	eax, [esi]
	mov	[edi], eax

Dlatego wymyślono prefiks REP (powtarzaj). Jest on ważny tylko dla instrukcji operujących na łańcuchach znaków oraz instrukcji INS i OUTS. Powoduje on powtórzenie działania instrukcji ECX razy. Teraz już widać możliwości tej instrukcji. Chcemy przenieść 128 bajtów? Proszę bardzo:

	mov	esi, zrodlo
	mov	edi, cel
	cld				; idź do przodu
	mov	ecx, 128
	rep	movsb

Oczywiście, dwie ostatnie linijki można było zastąpić czymś takim i też by podziałało:

	mov	ecx, 32
	rep	movsd

Sposób drugi oczywiście jest lepszy, bo ma mniej operacji (choć najwięcej czasu i tak zajmuje samo rozpędzenie się instrukcji REP).

Instrukcji REP MOVS* można używać do przenoszenia małej ilości danych. Gdy ilości danych rosną, lepiej sprawują się MMX i SSE (patrz: część 6).




CMPS


(przeskocz CMPS)

Ta instrukcja porównuje odpowiednią liczbę bajtów spod [ESI] i [EDI]. Ale nas oczywiście nie interesuje porównywanie pojedynczych ilości. Myślimy więc o prefiksie REP, ale po chwili zastanowienia dochodzimy do wniosku, że w ten sposób otrzymamy tylko wynik ostatniego porównania, wszystkie wcześniejsze zostaną zaniedbane. Dlatego wymyślono prefiksy REPE/REPZ (powtarzaj, dopóki równe/flaga ZF ustawiona) oraz REPNE/REPNZ (powtarzaj, dopóki nie równe/flaga ZF = 0).
Na przykład, aby sprawdzić równość dwóch łańcuchów, zrobimy tak:

	mov	esi, lancuch1
	mov	edi, lancuch2

	mov	ecx, 256	; tyle bajtów maksymalnie chcemy porównać
	cld
	repe	cmpsb		; dopóki są równe, porównuj dalej
	jnz	lancuchy_nie_rowne

REPE przestanie działać na pierwszych różniących się bajtach. W ECX otrzymamy pewną liczbę. Różnica liczby 256 i tej liczby mówi o ilości identycznych znaków i jednocześnie o tym, na której pozycji znajdują się różniące się znaki.
Oczywiście, jeśli po ukończeniu REPE rejestr ECX=0, to znaczy że sprawdzono wszystkie znaki (i wszystkie dotychczas były równe). Wtedy flagi mówią o ostatnim porównaniu.
REPE CMPS ustawia flagi jak normalna instrukcja CMP.




SCAS


(przeskocz SCAS)

Ta instrukcja przeszukuje łańcuch znaków pod [EDI] w poszukiwaniu bajtu z AL, słowa z AX lub podwójnego słowa z EAX. Służy to do szybkiego znalezienia pierwszego wystąpienia danego elementu w łańcuchu.
Przykład: znaleźć pozycję pierwszego wystąpienia litery Z w łańcuchu lancuch1:

	mov	al, "Z"		; poszukiwany element
	mov	edi, lancuch1
	mov	ecx, 256
	cld
	repne	scasb		; dopóki sprawdzany znak różny
				; 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 łańcuchu

REPNE przestanie działać w dwóch przypadkach: ECX=0 (wtedy nie znaleziono szukanego elementu) oraz wtedy, gdy ZF=1 (gdy po drodze natrafiła na szukany element, wynik porównania ustawił flagę ZF).




LODS


(przeskocz LODS)

Instrukcje LODS* pobierają do AL/AX/EAX odpowiednią liczbę bajtów spod [ESI]. Jak widać, prefiksy REP* nie mają tutaj sensu, bo w rejestrze docelowym i tak zawsze wyląduje ostatni element.
Ale za to tej instrukcji można używać do pobierania poszczególnych znaków do dalszego sprawdzania, na przykład

	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 przykład zapisywanie kolejnych podawanych przez użytkownika znaków gdzieś do tablicy, STOS można też użyć do szybkiej inicjalizacji tablicy w czasie działania programu lub do wyzerowania pamięci:

	mov	edi, tablica

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

	tablica: times 1000 dd 0



Wyrażenia regularne

Wyrażenia regularne (regular expressions, regex) to po prostu ciągi znaczków, przy użyciu których możemy opisywać dowolne łańcuchy znaków (adresy e-mail, WWW, nazwy plików z pełnymi ścieżkami, ...).
Na wyrażenie regularne składają się różne symbole. Postaram się je teraz po krótce omówić.

Dalsze przykłady:



Poprzednia część kursu (klawisz dostępu 3)
Kolejna część kursu (klawisz dostępu 4)
Spis treści off-line (klawisz dostępu 1)
Spis treści on-line (klawisz dostępu 2)
Ułatwienia dla niepełnosprawnych (klawisz dostępu 0)

Ćwiczenia

  1. Napisać program zawierający 2 tablice DWORDów o wymiarach 17 na 31, po czym w trakcie działania programu wypełnić każde pole pierwszej wartością FFEEDDCCh. Potem, 8 pierwszych elementów skopiować do drugiej tablicy, a resztę drugiej wypełnić wartością BA098765h. Wtedy porównać zawartość obu tablic i wyliczyć pierwszą pozycję, na której się różnią (powinna oczywiście wynosić 9)

  2. Napisać wyrażenie regularne, które opisze: