Naprawianie błędów segmentacji w C ++

95

Piszę wieloplatformowy program w C ++ dla systemów Windows i Unix. Po stronie okna kod będzie się kompilował i wykonywał bez problemu. Po stronie systemu Unix będzie się kompilować, jednak gdy próbuję go uruchomić, pojawia się błąd segmentacji. Moje początkowe przeczucie jest takie, że jest problem ze wskazówkami.

Jakie są dobre metodologie wyszukiwania i naprawiania błędów błędów segmentacji?

Elpezmuerto
źródło

Odpowiedzi:

137
  1. Skompiluj swoją aplikację za pomocą -g, będziesz mieć symbole debugowania w pliku binarnym.

  2. Służy gdbdo otwierania konsoli gdb.

  3. Użyj filei przekaż plik binarny swojej aplikacji w konsoli.

  4. Użyj runi przekaż wszystkie argumenty, których aplikacja potrzebuje do uruchomienia.

  5. Zrób coś, co spowoduje błąd segmentacji .

  6. Wpisz btw gdbkonsoli, aby uzyskać ślad stosu błędu segmentacji .

Svisstack
źródło
Co to znaczy skompilować go gw kontekście CMake?
Schütze
3
Włącz typ kompilacji do debugowania. Jeden sposób cmake -DCMAKE_BUILD_TYPE=Debug.
Antonin Décimo
36

Czasami sama awaria nie jest prawdziwą przyczyną problemu - być może pamięć została zniszczona na wcześniejszym etapie, ale uszkodzenie ujawniło się dopiero po pewnym czasie. Sprawdź valgrind , który ma wiele sprawdzeń pod kątem problemów ze wskaźnikami (w tym sprawdzanie granic tablicy). Poinformuje Cię, gdzie zaczyna się problem , a nie tylko w linii, w której występuje awaria.

paleozogt
źródło
19

Zanim pojawi się problem, staraj się go jak najbardziej unikać:

  • Kompiluj i uruchamiaj swój kod tak często, jak to możliwe. Łatwiej będzie zlokalizować wadliwą część.
  • Spróbuj hermetyzować procedury niskiego poziomu / podatne na błędy, aby rzadko musieć pracować bezpośrednio z pamięcią (zwróć uwagę na modelowanie programu)
  • Utrzymuj zestaw testów. Przegląd tego, co obecnie działa, co już nie działa itp., Pomoże ci ustalić, gdzie jest problem ( test przyspieszenia jest możliwym rozwiązaniem, sam go nie używam, ale dokumentacja może pomóc zrozumieć, jakiego rodzaju informacji musi zostać wyświetlona).

Użyj odpowiednich narzędzi do debugowania. W systemie Unix:

  • GDB może ci powiedzieć, gdzie nastąpiła awaria programu i pozwoli ci zobaczyć, w jakim kontekście.
  • Valgrind pomoże Ci wykryć wiele błędów związanych z pamięcią.
  • Z GCC możesz również użyć błotnika Z GCC, Clang i od października eksperymentalnie MSVC możesz użyć Address / Memory Sanitizer . Potrafi wykryć pewne błędy, których Valgrind nie robi, a utrata wydajności jest mniejsza. Jest używany przez kompilację z -fsanitize=addressflagą.

Na koniec poleciłbym zwykłe rzeczy. Im bardziej Twój program jest czytelny, łatwy w utrzymaniu, przejrzysty i schludny, tym łatwiej będzie go debugować.

log0
źródło
6

W systemie Unix możesz użyć valgrinddo znalezienia problemów. Jest darmowy i potężny. Jeśli wolisz zrobić to sam, możesz przeciążyć operatory newi delete, aby ustawić konfigurację, w której masz 1 bajt 0xDEADBEEFprzed i po każdym nowym obiekcie. Następnie śledź, co się dzieje w każdej iteracji. Może to nie przechwycić wszystkiego (nie masz gwarancji, że nawet dotkniesz tych bajtów), ale działało to dla mnie w przeszłości na platformie Windows.

pszenicy
źródło
1
cóż, byłyby to 4 bajty zamiast 1 ... ale zasada jest w porządku.
Jonas Wagner
1
Czy mogę utworzyć łącze do mojego nieinwazyjnego debugera sterty ? :-)
fredoverflow
Idź po to. Naszym celem jest pomaganie innym, więc wszystko, co może pomóc, powinno zostać dodane.
pszenicy
Chociaż przeciążenie newi deletemoże być bardzo przydatne, użycie -fsanitize=addressjest lepszą opcją, ponieważ kompilator będzie kompilował się w wykrywaniu problemów w czasie wykonywania i automatycznie zrzuci pamięć na ekran, co znacznie ułatwia debugowanie.
Tarick Welling
Oprócz newi delete, możesz zawijać, mallocjeśli używasz gcc. Zobacz --wrap=symbol. Zamierzam to zrobić w kodzie wydania, aby uzyskać kilka diagnostyki w czasie wykonywania.
Daisuke Aramaki
3

Tak, jest problem ze wskazówkami. Bardzo prawdopodobne, że używasz takiego, który nie został poprawnie zainicjowany, ale możliwe jest również, że psujesz zarządzanie pamięcią przez podwójne zwolnienia lub coś takiego.

Aby uniknąć niezainicjowanych wskaźników jako zmiennych lokalnych, spróbuj zadeklarować je tak późno, jak to możliwe, najlepiej (a to nie zawsze jest możliwe), kiedy można je zainicjować znaczącą wartością. Przekonaj się, że będą miały wartość, zanim zostaną użyte, badając kod. Jeśli masz z tym trudności, zainicjuj je jako stałą wskaźnika zerowego (zwykle zapisaną jako NULLlub 0) i sprawdź je.

Aby uniknąć niezainicjowanych wskaźników jako wartości składowych, upewnij się, że są one poprawnie zainicjowane w konstruktorze i prawidłowo obsługiwane w konstruktorach kopiujących i operatorach przypisania. Nie polegaj na initfunkcji do zarządzania pamięcią, chociaż możesz do innej inicjalizacji.

Jeśli Twoja klasa nie potrzebuje konstruktorów kopiujących ani operatorów przypisania, możesz zadeklarować je jako prywatne funkcje składowe i nigdy ich nie definiować. Spowoduje to błąd kompilatora, jeśli zostaną użyte jawnie lub niejawnie.

W stosownych przypadkach używaj inteligentnych wskaźników. Dużą zaletą jest to, że jeśli będziesz się ich trzymać i konsekwentnie używać, możesz całkowicie uniknąć pisania deletei nic nie zostanie podwójnie usunięte.

Jeśli to możliwe, używaj ciągów C ++ i klas kontenerów zamiast ciągów i tablic w stylu C. Rozważ użycie .at(i)zamiast [i], ponieważ wymusi to sprawdzanie granic. Sprawdź, czy Twój kompilator lub bibliotekę można ustawić tak, aby sprawdzał ograniczenia [i], przynajmniej w trybie debugowania. Błędy segmentacji mogą być spowodowane przepełnieniem bufora, które zapisuje śmieci po idealnie dobrych wskaźnikach.

Robienie tych rzeczy znacznie zmniejszy prawdopodobieństwo błędów segmentacji i innych problemów z pamięcią. Bez wątpienia nie naprawią wszystkiego i dlatego powinieneś używać valgrind od czasu do czasu, kiedy nie masz problemów, a valgrind i gdb, kiedy to zrobisz.

David Thornley
źródło
1

Nie znam żadnej metodologii, aby naprawić takie rzeczy. Nie sądzę, by można było wymyślić taki, ponieważ sam problem polega na tym, że zachowanie twojego programu jest niezdefiniowane (nie znam żadnego przypadku, gdy SEGFAULT nie został spowodowany przez jakiś rodzaj UB) .

Istnieją różne „metodologie” pozwalające uniknąć problemu, zanim się on pojawi. Jednym z ważnych jest RAII.

Poza tym musisz po prostu rzucić w to swoje najlepsze psychiczne energie.

Edward Strange
źródło