c++ - Heap-Korruption unter Win32; wie zu finden?

Translate

Ich arbeite an einemMultithreadedC ++ - Anwendung, die den Heap beschädigt. Die üblichen Tools zum Auffinden dieser Beschädigung scheinen nicht anwendbar zu sein. Alte Builds (18 Monate alt) des Quellcodes weisen das gleiche Verhalten wie die neueste Version auf. Dies gibt es also schon lange und wurde einfach nicht bemerkt. Auf der anderen Seite können Quelldeltas nicht verwendet werden, um zu identifizieren, wann der Fehler eingeführt wurde - es gibtvielvon Codeänderungen im Repository.

Die Aufforderung zum Absturzverhalten besteht darin, einen Durchsatz in diesem System zu generieren - Socket-Übertragung von Daten, die in eine interne Darstellung umgewandelt werden. Ich habe eine Reihe von Testdaten, die in regelmäßigen Abständen zu Ausnahmen bei der App führen (verschiedene Stellen, verschiedene Ursachen - einschließlich fehlgeschlagener Heap-Zuweisung, also: Heap-Beschädigung).

Das Verhalten scheint mit der CPU-Leistung oder der Speicherbandbreite in Zusammenhang zu stehen. Je mehr von jedem die Maschine hat, desto leichter ist es zum Absturz. Durch Deaktivieren eines Hyper-Threading-Kerns oder eines Dual-Core-Kerns wird die Rate der Beschädigung verringert (aber nicht beseitigt). Dies deutet auf ein zeitliches Problem hin.

Hier ist das Problem:
Wenn es unter einer leichtgewichtigen Debug-Umgebung ausgeführt wird (zVisual Studio 98 / AKA MSVC6) Die Heap-Korruption ist relativ einfach zu reproduzieren - zehn oder fünfzehn Minuten vergehen, bevor etwas schrecklich ausfällt und Ausnahmen wie einealloc;bei Ausführung unter einer ausgeklügelten Debug-Umgebung (Rational Purify,VS2008/MSVC9oder sogar Microsoft Application Verifier) das System wird speichergeschwindigkeitsgebunden und stürzt nicht ab (speichergebunden: Die CPU wird nicht höher50%Wenn die Festplattenanzeige nicht leuchtet, läuft das Programm so schnell wie möglich, was die Box verbraucht1.3Gvon 2G RAM). Damit,Ich habe die Wahl, ob ich das Problem reproduzieren (aber nicht die Ursache identifizieren) oder die Ursache oder ein Problem, das ich nicht reproduzieren kann, identifizieren kann.

Meine derzeit besten Vermutungen, wohin ich als nächstes gehen soll, sind:

  1. Holen Sie sich eine wahnsinnig grunzende Box (um die aktuelle Entwicklungsbox zu ersetzen: 2 GB RAM in einemE6550 Core2 Duo); Auf diese Weise kann der Absturz erneut ausgeführt werden, der zu Fehlverhalten führt, wenn er in einer leistungsstarken Debug-Umgebung ausgeführt wird. oder
  2. Operatoren neu schreibennewunddeletebenutzenVirtualAllocundVirtualProtectum den Speicher als schreibgeschützt zu markieren, sobald er fertig ist. Laufen Sie unterMSVC6und lassen Sie das Betriebssystem den Bösen fangen, der in den freigegebenen Speicher schreibt. Ja, das ist ein Zeichen der Verzweiflung: Wer zum Teufel schreibt um?newunddelete?! Ich frage mich, ob dies dazu führen wird, dass es so langsam wird wie unter Purify et al.

Und nein: Der Versand mit integrierten Purify-Instrumenten ist keine Option.

Ein Kollege ging gerade vorbei und fragte: "Stapelüberlauf? Bekommen wir jetzt Stapelüberlauf?!?"

Und jetzt die Frage:Wie finde ich den Heap-Korruptor?


Update: Ausgleichnew[]unddelete[]scheint einen langen Weg zur Lösung des Problems zurückgelegt zu haben. Anstelle von 15 Minuten läuft die App jetzt ungefähr zwei Stunden vor dem Absturz. Noch nicht da. Weitere Vorschläge? Die Heap-Beschädigung bleibt bestehen.

Update: Ein Release-Build unter Visual Studio 2008 scheint dramatisch besser zu sein. Der derzeitige Verdacht beruht auf derSTLImplementierung, die mit geliefert wirdVS98.


  1. Reproduzieren Sie das Problem.Dr Watsonerzeugt einen Speicherauszug, der bei der weiteren Analyse hilfreich sein kann.

Ich werde das zur Kenntnis nehmen, aber ich mache mir Sorgen, dass Dr. Watson erst nachträglich gestolpert wird, nicht wenn der Haufen darauf stampft.

Ein anderer Versuch könnte verwendenWinDebugals Debugging-Tool, das ziemlich leistungsfähig ist und gleichzeitig auch leicht ist.

Habe das im Moment wieder in Gang gebracht: nicht viel Hilfe, bis etwas schief geht. Ich möchte den Vandal auf frischer Tat ertappen.

Vielleicht können Sie mit diesen Tools das Problem zumindest auf bestimmte Komponenten eingrenzen.

Ich habe nicht viel Hoffnung, aber verzweifelte Zeiten erfordern ...

Und sind Sie sicher, dass alle Komponenten des Projekts die richtigen Einstellungen für die Laufzeitbibliothek haben (C/C++ tab, Codegenerierungskategorie in VS 6.0-Projekteinstellungen)?

Nein, bin ich nicht, und ich werde morgen ein paar Stunden damit verbringen, den Arbeitsbereich (58 Projekte darin) zu durchsuchen und zu überprüfen, ob sie alle kompiliert und mit den entsprechenden Flags verknüpft sind.


Update: This took 30 seconds. Select all projects in the Settings dialog, unselect until you find the project(s) that don't have the right settings (they all had the right settings).

This question and all comments follow the "Attribution Required."

Alle Antworten

Jim Lee
Translate

Meine erste Wahl wäre ein dediziertes Heap-Tool wiepageheap.exe.

Das Umschreiben von Neuem und Löschen ist möglicherweise nützlich, erfasst jedoch nicht die Zuordnungen, die von Code auf niedrigerer Ebene festgelegt wurden. Wenn Sie dies möchten, sollten Sie den Umweg machenlow-level alloc APIs mit Microsoft Detours.

Überprüfen Sie auch, ob Ihre Laufzeitbibliotheken übereinstimmen (Release vs. Debug, Multithreaded vs. Single-Threaded, DLL vs. statische Bibliothek), und suchen Sie nach fehlerhaften Löschvorgängen (z. B. Löschen, wo delete [] hätte sein sollen verwendet), stellen Sie sicher, dass Sie Ihre Zuordnungen nicht mischen und anpassen.

Versuchen Sie auch, Threads selektiv auszuschalten und festzustellen, wann / ob das Problem behoben ist.

Wie sieht der Aufrufstapel usw. zum Zeitpunkt der ersten Ausnahme aus?

Quelle
Translate

Ich habe die gleichen Probleme in meiner Arbeit (wir verwenden auchVC6manchmal). Und es gibt keine einfache Lösung dafür. Ich habe nur einige Hinweise:

  • Versuchen Sie es mit automatischen Crash-Dumps auf der Produktionsmaschine (sieheProzesskipper). Meine Erfahrung sagt, dass Dr. Watson istnicht perfektzum Dumping.
  • Alles entfernenFang(...)von Ihrem Code. Sie verbergen oft schwerwiegende Speicherausnahmen.
  • PrüfenErweitertes Windows-Debugging- Es gibt viele gute Tipps für Probleme wie Ihres. Ich empfehle dies von ganzem Herzen.
  • Wenn du benutztSTLVersuchenSTLPortund überprüfte Builds. Ungültige Iteratoren sind die Hölle.

Viel Glück. Probleme wie Ihre brauchen Monate, um sie zu lösen. Sei bereit dafür ...

Quelle
Tal
Translate

Führen Sie die ursprüngliche Anwendung mit ausADplus -crash -pn appnename.exeWenn das Speicherproblem auftaucht, erhalten Sie einen schönen großen Speicherauszug.

Sie können den Speicherauszug analysieren, um festzustellen, welcher Speicherort beschädigt wurde. Wenn Sie Glück haben, ist der Überschreibspeicher eine eindeutige Zeichenfolge, mit der Sie herausfinden können, woher er stammt. Wenn Sie kein Glück haben, müssen Sie sich vertiefenwin32Haufen und herausfinden, was die ursprünglichen Speichermerkmale waren. (Haufen -x könnte helfen)

Nachdem Sie wissen, was durcheinander gebracht wurde, können Sie die Verwendung von Appverifizierern mit speziellen Heap-Einstellungen eingrenzen. dh Sie können angeben, wasDLLSie überwachen oder welche Zuordnungsgröße überwacht werden soll.

Hoffentlich beschleunigt dies die Überwachung genug, um den Täter zu fangen.

Nach meiner Erfahrung brauchte ich nie den vollständigen Heap-Überprüfungsmodus, aber ich habe viel Zeit damit verbracht, die Absturzabbilder zu analysieren und Quellen zu durchsuchen.

PS:Sie können verwendenDebugDiagdie Deponien zu analysieren. Es kann auf das hinweisenDLLSie besitzen den beschädigten Heap und geben Ihnen weitere nützliche Details.

Quelle
Translate

Wir hatten ziemlich viel Glück, indem wir unsere eigenen Malloc- und kostenlosen Funktionen geschrieben haben. In der Produktion nennen sie einfach das Standard-Malloc und kostenlos, aber beim Debuggen können sie tun, was Sie wollen. Wir haben auch eine einfache Basisklasse, die nichts anderes tut, als die Operatoren new und delete zu überschreiben, um diese Funktionen zu verwenden. Dann kann jede Klasse, die Sie schreiben, einfach von dieser Klasse erben. Wenn Sie eine Menge Code haben, kann es eine große Aufgabe sein, Anrufe an malloc und free an das neue malloc und free zu ersetzen (vergessen Sie nicht realloc!), Aber auf lange Sicht ist es sehr hilfreich.

In Steve Maguires BuchSoliden Code schreiben(sehr zu empfehlen) Es gibt Beispiele für Debug-Aktionen, die Sie in diesen Routinen ausführen können, z.

  • Verfolgen Sie die Zuordnungen, um Lecks zu finden
  • Weisen Sie mehr Speicher als erforderlich zu und setzen Sie Markierungen an den Anfang und das Ende des Speichers. Während der freien Routine können Sie sicherstellen, dass diese Markierungen noch vorhanden sind
  • Memset den Speicher mit einem Marker auf Zuordnung (um die Verwendung von nicht initialisiertem Speicher zu finden) und auf frei (um die Verwendung von freiem Speicher zu finden)

Eine andere gute Idee ist zunoch niebenutze Dinge wiestrcpy, strcat, odersprintf-- Verwenden Sie immerstrncpy, strncat, undsnprintf. Wir haben auch unsere eigenen Versionen davon geschrieben, um sicherzustellen, dass wir nicht das Ende eines Puffers abschreiben, und diese haben auch viele Probleme verursacht.

Quelle
Translate

Sie sollten dieses Problem sowohl mit Laufzeit- als auch mit statischer Analyse angreifen.

Für die statische Analyse sollten Sie mit PREfast kompilieren (cl.exe /analyze). Es erkennt eine Nichtübereinstimmungdeleteunddelete[], Pufferüberläufe und eine Vielzahl anderer Probleme. Seien Sie jedoch darauf vorbereitet, durch viele Kilobyte L6-Warnung zu waten, insbesondere wenn Ihr Projekt noch vorhanden istL4nicht behoben.

PREfast ist verfügbar mit Visual Studio Team System und,offenbarals Teil des Windows SDK.

Quelle
Translate

Die offensichtliche Zufälligkeit der Speicherbeschädigung klingt sehr nach einem Thread-Synchronisationsproblem - ein Fehler wird abhängig von der Maschinengeschwindigkeit reproduziert. Wenn Objekte (Speicherblöcke) von Threads gemeinsam genutzt werden und Synchronisationsprimitive (kritischer Abschnitt, Mutex, Semaphor, andere) nicht pro Klasse (pro Objekt, pro Klasse) vorliegen, kann es zu einer Situation kommen Dabei wird die Klasse (Speicherblock) während der Verwendung gelöscht / freigegeben oder nach dem Löschen / Freigeben verwendet.

Als Test dafür können Sie jeder Klasse und Methode Synchronisationsprimitive hinzufügen. Dies verlangsamt Ihren Code, da viele Objekte aufeinander warten müssen. Wenn dies jedoch die Heap-Beschädigung beseitigt, wird Ihr Heap-Beschädigungsproblem zu einem Problem der Codeoptimierung.

Quelle
Translate

Ist dies unter Bedingungen mit wenig Speicher? Wenn ja, könnte es sein, dass neu zurückkehrtNULLanstatt std :: bad_alloc zu werfen. ÄlterVC++Compiler haben dies nicht richtig implementiert. Es gibt einen Artikel überLegacy-SpeicherzuordnungsfehlerabstürzenSTLApps gebaut mitVC6.

Quelle
Translate

Sie haben alte Builds ausprobiert, aber gibt es einen Grund, warum Sie nicht weiter im Repository-Verlauf zurückgehen und genau sehen können, wann der Fehler eingeführt wurde?

Andernfalls würde ich vorschlagen, eine einfache Protokollierung hinzuzufügen, um das Problem aufzuspüren, obwohl ich nicht weiß, was Sie speziell protokollieren möchten.

Wenn Sie über Google und die Dokumentation der Ausnahmen, die Sie erhalten, herausfinden können, was genau dieses Problem verursachen kann, erhalten Sie möglicherweise weitere Informationen darüber, worauf Sie im Code achten müssen.

Quelle
Translate

Meine erste Aktion wäre wie folgt:

  1. Erstellen Sie die Binärdateien in der Version "Release", erstellen Sie jedoch eine Debug-Info-Datei (diese Möglichkeit finden Sie in den Projekteinstellungen).
  2. Verwenden Sie Dr. Watson als Defualt-Debugger (DrWtsn32 -I) auf einem Computer, auf dem Sie das Problem reproduzieren möchten.
  3. Führen Sie das Problem erneut ein. Dr. Watson wird einen Dump erstellen, der bei der weiteren Analyse hilfreich sein könnte.

Ein weiterer Versuch könnte darin bestehen, WinDebug als Debugging-Tool zu verwenden, das sehr leistungsfähig und gleichzeitig leicht ist.

Vielleicht können Sie mit diesen Tools das Problem zumindest auf bestimmte Komponenten eingrenzen.

Und sind Sie sicher, dass alle Komponenten des Projekts über die richtigen Einstellungen für die Laufzeitbibliothek verfügen (Registerkarte C / C ++, Kategorie Codegenerierung in den VS 6.0-Projekteinstellungen)?

Quelle
Translate

Aufgrund der begrenzten Informationen, die Sie haben, kann dies eine Kombination aus einem oder mehreren Dingen sein:

  • Schlechte Heap-Nutzung, dh doppelte Freigabe, Lesen nach Freie, Schreiben nach Freie, Setzen des HEAP_NO_SERIALIZE-Flags mit Zuweisungen und Freigeben von mehreren Threads auf demselben Heap
  • Nicht genügend Speicher
  • Schlechter Code (dh Pufferüberläufe, Pufferunterläufe usw.)
  • "Zeitprobleme

Wenn es überhaupt die ersten beiden, aber nicht die letzten sind, sollten Sie es jetzt mit einer der beiden Seiten pageheap.exe abgefangen haben.

Dies bedeutet höchstwahrscheinlich, dass der Code auf den gemeinsam genutzten Speicher zugreift. Leider wird es ziemlich schmerzhaft sein, das aufzuspüren. Der nicht synchronisierte Zugriff auf den gemeinsam genutzten Speicher äußert sich häufig in seltsamen "Timing" -Problemen. Dinge wie die Nichtverwendung der Erfassungs- / Freigabesemantik zum Synchronisieren des Zugriffs auf den gemeinsam genutzten Speicher mit einem Flag, die nicht ordnungsgemäße Verwendung von Sperren usw.

Zumindest würde es helfen, die Zuweisungen irgendwie verfolgen zu können, wie bereits vorgeschlagen. Zumindest können Sie dann anzeigen, was bis zur Heap-Beschädigung tatsächlich passiert ist, und versuchen, daraus eine Diagnose zu stellen.

Wenn Sie Zuordnungen problemlos auf mehrere Heaps umleiten können, sollten Sie dies versuchen, um festzustellen, ob das Problem dadurch behoben wird oder das Verhalten von Buggys reproduzierbarer wird.

Wurden Sie beim Testen mit VS2008 mit HeapVerifier ausgeführt, wobei der Speicherbedarf auf Ja gesetzt war? Dies kann die Auswirkungen des Heap-Allokators auf die Leistung verringern. (Außerdem müssen Sie damit Debug-> Start with Application Verifier ausführen, aber das wissen Sie vielleicht schon.)

Sie können auch versuchen, mit Windbg und verschiedenen Verwendungen des Befehls! Heap zu debuggen.

MSN

Quelle
Translate

Wenn Sie sich dafür entscheiden, new / delete neu zu schreiben, habe ich dies getan und habe einfachen Quellcode unter:

http://gandolf.homelinux.org/~smhanov/blog/?id=10

Dies fängt Speicherlecks ab und fügt Schutzdaten vor und nach dem Speicherblock ein, um die Heap-Beschädigung zu erfassen. Sie können es einfach integrieren, indem Sie #include "debug.h" oben in jede CPP-Datei setzen und DEBUG und DEBUG_MEM definieren.

Quelle
Translate

Graemes Vorschlag von Custom Malloc / Free ist eine gute Idee. Prüfen Sie, ob Sie ein Muster für die Beschädigung charakterisieren können, um eine Hebelwirkung zu erzielen.

Wenn es sich beispielsweise immer in einem Block derselben Größe befindet (z. B. 64 Byte), ändern Sie Ihr malloc / free-Paar so, dass immer 64-Byte-Blöcke auf der eigenen Seite zugewiesen werden. Wenn Sie einen 64-Byte-Block freigeben, setzen Sie die Speicherschutzbits auf dieser Seite, um Lese- und Schreibvorgänge zu verhindern (mithilfe von VirtualQuery). Dann generiert jeder, der versucht, auf diesen Speicher zuzugreifen, eine Ausnahme, anstatt den Heap zu beschädigen.

Dies setzt voraus, dass die Anzahl der ausstehenden 64-Byte-Chunks nur moderat ist oder Sie viel Speicher in der Box brennen müssen!

Quelle
Nicola Lee
Translate

Die kleine Zeit musste ich ein ähnliches Problem lösen. Wenn das Problem weiterhin besteht, empfehlen wir Ihnen Folgendes: Überwachen Sie alle Aufrufe von new / delete und malloc / calloc / realloc / free. Ich mache eine einzelne DLL, die eine Funktion zum Registrieren aller Anrufe exportiert. Diese Funktion empfängt Parameter zum Identifizieren Ihrer Codequelle, des Zeigers auf den zugewiesenen Bereich und der Art des Anrufs, wobei diese Informationen in einer Tabelle gespeichert werden. Alle zugewiesenen / freigegebenen Paare werden eliminiert. Am Ende oder nach Bedarf rufen Sie eine andere Funktion auf, um einen Bericht für die linken Daten zu erstellen. Damit können Sie falsche Anrufe (neu / frei oder malloc / löschen) oder fehlende identifizieren. Wenn in Ihrem Code ein Puffer überschrieben wird, können die gespeicherten Informationen falsch sein, aber jeder Test kann eine identifizierte Fehlerlösung erkennen / entdecken / einschließen. Viele Läufe helfen, die Fehler zu identifizieren. Viel Glück.

Quelle
Helen Lee
Translate

Glaubst du, das ist eine Rennbedingung? Teilen sich mehrere Threads einen Heap? Können Sie jedem Thread mit HeapCreate einen privaten Heap geben, dann können sie mit HEAP_NO_SERIALIZE schnell ausgeführt werden. Andernfalls sollte ein Heap threadsicher sein, wenn Sie die Multithread-Version der Systembibliotheken verwenden.

Quelle
Ann Lee
Translate

Ein paar Vorschläge. Sie erwähnen die zahlreichen Warnungen bei W4 - ich würde empfehlen, sich die Zeit zu nehmen, um Ihren Code zu reparieren, damit er auf Warnstufe 4 sauber kompiliert werden kann - dies trägt wesentlich dazu bei, subtile, schwer zu findende Fehler zu vermeiden.

Zweitens - für den Schalter / analyse - werden tatsächlich zahlreiche Warnungen generiert. Um diesen Schalter in meinem eigenen Projekt zu verwenden, habe ich eine neue Header-Datei erstellt, in der mithilfe der # Pragma-Warnung alle zusätzlichen Warnungen deaktiviert wurden, die von / analyse generiert wurden. Dann weiter unten in der Datei schalte ich nur die Warnungen ein, die mir wichtig sind. Verwenden Sie dann den Compiler-Schalter / FI, um zu erzwingen, dass diese Header-Datei zuerst in alle Ihre Kompilierungseinheiten aufgenommen wird. Dies sollte es Ihnen ermöglichen, den Schalter / analyse zu verwenden, während Sie den Ausgang steuern

Quelle