Co to jest debugger i jak może mi pomóc zdiagnozować problemy?

107

Ma to być pytanie ogólnego przeznaczenia, aby pomóc nowym programistom, którzy mają problem z programem, ale nie wiedzą, jak użyć debugera do zdiagnozowania przyczyny problemu.

To pytanie obejmuje trzy klasy bardziej szczegółowych pytań:

  • Kiedy uruchamiam program, nie generuje on wyników, których oczekuję od danych wejściowych, które mu podałem.
  • Kiedy uruchamiam program, ulega awarii i wyświetla mi ślad stosu. Mam zbadać ślad stosu , ale nadal nie wiem przyczynę problemu, ponieważ ślad stosu nie daje mi wystarczająco dużo informacji.
  • Kiedy uruchamiam program, ulega awarii z powodu błędu segmentacji (SEGV).
Raedwald
źródło
3
Niezła robota - dobrze byłoby mieć również powiązane pytania i odpowiedzi typu „przejdź do” dotyczące technik debugowania , np. Używania debugera, innych narzędzi debugowania (np. Valgrind), strategicznych printfs, testów warunków skrajnych, dziel i rządź itp.
Paul R
1
Zgadzam się z @PaulR, FAQ powinno zawierać takie rzeczy.
Nicu Stiurca

Odpowiedzi:

73

Debugger to program, który może zbadać stan programu podczas jego działania. Środki techniczne, których używa do tego, nie są ważne dla zrozumienia podstaw korzystania z debugera. Możesz użyć debugera, aby zatrzymać wykonywanie programu, gdy osiągnie on określone miejsce w kodzie, a następnie zbadać wartości zmiennych w programie. Możesz użyć debugera do bardzo powolnego uruchamiania programu, po jednym wierszu kodu na raz (nazywanym pojedynczym krokiem ), podczas badania wartości jego zmiennych.

Korzystanie z debugera to oczekiwana podstawowa umiejętność

Debugger to bardzo potężne narzędzie pomagające w diagnozowaniu problemów z programami. Debuggery są dostępne dla wszystkich praktycznych języków programowania. Dlatego umiejętność korzystania z debuggera jest uważana za podstawową umiejętność każdego profesjonalnego lub entuzjastycznego programisty. I za pomocą debuggera samemu uważa się za podstawowe prace należy zrobić sobie przed pytając innych o pomoc. Ponieważ ta witryna jest przeznaczona dla profesjonalnych i entuzjastów programistów, a nie jest stroną pomocy technicznej lub mentoringu, jeśli masz pytanie dotyczące problemu z określonym programem, ale nie korzystałeś z debugera, jest bardzo prawdopodobne, że Twoje pytanie zostanie zamknięte i odrzucone. Jeśli będziesz upierał się przy takich pytaniach, w końcu zostaniesz zablokowany przed publikowaniem kolejnych.

Jak debugger może ci pomóc

Używając debuggera, możesz odkryć, czy zmienna ma złą wartość i gdzie w programie została zmieniona na niewłaściwą wartość.

Korzystając z pojedynczych kroków, możesz również sprawdzić, czy przepływ sterowania jest zgodny z oczekiwaniami. Na przykład, czy ifgałąź została wykonana, kiedy się tego spodziewasz.

Ogólne uwagi dotyczące korzystania z debuggera

Specyfika korzystania z debugera zależy od debugera i, w mniejszym stopniu, od używanego języka programowania.

  • Możesz dołączyć debugger do procesu, który już działa w twoim programie. Możesz to zrobić, jeśli program utknął.

  • W praktyce często łatwiej jest uruchomić program od samego początku pod kontrolą debuggera.

  • Wskazujesz miejsce, w którym twój program powinien przestać działać, wskazując plik z kodem źródłowym i numer wiersza, w którym ma się zatrzymać wykonywanie, lub wskazując nazwę metody / funkcji, na której program ma się zatrzymać (jeśli chcesz zatrzymać jako jak tylko wykonanie wejdzie do metody). Środek techniczny używany przez debuger do zatrzymania programu nazywany jest punktem przerwania, a ten proces nazywa się ustawieniem punktu przerwania .

  • Większość nowoczesnych debugerów jest częścią IDE i zapewnia wygodny interfejs graficzny do badania kodu źródłowego i zmiennych programu, z interfejsem typu „wskaż i kliknij” do ustawiania punktów przerwania, uruchamiania programu i wykonywania jednego kroku.

  • Korzystanie z debugera może być bardzo trudne, chyba że pliki wykonywalne programu lub pliki kodu bajtowego zawierają informacje o symbolach debugowania i odniesienia do kodu źródłowego. Być może będziesz musiał skompilować (lub ponownie skompilować) swój program w nieco inny sposób, aby upewnić się, że informacje są obecne. Jeśli kompilator przeprowadza rozległe optymalizacje, te odniesienia mogą stać się mylące. Dlatego może być konieczne ponowne skompilowanie programu z wyłączoną optymalizacją .

Raedwald
źródło
4
Jest to niekompletne, ponieważ pomija najważniejszy debugger ze wszystkich, który może bardzo znacząco zmniejszyć liczbę pytań na Stackoverflow (przewiduję co najmniej o 20%) - debuggery javascript: firebug, Chrome, Firefox, IE9 + zintegrowany debugger , IE8- Visual Studio itp.
slebetman,
1
Również dla node.js - inspektor węzłów. Jednak programiści node.js nie zadają tylu podstawowych i / lub napraw-mój-kod, co zwykli programiści javascript.
slebetman,
40

Chcę dodać, że debugger nie zawsze jest idealnym rozwiązaniem i nie zawsze powinien być rozwiązaniem do debugowania. Oto kilka przypadków, w których debugger może nie działać dla Ciebie:

  • Część twojego programu, która zawodzi, jest naprawdę duża (być może słaba modularyzacja?) I nie jesteś do końca pewien, od czego zacząć przechodzenie przez kod. Przejście przez to wszystko może być zbyt czasochłonne.
  • Twój program używa wielu wywołań zwrotnych i innych nieliniowych metod kontroli przepływu, co sprawia, że ​​debugger jest zdezorientowany, gdy przez niego przechodzisz.
  • Twój program jest wielowątkowy. Albo co gorsza, twój problem jest spowodowany stanem wyścigu.
  • Kod, w którym jest błąd, jest uruchamiany wiele razy, zanim zostanie usunięty. Może to być szczególnie problematyczne w głównych pętlach lub, co gorsza, w silnikach fizycznych, gdzie problem może być numeryczny. Nawet ustawienie punktu przerwania w tym przypadku po prostu spowodowałoby, że uderzyłbyś go wiele razy, a błąd się nie pojawił.
  • Twój program musi działać w czasie rzeczywistym. Jest to duży problem dla programów łączących się z siecią. Jeśli skonfigurujesz punkt przerwania w kodzie sieci, drugi koniec nie będzie czekał, aż przejdziesz, po prostu wygaśnie. Programy, które opierają się na zegarze systemowym, np. Gry z pomijaniem klatek, również nie są dużo lepsze.
  • Twój program wykonuje jakąś formę destrukcyjnych działań, takich jak zapisywanie w plikach lub wysyłanie e-maili, i chciałbyś ograniczyć liczbę razy, kiedy musisz przez niego przejść.
  • Możesz powiedzieć, że twój błąd jest spowodowany przez nieprawidłowe wartości docierające do funkcji X, ale nie wiesz, skąd te wartości pochodzą. Konieczność ciągłego przeglądania programu i ustawiania punktów przerwania coraz dalej i dalej wstecz może być ogromnym kłopotem. Zwłaszcza jeśli funkcja X jest wywoływana z wielu miejsc w programie.

We wszystkich tych przypadkach albo nagłe zatrzymanie programu może spowodować różnice w wynikach końcowych, albo ręczne przejście w poszukiwaniu jednej linii, w której spowodowany jest błąd, jest zbyt kłopotliwe. Może się to również zdarzyć niezależnie od tego, czy błąd zachowuje się nieprawidłowo, czy też się zawiesił. Na przykład, jeśli uszkodzenie pamięci powoduje awarię, do momentu wystąpienia awarii jest zbyt daleko od miejsca, w którym nastąpiło pierwsze uszkodzenie pamięci i nie pozostają żadne przydatne informacje.

Więc jakie są alternatywy?

Najprostsze jest po prostu logowanie i asercje. Dodawaj dzienniki do programu w różnych miejscach i porównuj otrzymane wyniki z oczekiwaniami. Na przykład sprawdź, czy funkcja, w której myślisz, że jest błąd, jest w ogóle wywoływana w pierwszej kolejności. Sprawdź, czy zmienne na początku metody są tym, czym myślisz, że są. W przeciwieństwie do punktów przerwania, istnieje wiele wierszy dziennika, w których nie dzieje się nic specjalnego. Możesz po prostu przeszukać dziennik później. Gdy trafisz w wiersz dziennika, który różni się od tego, czego się spodziewasz, dodaj więcej w tym samym obszarze. Zawężaj go coraz dalej, aż stanie się wystarczająco mały, aby móc zarejestrować każdą linię w zanieczyszczonym obszarze.

Asercji można używać do wychwytywania nieprawidłowych wartości w momencie ich wystąpienia, a nie wtedy, gdy mają one wpływ widoczny dla użytkownika końcowego. Im szybciej złapiesz nieprawidłową wartość, tym bliżej jesteś linii, która ją utworzyła.

Refaktoryzacja i test jednostkowy. Jeśli Twój program jest zbyt duży, warto przetestować go pojedynczo po jednej klasie lub funkcji. Podaj dane wejściowe, spójrz na wyniki i zobacz, które nie są zgodne z oczekiwaniami. Możliwość zawężenia błędu z całego programu do pojedynczej funkcji może mieć ogromny wpływ na czas debugowania.

W przypadku wycieków pamięci lub tępienia pamięci użyj odpowiednich narzędzi, które są w stanie analizować i wykrywać je w czasie wykonywania. Umiejętność wykrycia, gdzie faktycznie występuje korupcja, jest pierwszym krokiem. Następnie możesz użyć dzienników, aby wrócić do miejsca, w którym wprowadzono nieprawidłowe wartości.

Pamiętaj, że debugowanie to proces cofający się. Masz wynik końcowy - błąd - i znajdź przyczynę, która go poprzedzała. Chodzi o cofanie się i, niestety, debuggery robią tylko krok naprzód. W tym miejscu dobre logowanie i analiza pośmiertna mogą dać znacznie lepsze wyniki.

SlugFiller
źródło
10
To byłaby dobra odpowiedź ... na inne pytanie. To zła odpowiedź na to pytanie. Być może powinieneś zadać to pytanie i zamieścić to jako odpowiedź na nie.
Raedwald,
10
Właściwe pytanie jest opisane jako „pomoc nowym programistom, którzy mają problem z programem”, „nie daje oczekiwanych wyników” oraz „Zbadałem ślad stosu, ale nadal nie znam przyczyny problemu” . Wszystko to jest wspomagane tą odpowiedzią. Dodatkowo, wyjaśniając, co robi debugger, równie ważne jest wyjaśnienie, czego nie robi.
SlugFiller
5
Świetna odpowiedź. Zawsze używałem debuggera jako głównego narzędzia do znajdowania błędów. Ale teraz pracuję nad projektem, w którym ogromny komponent infrastruktury wykorzystuje wiele wątków i dużo kodu sieciowego (klient / serwer) i zauważam, że debugger jest ostatnią rzeczą, która mi pomaga. Wspomniałeś o wielu rzeczach, w których naprawdę powinieneś używać innego narzędzia zamiast polegać na starym dobrym debugerze (najważniejsze: logowanie).
Tim Schmelter
„Można stwierdzić, że przyczyną błędu są nieprawidłowe wartości docierające do funkcji X, ale nie wiadomo, skąd te wartości pochodzą”. Jest to szczególnie trudne do debugowania. Jak zazwyczaj rozwiązujesz coś takiego?
Ayxan Haqverdili
3
@Ayxan Do pewnego stopnia, jeśli udało Ci się przerwać działanie funkcji w asercie, możesz użyć stosu wywołań, aby pobrać wywołującego. Ale samo to nie daje źródła wartości, ponieważ najprawdopodobniej wartość pochodzi z wcześniejszej linii. Zasadniczo musisz śledzić wartość z powrotem, poprzez różne zmienne, przez które przechodzi. Jeśli masz dobre pojęcie o ścieżce danych, możesz po prostu utworzyć kilka wydruków dziennika i spróbować określić, gdzie „idzie źle”. Jeśli nie, w zasadzie będziesz potrzebować osobnego uruchomienia programu (odtwarzającego błąd) dla każdego kroku wstecz.
SlugFiller