Frage

Ich arbeite an einem Multithreaded C++-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 zeigen das gleiche Verhalten wie die neueste Version, das gibt es also schon seit langer Zeit und wurde einfach nicht bemerkt;Der Nachteil ist, dass Quelldeltas nicht verwendet werden können, um festzustellen, wann der Fehler aufgetreten ist – das ist der Fall eine Menge von Codeänderungen im Repository.

Die Ursache für das Absturzverhalten besteht darin, in diesem System einen Durchsatz zu erzeugen – eine Socket-Übertragung von Daten, die in eine interne Darstellung eingebunden werden.Ich habe eine Reihe von Testdaten, die in regelmäßigen Abständen dazu führen, dass die App eine Ausnahme auslöst (verschiedene Orte, verschiedene Ursachen – einschließlich fehlgeschlagener Heap-Zuordnung, also:Heap-Korruption).

Das Verhalten scheint mit der CPU-Leistung oder der Speicherbandbreite zusammenzuhängen.Je mehr davon die Maschine hat, desto leichter kann es zum Absturz kommen.Das Deaktivieren eines Hyper-Threading-Kerns oder eines Dual-Core-Kerns verringert die Korruptionsrate (beseitigt sie jedoch nicht).Dies deutet auf ein Timing-Problem hin.

Hier ist nun das Problem:
Wenn es in einer einfachen Debug-Umgebung ausgeführt wird (z. B Visual Studio 98 / AKA MSVC6) lässt sich die Heap-Beschädigung einigermaßen leicht reproduzieren – es vergehen zehn oder fünfzehn Minuten, bevor etwas schrecklich fehlschlägt und Ausnahmen wie ein alloc; wenn es in einer anspruchsvollen Debug-Umgebung ausgeführt wird (Rational Purify, VS2008/MSVC9 oder sogar Microsoft Application Verifier) ​​wird das System an die Speichergeschwindigkeit gebunden und stürzt nicht ab (speichergebunden:Die CPU kommt nicht weiter 50%, Festplatten-LED leuchtet nicht, das Programm läuft so schnell es kann, Box-verbrauchend 1.3G von 2G RAM).Also, Ich habe die Wahl, ob ich das Problem reproduzieren kann (aber die Ursache nicht identifizieren kann) oder ob ich die Ursache oder ein Problem identifizieren kann, das ich nicht reproduzieren kann.

Meine derzeit beste Vermutung, wo ich als nächstes hinkomme, ist:

  1. Holen Sie sich eine wahnsinnig grunzende Box (um die aktuelle Entwicklungsbox zu ersetzen:2 GB RAM in einem E6550 Core2 Duo);Dadurch wird es möglich, den Absturz zu reproduzieren, der zu Fehlverhalten führt, wenn es in einer leistungsstarken Debug-Umgebung ausgeführt wird.oder
  2. Operatoren umschreiben new Und delete benutzen VirtualAlloc Und VirtualProtect um den Speicher als schreibgeschützt zu markieren, sobald er fertig ist.Unterlaufen MSVC6 und lassen Sie das Betriebssystem den Bösewicht fangen, der in den freigegebenen Speicher schreibt.Ja, das ist ein Zeichen der Verzweiflung:Wer zum Teufel schreibt um? new Und delete?!Ich frage mich, ob es dadurch genauso langsam wird wie unter Purify et al.

Und nein:Der Versand mit eingebauter Purify-Instrumentierung ist keine Option.

Ein Kollege kam gerade vorbei und fragte „Stack Overflow?“Bekommen wir jetzt Stapelüberläufe?!?"

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


Aktualisieren:ausgleichend new[] Und delete[] scheint der Lösung des Problems einen großen Schritt vorangekommen zu sein.Statt 15 Minuten läuft die App jetzt etwa zwei Stunden, bevor sie abstürzt.Noch nicht da.Irgendwelche weiteren Vorschläge?Die Heap-Beschädigung bleibt bestehen.

Aktualisieren:ein Release-Build unter Visual Studio 2008 scheint deutlich besser zu sein;Der aktuelle Verdacht liegt auf der STL Implementierung, die mitgeliefert wird VS98.


  1. Reproduzieren Sie das Problem. Dr Watson wird einen Speicherauszug erstellen, der für die weitere Analyse hilfreich sein könnte.

Ich werde das zur Kenntnis nehmen, aber ich mache mir Sorgen, dass Dr. Watson erst im Nachhinein ins Stolpern gerät und nicht, wenn auf dem Haufen herumgetrampelt wird.

Ein weiterer Versuch könnte sein WinDebug als Debugging-Tool, das sehr leistungsstark und gleichzeitig leichtgewichtig ist.

Habe das im Moment wieder am Laufen:Es hilft nicht viel, bis etwas schief geht.Ich möchte den Vandalen auf frischer Tat ertappen.

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

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

Und sind Sie sicher, dass alle Komponenten des Projekts über die richtigen Laufzeitbibliothekseinstellungen verfügen (C/C++ tab, Kategorie „Codegenerierung“ in den VS 6.0-Projekteinstellungen)?

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


Aktualisieren:Dies dauerte 30 Sekunden.Wählen Sie alle Projekte im aus Settings Deaktivieren Sie im Dialogfeld die Auswahl, bis Sie die Projekte finden, die nicht über die richtigen Einstellungen verfügen (sie hatten alle die richtigen Einstellungen).

War es hilfreich?

Lösung

Meine erste Wahl wäre ein dediziertes Heap-Tool wie z pageheap.exe.

Das Umschreiben von „new“ und „delete“ kann nützlich sein, aber dadurch werden die von Code auf niedrigerer Ebene festgeschriebenen Zuweisungen nicht erfasst.Wenn Sie dies möchten, ist es besser, den Umweg zu wählen low-level alloc APIIch verwende Microsoft Detours.

Auch Plausibilitätsprüfungen wie:Überprüfen Sie, ob Ihre Laufzeitbibliotheken übereinstimmen (Release vs.debuggen, Multithread vs.Single-Threaded, DLL vs.static lib), suchen Sie nach fehlerhaften Löschungen (z. B. löschen, wo delete [] hätte verwendet werden sollen), stellen Sie sicher, dass Sie Ihre Zuordnungen nicht vermischen und anpassen.

Versuchen Sie auch, Threads gezielt zu deaktivieren, und prüfen Sie, wann/ob das Problem behoben ist.

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

Andere Tipps

Ich habe die gleichen Probleme bei meiner Arbeit (wir verwenden auch VC6 Manchmal).Und es gibt keine einfache Lösung dafür.Ich habe nur ein paar Hinweise:

  • Versuchen Sie es mit automatischen Crash-Dumps auf der Produktionsmaschine (siehe Prozess-Dumper).Meine Erfahrung sagt Dr.Watson ist nicht perfekt zum Abladen.
  • Alles entfernen fangen(...) aus Ihrem Code.Sie verbergen oft schwerwiegende Gedächtnisausfälle.
  • Überprüfen Erweitertes Windows-Debugging - es gibt viele tolle Tipps für Probleme wie Ihres.Ich kann es von ganzem Herzen empfehlen.
  • Wenn du benutzt STL versuchen STLPort und überprüfte Builds.Ungültige Iteratoren sind die Hölle.

Viel Glück.Es dauert Monate, bis wir Probleme wie Ihres lösen.Seien Sie darauf vorbereitet...

Führen Sie die Originalanwendung mit aus ADplus -crash -pn appnename.exeWenn das Speicherproblem auftritt, erhalten Sie einen schönen großen Speicherauszug.

Sie können den Dump analysieren, um herauszufinden, welcher Speicherort beschädigt wurde.Wenn Sie Glück haben, ist der Überschreibspeicher eine eindeutige Zeichenfolge, aus der Sie herausfinden können, woher er stammt.Wenn Sie kein Glück haben, müssen Sie sich damit befassen win32 Haufen und finden Sie heraus, was die ursprünglichen Speichereigenschaften waren.(heap -x könnte helfen)

Nachdem Sie herausgefunden haben, was fehlerhaft war, können Sie die Appverifier-Nutzung mit speziellen Heap-Einstellungen einschränken.d.h.Sie können angeben, was DLL Sie überwachen oder welche Zuordnungsgröße überwacht werden soll.

Hoffentlich beschleunigt dies die Überwachung so weit, dass der Täter gefasst werden kann.

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

P.S.:Sie können verwenden DebugDiag um die Dumps zu analysieren.Es kann darauf hinweisen DLL Eigentümer des beschädigten Heaps und geben Ihnen weitere nützliche Details.

Wir hatten ziemlich viel Glück beim Schreiben unserer eigenen malloc- und kostenlosen Funktionen.In der Produktion rufen sie einfach das Standard-Malloc und Free auf, aber beim Debuggen können sie tun, was Sie wollen.Wir haben auch eine einfache Basisklasse, die nichts anderes tut, als die Operatoren „Neu“ und „Löschen“ zu überschreiben, um diese Funktionen zu verwenden. Anschließend kann jede von Ihnen geschriebene Klasse einfach von dieser Klasse erben.Wenn Sie eine Menge Code haben, kann es eine große Aufgabe sein, Aufrufe von malloc und free durch das neue malloc und free zu ersetzen (realloc nicht vergessen!), aber auf lange Sicht ist es sehr hilfreich.

Im Buch von Steve Maguire Soliden Code schreiben (sehr empfehlenswert), es gibt Beispiele für Debug-Aufgaben, die Sie in diesen Routinen ausführen können, wie zum Beispiel:

  • Verfolgen Sie die Zuordnungen, um Lecks zu finden
  • Weisen Sie mehr Speicher als nötig zu und setzen Sie Markierungen am Anfang und Ende des Speichers. Während der freien Routine können Sie sicherstellen, dass diese Markierungen noch vorhanden sind
  • Setzen Sie den Speicher mit einer Markierung auf Zuweisung (um die Nutzung von nicht initialisiertem Speicher zu finden) und auf Frei (um die Nutzung von freiem Speicher zu finden)

Eine weitere gute Idee ist es niemals Verwenden Sie Dinge wie strcpy, strcat, oder sprintf -- Verwenden Sie immer strncpy, strncat, Und snprintf.Wir haben auch unsere eigenen Versionen davon geschrieben, um sicherzustellen, dass wir das Ende eines Puffers nicht abschreiben, und auch diese haben viele Probleme verursacht.

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

Erwägen Sie für die statische Analyse die Kompilierung mit PREfast (cl.exe /analyze).Es erkennt Nichtübereinstimmungen delete Und delete[], Pufferüberläufe und viele andere Probleme.Seien Sie jedoch darauf vorbereitet, viele Kilobyte an L6-Warnungen durchzuwühlen, insbesondere wenn Ihr Projekt dies noch tut L4 nicht behoben.

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

Die offensichtliche Zufälligkeit der Speicherbeschädigung klingt sehr nach einem Thread-Synchronisierungsproblem – ein Fehler wird abhängig von der Maschinengeschwindigkeit reproduziert.Wenn Objekte (Speicherblöcke) von Threads gemeinsam genutzt werden und die Synchronisierungsprimitive (kritischer Abschnitt, Mutex, Semaphor usw.) nicht pro Klasse (pro Objekt, pro Klasse) erfolgen, 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 hierfür könnten Sie jeder Klasse und Methode Synchronisierungsprimitive hinzufügen.Dadurch wird Ihr Code langsamer, da viele Objekte aufeinander warten müssen. Wenn dadurch jedoch die Heap-Beschädigung beseitigt wird, wird Ihr Heap-Beschädigungsproblem zu einem Problem der Codeoptimierung.

Liegt das an Bedingungen mit wenig Speicher?Wenn ja, könnte es sein, dass das Neue zurückkommt NULL anstatt std::bad_alloc zu werfen.Älter VC++ Compiler haben dies nicht richtig implementiert.Es gibt einen Artikel darüber Fehler bei der alten Speicherzuordnung stürzt ab STL Apps, die mit erstellt wurden VC6.

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 aufgetreten ist?

Andernfalls würde ich vorschlagen, eine einfache Protokollierung hinzuzufügen, um das Problem aufzuspüren, obwohl ich nicht weiß, was Sie konkret 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 Einblicke, worauf Sie im Code achten sollten.

Meine erste Aktion wäre wie folgt:

  1. Erstellen Sie die Binärdateien in der „Release“-Version, erstellen Sie jedoch eine Debug-Infodatei (diese Möglichkeit finden Sie in den Projekteinstellungen).
  2. Verwenden Sie Dr. Watson als Standard-Debugger (DrWtsn32 -I) auf einem Computer, auf dem Sie das Problem reproduzieren möchten.
  3. Reproduzieren Sie das Problem.Dr. Watson wird einen Dump erstellen, der für die weitere Analyse hilfreich sein könnte.

Ein anderer Versuch könnte darin bestehen, WinDebug als Debugging-Tool zu verwenden, das recht leistungsstark und gleichzeitig leichtgewichtig ist.

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

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

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

  • Schlechte Heap-Nutzung, d. h. doppelte Freigaben, Lesen nach Freigeben, Schreiben nach Freigeben, Setzen des HEAP_NO_SERIALIZE-Flags mit Zuweisungen und Freigaben von mehreren Threads auf demselben Heap
  • Kein Speicher mehr
  • Fehlerhafter Code (d. h. Pufferüberläufe, Pufferunterläufe usw.)
  • "Zeitprobleme

Wenn es überhaupt die ersten beiden sind, aber nicht das letzte, sollten Sie es inzwischen mit pageheap.exe erkannt haben.

Was höchstwahrscheinlich bedeutet, dass es daran liegt, wie der Code auf den gemeinsamen Speicher zugreift.Leider wird es ziemlich mühsam sein, das herauszufinden.Ein unsynchronisierter Zugriff auf den gemeinsam genutzten Speicher äußert sich oft in seltsamen „Timing“-Problemen.Dinge wie die Nichtverwendung der Erwerbs-/Freigabesemantik zum Synchronisieren des Zugriffs auf den gemeinsam genutzten Speicher mit einem Flag, die nicht ordnungsgemäße Verwendung von Sperren usw.

Zumindest wäre es hilfreich, die Zuteilungen irgendwie verfolgen zu können, wie bereits vorgeschlagen wurde.Dann können Sie zumindest sehen, was bis zur Heap-Beschädigung tatsächlich passiert ist, und versuchen, daraus eine Diagnose zu stellen.

Wenn Sie Zuweisungen problemlos auf mehrere Heaps umleiten können, sollten Sie dies auch ausprobieren, um zu sehen, ob das Problem dadurch entweder behoben wird oder zu besser reproduzierbarem fehlerhaftem Verhalten führt.

Haben Sie beim Testen mit VS2008 den HeapVerifier mit der Einstellung „Speicher sparen“ auf „Ja“ ausgeführt?Dies könnte die Leistungsauswirkungen des Heap-Allokators verringern.(Außerdem müssen Sie damit Debug->Start with Application Verifier ausführen, aber das wissen Sie vielleicht schon.)

Sie können auch das Debuggen mit Windbg und verschiedene Verwendungen des Befehls !heap ausprobieren.

MSN

Wenn Sie sich für „Neu schreiben/Löschen“ entscheiden, habe ich dies getan und habe einen einfachen Quellcode unter:

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

Dies fängt Speicherlecks ab und fügt außerdem Schutzdaten vor und nach dem Speicherblock ein, um Heap-Beschädigungen zu erfassen.Sie können es einfach integrieren, indem Sie #include „debug.h“ oben in jede CPP-Datei einfügen und DEBUG und DEBUG_MEM definieren.

Graemes Vorschlag von Custom Malloc/Free ist eine gute Idee.Sehen Sie nach, ob Sie ein Muster der Korruption charakterisieren können, das Ihnen eine Handhabe gibt, die Sie nutzen können.

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

Dies setzt jedoch voraus, dass die Anzahl der ausstehenden 64-Byte-Blöcke nur mäßig ist oder Sie viel Speicher zum Brennen in der Box haben!

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

Glauben Sie, dass dies eine Rennbedingung ist?Teilen sich mehrere Threads einen Heap?Können Sie jedem Thread mit HeapCreate einen privaten Heap zuweisen, 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.

Ein paar Vorschläge.Sie erwähnen die zahlreichen Warnungen bei W4 – ich würde vorschlagen, dass Sie sich die Zeit nehmen, Ihren Code so zu korrigieren, dass er bei Warnstufe 4 sauber kompiliert wird – das wird einen großen Beitrag dazu leisten, subtile, schwer zu findende Fehler zu verhindern.

Zweitens generiert der /analyze-Schalter tatsächlich zahlreiche Warnungen.Um diesen Schalter in meinem eigenen Projekt zu verwenden, habe ich eine neue Header-Datei erstellt, die #pragma warning verwendet, um alle zusätzlichen Warnungen zu deaktivieren, die von /analyze generiert werden.Weiter unten in der Datei aktiviere ich dann nur die Warnungen, die mich interessieren.Verwenden Sie dann den Compilerschalter /FI, um zu erzwingen, dass diese Headerdatei zuerst in alle Ihre Kompilierungseinheiten einbezogen wird.Dies sollte es Ihnen ermöglichen, den Schalter /analyze zu verwenden, während Sie die Ausgabe steuern

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top