Jak debugować błędy uszkodzenia sterty?

165

Debuguję (natywną) wielowątkową aplikację C ++ w programie Visual Studio 2008. W pozornie przypadkowych przypadkach pojawia się komunikat o błędzie „Windows wyzwolił punkt przerwania ...” z informacją, że może to być spowodowane uszkodzeniem sterta. Te błędy nie zawsze powodują awarię aplikacji od razu, chociaż prawdopodobnie nastąpi awaria wkrótce potem.

Duży problem z tymi błędami polega na tym, że pojawiają się one dopiero po wystąpieniu uszkodzenia, co bardzo utrudnia ich śledzenie i debugowanie, szczególnie w przypadku aplikacji wielowątkowych.

  • Jakie rzeczy mogą powodować te błędy?

  • Jak je debugować?

Wskazówki, narzędzia, metody, oświecenia ... są mile widziane.

Peter Mortensen
źródło

Odpowiedzi:

128

Application Verifier w połączeniu z Debugging Tools for Windows to niesamowita konfiguracja. Możesz uzyskać oba jako część zestawu Windows Driver Kit lub lżejszego Windows SDK . (Dowiedziałem się o Application Verifier podczas badania wcześniejszego pytania o problem z uszkodzeniem sterty ). W przeszłości również korzystałem z BoundsChecker i Insure ++ (wspomnianych w innych odpowiedziach), chociaż byłem zaskoczony, ile funkcji jest w Application Verifier.

Warto wspomnieć o Electric Fence (aka „efence”), dmalloc , valgrind i tak dalej, ale większość z nich jest znacznie łatwiejsza do uruchomienia pod * nix niż Windows. Valgrind jest absurdalnie elastyczny: debugowałem oprogramowanie dużego serwera z wieloma problemami ze stertą, używając go.

Kiedy wszystko inne zawiedzie, możesz zapewnić własnego globalnego operatora przeciążenia new / delete i malloc / calloc / realloc - jak to zrobić, będzie się nieco różnić w zależności od kompilatora i platformy - i będzie to trochę inwestycja - ale na dłuższą metę może się opłacić. Lista pożądanych funkcji powinna wyglądać znajomo z dmalloc i electricfence oraz zaskakująco doskonałej książki Writing Solid Code :

  • wartości wartowników : zapewniają trochę więcej miejsca przed i po każdym przydziale, przestrzegając maksymalnego wymogu dostosowania; wypełnij magicznymi liczbami (pomaga wychwycić przepełnienia i niedomiary bufora oraz sporadyczny „dziki” wskaźnik)
  • przydziel wypełnienie : wypełnij nowe alokacje magiczną wartością różną od 0 - Visual C ++ zrobi to już za Ciebie w kompilacjach debugowania (pomaga złapać użycie niezainicjowanych zmiennych)
  • bezpłatne wypełnienie : wypełnij zwolnioną pamięć magiczną wartością różną od 0, zaprojektowaną w celu wywołania segfault, jeśli jest wyłuskana w większości przypadków (pomaga złapać wiszące wskaźniki)
  • opóźnione wolne : nie zwracaj zwolnionej pamięci na stos przez chwilę, utrzymuj ją jako wolną wypełnioną, ale niedostępną (pomaga złapać więcej zwisających wskaźników, łapie bliższe podwójne zwolnienia)
  • śledzenie : możliwość zarejestrowania, gdzie dokonano alokacji, może być czasami przydatna

Zwróć uwagę, że w naszym lokalnym systemie homebrew (dla osadzonego celu) śledzenie jest oddzielone od większości innych rzeczy, ponieważ narzut czasu wykonywania jest znacznie wyższy.


Jeśli interesuje Cię więcej powodów, aby przeciążać te funkcje / operatory alokacji, spójrz na moją odpowiedź na pytanie „Czy jest jakiś powód, aby przeciążać operatora globalnego nowy i usunąć?” ; Pomijając bezwstydną autopromocję, wymienia inne techniki, które są pomocne w śledzeniu błędów uszkodzenia sterty, a także inne odpowiednie narzędzia.


Ponieważ wciąż znajduję tutaj własną odpowiedź, szukając wartości przydziału / wolnego / ogrodzenia, których używa MS, oto kolejna odpowiedź, która obejmuje wartości wypełnienia dbgheap firmy Microsoft .

leander
źródło
3
Jedna drobna rzecz, na którą warto zwrócić uwagę w narzędziu Application Verifier: musisz zarejestrować symbole Application Verifier przed symbolami serwera symboli firmy Microsoft w ścieżce wyszukiwania symboli, jeśli tego używasz ... Potrzebowałem trochę wyszukiwania, aby dowiedzieć się, dlaczego! Avrf nie znalezienie potrzebnych symboli.
leander
Application Verifier był bardzo pomocny i w połączeniu z pewnym zgadywaniem udało mi się rozwiązać problem! Bardzo dziękuję i wszystkim innym za poruszenie przydatnych punktów.
Czy weryfikator aplikacji musi być używany z WinDbg, czy powinien działać z debugerem programu Visual Studio? Próbowałem go użyć, ale nie generuje żadnych błędów ani najwyraźniej nic nie robi podczas debugowania w VS2012.
Nathan Reed,
@NathanReed: Wydaje mi się, że działa również z VS - patrz msdn.microsoft.com/en-us/library/ms220944(v=vs.90).aspx - chociaż zauważ, że ten link jest dla VS2008, nie jestem pewnie o późniejszych wersjach. Pamięć jest trochę rozmyta, ale wydaje mi się, że kiedy miałem problem z linkiem „wcześniejsze pytanie”, właśnie uruchomiłem narzędzie Application Verifier i zapisałem opcje, uruchomiłem program, a kiedy się zawiesił, wybrałem VS do debugowania. AV właśnie spowodował awarię / potwierdzenie wcześniej. O ile wiem, polecenie! Avrf jest specyficzne dla WinDbg. Miejmy nadzieję, że inni mogą dostarczyć więcej informacji!
leander
Dzięki. Naprawdę rozwiązałem mój pierwotny problem i okazało się, że nie jest to jednak uszkodzenie sterty, ale coś innego, więc to prawdopodobnie wyjaśnia, dlaczego App Verifier niczego nie znalazł. :)
Nathan Reed
35

Możesz wykryć wiele problemów z uszkodzeniem sterty, włączając stertę strony dla swojej aplikacji. Aby to zrobić, musisz użyć programu gflags.exe, który jest częścią narzędzi debugowania dla systemu Windows

Uruchom Gflags.exe iw opcjach pliku obrazu dla pliku wykonywalnego zaznacz opcję „Włącz stos stron”.

Teraz uruchom ponownie exe i dołącz do debugera. Po włączeniu stosu strony aplikacja włamie się do debugera za każdym razem, gdy nastąpi uszkodzenie sterty.

Canopus
źródło
tak, ale kiedy otrzymam wywołanie tej funkcji w moim zrzucie wywołań (po awarii uszkodzenia pamięci): wow64! Wow64NotifyDebugger, co mogę zrobić? Nadal nie wiem, co jest nie tak w mojej aplikacji
Guillaume07,
Właśnie wypróbowałem gflags, aby debugować tutaj uszkodzenie sterty, BARDZO przydatne małe narzędzie, wysoce zalecane. Okazało się, że uzyskiwałem dostęp do zwolnionej pamięci, która po oprzyrządowaniu za pomocą gflagów natychmiast włamie się do debuggera ... Handy!
Dave F,
Świetne narzędzie! Właśnie znalazłem błąd, na który polowałem kilka dni, ponieważ Windows nie podaje adresu uszkodzenia, tylko to „coś” jest nie tak, co nie jest pomocne.
Devolus
Trochę spóźniłem się na imprezę, ale zauważyłem znaczny wzrost użycia pamięci w mojej aplikacji, którą debuguję po włączeniu Page Heap. Niestety, do momentu, w którym (32-bitowa) aplikacja zabraknie pamięci, zanim zostanie wyzwolone wykrywanie uszkodzenia sterty. Jakieś pomysły, jak rozwiązać ten problem?
uceumern
13

Aby naprawdę spowolnić działanie i przeprowadzić wiele sprawdzeń w czasie wykonywania, spróbuj dodać następujący element u góry swojego main()lub odpowiednika w programie Microsoft Visual Studio C ++

_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CRTDBG_CHECK_ALWAYS_DF );
Dave Van Wagner
źródło
8

Jakie rzeczy mogą powodować te błędy?

Wykonywanie niegrzecznych rzeczy z pamięcią, np. Zapisywanie po zakończeniu bufora lub zapisywanie do bufora po jego zwolnieniu z powrotem na stertę.

Jak je debugować?

Użyj narzędzia, które dodaje automatyczne sprawdzanie granic do twojego pliku wykonywalnego: np. Valgrind w systemie Unix lub narzędzie takie jak BoundsChecker (Wikipedia sugeruje również Purify i Insure ++) w systemie Windows.

Pamiętaj, że spowalniają one twoją aplikację, więc mogą być bezużyteczne, jeśli twoja aplikacja jest aplikacją czasu rzeczywistego.

Inną możliwą pomocą / narzędziem debugowania może być HeapAgent firmy MicroQuill.

ChrisW
źródło
1
Przebudowa aplikacji z debugowaniem runtime (flaga / MDd lub / MTd) byłaby moim pierwszym krokiem. Wykonują one dodatkowe testy w malloc i free, i często nie są skuteczne w zawężaniu lokalizacji błędów.
Zatrudniony w Rosji
MicroQuill's HeapAgent: Niewiele o tym napisano ani nie słyszano, ale w przypadku uszkodzenia sterty powinien znajdować się na Twojej liście.
Samrat Patil
1
BoundsChecker działa dobrze jako test dymny, ale nawet nie myśl o uruchomieniu programu pod nim podczas próby uruchomienia tego programu również w środowisku produkcyjnym. Spowolnienie może wynosić od 60x do 300x, w zależności od używanych opcji i od tego, czy używasz funkcji instrumentacji kompilatora. Zastrzeżenie: jestem jednym z facetów, którzy zajmują się obsługą produktu dla Micro Focus.
Rick Papo
8

Jedna szybka wskazówka, którą otrzymałem od wykrywania dostępu do zwolnionej pamięci, jest taka:

Jeśli chcesz szybko zlokalizować błąd, bez sprawdzania każdej instrukcji, która uzyskuje dostęp do bloku pamięci, możesz ustawić wskaźnik pamięci na nieprawidłową wartość po zwolnieniu bloku:

#ifdef _DEBUG // detect the access to freed memory
#undef free
#define free(p) _free_dbg(p, _NORMAL_BLOCK); *(int*)&p = 0x666;
#endif
StackedCrooked
źródło
5

Najlepszym narzędziem, które uznałem za przydatne i działało za każdym razem, jest recenzja kodu (z dobrymi recenzentami kodu).

Oprócz przeglądu kodu, najpierw spróbuję Page Heap . Konfiguracja strony Heap zajmuje kilka sekund i przy odrobinie szczęścia może wskazać problem.

Jeśli nie masz szczęścia ze stosem strony, pobierz narzędzia debugowania dla systemu Windows od firmy Microsoft i naucz się korzystać z WinDbg. Niestety nie mogłem udzielić ci bardziej szczegółowej pomocy, ale debugowanie wielowątkowego uszkodzenia sterty jest bardziej sztuką niż nauką. Google dla hasła „uszkodzenie stosu WinDbg” i powinieneś znaleźć wiele artykułów na ten temat.

Shing Yip
źródło
4

Możesz również sprawdzić, czy tworzysz link do dynamicznej, czy statycznej biblioteki wykonawczej C. Jeśli pliki DLL są łączone ze statyczną biblioteką wykonawczą C, pliki DLL mają oddzielne stosy.

Dlatego gdybyś utworzył obiekt w jednej bibliotece DLL i spróbował zwolnić go w innej bibliotece DLL, otrzymasz ten sam komunikat, który widzisz powyżej. Do tego problemu odwołuje się inne pytanie dotyczące przepełnienia stosu, zwalnianie pamięci przydzielonej w innej bibliotece DLL .

dreadpirateryan
źródło
3

Jakiego rodzaju funkcji alokacji używasz? Niedawno napotkałem podobny błąd, używając funkcji alokacji w stylu Heap *.

Okazało się, że omyłkowo tworzyłem stertę z HEAP_NO_SERIALIZEopcją. To zasadniczo sprawia, że ​​funkcje Heap działają bez zabezpieczenia wątków. Jest to poprawa wydajności, jeśli jest używana prawidłowo, ale nie powinna być używana, jeśli używasz HeapAlloc w programie wielowątkowym [1]. Wspominam o tym tylko dlatego, że w Twoim poście wspomniano, że masz aplikację wielowątkową. Jeśli używasz HEAP_NO_SERIALIZE gdziekolwiek, usuń to, a to prawdopodobnie rozwiąże problem.

[1] Istnieją pewne sytuacje, w których jest to dozwolone, ale wymaga serializacji wywołań Heap * i zazwyczaj nie ma to miejsca w przypadku programów wielowątkowych.

JaredPar
źródło
Tak: spójrz na opcje kompilatora / kompilacji aplikacji i upewnij się, że jest ona budowana pod kątem łączenia z „wielowątkową” wersją biblioteki wykonawczej C.
ChrisW
@ChrisW dla interfejsów API w stylu HeapAlloc jest inaczej. W rzeczywistości jest to parametr, który można zmienić w czasie tworzenia sterty, a nie w czasie łączenia.
JaredPar
O. Nie przyszło mi do głowy, że OP może mówić o tej stercie, a nie o stercie w CRT.
ChrisW
@ChrisW, pytanie jest raczej niejasne, ale właśnie natrafiłem na problem, który szczegółowo opisałem ~ tydzień temu, więc mam go świeżo w pamięci.
JaredPar
3

Jeśli te błędy pojawiają się losowo, istnieje duże prawdopodobieństwo, że napotkałeś wyścig danych. Proszę sprawdzić: czy modyfikujesz wskaźniki pamięci współdzielonej z różnych wątków? Intel Thread Checker może pomóc wykryć takie problemy w programie wielowątkowym.

Vladimir Obrizan
źródło
1

Oprócz szukania narzędzi, rozważ poszukanie prawdopodobnego sprawcy. Czy jest jakiś komponent, którego używasz, być może nie napisany przez Ciebie, który mógł nie zostać zaprojektowany i przetestowany do działania w środowisku wielowątkowym? Lub po prostu taki, o którym nie wiesz , działał w takim środowisku.

Ostatnim razem, gdy mi się to przydarzyło, był to pakiet natywny, który przez lata był z powodzeniem używany z zadań wsadowych. Ale to był pierwszy raz w tej firmie, kiedy został użyty z usługi internetowej .NET (która jest wielowątkowa). To było to - skłamali, że kod jest bezpieczny dla wątków.

John Saunders
źródło
1

Możesz użyć makr VC CRT Heap-Check dla _CrtSetDbgFlag : _CRTDBG_CHECK_ALWAYS_DF lub _CRTDBG_CHECK_EVERY_16_DF .. _CRTDBG_CHECK_EVERY_1024_DF .

KindDragon
źródło
0

Chciałbym dodać swoje doświadczenie. W ciągu ostatnich kilku dni rozwiązałem wystąpienie tego błędu w mojej aplikacji. W moim przypadku błędy w kodzie to:

  • Usuwanie elementów z kolekcji STL podczas iteracji po niej (uważam, że w programie Visual Studio są flagi debugowania, aby złapać te rzeczy; złapałem to podczas przeglądu kodu)
  • Ten jest bardziej złożony, podzielę go na kroki:
    • Z natywnego wątku C ++ wywołaj ponownie do kodu zarządzanego
    • Na obszarze zarządzanym wywołaj Control.Invokei usuń obiekt zarządzany, który opakowuje obiekt natywny, do którego należy wywołanie zwrotne.
    • Ponieważ obiekt nadal żyje w rodzimym wątku (pozostanie zablokowany w wywołaniu zwrotnym do Control.Invokekońca). Powinienem wyjaśnić, że używam boost::thread, więc używam funkcji składowej jako funkcji wątku.
    • Rozwiązanie : Control.BeginInvokeZamiast tego użyj (mój GUI jest tworzony za pomocą Winforms), aby natywny wątek mógł zakończyć się przed zniszczeniem obiektu (celem wywołania zwrotnego jest precyzyjne powiadomienie, że wątek się skończył i obiekt może zostać zniszczony).
dario_ramos
źródło
0

Miałem podobny problem - i pojawiał się dość przypadkowo. Być może coś było uszkodzone w plikach kompilacji, ale ostatecznie naprawiłem to, najpierw czyszcząc projekt, a następnie przebudowując.

Więc oprócz innych udzielonych odpowiedzi:

Jakie rzeczy mogą powodować te błędy? Coś jest uszkodzone w pliku kompilacji.

Jak je debugować? Czyszczenie projektu i przebudowa. Jeśli problem został rozwiązany, prawdopodobnie był to problem.

Marty
źródło