Wprowadzenie do debugowania aplikacji za pomocą GDB
Rozpoczynając przygodę debugowania własnej aplikacji warto mieć świadomość jakie istnieją polecenia, służące do śledzenia przebiegu działania aplikacji. Przedstawianie wszystkich możliwości GDB w ramach jednego rozdziału oczywiście nie ma sensu, dlatego też niniejszy rozdział będzie zawierał tylko te polecenia, które uznałem za najważniejsze dla początkującego użytkownika debuggera GDB. Polecenia te zostały zamieszczone w tabeli poniżej. Pierwsza kolumna w tabeli zawiera pełną nazwę polecenia. W drugiej umieściłem skróconą nazwę polecenia, którą zapewne będziesz używał znacznie chętniej podczas pracy z GDB. Ostatnia kolumna tabeli zawiera krótki opis zastosowania polecenia. W dalszej części niniejszego rozdziału zostaną omówione poszczególne polecenia wraz z przykładami ich użycia.
Uruchamianie aplikacji z poziomu GDB
Polecenie służące do uruchamiania aplikacji z poziomu GDB zostało przedstawione w rozdziale
Uruchomienie lokalnego debugowania aplikacji. Przykłady uruchamiania zostały przedstawione poniżej.
Parametry aplikacji określane po uruchomieniu GDB
(konsola) gdb NAZWA_TWOJEJ_APLIKACJI
(gdb) run [parametr1] [parametr2] [parametr3]
Parametry aplikacji określane w chwili uruchamiania GDB
(konsola) gdb –args NAZWA_TWOJEJ_APLIKACJI [parametr1] [parametr2] [parametr3]
(gdb) run
Wstrzymywanie debugowania
Wstrzymanie debugowania w celu wprowadzenia poleceń
Jeżeli masz uruchomiony debugger GDB oraz aplikacja jest uruchomiona i aktywna (reaguje na działania użytkownika) to wprowadzanie poleceń w debuggerze jest zablokowane. Aby móc wpisywać polecenia należy wstrzymać działanie aplikacji za pomocą skrótu klawiszowego
CTRL+C. Wówczas na ekranie debuggera pojawi się tzw. 'znak zachęty', który informuje użytkownika o możliwości wprowadzania poleceń do GDB. Wygląda to tak:
^C
Program received signal SIGINT, Interrupt.
0x00007ffff60aeca0 in __poll_nocancel () at ../sysdeps/unix/syscall-template.S:81
81 ../sysdeps/unix/syscall-template.S: No such file or directory.
(gdb) q
A debugging session is active.
Skrót klawiszowy
CTRL+C.
Wstrzymanie debugowania po wykonaniu jednej instrukcji
Jeżeli aplikacja jest już w stanie wstrzymania i masz możliwość podawania poleceń, to do dyspozycji masz wiele poleceń umożliwiających śledzenie działania programu. Aby wykonać jedną instrukcję i wstrzymać debugowanie należy użyć polecenia
nexti. Przykład:
(gdb) nexti
Wstrzymanie debugowania po wykonaniu jednego wiersza instrukcji w bieżącej ramce
Jeżeli chcesz wykonać jeden wiersz kodu w bieżącej ramce (czyli w praktyce przejść do kolejnej linii kodu w aktualnie wykonywanej funkcji lub metodzie), to należy użyć polecenia
next. Przykład:
(gdb) next
Wstrzymanie debugowania po napotkaniu innego wiersza kodu w dowolnej ramce
Jeżeli w aktualnie wykonywanym kodzie natrafiłeś na funkcję/metodę do której chciałbyś wejść, aby prześledzić jej działanie to wówczas powinieneś skorzystać z polecenia
step. Polecenie wykonuje aplikację aż do napotkania innego wiersza w kodzie. W praktyce oznacza to, że:
Przykład:
(gdb) step
Wstrzymanie debugowania po wyjściu z bieżącej ramki
Jeżeli wszedłeś do funkcji lub metody, która Ciebie nie interesuje (bądź stwierdziłeś, że jednak nie chcesz analizować danego kodu) to do dyspozycji mamy polecenie
final, które wstrzyma debugowanie aplikacji zaraz po wyjściu z bieżącej ramki (czyli zaraz po wyjściu z aktualnie analizowanej metody/funkcji).
(gdb) finish
Kontynuowanie debugowania
Jeżeli chcesz wznowić działanie aplikacji i nie masz potrzeby wprowadzania kolejnych poleceń (np. nie chcesz na chwilę obecną stawiać więcej breakpointów), to możesz użyć do tego celu polecenie
continue. Polecenie
continue wznawia działanie debugowanej aplikacji oraz odbiera nam prawo do wprowadzania kolejnych poleceń (aż do kolejnego wstrzymania aplikacji). Przykład:
(gdb) continue
Wstawianie breakpointa
Jeżeli chciałbyś, aby debugger monitorował kod w trakcie działania aplikacji i zatrzymał się automatycznie w chwili napotkania określonej linii kodu, to wówczas będziesz potrzebował postawić breakpointa (po polsku stosuje się określenie 'postawienia pułapki'). Do stawiania pułapki używa się polecenia
breakpoint, które jako parametr może przyjmować różne wartości, takie jak:
Sposobów do określania miejsca postawienia breakpointów jest jeszcze więcej, jednak w niniejszym rozdziale skupiłem się na absolutnych podstawach o których należy wiedzieć. Przykład:
(gdb) breakpoint nazwa_pliku.cpp:123
(gdb) breakpoint przestrzen_nazw::Klasa::metoda
(gdb) breakpoint przestrzen_nazw::funkcja
Lista breakpointów
Aby wyświetlić aktualną listę postawionych breakpointów należy użyć polecenia
info breakpoints. Przykład:
(gdb) info breakpoints
Kasowanie breakpointów
Do kasowania breakpointów służy polecenie
delete. Jeżeli polecenie zostanie wywołane bez argumentów, to wówczas zostaną skasowane wszystkie breakpointy. Jeżeli za poleceniem wskażemy numer breakpointa, który chcemy usunąć, to wówczas zostanie skasowany tylko ten breakpoint, który podaliśmy. Przykład:
(gdb) delete
Wyłączanie breakpointów
Jeżeli masz potrzebę wyłączenia jakiegoś breakpointa, aby debugger się na nim nie zatrzymywał, ale jednocześnie wiesz, że będziesz chciał go później przywrócić - możesz wówczas użyć polecenia
disable. Polecenie
disable służy do wyłączania określonego breakpointa, dzięki czemu debugger nie będzie się na nim zatrzymywał. Przykład:
(gdb) disable 1
Włączanie breakpointów
Jeżeli chesz przywrócić breakpointa, który uprzednio został wyłączony to należy użyć polecenia
enable oraz jako parametr podać numer breakpointa jaki powinien zostać aktywowany. Przykład:
(gdb) enable 1
Wyświetlanie callstacka
Aby wyświetlić aktualny stos ramek należy użyć polecenia
backtrace. Każda ramka opisuje jedno wywołanie funkcji/metody, więc
backtrace daje Ci możliwość analizy wywołań metod jak i funkcji oraz dostarcza cennej informacji w postaci wartości z jakimi została wywołana każda funkcja/metoda w stosie ramek. W wysokopoziomowych środowiskach backtrace nazywany jest callstack-iem, czyli stosem wywołań metod i funkcji. Przykład użycia polecenia
backtrace:
(gdb) backtrace
Wypisywanie wartości zmiennej
Jeżeli chcesz wypisać wartość konkretnej zmiennej jaka jest widoczna z poziomu aktywnej ramki, to wówczas należy skorzystać z polecenia
print. Polecenie
print przyjmuje jako parametr nazwę zmiennej, której wartość chcesz wypisać. Przykład:
(gdb) print nazwa_zmiennej
Wyświetlanie listy załadowanych bibliotek i symboli debugowych
Jeżeli chcesz się dowiedzieć jakie biblioteki są używane przez aplikację, którą debugujesz lub chcesz się dowiedzieć jakie symbole debugowe są widoczne przez GDB, to wówczas powinieneś skorzystać z polecenia
info sharedlibrary. Przykład:
(gdb) info sharedlibrary