GDB (GNU Project Debugger) jest to program służący do "śledzenia" wykonywania innych programów. Umożliwia on wykonywanie programu krok po kroku, wypisywanie zawartości zmiennych, stosu itd. Dzięki temu łatwiejsze staje się szukanie błędów w programie. Niniejsze wprowadzenie ma za zadanie przedstawienie jego podstawowych komend i możliwości - nie jest kompletnym opisem. Pełny opis programu zawiera GDB User Manual.
W tym dokumencie użyto następujących oznaczeń:
$
oznaczają polecenia
powłoki,
(gdb)
oznaczają polecenia
programu gdb.
Aby umożliwić śledzenie kodu źródłowego programu podczas jego wykonywania,
musimy skompilować nasz program dodając przełącznik -g
jako parametr dla gcc
. Następnie wczytujemy
go do debuggera, poleceniem:
$ gdb nazwa_pliku_wykonywalnego
list
- wyświetla listing kodu
źródłowego. Opcjonalne parametry to nazwa funkcji, numer linii do wyświetlenia
itp. (patrz help list
).
break N
- ustawia punkt przerwania (breakpoint) na linii
kodu o numerze N.
set args arg_1 arg_2 ...
- ustawia parametry wywołania
programu
run
- uruchamia wczytany program.
print x
- wyświetla bieżącą zawartość zmiennej x.
watch x
- śledzenie zmiennej x - program zatrzyma się gdy jej
zawartość ulegnie zmianie.
backtrace
- wyświetla bieżącą zawartość stosu (dzięki temu
możemy się zorientować jaki był ciąg wywołań funkcji i w którym miejscu
programu jesteśmy obecnie).
next
- wykonuje jeden krok programu nie pokazując tego co
dzieje się wewnątrz wywoływanych funkcji (nie "wchodzi" do ich wnętrza tylko
je wykonuje)
step
- wykonuje pojedynczą linię programu ("wchodząc" do
wnętrza wywoływanych funkcji).
info break
- wyświetla listę punktów przerwania.
continue
- wznawia działanie programu (np. po zatrzymaniu się
na punkcie przerwania)
kill
- kończy działanie debuggowanego programu
quit
- kończy działanie gdb
Polecenia posiadają swoje skróty, jak: list - l, next - n, step - s, break
- b, backtrace - bt
itd.
Na temat każdego z poleceń gdb możemy przeczytać pomoc:
(gdb) help nazwa_polecenia
Użyjemy gdb by prześledzić działanie krótkiego programu: vectors.c (opis - w jego komentarzach). Program bądź to wyrzuca "segmentation fault", bądź też daje wyniki co najmniej niespodziewane i na pierwszy rzut oka nie wiemy dlaczego. Najpierw kompilujemy go odpowiednio:
$ gcc -Wall -g -o vectors vectors.c
Następnie uruchamiamy debugger:
$ gdb vectors
Teraz z linii poleceń gdb, listujemy wczytany program:
(gdb) l
(literka 'l' a nie cyfra '1')
Zobaczyliśmy nagłówek funkcji main
i kilka linii dookoła niej.
Chcemy teraz obejrzeć trochę więcej - linie od 27 do 50:
(gdb) l 27,50
Ustawimy teraz jeden breakpoint na linii 31 i drugi na 43:
(gdb) b 31
(gdb) b 43
Program pobiera jeden argument wywołania - długość losowanych wektorów, ustawimy więc go teraz:
(gdb) set args 12
Wszystko gotowe - możemy więc uruchomić nasz program:
(gdb) run
Dostajemy komunikat wyglądający mniej więcej tak:
Starting program: /home/username/progs/vectors 12
Breakpoint 1, main (argc=2, argv=0xbffff7d4) at vectors.c:31
31 if((sscanf(argv[1],"%d",&n)==0) || (n<=0)){
Widzimy że program wystartował i zatrzymał się na linii 31 - tam gdzie
ustawiliśmy breakpoint. Teraz używając polecenia s
(czyli step
) możemy przesuwać się - wykonując kod po jednej
linii. Po przejściu
o jeden krok dalej, sprawdźmy jaka jest wartość zmiennej n
:
(gdb) print n
Wygląda na to że jest w porządku. Idziemy więc dalej - poleceniem
s
. Ups... byliśmy w linii 37 a jesteśmy w 16. Co się stało?
Aha, chyba weszliśmy do środka funkcji fmalloc
.
Wypiszmy zawartość stosu:
(gdb) bt
rzeczywiście - jesteśmy wewnątrz funkcji fmalloc - i widzimy że była wywołana z
argumentem size=12
. Za nią na stosie widzimy adres powrotu do
miejsca w funkcji main
z którego został wywołana
fmalloc
i na samym spodzie:
__libc_start_main
z
biblioteki libc
(skąd została wywołana main
).
Teraz używamy kilkakrotnie s
i
parzymy co się dzieje - pamięć jest przydzielana poprawnie (możemy sprawdzić
wskaźnik przy pomocy print
). I wychodzimy z funkcji z powrotem
do main
.
Ponieważ nie chcemy śledzić funkcji fmalloc
dla drugiego jej wywołania - teraz poruszamy się po kodzie używając
n
(czyli next
). Możemy też wyświetlić listę
breakpoint-ów poleceniem:
(gdb) info b
Zobaczymy też wówczas ile razy minęliśmy po drodze każdy z nich. Ponieważ przypuszczamy że "ciekawe" rzeczy zaczną się dziać dopiero dalej - możemy teraz kontynuować nasz program aż do następnego punktu przerwania:
(gdb) c
(lub continue
).
Wchodzimy teraz w pętli biegnącej po i
. Tak... wciskanie
kilkadziesiąt razy n - aby znaleźć się w ostatniej iteracji pętli - może się
okazać dość żmudne. No ale przecież możemy ustawić sobie jeszcze jeden
breakpoint - tym razem warunkowy:
(gdb) b 44 if i >=11
Oznacza to że wykonanie programu zostanie wstrzymane na linii 44, tylko wtedy
gdy zmienna i
będzie większa lub równa 11
(wiemy gdzie powinna się nasza pętla zatrzymać, a w razie potrzeby - możemy
jeszcze raz sprawdzić zmienną n
).
Teraz pozostaje nam kontynuować wykonanie programu:
(gdb) c
Zatrzymaliśmy się na ostatniej iteracji pętli.
Możemy teraz sprawdzać jakie są wartości umieszczone w tablicach używając
print
.
Ponieważ nie znaleźliśmy niczego podejrzanego - wykonujemy program krokowo - aż
do następnej pętli. Wchodzimy do niej - i sprawdzamy dokąd przebiega:
(gdb) print m
Ups... a co to za wartość?? I skąd w ogóle się tu wzięła ta zmienna
m
??!?
Aha.. użyliśmy niezainicjowanej zmiennej - która z jakiegoś powodu znalazła się
w naszym programie. Powinno być n
zamiast m
. A ta
druga zmienna jest nam przecież do niczego nie potrzebna :-)
Tym razem poszło nam sprawnie - oby nasze następne sesje z debuggerem kończyły się równie szybko i pomyślnie. Czego zarówno wszystkim czytającym jak i sobie - życzy autor niniejszego opracowania.