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ę.
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 |
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 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
ś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
).
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.
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 (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
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
.
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
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.
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.
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
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
to w miarę sensowny analizator statyczny dla C++.
Przykładowe użycie – analizuje pliki w bieżącym katalogu:
cppcheck-gui ./
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
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.