Wie kann man Heap-Korruptionsfehler debuggen?

165

Ich debugge eine (native) C ++ - Multithread-Anwendung unter Visual Studio 2008. Bei scheinbar zufälligen Gelegenheiten erhalte ich den Fehler "Windows hat einen Haltepunkt ausgelöst ..." mit dem Hinweis, dass dies möglicherweise auf eine Beschädigung in der Datei zurückzuführen ist Haufen. Diese Fehler stürzen die Anwendung nicht immer sofort ab, obwohl sie wahrscheinlich kurz danach abstürzt.

Das große Problem bei diesen Fehlern besteht darin, dass sie erst angezeigt werden, nachdem die Beschädigung tatsächlich stattgefunden hat, was es sehr schwierig macht, sie zu verfolgen und zu debuggen, insbesondere in einer Multithread-Anwendung.

  • Welche Art von Dingen kann diese Fehler verursachen?

  • Wie debugge ich sie?

Tipps, Werkzeuge, Methoden, Erkenntnisse ... sind willkommen.

Peter Mortensen
quelle

Antworten:

128

Application Verifier in Kombination mit Debugging Tools für Windows ist ein erstaunliches Setup. Sie können beides als Teil des Windows-Treiberkits oder des leichteren Windows SDK erhalten . ( Ich habe bei der Untersuchung einer früheren Frage zu einem Problem mit der Heap-Beschädigung von Application Verifier erfahren.) Ich habe in der Vergangenheit auch BoundsChecker und Insure ++ (in anderen Antworten erwähnt) verwendet, obwohl ich überrascht war, wie viel Funktionalität in Application Verifier vorhanden war.

Electric Fence (auch bekannt als "efence"), dmalloc , valgrind usw. sind alle erwähnenswert, aber die meisten davon sind unter * nix viel einfacher zum Laufen zu bringen als unter Windows. Valgrind ist lächerlich flexibel: Ich habe große Serversoftware mit vielen Heap-Problemen getestet.

Wenn alles andere fehlschlägt, können Sie Ihrem eigenen globalen Operator neue / delete- und malloc / calloc / realloc-Überladungen bereitstellen. Die Vorgehensweise hängt vom Compiler und der Plattform ab. Dies ist eine Investition. aber es kann sich auf lange Sicht auszahlen. Die Liste der wünschenswerten Funktionen sollte aus dmalloc und electricfence und dem überraschend hervorragenden Buch Writing Solid Code bekannt sein :

  • Wachposten : Lassen Sie vor und nach jeder Zuweisung etwas mehr Platz, um die maximale Ausrichtungsanforderung zu berücksichtigen. Füllen Sie mit magischen Zahlen (hilft dabei, Pufferüber- und -unterläufe und gelegentliche "wilde" Zeiger abzufangen)
  • Alloc Fill : Füllen Sie neue Allokationen mit einem magischen Wert ungleich 0 - Visual C ++ erledigt dies bereits in Debug-Builds für Sie (hilft bei der Verwendung nicht initialisierter Vars).
  • free fill : Füllen Sie den freigegebenen Speicher mit einem magischen Wert ungleich 0 aus, der einen Segfault auslöst, wenn er in den meisten Fällen dereferenziert wird (hilft beim Auffangen baumelnder Zeiger).
  • verzögert frei : Geben Sie den freigegebenen Speicher für eine Weile nicht auf den Heap zurück, halten Sie ihn frei gefüllt, aber nicht verfügbar (hilft dabei, mehr baumelnde Zeiger abzufangen, fängt nahegelegene doppelte Freigaben ab).
  • Tracking : Manchmal kann es nützlich sein, aufzuzeichnen, wo eine Zuordnung vorgenommen wurde

Beachten Sie, dass wir in unserem lokalen Homebrew-System (für ein eingebettetes Ziel) das Tracking von den meisten anderen Dingen getrennt halten, da der Laufzeitaufwand viel höher ist.


Wenn Sie an weiteren Gründen für die Überladung dieser Zuordnungsfunktionen / Operatoren interessiert sind, lesen Sie meine Antwort auf " Gibt es einen Grund, den globalen Operator neu zu überladen und zu löschen?" ;; Abgesehen von der schamlosen Eigenwerbung werden andere Techniken aufgelistet, die bei der Verfolgung von Heap-Korruptionsfehlern hilfreich sind, sowie andere anwendbare Tools.


Da ich hier bei der Suche nach zugewiesenen / freien / Zaunwerten, die MS verwendet, immer wieder meine eigene Antwort finde, finden Sie hier eine weitere Antwort, die Microsoft-Dbgheap-Füllwerte abdeckt .

schlanker
quelle
3
Eine winzige Sache, die Sie bei Application Verifier beachten sollten: Sie müssen die Symbole von Application Verifier vor den Symbolen des Microsoft Symbol Servers in Ihrem Symbolsuchpfad registrieren, wenn Sie diese verwenden ... Ich habe ein bisschen gesucht, um herauszufinden, warum! Avrf nicht war Finden der benötigten Symbole.
Leander
Application Verifier war eine große Hilfe, und zusammen mit einigen Vermutungen konnte ich das Problem lösen! Vielen Dank und auch für alle anderen, die hilfreiche Punkte angesprochen haben.
Muss Application Verifier mit WinDbg verwendet werden oder sollte es mit dem Visual Studio-Debugger funktionieren? Ich habe versucht, es zu verwenden, aber es wirft keine Fehler auf oder macht anscheinend nichts, wenn ich in VS2012 debugge.
Nathan Reed
@ NathanReed: Ich glaube, es funktioniert auch mit VS - siehe msdn.microsoft.com/en-us/library/ms220944(v=vs.90).aspx - obwohl dieser Link für VS2008 ist, bin ich nicht sicher über spätere Versionen. Der Speicher ist etwas unscharf, aber ich glaube, als ich das Problem im Link "Frühere Frage" hatte, habe ich gerade Application Verifier ausgeführt und die Optionen gespeichert, das Programm ausgeführt und als es abstürzte, habe ich VS zum Debuggen ausgewählt. AV hat es gerade früher zum Absturz gebracht / behauptet. Der Befehl! Avrf ist meines Wissens jedoch spezifisch für WinDbg. Hoffentlich können andere mehr Informationen liefern!
Leander
Vielen Dank. Ich habe mein ursprüngliches Problem tatsächlich gelöst und es stellte sich schließlich heraus, dass es sich nicht um eine Heap-Beschädigung handelte, sondern um etwas anderes. Dies erklärt wahrscheinlich, warum App Verifier nichts gefunden hat. :)
Nathan Reed
35

Sie können viele Heap-Beschädigungsprobleme erkennen, indem Sie Page Heap für Ihre Anwendung aktivieren. Dazu müssen Sie gflags.exe verwenden, das Teil der Debugging-Tools für Windows ist

Führen Sie Gflags.exe aus und aktivieren Sie in den Image-Dateioptionen für Ihre ausführbare Datei die Option "Seitenheap aktivieren".

Starten Sie nun Ihre Exe neu und hängen Sie sie an einen Debugger an. Wenn der Seitenheap aktiviert ist, bricht die Anwendung in den Debugger ein, wenn eine Heap-Beschädigung auftritt.

Canopus
quelle
Ja, aber sobald ich diesen Funktionsaufruf in meinem Callstack-Dump habe (nach einem Absturz der Speicherbeschädigung): wow64! Wow64NotifyDebugger, was kann ich tun? Ich weiß immer noch nicht, was in meiner Bewerbung falsch läuft
Guillaume07
Ich habe gerade Gflags ausprobiert, um Heap-Korruption hier zu debuggen. Sehr nützliches kleines Tool, sehr zu empfehlen. Es stellte sich heraus, dass ich auf freigegebenen Speicher zugegriffen habe, der, wenn er mit Gflags instrumentiert ist, sofort in den Debugger einbricht ... Praktisch!
Dave F
Tolles Werkzeug! Ich habe gerade einen Fehler gefunden, den ich tagelang gesucht habe, weil Windows nicht die Adresse der Korruption angibt, sondern nur, dass "etwas" nicht stimmt, was nicht wirklich hilfreich ist.
Devolus
Ein bisschen spät zur Party, aber ich bemerkte eine signifikante Zunahme der Speichernutzung in der Anwendung, die ich debugge, als ich Page Heap einschaltete. Leider geht der (32-Bit-) Anwendung bis zu dem Punkt der Speicherplatz aus, bevor die Heap-Beschädigungserkennung ausgelöst wird. Irgendwelche Ideen, wie man dieses Problem angeht?
Uceumern
13

Fügen Sie main()in Microsoft Visual Studio C ++ Folgendes hinzu, um die Laufzeit erheblich zu verlangsamen und zahlreiche Laufzeitprüfungen durchzuführen

_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CRTDBG_CHECK_ALWAYS_DF );
Dave Van Wagner
quelle
8

Welche Art von Dingen kann diese Fehler verursachen?

Machen Sie ungezogene Dinge mit Speicher, z. B. Schreiben nach dem Ende eines Puffers oder Schreiben in einen Puffer, nachdem dieser wieder auf dem Heap freigegeben wurde.

Wie debugge ich sie?

Verwenden Sie ein Instrument, das Ihrer ausführbaren Datei eine automatische Überprüfung der Grenzen hinzufügt: z. B. valgrind unter Unix oder ein Tool wie BoundsChecker (Wikipedia schlägt auch Purify and Insure ++ vor) unter Windows.

Beachten Sie, dass diese Ihre Anwendung verlangsamen, sodass sie möglicherweise unbrauchbar werden, wenn es sich bei Ihrer Anwendung um eine Soft-Echtzeit-Anwendung handelt.

Ein weiteres mögliches Debugging-Hilfsmittel / Tool könnte der HeapAgent von MicroQuill sein.

ChrisW
quelle
1
Das Neuerstellen der Anwendung mit Debugging-Laufzeit (/ MDd- oder / MTd-Flag) wäre mein erster Schritt. Diese führen zusätzliche Überprüfungen bei malloc und free durch und werden häufig beendet, um die Position der Fehler einzugrenzen.
Angestellt Russisch
MicroQuills HeapAgent: Es wird nicht viel darüber geschrieben oder gehört, aber für Heap-Korruption sollte es auf Ihrer Liste stehen.
Samrat Patil
1
BoundsChecker funktioniert gut als Rauchtest, aber denken Sie nicht einmal daran, ein Programm darunter auszuführen, während Sie versuchen, dieses Programm auch in der Produktion auszuführen. Die Verlangsamung kann zwischen 60x und 300x liegen, je nachdem, welche Optionen Sie verwenden und ob Sie die Compiler-Instrumentierungsfunktion verwenden oder nicht. Haftungsausschluss: Ich bin einer der Leute, die das Produkt für Micro Focus warten.
Rick Papo
8

Ein kurzer Tipp, den ich beim Erkennen des Zugriffs auf freigegebenen Speicher erhalten habe, lautet:

Wenn Sie den Fehler schnell lokalisieren möchten, ohne jede Anweisung zu überprüfen, die auf den Speicherblock zugreift, können Sie den Speicherzeiger nach dem Freigeben des Blocks auf einen ungültigen Wert setzen:

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

Das beste Tool, das ich nützlich fand und das jedes Mal funktioniert hat, ist die Codeüberprüfung (mit guten Codeüberprüfern).

Abgesehen von der Codeüberprüfung würde ich zuerst Page Heap ausprobieren . Das Einrichten von Page Heap dauert einige Sekunden und kann mit etwas Glück Ihr Problem lokalisieren.

Wenn Sie mit Page Heap kein Glück haben, laden Sie die Debugging-Tools für Windows von Microsoft herunter und lernen Sie die Verwendung von WinDbg. Leider konnte ich Ihnen keine spezifischere Hilfe geben, aber das Debuggen von Multithread-Heap-Korruption ist eher eine Kunst als eine Wissenschaft. Google für "WinDbg Heap Korruption" und Sie sollten viele Artikel zu diesem Thema finden.

Shing Yip
quelle
4

Möglicherweise möchten Sie auch überprüfen, ob Sie eine Verknüpfung mit der dynamischen oder statischen C-Laufzeitbibliothek herstellen. Wenn Ihre DLL-Dateien mit der statischen C-Laufzeitbibliothek verknüpft sind, haben die DLL-Dateien separate Heaps.

Wenn Sie also ein Objekt in einer DLL erstellen und versuchen, es in einer anderen DLL freizugeben, erhalten Sie dieselbe Meldung wie oben. Auf dieses Problem wird in einer anderen Frage zum Stapelüberlauf verwiesen: Freigeben des in einer anderen DLL zugewiesenen Speichers .

Dreadpirateryan
quelle
3

Welche Art von Zuordnungsfunktionen verwenden Sie? Ich habe kürzlich einen ähnlichen Fehler bei der Zuweisung von Heap * -Stilen festgestellt.

Es stellte sich heraus, dass ich fälschlicherweise den Heap mit der HEAP_NO_SERIALIZEOption erstellt habe. Dadurch werden die Heap-Funktionen im Wesentlichen ohne Thread-Sicherheit ausgeführt. Es ist eine Leistungsverbesserung, wenn es richtig verwendet wird, sollte aber niemals verwendet werden, wenn Sie HeapAlloc in einem Multithread-Programm verwenden [1]. Ich erwähne dies nur, weil in Ihrem Beitrag erwähnt wird, dass Sie eine Multithread-App haben. Wenn Sie HEAP_NO_SERIALIZE irgendwo verwenden, löschen Sie das und es wird wahrscheinlich Ihr Problem beheben.

[1] Es gibt bestimmte Situationen, in denen dies legal ist. Sie müssen jedoch Aufrufe an Heap * serialisieren. Dies ist bei Multithread-Programmen normalerweise nicht der Fall.

JaredPar
quelle
Ja: Sehen Sie sich die Compiler- / Build-Optionen der Anwendung an und stellen Sie sicher, dass eine Verknüpfung mit einer "Multithread" -Version der C-Laufzeitbibliothek hergestellt wird.
ChrisW
@ChrisW für die HeapAlloc-APIs ist dies anders. Es ist tatsächlich ein Parameter, der zum Zeitpunkt der Heap-Erstellung und nicht zur Verknüpfungszeit geändert werden kann.
JaredPar
Oh. Mir ist nicht in den Sinn gekommen, dass das OP über diesen Heap spricht und nicht über den Heap in der CRT.
ChrisW
@ChrisW, die Frage ist ziemlich vage, aber ich habe gerade das Problem getroffen, das ich vor ~ 1 Woche beschrieben habe, also ist es frisch in meinem Kopf.
JaredPar
3

Wenn diese Fehler zufällig auftreten, besteht eine hohe Wahrscheinlichkeit, dass Sie auf Datenrennen gestoßen sind. Überprüfen Sie bitte: Ändern Sie gemeinsam genutzte Speicherzeiger aus verschiedenen Threads? Intel Thread Checker kann helfen, solche Probleme in Multithread-Programmen zu erkennen.

Vladimir Obrizan
quelle
1

Neben der Suche nach Werkzeugen sollten Sie auch nach einem wahrscheinlichen Schuldigen suchen. Gibt es eine Komponente, die Sie verwenden, die möglicherweise nicht von Ihnen geschrieben wurde und die möglicherweise nicht für die Ausführung in einer Multithread-Umgebung entwickelt und getestet wurde? Oder einfach eine, die Sie nicht kennen, ist in einer solchen Umgebung gelaufen.

Das letzte Mal, als es mir passiert ist, war es ein natives Paket, das seit Jahren erfolgreich aus Batch-Jobs verwendet wurde. Es war jedoch das erste Mal in diesem Unternehmen, dass es von einem .NET-Webdienst (der Multithread-fähig ist) verwendet wurde. Das war es - sie hatten gelogen, dass der Code threadsicher sei.

John Saunders
quelle
1

Sie können VC CRT Heap-Check-Makros für _CrtSetDbgFlag verwenden : _CRTDBG_CHECK_ALWAYS_DF oder _CRTDBG_CHECK_EVERY_16_DF .. _CRTDBG_CHECK_EVERY_1024_DF .

KindDragon
quelle
0

Ich möchte meine Erfahrung hinzufügen. In den letzten Tagen habe ich eine Instanz dieses Fehlers in meiner Anwendung behoben. In meinem speziellen Fall waren die Fehler im Code:

  • Entfernen von Elementen aus einer STL-Auflistung beim Durchlaufen (Ich glaube, es gibt Debug-Flags in Visual Studio, um diese Dinge abzufangen; ich habe sie während der Codeüberprüfung abgefangen).
  • Dieser ist komplexer, ich werde ihn in Schritte unterteilen:
    • Rufen Sie von einem nativen C ++ - Thread aus den verwalteten Code zurück
    • Rufen Sie in verwaltetem Land Control.Invokeein verwaltetes Objekt auf und entsorgen Sie es, das das native Objekt umschließt, zu dem der Rückruf gehört.
    • Da das Objekt im nativen Thread noch aktiv ist (bleibt es im Rückrufaufruf bis zum Control.InvokeEnde blockiert ). Ich sollte klarstellen, dass ich verwende boost::thread, also verwende ich eine Member-Funktion als Thread-Funktion.
    • Lösung : Verwenden Control.BeginInvokeSie stattdessen (meine GUI wird mit Winforms erstellt), damit der native Thread beendet werden kann, bevor das Objekt zerstört wird (der Zweck des Rückrufs besteht darin, genau zu benachrichtigen, dass der Thread beendet wurde und das Objekt zerstört werden kann).
dario_ramos
quelle
0

Ich hatte ein ähnliches Problem - und es tauchte ganz zufällig auf. Vielleicht war etwas in den Build-Dateien beschädigt, aber ich habe es behoben, indem ich zuerst das Projekt bereinigt und dann neu erstellt habe.

Also zusätzlich zu den anderen Antworten:

Welche Art von Dingen kann diese Fehler verursachen? Etwas beschädigt in der Build-Datei.

Wie debugge ich sie? Projekt reinigen und neu erstellen. Wenn es behoben ist, war dies wahrscheinlich das Problem.

Marty
quelle