Jak pisać programy w języku asembler?

Część 9 - Narzędzia programisty, czyli co może nam pomagać w programowaniu



Debugery


(przeskocz debugery)

Wszystkim się może zdarzyć, że nieustanne, wielogodzinne gapienie się w kod programu nic nie daje i program ciągle nie chce nam działać. Wtedy z pomocą przychodzą debugery. W tej części zaprezentuję kilka wartych uwagi programów tego typu. Nie będę szczegółowo mówił, jak ich używać, bo zwykle posiadają albo menu, albo dokumentację czy inną pomoc.

UWAGA: Niektóre debugery mogą wpływać na wartości widziane w rejestrach. Może się tak stać na przykład wtedy, gdy przerwanie, które wywołujemy, jest wewnętrznie wykorzystywane przez debuger. W szczególności, może to mieć miejsce w przypadku Turbo Debuggera i funkcji 3Fh i 40h przerwania DOS-a (int 21h).

Debuggery programów DOS-owych:


(przeskocz DOSowe debugery)
  1. Watcom Debugger (WD).

    Rozpowszechniany z pakietem OpenWatcom, WD jest najlepszym z darmowych debugerów. Umożliwia wyświetlanie rejestrów procesora, flag, koprocesora, MMX i SSE, śledzenie wartosci zmiennych, stawianie pułapek (breakpoint, klawisz F9), podgląd wyjścia programu (klawisz F4), wykonywanie do kursora i wiele innych działań. Posiada obsługę myszy. Pozwala debugować wszystko, co może być wytworzone przez pakiet OpenWatcom - .com, .exe (MZ i LE) i wiele innych.

  2. Turbo Debugger (TD) firmy Borland.

    Jeden z najlepszych dostępnych. Niestety, nie darmowy. Umożliwia wyświetlanie rejestrów 16/32-bit, rejestrów koprocesora, stosu i pewnych regionów pamięci (standardowo DS:0000) oraz flag i daje możliwość modyfikacji ich wszystkich. Można obserwować zmienne oraz to, co się dzieje na ekranie poza debuggerem. Gdy testowałem program działający w trybie graficznym, to po każdej komendzie monitor przełączał się w ten tryb, po czym wracał do debuggera, co umożliwia podglądanie naprawdę każdego kroku. Niestety, zrobienie czegokolwiek poza przejściem do kolejnej instrukcji wymagało przejścia w tryb pełnego ekranu, gdyż okienko w Windows nie za bardzo było potem odświeżane...
    Niestety, TD nie ma on pojęcia o technologiach takich, jak MMX czy SSE (jest po prostu za stary). Posiada wiele opcji debugowania: trace-over, step-into, execute-to, animate, ...
    Nadaje się do plików .com/.exe (najnowsza wersja 5.5 obsługuje tylko pliki 32-bitowe). Można go ściągnąć ze stron Borlanda po zarejestrowaniu się.

  3. D86.

    Darmowy obsługuje tylko procesory 16-bitowe (brak rozszerzonych rejestrów), ale można podglądać rejestry, flagi, pamięć i koprocesor. D86 jest rozprowadzany razem z A86, darmowym kompilatorem języka asembler, i rozpoznaje symbole (nazwy zmiennych itp.), które A86 umieszcza w plikach .sym, co ułatwia proces debugowania.
    Posiada pełną dokumentację. Pomoc w sprawie klawiszy jest w każdej chwili dostępna pod kombinacją klawiszy Alt-F10.
    Niestety, miałem problem z przeglądaniem aplikacji graficznej: po uruchomieniu trybu graficznego nie mogłem wrócić do ekranu debuggera i musiałem poczekać, aż program się zakończy. D86 zna instrukcje koprocesora.
    Płatna wersja, D386, zna MMX, SSE i 3DNow!.

  4. Codeview (CV) firmy Microsoft.

    Moje doświadczenie z tym debuggerem jest krótkie, gdyż nie spełnił on wszystkich moich oczekiwań. Po uruchomieniu od razu trzeba otworzyć jakiś program (i z tego co pamiętam, aby otworzyć inny program, trzeba wyjść z całego debuggera. Te programy, które chciałem otworzyć, CV otwierał tak długo, że można było pomyśleć, że komputer się zawiesił...
    Nawet chyba nie jest rozprowadzany osobno, tylko razem z MASMem (za darmo). Trzeba przejść długi proces instalacji, ustawiać zmienne środowiska, ...

  5. Insight

    Natrafiłem na niego, gdy szukałem czegoś (nowszego), co mogło by zastąpić Turbo Debuggera. Wyglądem nawet przypomina TD, ale ma kilka wad. Pierwszą jest brak rozpoznawania instrukcji koprocesora (wszystkie dekoduje jako ESC + coś tam). O MMX nie ma co myśleć. Drugą wadą, którą znalazłem jest to, że po wejściu w tryb graficzny okienko Tryb MS-DOS z debuggerem przestaje się odświeżać i trzeba się przełączyć na pełny ekran.
    Ale jako-tako, działa. Posiada opcje step-over, trace-into, animate. Można zmieniać wartości rejestrów.

  6. Advanced Fullscreen Debugger

    Nawet ładne narzędzie. Pozwala w jednej chwili oglądać kod, stos, rejestry i 2 bloki pamięci (standardowo ustawiane na DS:0000). Obsługa jest prosta: na dole ekranu jest pokazane, co robią klawisze funkcyjne, ale można też wpisywać komendy. Bardzo pomocne jest to, że po wpisaniu pierwszej literki pojawiają się dostępne komendy zaczynające się od niej. Niestety, ma te dwa problemy, które ma Insight: po uruchomieniu trybu graficznego okienku z debuggerem przestaje być odświeżane (trzeba się przełączyć na pełny ekran) i nie rozpoznaje instrukcji koprocesora.

  7. TRW2000

    Umie debugować programy typu .com i .exe. Jednak coś jest nie tak z obsługą myszy a praca z nim nie jest zbyt wygodna. Strona domowa TRW: www.hktk.com/soft/soft_tools/trw_1.html

Debuggery programów dla Windows:


(przeskocz windowsowe debuggery)
  1. GoBug

    Część pakietu GoDevTools (www.godevtool.com). Poza nim są m.in kompilator języka asembler oraz resource compiler. Wszystko to przydać się może przy pisaniu programów dla Windows. Ja osobiście używam FASMa, ale moim debuggerem jest właśnie GoBug. Ma miły dla oka wygląd, zna rejestry FPU, MMX. Wyświetla kod programu, stan rejestrów, oraz stos względem ESP oraz EBP. Obsługuje wieloprocesowość oraz symbole użyte w kodzie, o ile znajdzie odpowiedni plik z nimi. Po przytrzymaniu prawego klawisza myszki na instrukcji pojawiają się bajty zapisane szesnastkowo, które się na tę instrukcję składają.
    GoBug rozpoznaje uruchomienia procedur Windows-owych z bibliotek DLL.
    Dobre narzędzie.

  2. Olly Debugger

    Można go za darmo ściągnąć z jego strony domowej: ollydbg.de. Wygląda bardziej profesjonalnie niż GoBug i podobnie jak on, rozpoznaje uruchomienia procedur systemowych. Stos jest wyświetlany tylko względem ESP. Wyświetla rejestry i flagi. Stara się łączyć umieszczanie parametrów na stosie z uruchomieniem procedury, ale nie zawsze to wychodzi. Przewijając okienko z kodem niektóre instrukcje mogą się nagle zmieniać. Obsługa jest według mnie trudniejsza. Czcionka instrukcji jest mniejsza, co jeszcze bardziej utrudnia ich rozczytanie. Bardziej nie wnikałem w jego obsługę.
    W tej chwili bardziej polecam GoBug niż OllyDbg.

Wiem, że nie wszyscy od razu z entuzjazmem rzucą się do ściągania i testowania przedstawionych wyżej programów i do debugowania własnych.
Niektórzy mogą uważać, że debugger nie jest im potrzebny. Może i tak być, ale nie zawsze i nie u wszystkich. Czasem (zwykle po długim sterczeniu przed ekranem) przychodzi chęć do użycia czegoś, co tak bardzo może ułatwić nam wszystkim życie.
Pomyślcie, że gdyby nie było debuggerów, znajdowanie błędów w programie musielibyśmy pozostawić naszej nie zawsze wyćwiczonej wyobraźni. Dlatego zachęcam Was do korzystania z programów tego typu (tylko tych posiadanych legalnie, oczywiście).




Środowiska programowania, edytory i disasemblery/hex-edytory


(przeskocz ten dział)

Środowisko programowania (Integrated Development Environment, IDE) to, jak wszyscy wiemy, program, który umożliwia edycję kodu, jego kompilację i uruchamianie programu wynikowego. Znanych jest wiele IDE dla języków wysokiego poziomu, ale język asembler też ma kilka swoich:


(przeskocz środowiska)

Jeśli mimo tego ktoś nie chce lub nie lubi używać IDE, zawsze może skorzystać z któregoś ze zwykłych edytorów. Przedstawione poniżej propozycje to co prawda nie muszą być edytorami napisanymi specjalnie do programowania w asemblerze, ale może coś Wam przypadnie do gustu:


(przeskocz edytory)

Jeśli nie podoba się Wam żaden z wymienionych, to możecie wejść na stronę The Free Country.com - edytory, gdzie przedstawionych jest wiele edytorów dla programistów.

Kolejną przydatną rzeczą może okazać się disasembler lub hex-edytor. Jest to program, który podobnie jak debugger czyta plik i ewentualnie tłumaczy zawarte w nim bajty na instrukcje asemblera, jednak bez możliwości uruchomienia czytanego programu.
Disasemblery mogą być przydatne w wielu sytuacjach, na przykład gdy chcemy modyfikować pojedyncze bajty po kompilacji programu, zobaczyć adresy zmiennych, itp.
Oto kilka przykładów programów tego typu:


(przeskocz hex-edytory)

I ponownie, jeśli nie spodoba się Wam żaden z wymienionych, to możecie wejść na stronę The Free Country.com - disasemblery, aby poszukać wśród pokazanych tam programów czegoś dla siebie.


Programy typu MAKE

Programy typu MAKE (na przykład GNU MAKE) służą do automatyzacji budowania dużych i małych projektów. Taki program działa dość prosto: uruchamiamy go, a on szuka pliku o nazwie Makefile w bieżącym katalogu i wykonuje komendy w nim zawarte. Teraz zajmiemy się omówieniem podstaw składni pliku Makefile.

W pliku takim są zadania do wykonania. Nazwa zadania zaczyna się w pierwszej kolumnie, kończy dwukropkiem. Po dwukropku są podane nazwy zadań (lub plików) , od wykonania których zależy wykonanie tego zadania. W kolejnych wierszach są komendy służące do wykonania danego zadania.
UWAGA: komendy NIE MOGĄ zaczynać się od pierwszej kolumny! Należy je pisać je po jednym tabulatorze (ale nie wolno zamiast tabulatora stawiać ośmiu spacji).
Aby wykonać dane zadanie, wydajemy komendę make nazwa_zadania. Jeśli nie podamy nazwy zadania (co jest często spotykane), wykonywane jest zadanie o nazwie all (wszystko).

A teraz krótki przykład:


(przeskocz przykład)
all:	kompilacja linkowanie
	echo "Wszystko zakonczone pomyslnie"

kompilacja:
	nasm -O999 -f obj -o plik1.obj plik1.asm
	nasm -O999 -f obj -o plik2.obj plik2.asm
	nasm -O999 -f obj -o plik3.obj plik3.asm

	tasm /z /m plik4.asm
	tasm /z /m plik5.asm
	tasm /z /m plik6.asm

linkowanie: plik1.obj plik2.obj plik3.obj plik4.obj plik5.obj plik6.obj
	alink -o wynik.exe plik1.obj plik2.obj plik3.obj plik4.obj \
		 plik5.obj plik6.obj -c- -oEXE -m-

help:
	echo "Wpisz make bez argumentow"

Ale MAKE jest mądrzejszy, niż może się to wydawać!
Mianowicie, jeśli stwierdzi, że wynik.exe został stworzony PÓŹNIEJ niż pliki .obj podane w linii zależności, to nie wykona bloku linkowanie, bo nie ma to sensu skoro program wynikowy i tak jest aktualny. MAKE robi tylko to, co trzeba. Oczywiście, niezależnie od wieku plików .obj, dział kompilacja i tak zostanie wykonany (bo nie ma zależności, więc MAKE nie będzie sprawdzał wieku plików).

Znak odwrotnego ukośnika \ powoduje zrozumienie, że następna linia jest kontynuacją bieżącej, znak krzyżyka # powoduje traktowanie reszty linijki jako komentarza.

Jeśli w czasie wykonywanie któregokolwiek z poleceń w bloku wystąpi błąd (ściśle mówiąc, to gdy błąd zwróci wykonywane polecenie, jak u nas TASM czy NASM), to MAKE natychmiast przerywa działanie z informacją o błędzie i nie wykona żadnych dalszych poleceń (pamiętajcie więc o umieszczeniu w zmiennej środowiskowej PATH ścieżki do kompilatorów).

W powyższym pliku widać jeszcze jedno: zmiana nazwy któregoś z plików lub jakieś opcji sprawi, że trzeba ją będzie zmieniać wielokrotnie, w wielu miejscach pliku. Bardzo niewygodne w utrzymaniu, prawda?
Na szczęście z pomocą przychodzą nam ... zmienne, które możemy deklarować w Makefile i które zrozumie program MAKE.
Składnia deklaracji zmiennej jest wyjątkowo prosta i wygląda tak:

		NAZWA_ZMIENNEJ = wartosc

A użycie:

		$(NAZWA_ZMIENNEJ)

Polecam nazwy zmiennych pisać wielkimi literami w celu odróżnienia ich od innych elementów. Pole wartości zmiennej może zawierać dowolny ciąg znaków.

Jeśli chcemy, aby treść polecenia NIE pojawiała się na ekranie, do nazwy tego polecenia dopisujemy z przodu znak małpki @, na przykład

		@echo "Wszystko zakonczone pomyslnie"

Uzbrojeni w te informacje, przepisujemy nasz wcześniejszy Makefile:


(przeskocz drugi przykład)
# Mój pierwszy Makefile

NASM	    = nasm     # ale można tu w przyszłości wpisać pełną ścieżkę
NASM_OPCJE  = -O999 -f obj

TASM	    = tasm
TASM_OPCJE  = /z /m

ALINK	    = alink
ALINK_OPCJE = -c- -oEXE -m-

PLIKI_OBJ   = plik1.obj plik2.obj plik3.obj plik4.obj plik5.obj plik6.obj
PROGRAM	    = wynik.exe

all:	kompilacja linkowanie
	@echo "Wszystko zakonczone pomyslnie"

kompilacja:
	$(NASM) $(NASM_OPCJE) -o plik1.obj plik1.asm
	$(NASM) $(NASM_OPCJE) -o plik2.obj plik2.asm
	$(NASM) $(NASM_OPCJE) -o plik3.obj plik3.asm

	$(TASM) $(TASM_OPCJE) plik4.asm
	$(TASM) $(TASM_OPCJE) plik5.asm
	$(TASM) $(TASM_OPCJE) plik6.asm

linkowanie:	$(PLIKI_OBJ)
	$(ALINK) -o $(PROGRAM) $(PLIKI_OBJ) $(ALINK_OPCJE)

help:
	@echo "Wpisz make bez argumentow"

Oczywiście, w końcowym Makefile należy napisać takie regułki, które pozwolą na ewentualną kompilację pojedynczych plików, na przykład

plik1.obj:	plik1.asm plik1.inc
	$(NASM) $(NASM_OPCJE) -o plik1.obj plik1.asm

Choć na razie być może niepotrzebna, umiejętność pisania plików Makefile może się przydać już przy projektach zawierających tylko kilka modułów (bo nikt nigdy nie pamięta, które pliki są aktualne, a które nie).
O tym, ile Makefile może zaoszczędzić czasu przekonałem się sam, pisząc swoją bibliotekę - kiedyś kompilowałem każdy moduł z osobna, teraz wydaję jedno jedyne polecenie make i wszystko się samo robi. Makefile z biblioteki jest spakowany razem z nią i możecie go sobie zobaczyć.


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