c++ - Corruption de tas sous Win32; comment localiser?

Translate

Je travaille sur unmultithreadApplication C ++ qui endommage le tas. Les outils habituels pour localiser cette corruption semblent inapplicables. Les anciennes versions (18 mois) du code source présentent le même comportement que la version la plus récente, donc cela existe depuis longtemps et n'a tout simplement pas été remarqué; Par contre, les deltas de source ne peuvent pas être utilisés pour identifier quand le bogue a été introduit - il y abeaucoupdes changements de code dans le référentiel.

L'invite pour le comportement de plantage est de générer un débit dans ce système - transfert de socket de données qui est intégré dans une représentation interne. J'ai un ensemble de données de test qui provoqueront périodiquement une exception de l'application (divers endroits, diverses causes - y compris l'échec de l'allocation du tas, donc: la corruption du tas).

Le comportement semble lié à la puissance du processeur ou à la bande passante mémoire; plus la machine en possède, plus il est facile de planter. La désactivation d'un cœur hyper-threading ou d'un cœur dual-core réduit le taux de corruption (mais n'élimine pas). Cela suggère un problème lié au calendrier.

Maintenant, voici le hic:
Lorsqu'il est exécuté dans un environnement de débogage léger (disonsVisual Studio 98 / AKA MSVC6) la corruption du tas est raisonnablement facile à reproduire - dix ou quinze minutes s'écoulent avant que quelque chose échoue horriblement et des exceptions, comme unalloc;lors de l'exécution dans un environnement de débogage sophistiqué (Rational Purify,VS2008/MSVC9ou même Microsoft Application Verifier) le système devient lié à la vitesse de la mémoire et ne plante pas (lié à la mémoire: le processeur ne dépasse pas50%, la lumière du disque n'est pas allumée, le programme va aussi vite qu'il le peut, la boîte consomme1.3Gde 2G de RAM). Alors,J'ai le choix entre être capable de reproduire le problème (mais pas d'identifier la cause) ou être capable d'identifier la cause ou un problème que je ne peux pas reproduire.

Mes meilleures hypothèses actuelles quant à la prochaine étape sont:

  1. Obtenez une boîte incroyablement grunty (pour remplacer la boîte de développement actuelle: 2 Go de RAM dans unE6550 Core2 Duo); cela permettra de reprocher le crash provoquant un mauvais comportement lors de l'exécution sous un environnement de débogage puissant; ou
  2. Réécrire les opérateursnewetdeleteutiliserVirtualAllocetVirtualProtectpour marquer la mémoire en lecture seule dès que c'est fini avec. Courir sousMSVC6et que le système d'exploitation attrape le méchant qui écrit pour libérer la mémoire. Oui, c'est un signe de désespoir: qui diable réécritnewetdelete?! Je me demande si cela va le rendre aussi lent que sous Purify et al.

Et non: l'expédition avec l'instrumentation Purify intégrée n'est pas une option.

Un collègue est juste passé et a demandé "Stack Overflow? Avons-nous des débordements de pile maintenant?!?"

Et maintenant, la question:Comment localiser le correcteur de tas?


Mise à jour: équilibragenew[]etdelete[]semble avoir parcouru un long chemin pour résoudre le problème. Au lieu de 15 minutes, l'application passe maintenant environ deux heures avant de planter. Pas encore là. Avez-vous d'autres suggestions? La corruption du tas persiste.

Mise à jour: une version compilée sous Visual Studio 2008 semble nettement meilleure; la suspicion actuelle repose surSTLmise en œuvre livrée avecVS98.


  1. Reproduisez le problème.Dr Watsonproduira un vidage qui pourrait être utile dans une analyse plus approfondie.

Je vais prendre note de cela, mais je crains que le Dr Watson ne soit déclenché qu'après coup, pas lorsque le tas est piétiné.

Un autre essai pourrait être d'utiliserWinDebugen tant qu'outil de débogage assez puissant et en même temps léger.

Ça va pour le moment, encore une fois: pas beaucoup d'aide jusqu'à ce que quelque chose se passe mal. Je veux attraper le vandale en flagrant délit.

Peut-être que ces outils vous permettront au moins de limiter le problème à certains composants.

Je n'ai pas beaucoup d'espoir, mais les temps désespérés appellent ...

Et êtes-vous sûr que tous les composants du projet ont des paramètres de bibliothèque d'exécution corrects (C/C++ tab, Catégorie Génération de code dans les paramètres du projet VS 6.0)?

Non, je ne le suis pas, et je passerai quelques heures demain à parcourir l'espace de travail (58 projets) et à vérifier qu'ils sont tous en train de compiler et de créer des liens avec les indicateurs appropriés.


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."

Toutes les réponses

Jim Lee
Translate

Mon premier choix serait un outil de tas dédié tel quepageheap.exe.

La réécriture de new et delete peut être utile, mais cela n'attrape pas les allocs validés par le code de niveau inférieur. Si c'est ce que vous voulez, mieux vaut faire un détourlow-level alloc APIs utilisant Microsoft Detours.

Vérifiez également la cohérence de vos bibliothèques d'exécution (version vs debug, multithread vs mono-thread, dll vs lib statique), recherchez les mauvaises suppressions (par exemple, supprimez où delete [] aurait dû être utilisé), assurez-vous de ne pas mélanger et faire correspondre vos allocs.

Essayez également de désactiver sélectivement les threads et voyez quand / si le problème disparaît.

À quoi ressemble la pile d'appels, etc. au moment de la première exception?

La source
Translate

J'ai les mêmes problèmes dans mon travail (nous utilisons égalementVC6parfois). Et il n'y a pas de solution facile pour cela. Je n'ai que quelques indices:

  • Essayez avec des vidages automatiques sur incident sur la machine de production (voirDumper de processus). Mon expérience dit que le Dr Watson estpas parfaitpour le dumping.
  • Enlever toutcapture(...)à partir de votre code. Ils cachent souvent de graves exceptions de mémoire.
  • VérifierDébogage Windows avancé- il existe de nombreux conseils utiles pour des problèmes comme le vôtre. Je recommande cela de tout mon cœur.
  • Si tu utilisesSTLessayerSTLPortet vérifié les versions. L'itérateur invalide est l'enfer.

Bonne chance. Des problèmes comme le vôtre prennent des mois à être résolus. Soyez prêt pour ça ...

La source
Tal
Translate

Exécutez l'application d'origine avecADplus -crash -pn appnename.exeLorsque le problème de mémoire apparaît, vous obtiendrez un gros vidage.

Vous pouvez analyser le vidage pour déterminer quel emplacement mémoire a été corrompu. Si vous avez de la chance, la mémoire d'écrasement est une chaîne unique, vous pouvez déterminer d'où elle vient. Si vous n'êtes pas chanceux, vous devrez creuserwin32tas et comprendre quelles étaient les caractéristiques de la mémoire d'origine. (heap -x pourrait aider)

Une fois que vous savez ce qui a été gâché, vous pouvez limiter l'utilisation de l'appverifier avec des paramètres de tas spéciaux. c'est-à-dire que vous pouvez spécifier ceDLLvous surveillez, ou quelle taille d'allocation surveiller.

Espérons que cela accélérera suffisamment la surveillance pour attraper le coupable.

D'après mon expérience, je n'ai jamais eu besoin du mode vérificateur de tas complet, mais j'ai passé beaucoup de temps à analyser le (s) vidage (s) sur incident et à parcourir les sources.

PS:Vous pouvez utiliserDebugDiagpour analyser les décharges. Il peut indiquer leDLLposséder le tas corrompu, et vous donner d'autres détails utiles.

La source
Translate

Nous avons eu assez de chance en écrivant nos propres fonctions malloc et gratuites. En production, ils appellent simplement le malloc standard et gratuit, mais en débogage, ils peuvent faire ce que vous voulez. Nous avons également une classe de base simple qui ne fait que remplacer les opérateurs new et delete pour utiliser ces fonctions, alors toute classe que vous écrivez peut simplement hériter de cette classe. Si vous avez une tonne de code, cela peut être un gros travail de remplacer les appels à malloc et gratuitement vers le nouveau malloc et gratuit (n'oubliez pas de réallouer!), Mais à long terme, c'est très utile.

Dans le livre de Steve MaguireÉcrire du code solide(fortement recommandé), il existe des exemples de choses de débogage que vous pouvez faire dans ces routines, comme:

  • Gardez une trace des allocations pour trouver des fuites
  • Allouez plus de mémoire que nécessaire et placez des marqueurs au début et à la fin de la mémoire - pendant la routine libre, vous pouvez vous assurer que ces marqueurs sont toujours là
  • memset la mémoire avec un marqueur sur l'allocation (pour trouver l'utilisation de la mémoire non initialisée) et sur libre (pour trouver l'utilisation de la mémoire libre)

Une autre bonne idée est dejamaisutiliser des choses commestrcpy, strcat, ousprintf- utilisez toujoursstrncpy, strncat, etsnprintf. Nous avons également écrit nos propres versions de ceux-ci, pour nous assurer de ne pas effacer la fin d'un tampon, et ceux-ci ont également rencontré de nombreux problèmes.

La source
Translate

Vous devez attaquer ce problème avec à la fois l'exécution et l'analyse statique.

Pour l'analyse statique, envisagez de compiler avec PREfast (cl.exe /analyze). Il détecte les incompatibilitésdeleteetdelete[], les dépassements de tampon et une foule d'autres problèmes. Soyez prêt, cependant, à parcourir plusieurs kilo-octets d'avertissement L6, surtout si votre projet a encoreL4pas fixe.

PREfast est disponible avec Visual Studio Team System et,Apparemment, dans le cadre du SDK Windows.

La source
Translate

Le caractère aléatoire apparent de la corruption de la mémoire ressemble beaucoup à un problème de synchronisation des threads - un bogue est reproduit en fonction de la vitesse de la machine. Si les objets (morceaux de mémoire) sont partagés entre les threads et que les primitives de synchronisation (section critique, mutex, sémaphore, autres) ne sont pas par classe (par objet, par classe), alors il est possible d'arriver à une situation où la classe (morceau de mémoire) est supprimée / libérée pendant son utilisation, ou utilisée après avoir été supprimée / libérée.

Pour tester cela, vous pouvez ajouter des primitives de synchronisation à chaque classe et méthode. Cela ralentira votre code car de nombreux objets devront attendre les uns les autres, mais si cela élimine la corruption du tas, votre problème de corruption du tas deviendra un problème d'optimisation du code.

La source
Translate

Est-ce dans des conditions de mémoire insuffisante? Si c'est le cas, il se peut que le nouveau retourneNULLplutôt que de lancer std :: bad_alloc. Plus âgéeVC++les compilateurs ne l'ont pas correctement implémenté. Il y a un article surÉchecs d'allocation de mémoire héritées'écraserSTLapplications conçues avecVC6.

La source
Translate

Vous avez essayé d'anciennes versions, mais y a-t-il une raison pour laquelle vous ne pouvez pas continuer à remonter plus loin dans l'historique du référentiel et voir exactement quand le bogue a été introduit?

Sinon, je suggérerais d'ajouter une simple journalisation pour aider à localiser le problème, même si je ne sais pas exactement ce que vous voudrez peut-être enregistrer.

Si vous pouvez découvrir ce qui PEUT exactement causer ce problème, via google et la documentation des exceptions que vous obtenez, cela vous donnera peut-être plus d'informations sur ce qu'il faut rechercher dans le code.

La source
Translate

Ma première action serait la suivante:

  1. Construisez les binaires en version "Release" mais en créant un fichier d'informations de débogage (vous trouverez cette possibilité dans les paramètres du projet).
  2. Utilisez Dr Watson comme débogueur par défaut (DrWtsn32 -I) sur une machine sur laquelle vous souhaitez reproduire le problème.
  3. Répétez le problème. Le Dr Watson produira une décharge qui pourrait être utile pour une analyse plus approfondie.

Un autre essai pourrait être d'utiliser WinDebug comme un outil de débogage qui est assez puissant et en même temps léger.

Peut-être que ces outils vous permettront au moins de limiter le problème à certains composants.

Et êtes-vous sûr que tous les composants du projet ont des paramètres de bibliothèque d'exécution corrects (onglet C / C ++, catégorie Génération de code dans les paramètres de projet VS 6.0)?

La source
Translate

Donc, à partir des informations limitées dont vous disposez, cela peut être une combinaison d'une ou plusieurs choses:

  • Mauvaise utilisation du tas, c'est-à-dire, double libération, lecture après libre, écriture après libre, définition de l'indicateur HEAP_NO_SERIALIZE avec allocs et libère de plusieurs threads sur le même tas
  • Mémoire insuffisante
  • Mauvais code (c'est-à-dire, débordements de tampon, sous-dépassement de tampon, etc.)
  • Problèmes de "timing"

S'il s'agit du tout des deux premiers mais pas du dernier, vous devriez l'avoir attrapé maintenant avec l'un ou l'autre pageheap.exe.

Ce qui signifie très probablement que cela est dû à la façon dont le code accède à la mémoire partagée. Malheureusement, le repérage de ce problème sera plutôt douloureux. L'accès non synchronisé à la mémoire partagée se manifeste souvent par d'étranges problèmes de «synchronisation». Des choses comme ne pas utiliser la sémantique d'acquisition / libération pour synchroniser l'accès à la mémoire partagée avec un indicateur, ne pas utiliser les verrous de manière appropriée, etc.

À tout le moins, il serait utile de pouvoir suivre les allocations d'une manière ou d'une autre, comme cela a été suggéré plus tôt. Au moins, vous pouvez voir ce qui s'est réellement passé jusqu'à la corruption du tas et tenter de diagnostiquer à partir de cela.

De plus, si vous pouvez facilement rediriger les allocations vers plusieurs tas, vous voudrez peut-être essayer cela pour voir si cela résout le problème ou entraîne un comportement de bogue plus reproductible.

Lorsque vous avez testé avec VS2008, avez-vous exécuté avec HeapVerifier avec Conserve Memory défini sur Oui? Cela peut réduire l'impact sur les performances de l'allocateur de tas. (De plus, vous devez exécuter avec Debug-> Démarrer avec Application Verifier, mais vous le savez peut-être déjà.)

Vous pouvez également essayer le débogage avec Windbg et diverses utilisations de la commande! Heap.

MSN

La source
Translate

Si vous choisissez de réécrire nouveau / supprimer, je l'ai fait et j'ai un code source simple à:

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

Cela détecte les fuites de mémoire et insère également des données de garde avant et après le bloc de mémoire pour capturer la corruption du tas. Vous pouvez simplement l'intégrer en mettant #include "debug.h" en haut de chaque fichier CPP, et en définissant DEBUG et DEBUG_MEM.

La source
Translate

La suggestion de Graeme de malloc personnalisé / gratuit est une bonne idée. Voyez si vous pouvez caractériser un modèle sur la corruption pour vous donner une idée de l'effet de levier.

Par exemple, s'il est toujours dans un bloc de la même taille (disons 64 octets), changez votre paire malloc / free pour toujours allouer des morceaux de 64 octets dans leur propre page. Lorsque vous libérez un bloc de 64 octets, définissez les bits de protection de la mémoire sur cette page pour empêcher les lectures et les wites (à l'aide de VirtualQuery). Ensuite, toute personne tentant d'accéder à cette mémoire générera une exception plutôt que de corrompre le tas.

Cela suppose que le nombre de morceaux de 64 octets en circulation n'est que modéré ou que vous avez beaucoup de mémoire à graver dans la boîte!

La source
Nicola Lee
Translate

Le peu de temps que j'avais pour résoudre un problème similaire. Si le problème persiste, je vous suggère de faire ceci: Surveillez tous les appels à new / delete et malloc / calloc / realloc / free. Je crée une seule DLL en exportant une fonction pour enregistrer tous les appels. Cette fonction reçoit le paramètre pour identifier la source de votre code, le pointeur vers la zone allouée et le type d'appel en enregistrant ces informations dans un tableau. Toute paire allouée / libérée est éliminée. À la fin ou après avoir besoin, vous appelez une autre fonction pour créer un rapport pour les données de gauche. Avec cela, vous pouvez identifier les faux appels (nouveaux / gratuits ou malloc / supprimer) ou manquants. Si un cas de tampon est écrasé dans votre code, les informations enregistrées peuvent être erronées, mais chaque test peut détecter / découvrir / inclure une solution de défaillance identifiée. De nombreux essais permettent d'identifier les erreurs. Bonne chance.

La source
Helen Lee
Translate

Pensez-vous que ce soit une condition de course? Plusieurs threads partagent-ils un tas? Pouvez-vous donner à chaque thread un tas privé avec HeapCreate, puis ils peuvent s'exécuter rapidement avec HEAP_NO_SERIALIZE. Sinon, un tas doit être thread-safe, si vous utilisez la version multithread des bibliothèques système.

La source
Ann Lee
Translate

Quelques suggestions. Vous mentionnez les nombreux avertissements à W4 - je suggérerais de prendre le temps de corriger votre code pour compiler proprement au niveau d'avertissement 4 - cela contribuera grandement à éviter les bogues subtils difficiles à trouver.

Deuxièmement - pour le commutateur / analyser - il génère en effet de nombreux avertissements. Pour utiliser ce commutateur dans mon propre projet, j'ai créé un nouveau fichier d'en-tête qui utilisait #pragma warning pour désactiver tous les avertissements supplémentaires générés par / analyser. Ensuite, plus bas dans le fichier, je n'active que les avertissements qui me tiennent à cœur. Utilisez ensuite le commutateur de compilateur / FI pour forcer ce fichier d'en-tête à être inclus en premier dans toutes vos unités de compilation. Cela devrait vous permettre d'utiliser le commutateur / analyser tout en contrôlant la sortie

La source