Mam tutaj problem typu „Kot Schroedingera” - mój program (właściwie zestaw testowy dla mojego programu, ale program mimo wszystko) ulega awarii, ale tylko wtedy, gdy jest zbudowany w trybie wydania i tylko wtedy, gdy jest uruchamiany z wiersza poleceń . Poprzez debugowanie jaskiniowców (tj. Nieprzyjemne komunikaty printf () w każdym miejscu), określiłem metodę testową, w której kod ulega awarii, chociaż niestety rzeczywista awaria wydaje się mieć miejsce w jakimś destruktorze, ponieważ ostatnie wiadomości śledzenia, które widzę, znajdują się w inne destruktory, które działają czysto.
Kiedy próbuję uruchomić ten program w Visual Studio, nie ulega awarii. To samo dotyczy uruchamiania z WinDbg.exe. Awaria występuje tylko podczas uruchamiania z wiersza poleceń. Dzieje się to w systemie Windows Vista, przy okazji, i niestety nie mam teraz dostępu do komputera XP, aby przetestować.
Byłoby naprawdę miło, gdybym mógł zmusić system Windows do wydrukowania śladu stosu lub czegoś innego niż zwykłe zakończenie programu tak, jakby zakończył się czysto. Czy ktoś ma jakąś radę, jak mogę uzyskać tutaj bardziej znaczące informacje i miejmy nadzieję, że naprawię ten błąd?
Edycja: Problem był rzeczywiście spowodowany przez tablicę poza granicami, którą opisuję bardziej w tym poście . Dziękuję wszystkim za pomoc w znalezieniu tego problemu!
Odpowiedzi:
W 100% przypadków, które widziałem lub słyszałem, gdzie program C lub C ++ działa dobrze w debugerze, ale kończy się niepowodzeniem, gdy jest uruchamiany na zewnątrz, przyczyną jest zapisywanie poza końcem lokalnej tablicy funkcji. (Debugger umieszcza więcej na stosie, więc jest mniej prawdopodobne, że nadpiszesz coś ważnego).
źródło
Kiedy napotkałem wcześniej takie problemy, zwykle było to spowodowane zmienną inicjalizacją. W trybie debugowania zmienne i wskaźniki są automatycznie inicjowane do zera, ale w trybie wydania nie. Dlatego jeśli masz taki kod
int* p; .... if (p == 0) { // do stuff }
W trybie debugowania kod w if nie jest wykonywany, ale w trybie wydania p zawiera niezdefiniowaną wartość, która prawdopodobnie nie będzie równa 0, więc kod jest wykonywany często, powodując awarię.
Sprawdziłbym twój kod pod kątem niezainicjowanych zmiennych. Może to również dotyczyć zawartości tablic.
źródło
Do tej pory żadna odpowiedź nie próbowała dać poważnego przeglądu dostępnych technik debugowania aplikacji w wersji:
Kompilacje wydania i debugowania zachowują się różnie z wielu powodów. Oto doskonały przegląd. Każda z tych różnic może powodować błąd w kompilacji wydania, który nie istnieje w kompilacji debugowania.
Obecność debugera może również zmienić zachowanie programu , zarówno w przypadku kompilacji do wydania, jak i do debugowania. Zobacz tę odpowiedź. Krótko mówiąc, przynajmniej Visual Studio Debugger automatycznie używa sterty debugowania po dołączeniu do programu. Stertę debugowania można wyłączyć za pomocą zmiennej środowiskowej _NO_DEBUG_HEAP. Możesz to określić we właściwościach komputera lub w ustawieniach projektu w programie Visual Studio. Może to spowodować odtworzenie awarii po podłączeniu debugera.
Więcej na temat debugowania uszkodzenia sterty tutaj.
Jeśli poprzednie rozwiązanie nie działa, musisz przechwycić nieobsługiwany wyjątek i dołączyć debuger sekcji zwłok, gdy wystąpi awaria. Możesz użyć do tego np. WinDbg, szczegółów na temat dostępnych debuggerów post-mortem i ich instalacji w MSDN
Możesz ulepszyć swój kod obsługi wyjątków, a jeśli jest to aplikacja produkcyjna, powinieneś:
za. Zainstaluj niestandardową procedurę obsługi zakończenia przy użyciu
std::set_terminate
Jeśli chcesz zdebugować ten problem lokalnie, możesz uruchomić nieskończoną pętlę wewnątrz modułu obsługi zakończenia i przesłać tekst do konsoli, aby powiadomić Cię o
std::terminate
wywołaniu. Następnie dołącz debuger i sprawdź stos wywołań. Lub drukujesz ślad stosu zgodnie z opisem w tej odpowiedzi.W aplikacji produkcyjnej możesz chcieć wysłać raport o błędzie do domu, najlepiej razem z małym zrzutem pamięci, który pozwala przeanalizować problem zgodnie z opisem tutaj.
b. Skorzystaj ze strukturalnego mechanizmu obsługi wyjątków firmy Microsoft, który umożliwia wychwytywanie wyjątków sprzętowych i programowych. Zobacz MSDN . Możesz zabezpieczyć części swojego kodu za pomocą SEH i użyć tego samego podejścia, co w a), aby debugować problem. SEH zawiera więcej informacji na temat wyjątku, który wystąpił, którego można użyć podczas wysyłania raportu o błędzie z aplikacji produkcyjnej.
źródło
Na co zwrócić uwagę:
Array overruns - debugger Visual Studio wstawia wypełnienie, które może zatrzymać awarie.
Warunki wyścigu - czy masz zaangażowanych wiele wątków, jeśli tak, sytuacja wyścigu pojawia się tylko wtedy, gdy aplikacja jest wykonywana bezpośrednio.
Łączenie - czy kompilacja wydania pobiera poprawne biblioteki.
Rzeczy do wypróbowania:
Minidump - naprawdę łatwy w użyciu (po prostu sprawdź w msdn) daje pełny zrzut awaryjny dla każdego wątku. Po prostu ładujesz dane wyjściowe do programu Visual Studio i wygląda to tak, jakbyś debugował w momencie awarii.
źródło
Możesz ustawić WinDbg jako debugger postmortem. Spowoduje to uruchomienie debugera i dołączenie go do procesu po wystąpieniu awarii. Aby zainstalować WinDbg do debugowania pośmiertnego, użyj opcji / I (pamiętaj, że jest to pisane wielkimi literami ):
Więcej szczegółów tutaj .
Jeśli chodzi o przyczynę, jest to najprawdopodobniej zmienna zjednolicona, jak sugerują inne odpowiedzi.
źródło
Po wielu godzinach debugowania w końcu znalazłem przyczynę problemu, który rzeczywiście był spowodowany przepełnieniem bufora, spowodowanym różnicą jednego bajtu:
char *end = static_cast<char*>(attr->data) + attr->dataSize;
To jest błąd słupka ogrodzenia (błąd oddzielenia o jeden) i został naprawiony przez:
char *end = static_cast<char*>(attr->data) + attr->dataSize - 1;
Dziwne było to, że wykonałem kilka wywołań funkcji _CrtCheckMemory () wokół różnych części mojego kodu i zawsze zwracały one 1. Udało mi się znaleźć źródło problemu, umieszczając „return false”; wywołania w przypadku testowym, a następnie ostatecznie określają metodą prób i błędów, gdzie wystąpił błąd.
Dziękuję wszystkim za komentarze - wiele się dziś nauczyłem o windbg.exe! :)
źródło
Nawet jeśli zbudowałeś swój exe jako wydanie, nadal możesz generować pliki PDB (baza danych programu), które pozwolą ci na śledzenie stosu i wykonywanie ograniczonej ilości inspekcji zmiennych. W ustawieniach kompilacji istnieje opcja tworzenia plików PDB. Włącz to i połącz ponownie. Następnie spróbuj najpierw uruchomić z IDE, aby sprawdzić, czy wystąpiła awaria. Jeśli tak, to świetnie - wszystko jest gotowe. Jeśli nie, to podczas uruchamiania z wiersza poleceń możesz zrobić jedną z dwóch rzeczy:
Gdy pojawi się monit o wskazanie plików PDB, wyszukaj je. Jeśli pliki PDB zostały umieszczone w tym samym folderze wyjściowym, co pliki EXE lub DLL, prawdopodobnie zostaną automatycznie pobrane.
PDB zapewniają łącze do źródła z wystarczającą ilością informacji o symbolach, aby można było zobaczyć ślady stosu, zmienne itp. Możesz sprawdzić wartości normalnie, ale pamiętaj, że możesz uzyskać fałszywe odczyty, ponieważ przebieg optymalizacji może oznaczać tylko rzeczy pojawiają się w rejestrach lub dzieje się w innej kolejności niż się spodziewasz.
NB: Zakładam tutaj środowisko Windows / Visual Studio.
źródło
Takie awarie są prawie zawsze powodowane, ponieważ IDE zwykle ustawia zawartość niezainicjowanej zmiennej na zera, null lub inną taką „rozsądną” wartość, podczas gdy podczas pracy natywnej otrzymasz dowolne losowe śmieci, które zbierze system.
Twój błąd jest zatem prawie na pewno, że używasz czegoś takiego, jak używasz wskaźnika, zanim zostanie on poprawnie zainicjowany i unikniesz tego w IDE, ponieważ nie wskazuje on na żadne niebezpieczne - lub wartość jest obsługiwana przez sprawdzanie błędów - ale w trybie wydania robi coś paskudnego.
źródło
Aby mieć zrzut awaryjny, który możesz przeanalizować:
Powinieneś także sprawdzić narzędzia w Narzędziach do debugowania dla systemu Windows . Możesz monitorować aplikację i zobaczyć wszystkie wyjątki pierwszej szansy, które były przed Twoim wyjątkiem drugiej szansy.
Mam nadzieję, że to pomoże...
źródło
Świetnym sposobem debugowania takiego błędu jest włączenie optymalizacji dla kompilacji debugowania.
źródło
Kiedyś miałem problem, gdy aplikacja zachowywała się podobnie do Twojej. Okazało się, że to paskudne przekroczenie bufora w sprintfie. Oczywiście zadziałało, gdy został uruchomiony z dołączonym debugerem. To, co zrobiłem, to zainstalowanie nieobsługiwanego filtru wyjątków ( SetUnhandledExceptionFilter ), w którym po prostu blokowałem nieskończenie (używając WaitForSingleObject na fałszywym dojściu z wartością limitu czasu INFINITE).
Więc mógłbyś coś w stylu:
Następnie podłączyłem debugger po tym, jak błąd się ujawnił (program gui przestał odpowiadać).
Następnie możesz albo zrobić zrzut i pracować z nim później:
Lub od razu go zdebuguj. Najprostszym sposobem jest śledzenie, gdzie kontekst procesora został zapisany przez maszynę obsługującą wyjątki środowiska wykonawczego:
Polecenie przeszuka przestrzeń adresową stosu pod kątem rekordów KONTEKST pod warunkiem długości wyszukiwania. Zwykle używam czegoś takiego jak „l? 10000” . Uwaga, nie używaj nietypowo dużych liczb jako rekordu, którego szukasz, zwykle w pobliżu nieobsługiwanej ramki filtru wyjątków. 1003f to kombinacja flag (uważam, że odpowiada CONTEXT_FULL) używanej do przechwytywania stanu procesora. Twoje wyszukiwanie wyglądałoby podobnie do tego:
Po uzyskaniu wyników użyj adresu w poleceniu cxr:
To przeniesie Cię do tego nowego KONTEKSTU dokładnie w momencie awarii (otrzymasz dokładny ślad stosu w momencie awarii aplikacji). Dodatkowo użyj:
aby dowiedzieć się, który dokładnie wyjątek wystąpił.
Mam nadzieję, że to pomoże.
źródło
Czasami dzieje się tak, ponieważ ważna operacja została zapakowana w makro „assert”. Jak być może wiesz, "assert" ocenia wyrażenia tylko w trybie debugowania.
źródło
W odniesieniu do problemów z uzyskiwaniem informacji diagnostycznych, czy próbowałeś użyć adplus.vbs jako alternatywy dla WinDbg.exe? Aby dołączyć do działającego procesu, użyj
Lub aby uruchomić aplikację w przypadku, gdy awaria nastąpi szybko:
Pełne informacje na temat adplus.vbs można znaleźć pod adresem : http://support.microsoft.com/kb/286350
źródło
Ntdll.dll z dołączonym debugerem
Niewielka różnica między uruchomieniem programu z IDE lub WinDbg a uruchomieniem go z wiersza poleceń / pulpitu polega na tym, że podczas uruchamiania z dołączonym debugerem (tj. IDE lub WinDbg), ntdll.dll używa innej implementacji sterty, która przeprowadza niewielką weryfikację na alokację / zwalnianie pamięci.
Możesz przeczytać kilka istotnych informacji w nieoczekiwanym punkcie przerwania użytkownika w ntdll.dll . Jednym z narzędzi, które mogą pomóc w zidentyfikowaniu problemu, jest PageHeap.exe .
Analiza awarii
Nie napisałeś, co to za „awaria”, której doświadczasz. Gdy program ulegnie awarii i zaproponuje wysłanie informacji o błędzie do firmy Microsoft, powinieneś być w stanie kliknąć informacje techniczne i sprawdzić przynajmniej kod wyjątku, a przy pewnym wysiłku możesz nawet przeprowadzić analizę pośmiertną (patrz Heisenbug : Program WinApi ulega awarii na niektórych komputerach) w celu uzyskania instrukcji)
źródło
Vista SP1 ma naprawdę fajny generator zrzutów awaryjnych wbudowany w system. Niestety nie jest on domyślnie włączony!
Zobacz ten artykuł: http://msdn.microsoft.com/en-us/library/bb787181(VS.85).aspx
Zaletą tego podejścia jest to, że w systemie, którego dotyczy luka, nie trzeba instalować żadnego dodatkowego oprogramowania. Chwyć i rozerwij, kochanie!
źródło
Z mojego doświadczenia wynika, że większość z nich dotyczy uszkodzenia pamięci.
Na przykład :
char a[8]; memset(&a[0], 0, 16); : /*use array a doing some thing */
bardzo możliwe jest normalne działanie w trybie debugowania po uruchomieniu kodu.
Ale w wydaniu to by / mogło się zawiesić.
Dla mnie grzebanie tam, gdzie pamięć jest poza zasięgiem, jest zbyt uciążliwe.
Użyj niektórych narzędzi, takich jak Visual Leak Detector (Windows) lub Valgrind (Linux), które są mądrzejsze.
źródło
Widziałem wiele prawidłowych odpowiedzi. Jednak nie ma nikogo, kto mi pomógł. W moim przypadku wystąpiło nieprawidłowe użycie instrukcji SSE z niewyrównaną pamięcią . Spójrz na swoją bibliotekę matematyczną (jeśli jej używasz) i spróbuj wyłączyć obsługę SIMD, ponownie skompiluj i odtworzyć awarię.
Przykład:
Projekt zawiera mathfu i używa klas z wektorem STL: std :: vector <mathfu :: vec2> . Takie użycie prawdopodobnie spowoduje zawieszenie się podczas konstruowania elementu mathfu :: vec2, ponieważ domyślny alokator STL nie gwarantuje wymaganego 16-bajtowego wyrównania. W tym przypadku, aby udowodnić pomysł, można zdefiniować
#define MATHFU_COMPILE_WITHOUT_SIMD_SUPPORT 1
przed każdym włączeniem mathfu , ponownie skompilować w konfiguracji wydania i sprawdzić ponownie.Do debugowania i RelWithDebInfo konfiguracje pracował dobrze dla mojego projektu, ale nie do wydania jednego. Przyczyną tego zachowania jest prawdopodobnie to, że debugger przetwarza żądania alokacji / zwalniania alokacji i wykonuje pewne księgowania pamięci, aby sprawdzić i zweryfikować dostęp do pamięci.
Doświadczyłem takiej sytuacji w środowiskach Visual Studio 2015 i 2017.
źródło
Coś podobnego spotkało mnie kiedyś z GCC. Okazało się, że była to zbyt agresywna optymalizacja, która została włączona tylko podczas tworzenia ostatecznej wersji, a nie podczas procesu rozwoju.
Cóż, prawdę mówiąc, to była moja wina, a nie gcc, ponieważ nie zauważyłem, że mój kod opiera się na fakcie, że ta konkretna optymalizacja nie została wykonana.
Znalezienie go zajęło mi dużo czasu i doszedłem do niego tylko dlatego, że zapytałem na grupie dyskusyjnej i ktoś zmusił mnie do przemyślenia. Więc pozwól mi odwzajemnić przysługę, na wypadek, gdyby ci się to przytrafiło.
źródło
Znalazłem to ten artykuł przydatny dla scenariusza. ISTR, opcje kompilatora były trochę nieaktualne. Rozejrzyj się po opcjach projektu programu Visual Studio, aby zobaczyć, jak generować pliki pdb dla kompilacji wydania itp.
źródło
Jest podejrzane, że wydarzy się to poza debugerem, a nie wewnątrz; uruchomienie w debugerze zwykle nie zmienia zachowania aplikacji. Sprawdziłbym różnice środowiskowe między konsolą a IDE. Oczywiście skompiluj wydanie bez optymalizacji iz informacjami o debugowaniu i sprawdź, czy ma to wpływ na zachowanie. Na koniec zapoznaj się z narzędziami do debugowania post-mortem, które sugerowały tutaj inne osoby, zwykle możesz uzyskać od nich jakąś wskazówkę.
źródło
Debugowanie kompilacji wersji może być uciążliwe ze względu na optymalizacje zmieniające kolejność, w której wiersze kodu wydają się być wykonywane. To naprawdę może być mylące!
Jedną z technik pozwalających przynajmniej zawęzić problem jest użycie MessageBox () do wyświetlenia szybkich instrukcji określających, do jakiej części programu należy twój kod ("Uruchamianie Foo ()", "Uruchamianie Foo2 ()"); zacznij umieszczać je na górze funkcji w obszarze twojego kodu, który podejrzewasz (co robiłeś w czasie, gdy się zawiesił?). Kiedy możesz określić, która funkcja, zmień pola komunikatów na bloki kodu lub nawet pojedyncze wiersze w ramach tej funkcji, dopóki nie zawęzisz jej do kilku wierszy. Następnie możesz rozpocząć drukowanie wartości zmiennych, aby zobaczyć, w jakim stanie są w momencie awarii.
źródło
Spróbuj użyć funkcji _CrtCheckMemory (), aby zobaczyć, w jakim stanie jest przydzielona pamięć. Jeśli wszystko pójdzie dobrze, _CrtCheckMemory zwraca TRUE , w przeciwnym razie FALSE .
źródło
Możesz uruchomić oprogramowanie z włączonymi flagami globalnymi (zajrzyj do narzędzi do debugowania dla systemu Windows). Bardzo często pomoże to rozwiązać problem.
źródło
Spraw, aby Twój program generował mini zrzut, gdy wystąpi wyjątek, a następnie otwórz go w debugerze (na przykład w WinDbg). Najważniejsze funkcje, którym należy się przyjrzeć: MiniDumpWriteDump, SetUnhandledExceptionFilter
źródło
Oto przypadek, który ktoś może uznać za pouczający. Zawiesił się tylko w wydaniu w Qt Creator, a nie w debugowaniu. Używałem plików .ini (ponieważ wolę aplikacje, które można skopiować na inne dyski, w porównaniu z tymi, które tracą ustawienia, jeśli rejestr zostanie uszkodzony). Dotyczy to wszystkich aplikacji, które przechowują swoje ustawienia w drzewie katalogów aplikacji. Jeśli kompilacje debugowania i wydania znajdują się w różnych katalogach, możesz mieć również inne ustawienia między nimi. Miałem zaznaczone preferencje w jednym, który nie był zaznaczony w drugim. Okazało się, że to było źródło mojej katastrofy. Dobrze, że to znalazłem.
Nienawidzę tego mówić, ale zdiagnozowałem awarię tylko w MS Visual Studio Community Edition; po zainstalowaniu VS, pozwoleniu mojej aplikacji na awarię w Qt Creator i wybraniu otwarcia jej w debugerze programu Visual Studio . Podczas gdy moja aplikacja Qt nie zawierała informacji o symbolach, okazuje się, że biblioteki Qt miały kilka. Doprowadziło mnie to do przestępstwa; ponieważ mogłem zobaczyć, jaka metoda została wywołana. (Mimo to uważam, że Qt to wygodny, potężny i wieloplatformowy framework LGPL.)
źródło
Ja też miałem ten problem. W moim przypadku tryb RELEASE miał msvscrtd.dll w definicji konsolidatora. Usunęliśmy go i problem został rozwiązany.
Alternatywnie dodanie / NODEFAULTLIB do argumentów wiersza poleceń konsolidatora również rozwiązało problem.
źródło
Miałem ten błąd i vs się zawiesił, nawet gdy próbowałem! Wyczyścić! mój projekt. Więc usunąłem pliki obj ręcznie z katalogu Release, a potem wszystko dobrze się zbudowało.
źródło
Zgadzam się z Rolfem. Ponieważ odtwarzalność jest tak ważna, nie powinieneś mieć trybu bez debugowania. Wszystkie twoje kompilacje powinny dać się debugować. Posiadanie dwóch celów do debugowania więcej niż podwaja obciążenie debugowania. Po prostu wyślij wersję „trybu debugowania”, chyba że jest bezużyteczna. W takim przypadku uczyń go użytecznym.
źródło