Jedną z wielkich zalet systemów wbudowanych opartych na Linuxie nad innymi systemami jest mnogość narzędzi do debugowania. Jest ich tak dużo, że czasem trudno wybrać właściwe. Dlatego też niesłabnącą popularnością cieszy się używanie funkcji printf() w podejrzanych miejscach 🙂
Jednym z najpopularniejszych programów do debugowania w trudnych sytuacjach jest GDB, czyli GNU Debugger.
W pracy przy aplikacjach embedded często nasze główne środowisko jest na hoście (np. laptop z procesorem x86) a naszym targetem jest urządzenie o małych zasobach sprzętowych i innej architekturze, na przykład ARM. W takiej sytuacji bardzo wygodnie jest debugować program zdalnie, czyli z użyciem programu GDB server.
Skąd wziąć GDB i server?
Załóżmy, że pracujemy z systemem na Raspberry Pi. Na początku będziemy potrzebować toolchaina, czyli zbioru aplikacji, które umożliwiają tworzenie programów na docelową architekturę, czyli w tym wypadku ARM 64bit. Chodzi tutaj m.in. o kompilator, linker, assembler i tym podobne.
Możemy korzystać z gotowych, pre-built toolchainów np. ze strony ARM: https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads
Często jednak lepiej jest mieć pełną kontrolę nad naszymi narzędziami. Wtedy najlepiej sięgnąć po rozwiązania open source, które możemy dostosować pod nasze konkretne zastosowanie. Jednym z takich toolchainów jest crosstool-ng.
crosstool-ng – setup
Na początku pobieramy źródła projektu z oficjalnego githuba:
git clone https://github.com/crosstool-ng/crosstool-ng
cd crosstool-ng/
crosstool jest oparty o system budowania GNU autotools. Musimy odpalić skrypty, które przygotowują pliki Make i ustawią odpowiednio cały proces kompilacji toolchaina:
./bootstrap
./configure --enable-local
make
Opcja –enbale-local oznacza wejście w tryb „hakerski” (zgodnie z oficjalną dokumentacją). Dzięki temu toolchain nie będzie instalowany w naszym systemie jako ogólnodostępne narzędzie, tylko pliki wynikowe razem ze źródłami będą w jednym miejscu. To ułatwi wprowadzanie zmian w konfiguracji.
Po kompilacji dostaniemy dostęp do narzędzia ct-ng, czyli konfiguratora crosstool-ng. Możemy teraz wylistować wszystkie możliwe konfiguracje (jest ich BARDZO dużo) i wpisać odpowiednie ustawienia dla danego targetu. W tym przypadku Raspberry Pi 4
./ct-ng list-samples | grep -i rpi
./ct-ng show-aarch64-rpi4-linux-gnu
# wpisanie odpowiedniego configa
./ct-ng aarch64-rpi4-linux-gnu
Jeśli chcemy wprowadzić dodatkowe zmiany, możemy to zrobić znanym m.in. z kernela narzędziem menuconfig:
./ct-ng menuconfig
Jesteśmy teraz gotowi do budowania. Potrwa około 20-30 minut.
./ct-ng build
Końcowym efektem budowania będzie powstanie folderu o nazwie x-tools, gdzie będą się znajdować wszystkie narzędzia potrzebne do cross-kompilacji oraz GDB i gdbserver.
gdbserver
Przechodzimy do ścieżki, gdzie znajduje się gdbserver i przerzucamy go za pomocą SCP na nasz target
cd crosstool-ng/x-tools/aarch64-rpi4-linux-gnu/aarch64-rpi4-linux-gnu/debug-root/usr/bin
scp gdbserver root@<TARGET IP>:/tmp
Logujemy się na target. W moim przypadku aplikacja, którą chcę debugować to prosty hello world, skompilowany wcześniej na hoście. Odpalamy gdbserver wraz z wybranym portem (tutaj 1234) oraz ścieżką do pliku, który chcemy zbadać:
cd /tmp
./gdbserver :1234 <SCIEZKA DO BINARKI>
W tym momencie na porcie 1234 mamy wystawiony interfejs do GDB. Pozostaje tylko połączyć się z nim z naszego komputera.
Na hoście przechodzimy do katalogu gdzie znajduje się główny program GDB:
cd crosstool-ng/x-tools/aarch64-rpi4-linux-gnu/bin
./aarch64-rpi4-linux-gnu-gdb <SCIEZKA DO BINARKI>
Musimy podać ścieżkę do skompilowanego pliku.
UWAGA: Żeby wszystko zadziałało jak trzeba, plik musi być skompilowany razem z tzw. 'debug symbols’. Dzięki temu będziemy dokładnie wiedzieć jakie są w nim zdefiniowane funkcje, zmienne itp. Możemy to zrobić dodając przy kompilacji flagę ’-g’. Tutaj więcej informacji: https://gcc.gnu.org/onlinedocs/gcc/Debugging-Options.html
Została ostatnia rzecz. Musimy przekazać GDB do jakiego targetu chcemy się podłączyć. Używamy tego samego portu, który określiliśmy przy wywołaniu gdbserver
target remote <IP TARGETU>>:1234
Możliwości GDB
Po połączeniu serwera z targetem mamy dostęp do najpopularniejszych funkcji gdb. Możemy np.
Ustawić breakpoint w programie, w konkretnym pliku, w konkretnej linijce:
break hello.c:12
Sprawdzić co doprowadziło do crashu aplikacji:
backtrace
Obserwować wartość zmiennych lokalnych:
info locals
Lub po prostu sprawić że program zacznie się wykonywać:
continue
Daj znać jeśli znasz inne ciekawe techniki debugowania poza GDB. Zawsze warto rozszerzać swój arsenał narzędziowy!
Źródła
W tym wpisie korzystałem z linków:
https://crosstool-ng.github.io/docs
https://gcc.gnu.org/onlinedocs/gcc/Debugging-Options.html
https://www.cs.umd.edu/~srhuang/teaching/cmsc212/gdb-tutorial-handout.pdf
Dodaj komentarz