Narzędzia użytkownika

Narzędzia witryny


Pasek boczny

sk2:debug_tools

Narzędzia pomocne przy diagnozowaniu problemów w trakcie pisania programów sieciowych

Lista narzędzi które mogą być przydatne przy diagnostyce dlaczego program nie działa jak powinien (z naciskiem na obsługę sieci).

Opis jest bardzo skrótowy. Jeśli mam go rozwinąć, proszę o informację które narzędzia opisać dokładniej.

Jeśli korzystacie z jakiegoś narzędzia które warto tutaj dodać, to chętnie je tutaj dopiszę.

Kompilacja z symbolami do debugu

Część opisanych tutaj narzędzi nie będzie produkować użytecznych wyników jeśli program nie zostanie zbudowany z symbolami debugowania.
Np. w gcc/clang należy dodać opcję -g do linii poleceń, dla cmake wybrać typ budowania Debug lub RelWithDebInfo (cmake -DCMAKE_BUILD_TYPE=Debug ścieżka).

Obecność symboli do debugu można sprawdzić np. przez:
objdump -h <prog> | grep -B1 DEBUGGING
lub readelf -S <prog> | grep '.debug_
a także w wynikach np. nm czy bingrep

W C++ nazwy funkcji w trakcie kompilacji są zniekształcone (ang. mangled) tak by linker poradził sobie z namespace'ami, temple'ami i funkcjami o tej samej nazwie z różną listą parametrów.

Stąd np. funkcja:std::chrono::microseconds::count()
która po rozwinięciu typedefów nazywa się: std::chrono::duration<long, std::ratio<1l, 1000000l> >::count()
w binarce nazywa się:_ZNKSt6chrono8durationIlSt5ratioILl1ELl1000000EEE5countEv
Aby dostać z powrotem czytelną nazwę, należy użyć programu c++filt <nazwa> lub ustawić odpowiednią opcję narzędzia które wypluwa nieczytelne nazwy tak, by przekształcało je do czytelnej formy (demangling). W valgrind i gdb demangling powinien być włączony automatycznie.

Tracing

Tracing to śledzenie wykonania programu, zwykle przez wypisywanie informacji o wybranych zdarzeniach. Poniższe narzędzia można traktować jako automatyczne dodanie wypisywania na ekran wywołań funkcji systemowych / bibliotecznych.

strace

strace śledzi wywołania systemowe i wypisuje je na standardowy błąd.
Dla każdego wywołania pokazuje w czytelny sposób argumenty i uzyskany wynik.

W programie wielowątkowym można śledzić wszystkie wątki podając opcję -f.

Dla analizy wywołań systemowych dla programu a.out z argumentami np. localhost i 1234 należy uruchomić:
strace -f ./a.out localhost 1234

Kiedy warto używać:
Kiedy podejrzewa się problem z łączeniem / nasłuchiwaniem / odczytami / zapisami, ale nie wiadomo do końca gdzie.

Uwaga: niektóre funkcje biblioteczne (np. getaddrinfo) nie są jednym wywołaniem systemowym, a kodem złożonym z zera lub więcej wywołań.

Uwaga: start programu to zwykle kilkadziesiąt wywołań systemowych, więc początek wyników strace można pominąć. Szukajcie np. pierwszego wywołania socket.

strace pozwala filtrować jakie wywołania was interesują (szczegóły w man strace).

Fragment ''strace -f nc localhost 1313'':

ltrace

ltrace śledzi wywołania biblioteczne i wypisuje je na standardowy błąd.
Dla każdego wywołania pokazuje argumenty i uzyskany wynik (ale nie tłumaczy co oznaczają te argumenty).

Użycie i zastosowanie podobnie jak dla strace.

Fragment ''ltrace -f nc 0 1313'':

bpftrace

Dla zaawansowanych. Śledzenie dynamiczne. Wymaga roota.
Więcej na: https://github.com/iovisor/bpftrace i https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md

Wypisywanie każdego wywołania funkcji nazwa_funkcji z programu wraz z pierwszym argumentem (nazwa funkcji w binarce, więc np. _ZN3foo3barEi a nie namespace foo { void bar(int baz); }):
# bpftrace -e 'uprobe:/pełna/ścieżka/do/programu:nazwa_funkcji {printf("Foo: %ld\n", arg0);}'
$ /pełna/ścieżka/do/programu argument1 argument2

Wypisywanie wyników każdego reada wywoływanego przez program o nazwie programu):

# bpftrace -e 'tracepoint:syscalls:sys_enter_read /comm == "programu"/ {@s[tid]=args->buf;} tracepoint:syscalls:sys_exit_read /comm == "programu"/ { if (args->ret==-1) { printf("Read failed\n"); } else { printf("Read of %ld bytes: |%s|\n", args->ret, str(@s[tid], args->ret)); } }'
$ /pełna/ścieżka/do/programu argument1 argument2

Profilowanie

Profilowanie (profiling) to zbieranie metadanych o działaniu programu (w trakcie jego działania) dla zaprezentowania statystyk np. zużycia procesora, pamięci, wystąpienia konkretnych zdarzeń (np. cache miss), … w konkretnych liniach, funkcjach, stosach wywołań, … . Profilowanie zwykle jest wykonywane dla dowiedzenia się co zjada zasoby lub do optymalizacji kodu.

perf

perf jest rozbudowanym narzędziem m. inn. do profilowania kodu w Linuksie.
Bez uprawnień roota lub ustawień kernela pozwalających zwykłemu użytkownikowi profilować kod działa w ograniczonym zakresie.

Wywołanie (dla programu a.out z argumentami localhost i 1234) składa się z dwóch etapów – uruchomienia programu połączonego ze zbierania danych i analizy:
perf record --call-graph=dwarf ./a.out localhost 1234
perf report

Kiedy warto używać:
Kiedy program nadużywa procesora, a nie powinien.

Wynik programu perf można przetworzyć na flame graph.
perf może zbierać informacje o innych zdarzeniach niż cykle procesora – wykonaj perf list.

Zarządzanie pamięcią

valgrind

valgrind (a dokładniej memcheck tool z valgrinda) śledzi dostępy, zajmowanie i zwalnianie pamięci i sprawdza poprawność każdego dostępu i zwalniania pamięci.
Jeśli jakaś operacja jest niepoprawna (dostęp poza przydzieloną pamięć, zwalnianie niezaalokowanej pamięci etc.) to opisuje na czym on polega i gdzie została wywołana (stack trace) i nie przerywa działania program jeśli to możliwe.
Po wykonaniu programu valgrind sprawdza czy cała zajęta pamięć została zwolniona, a jeśli nie to raportuje ile jej jest i gdzie została zaalokowana.

Wywołanie (dla programu a.out z argumentami localhost i 1234):
valgrind ./a.out localhost 1234

Kiedy warto używać:
Kiedy program kończy się segmentation fault lub w wynikach pojawiają się "śmieci" z RAMu.
Poza tym zawsze jeśli używa się dynamicznej alokacji pamięci dla sprawdzenia czy program nie ma memleaków.

Przykłady: https://www.valgrind.org/docs/manual/mc-manual.html

Debugging

gdb

gdb jest standardowym debuggerem w Linuksie. Zwykle prostych zadaniach wystarczy użycie gdb z IDE.
Tutaj krótko spisałem ważniejsze polecenia konsoli gdb.

Wywołanie (dla programu a.out z argumentami localhost i 1234):
gdb --args ./a.out localhost 1234
(gdb) ………

Wywołanie (dla pliku zrzutu pamięci core_dump' programu a.out):
gdb -c core_dump ./a.out
(gdb) ………

Pomoc do polecenia to help <polecenie>.
Podstawowe polecenia to:
Łapanie: (b)reak <expr> catch <what> watch <expr> condition <num> <expr>
Listowanie break/watch-pointów: info breakpints/watchpoints
Usuwanie break/watch-pointów: del break/watch
Uruchomienie programu: start (r)un kill
Komendy do kolejnych kroków: (n)ext (s)tep (fin)ish (u)ntil (c)ontinue
Wypisywanie informacji: print <expr> whatis <expr> ptype <expr> info arg list x
Stos: backtrace frame <nr>
Wątki: info threads thread <nr>

Kiedy warto używać:
Kiedy program nie działa jak powinien i nie wiecie dlaczego – należy ustawić breakpointy i sprawdzać do którego momentu program działa zgodnie z oczekiwaniami.
Kiedy program wygeneruje zrzut pamięci (core dump) i chcecie wiedzieć dlaczego uległ awarii.

Analizatory statyczne

Analiza statyczna to wnioskowanie o programie na podstawie kodu programu, bez uruchamiania go. Przez swoje ograniczenia (działanie tylko na kodzie) pozwala na znalezienie tylko pewnej grupy błędów, natomiast nie wymaga żeby te błędy się wydarzyły w trakcie uruchomienia.

Kiedy warto używać:
Zawsze dla sprawdzenia czy kod nie ma błędów.

kompilator

Kompilatory z włączonymi ostrzeżeniami czasami wskazują gdzie mógł zostać popełniony jakiś typowy błąd. Należy włączyć i czytać te ostrzeżenia.

scan-build z llvm

scan-build to analizator statyczny dla C/C++ "w pakiecie" z clang i LLVM.

Dla przykładowego użycia sprawdź: https://clang-analyzer.llvm.org/scan-build.html

Jeśli scan-build znajdzie problemy na etapie analizy statycznej, wygeneruje raport co i kiedy może pójść nie tak.

cppcheck

cppcheck to w miarę sensowny analizator statyczny dla C++.

Przykładowe użycie – analizuje pliki w bieżącym katalogu:
cppcheck-gui ./

Sieć

Analiza tego co faktycznie dzieje się w sieci pomaga w określeniu np. czy klient generuje błędne wiadomości czy też serwer źle przetwarza prawidłowe wiadomości.

wireshark

wireshark pokazuje wszystkie ramki przechodzące przez podany interfejs. Konsolowym odpowiednikiem jest tcpdump.
tcpdump może zapisać plik który później da sie przeglądać w wiresharku: tcpdump -w <plik> -i <interfejs>.

Kiedy warto używać:
Kiedy coś jest nie tak z komunikacją, szczególnie do porównania czy przesyłane dane wyglądają tak jak tego oczekuje programista.

sk2/debug_tools.txt · ostatnio zmienione: 2021/03/31 13:47 przez jkonczak