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:
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:
gdb naszprog.break nazwa_funkcji.
To powinno ustawić pułapkę (breakpoint) na pierwszej instrukcji
tej funkcji.
Nie zawsze jednak breakpoint działa na procedurze początkowej _start - wtedy tuż po
_start: (w naszym programie) należy
wstawić instrukcję nop i tuż po niej postawić
etykietę, na której już bez problemów ustawimy działający breakpoint.disassemble nazwa_funkcji.
Zostanie wyświetlony kod podanej funkcji do najbliższej etykiety.
Jeśli wolimy składnię Intela, piszemy set disassembly-flavor intel.
Jeśli nie, to set disassembly-flavor att.info r, aby wyświetlić
konkretny rejestr, piszemy na przykład print /x $eax.set $ebx=33.info float.x 0x08048081
lub print /x zmienna.
Aby wyświetlić więcej niż jedno 32-bitowe słowo, dajemy liczbę słów po ukośniku
po komendzie x, na przykład x/8 0x08048081.
Zamiast adresu można podać etykietę.set variable var1 = 0x1 lub
set variable *0x8049094 = 0x2, jeśli znamy tylko adres.print /x &var1.info functions.info stack.stepi.help.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.
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).
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
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 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:
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
:
# 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ć.