Aby zacząć debugować program, najpierw musimy go napisać ;]. Gdy mamy już program, np. ./test, to uruchamiamy go razem z debugerem gdb wpisując w linii komend:
gdb test
teraz możemy ustawić breakpointy dla naszego debugera. Robimy to przez wpisanie:
break nazwa_funkcji
czasami jednak jeśli mamy jedynie jedną funkcję główną programu, gdb ma problemy z zatrzymaniem się na niej. Aby tego uniknąć wystarczy po pierwszej operacji (lub specjalnie pustej operacji nop) wstawić etykietę, na której to ustawiamy później nasz breakpoint.
Po ustawieniu wszystkich breakpointʼów, możemy już uruchomić nasz program wpisując:
run
Program zacznie się wykonywać i zatrzyma się na pierwszym napotkanym breakpoincie.
Gdy już stoimy w miejscu breakpointʼa to możemy teraz:
info r - wszystkich rejestrów,
print /x $reg - rejestru reg,
info float - rejestrów koprocesora,
x 0x######## - pamięci o podanym adresie (4 bajty),
x/Y 0x######## - pamięci o podanym adresie (Y*4 bajty),
print /x zmienna - wartość zmiennej,
print /x $zmienna - adres zmiennej,
info stack - stosu wywołań.
Ponadto możemy wyświetlić listę zdefiniowanych funkcji wpisując:
info functions
i zdeasemblować wybraną funkcję poleceniem:
disassemble nazwa_funkcji
Aby zdefiniować jaką składnię chcemy uzyskać przy deasembacji, piszemy:
set disassembly-flavor xxx ;gdzie w miejsce xxx wstawiamy intel lub att
Gdy już wiemy, że nasz rejestr lub zmienna ma jakąś niechcianą wartość, ale chcemy spróbować, czy zadziałałoby dalej jakby była inna, to możemy ustawić jej wartość jednym z poleceń:
set $reg=??? - ustawia wartość rejestru reg na wartość ???,
set variable zmienna=??? - ustawia wartość zmiennej na ???,
set variable *0x########=??? - ustawia wartość ??? w pamięci pod adresem 0x########.
Jednak nie o to chodzi, aby zatrzymać się przyjrzeć temu co tam mamy i tyle. Musimy się więc jakoś poruszać po naszym programie:
stepi - przejście o jedną instrukcję dalej,
ni - przejdź jedną instrukcję (to samo co stepi),
si - przejdź dokładnie jedną instrukcję,
rni - cofa się o jedną instrukcję (odwrotność ni),
rsi - cofa o dokładnie jedną instrukcję (odwrotność si),
c - kontynuuje debugowanie do następnego breakpointʼa lub końca/błędu wykonania,
rc - kontynuuje debug programu wstecz (odwraca kolejność wykonywania instrukcji programowych),
where - wypisuje cały proces wywoływań którym program doszedł do danego miejsca.
Komendy z cofaniem (reverse steps) nie zawsze działają w programach.
Aby zatrzymać program w interesującym nas momencie nie trzeba się ograniczać do breakpointów ustawionych na etykiety. Można wykorzystać inne metody tj.:
condition - ustawia warunek zatrzymania dla danego breakpointʼa,
commands - ustawia komendy do wykonania gdy program zatrzymuje się na danym breakpoincie,
awatch - ustawia zatrzymanie programu na interesującą nas sytuację,
catch syscall - zatrzymuje na interesujących nas przerwaniach i wywołaniach systemowych,
watch - ustawia wachpoint na dane wyrażenie,
rwatch - ustawia read wachpoint na dane wyrażenie,
tbreak - ustawia tymczasowy breakpoint.
Dodatkowo możemy usuwać lub dezaktywować breakpointy i watchpointy.
disable breakpoints - dezaktywuje breakpointy,
disable - j/w,
enable - przywraca aktywność breakpointom,
delete breakpoints - usuwa breakpointʼy,
delete - j/w,
clear - czyści breakpoint w podanej linii lub funkcji,
ignore - ignoruje daną ilość razy podanego breakpointʼa.
Przykład. Chcemy aby program zatrzymywał się na etykiecie label, wypisywał zawartość rejestrów i kontynuował wykonanie programu
break label
commands 1
info r
continue
end
Przykładowy skrypt skrypt.gdb
set verbose off
break _start
commands 1
info r
continue
end
run
quit
Uruchamiamy gdb poleceniem
gdb ./program -x skrypt.gdb -q
Ciekawą opcją jest przejście w tryb TUI. Włączamy go kombinacją klawiszy CTRL+x, a lub poleceniem gdb --tui.
Terminal zostaje podzielony na kilka okienek tekstowych na których możemy jednocześnie obserwować kod programu, rejestry itd.
Kolejne widoki przełączamy kombinacją klawiszy CTRL+x,2
Szczegóły można znaleźć np. na tej stronie.
Uruchomić gdb na przykładowym programie debug lub debug64 ustawić break na etykietę stop i wypisać zawartość rejestrów oraz listę funkcji, a następnie zdeasemblować wszystkie funkcje programu i znaleźć błąd odpowiedzialny za niewyświetlanie napisu “Hello World!”, 10, 13
Naprawić błąd bezpośrednio w gdb tak aby program wykonał się poprawnie wyświetlając napis.
Napisać skrypt dla gdb, który automatycznie naprawia ten błąd.
Dostępny jest plik funcja.c z funkcją main i plik binarny funkcja32 programu w wersji 32-bit (fukcja32_O2 - wersja skompilowana z optymalizacją O2).
Chciano przekompilować ten program do wersji 64-bitowej. Niestety zaginął plik C z definicją funkcji
char f(char, char, char);
Odtwórz plik f.cpp (z implementacją funkcji f) używając gdb i analizując wersję binarną funkcji f.
Jako rozwiązanie prześlij tylko plik f.cpp.
Używając debugera do obejścia zabezpieczeń odczytaj sekretna informację.
Jako rozwiązanie prześlij hasło dostępu lub skrypt GDB obchodzący zabezpieczenia.