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

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.

Debugery programów Linuksowych:

  1. GDB, czyli Gnu Debugger + nakładki, na przykład DDD

    Podstawowy debuger pracujący w trybie tekstowym (nakładka DDD - w graficznym). Składnia podstawowa to AT&T (odwrotna do zwykłej składni Intela), podobnie jak w Gnu as i GCC.
    Aby używać GDB, nasz program musimy skompilować BEZ opcji -s u linkera (aby zostały zachowane symbole).

    Krótki kurs obsługi:


  2. Private ICE - PICE

    Ze zrzutów ekranowych na jego stronie domowej (pice.sf.net) wygląda całkiem obiecująco. Poza tym, jest to system-level debugger, czyli może on wnikać w zakamarki systemu. Szczegółów obsługi również niestety nie znam, gdyż kompilacja wymaga zabawy z kodem i posiadania źródeł jądra.

  3. Bochs

    Jest to emulator procesora, przydatny zwłaszcza w pisaniu i testowaniu bootsektorów i miniaturowych systemów operacyjnych, ale nie tylko. Posiada wbudowany debuger, dzięki któremu można debugować programy w takich środowiskach, w których nie da się uruchomić tradycyjnego debugera (na przykład właśnie w czasie bootowania systemu).

  4. Valgrind

    Może nie do końca jest to debuger, ale narzędzie do analizy pamięci. Pozwala wykryć między innymi wycieki pamięci, miejsca spowalniające program oraz poprawić wydajność pamięci podręcznej.

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 odpluskwiacz 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 debugeró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).

Warto jeszcze wspomnieć o dwóch programach: strace i ltrace. Pozwalają one na śledzenie, których funkcji systemowych i kiedy dany program używa. Jeśli coś Wam nie działa, można spojrzeć, na których wywołaniach funkcji są jakieś problemy. Uruchomienie jest proste: strace ./waszprogram


Edytory i disasemblery/hex-edytory


(przeskocz ten dział)

Do pisania programów w asemblerze wystarczy najzwyklejszy edytor tekstu (Emacs, VI, Joe, PICO, LPE, ...), ale jeśli nie podoba się Wam żaden z edytorów, to możecie wejść na stronę The Free Country.com - edytory, gdzie przedstawionych jest wiele edytorów dla programistów. Może znajdziecie coś dla siebie.
Zawsze można też przeszukać SourceForge.net

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 2 przykłady programów tego typu:

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


Programy typu MAKE

Programy typu 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 elf -o plik1.o plik1.asm
	nasm -O999 -f elf -o plik2.o plik2.asm
	nasm -O999 -f elf -o plik3.o plik3.asm

	fasm plik4.asm plik4.o
	fasm plik5.asm plik5.o
	fasm plik6.asm plik6.o

linkowanie:	plik1.o plik2.o plik3.o plik4.o plik5.o plik6.o
	ld -s -o wynik plik1.o plik2.o plik3.o plik4.o \
		 plik5.o plik6.o

help:
	echo "Wpisz make bez argumentow"

Ale MAKE jest mądrzejszy, niż może się to wydawać!
Mianowicie: jeśli stwierdzi, że wynik został stworzony PÓŹNIEJ niż pliki .o 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 .o , 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 FASM 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 = wartość

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

FASM		= fasm	# ale można tu w przyszłości wpisać pełną ścieżkę

NASM		= nasm
NASM_OPCJE	= -O999 -f elf

LD		= ld
LD_OPCJE	= -s

PLIKI_O		= plik1.o plik2.o plik3.o plik4.o plik5.o plik6.o
PROGRAM		= wynik

all:	kompilacja linkowanie
	@echo "Wszystko zakonczone pomyslnie"

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

	$(FASM) plik4.asm plik4.o
	$(FASM) plik5.asm plik5.o
	$(FASM) plik6.asm plik6.o

linkowanie:	$(PLIKI_O)
	$(LD) $(LD_OPCJE) -o $(PROGRAM) $(PLIKI_O)

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.o:	plik1.asm plik1.inc
	$(NASM) $(NASM_OPCJE) -o plik1.o plik1.asm

Choć na razie być może niepotrzebna, umiejętność pisania Makefile'ów 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)