corruption Heap sous Win32; comment localiser?

voix
54

Je travaille sur une multithread application C ++ qui corrompt le tas. Les outils habituels pour localiser cette corruption semblent être inapplicable. Old construit (18 mois) du code source présentent le même comportement que la version la plus récente, donc cela a été autour depuis longtemps et juste n'a pas été remarqué; à la baisse, deltas source ne peuvent pas être utilisées pour identifier lors de l'introduction du bug - il y a beaucoup de changements de code dans le référentiel.

L'invite pour écraser Coportement est de générer un débit dans ce système - transfert de prise de données qui est munged en une représentation interne. J'ai un ensemble de données de test qui entraînent périodiquement des l'application d'une exception (divers endroits, des causes diverses - y compris défaut alloc en tas, donc: la corruption du tas).

Le comportement semble lié à la puissance du processeur ou la bande passante de la mémoire; plus de chaque machine a, plus il est facile de tomber en panne. La désactivation d'un noyau hyper-threading ou un noyau dual-core réduit le taux de (mais ne l'élimine pas) la corruption. Cela suggère un problème lié à la synchronisation.

Maintenant , voici le hic:
Quand il est exécuté dans un environnement de débogage léger ( par exemple Visual Studio 98 / AKA MSVC6) la corruption du tas est relativement facile à reproduire - dix ou quinze minutes passent avant que quelque chose ne effroyablement et exceptions, comme un alloc;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é mémoire vitesse et ne tombe pas en panne (lié mémoire-: CPU ne reçoit pas au- dessus 50%, la lumière du disque n'est pas, le programme va aussi vite qu'il peut, boîte consommer 1.3Gde 2G de RAM) . Donc, j'ai un choix entre être capable de reproduire le problème (mais pas identifier la cause) ou d' être en mesure de idenify la cause ou un problème que je ne peux pas reproduire.

Mes meilleures estimations actuelles quant à l'endroit où la prochaine est la suivante:

  1. Obtenir une boîte grunty insensément (pour remplacer le bloc courant de dev: 2 Go de RAM dans un E6550 Core2 Duo); cela permettra de repro l'accident provoquant une mauvaise conduite lors de l' exécution dans un environnement de débogage puissant; ou
  2. Réécrire opérateurs newet deleteà utiliser VirtualAllocet VirtualProtectpour marquer la mémoire en lecture seule dès qu'il est fait avec. Exécuter sous MSVC6et ont le système d' exploitation attraper le mauvais gars qui écrit à la mémoire libérée. Oui, cela est un signe de désespoir: qui les réécritures de l' enfer newet delete?! Je me demande si cela va le faire aussi lentement que sous Purifier et al.

Et, non: Livraison avec l'instrumentation Purify construite en est pas une option.

Un collègue a juste marché passé et a demandé « Stack Overflow? Obtenons-nous pile déborde maintenant!? »

Et maintenant, la question suivante : Comment puis-je trouver le corrupteur tas?


Mise à jour: équilibre new[]et delete[]semble avoir obtenu un long chemin vers la résolution du problème. Au lieu de 15 minutes, l'application va maintenant environ deux heures avant de s'écraser. Pas encore là. Toutes les autres suggestions? La corruption du tas persiste.

Mise à jour: une version de version sous Visual Studio 2008 semble nettement mieux; la suspicion actuelle repose sur la STLmise en œuvre qui est livré avec VS98.


  1. Reproduisez le problème. Dr Watsonproduira une décharge qui pourrait être utile dans l' analyse plus loin.

Je vais prendre note de cela, mais je crains que le Dr Watson ne sera déclenché après le fait, pas quand le tas est se piétiné.

Un autre essai peut - être utiliser WinDebugcomme un outil de débogage qui est assez puissant étant en même temps aussi léger.

Vous avez qu'aller au moment, encore une fois: pas beaucoup d'aide jusqu'à ce que quelque chose va mal. Je veux attraper le vandale dans la loi.

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

Je ne tiens pas beaucoup d'espoir, mais les temps désespérés appellent des ...

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

Non, je ne suis pas, et je vais passer quelques heures demain en passant par l'espace de travail (58 projets dans ce) et de vérifier qu'ils sont tous la compilation et la liaison avec les drapeaux appropriés.


Mise à jour: Cela a pris 30 secondes. Sélectionnez tous les projets dans la Settingsboîte de dialogue, décochez jusqu'à ce que vous trouvez le projet (s) qui n'ont pas les bons réglages (ils avaient tous les bons réglages).

Créé 04/08/2008 à 08:30
source utilisateur
Dans d'autres langues...                            


15 réponses

voix
0

Vous avez essayé vieux builds, mais est-il une raison que vous ne pouvez pas continuer à aller plus loin dans l'histoire du référentiel et de voir exactement quand le bug a été introduit?

Dans le cas contraire, je vous suggère d'ajouter la journalisation simple une sorte pour aider à traquer le problème, même si je suis à une perte de ce que vous pourriez spécifiquement vouloir vous connecter.

Si vous ne pouvez savoir ce qui peut exactement causer ce problème, via Google et la documentation des exceptions que vous obtenez, peut-être cela donnera un aperçu plus loin sur ce qu'il faut rechercher dans le code.

Créé 04/08/2008 à 08:48
source utilisateur

voix
26

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

Réécriture nouvelle et supprimer peut être utile, mais qui ne se coince pas les allocs commises par le code de niveau inférieur. Si c'est ce que vous voulez, mieux Detour les low-level alloc APIs en utilisant Microsoft Detours.

Aussi vérifications de bonne santé tels que: vérifier vos bibliothèques d'exécution de match (version vs débogage, lib multi-thread par rapport à monothread, dll par rapport statique), rechercher des mauvaises suppressions (par exemple, supprimer ou supprimer [] aurait dû être utilisé), assurez-vous de ne pas mélanger et correspondant à vos allocs.

Essayez aussi tourner sélectivement des fils et voir quand / si le problème disparaît.

Qu'est-ce que la pile d'appels, etc ressembler au moment de la première exception?

Créé 04/08/2008 à 08:51
source utilisateur

voix
0

serait mon première action suit:

  1. Construire les binaires dans la version « Release », mais la création du fichier info de débogage (vous trouverez cette possibilité dans les paramètres du projet).
  2. Utilisez le Dr Watson comme débogueur defualt (drwtsn32 -I) sur une machine sur laquelle vous souhaitez reproduire le problème.
  3. Repdroduce le problème. Dr Watson produira une décharge qui pourrait être utile dans l'analyse plus loin.

Un autre essai peut-être utiliser WinDebug comme outil de débogage qui est assez puissant étant en même temps aussi léger.

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

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

Créé 04/08/2008 à 09:26
source utilisateur

voix
11

J'ai même des problèmes dans mon travail (nous utilisons aussi VC6parfois). Et il n'y a pas de solution facile pour elle. J'ai seulement quelques conseils:

  • Essayez avec plantage automatique des décharges sur la machine de production (voir processus Dumper ). Mon expérience , explique le Dr Watson est pas parfait pour le dumping.
  • Retirer toutes les prises (...) à partir de votre code. Ils se cachent souvent des exceptions de mémoire graves.
  • Vérifiez Advanced Windows Debugging - il y a beaucoup de bons conseils pour des problèmes tels que le vôtre. Je recommande de tout mon cœur.
  • Si vous utilisez STLessayer STLPortet construit vérifié. Invalide iterator sont l' enfer.

Bonne chance. Des problèmes comme le vôtre nous prennent des mois à résoudre. Soyez prêt pour cette ...

Créé 06/08/2008 à 13:41
source utilisateur

voix
1

Ainsi, les informations limitées dont vous avez, cela peut être une combinaison d'une ou plusieurs choses:

  • Bad utilisation du tas, par exemple, double libération, après lecture libre, écrire après gratuit, la mise en drapeau HEAP_NO_SERIALIZE avec allocs et libère de plusieurs threads sur le même tas
  • Mémoire insuffisante
  • du mauvais code (par exemple, les dépassements de tampon, sousverses tampons, etc.)
  • questions « Timing »

Si c'est du tout les deux premiers, mais pas la dernière, vous devriez avoir attrapé maintenant avec soit Pageheap.exe.

Ce qui signifie plus il est probable en raison de la façon dont le code accède à la mémoire partagée. Malheureusement, le suivi que vers le bas va être assez pénible. accès non synchronisé à mémoire partagée se manifeste souvent étranges problèmes de « timing ». Des choses comme ne pas utiliser acquisition / sémantique de libération pour synchroniser l'accès à la mémoire partagée avec un drapeau, ne pas utiliser de façon appropriée les serrures, etc.

À tout le moins, il serait utile d'être en mesure de suivre les allocations en quelque sorte, comme cela a été suggéré plus tôt. Au moins, vous pouvez voir ce qui est arrivé jusqu'à la corruption du tas et de tenter de diagnostiquer de cela.

En outre, si vous pouvez facilement rediriger les allocations à plusieurs tas, vous voudrez peut-être essayer de voir si cela soit résout le problème ou les résultats dans le comportement buggy plus reproductible.

Lorsque vous testez avec VS2008, avez-vous couru avec HeapVerifier avec mémoire Conserve réglé sur Oui? Cela pourrait réduire l'impact sur les performances de l'allocateur de tas. (De plus, vous devez courir avec Debug-> avec l'application Verifier, mais vous savez peut-être que.)

Vous pouvez également essayer de débogage avec WinDbg et diverses utilisations de la commande tas!.

MSN

Créé 22/08/2008 à 17:51
source utilisateur

voix
7

Nous avons eu assez bonne chance en écrivant nos propres fonctions malloc et libres. Dans la production, ils appellent tout simplement la norme malloc et libre, mais en debug, ils peuvent faire ce que vous voulez. Nous avons aussi une simple classe de base qui ne fait rien, mais remplaçons la nouvelle et supprimer les opérateurs d'utiliser ces fonctions, toute la classe que vous écrivez peut simplement hériter de cette classe. Si vous avez une tonne de code, il peut être un gros travail pour remplacer les appels à malloc et libre à la nouvelle malloc et libre (ne pas oublier realloc!), Mais à long terme, il est très utile.

Dans le livre de Steve Maguire Écrire Solid Code (fortement recommandé), il y a des exemples de choses de débogage que vous pouvez faire dans ces routines, comme:

  • Gardez une trace des allocations pour trouver des fuites
  • Allouer plus de mémoire que nécessaire et mettre des marqueurs au début et à la fin de la mémoire - au cours de la routine libre, vous pouvez vous assurer 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 libre (pour trouver l'utilisation de la mémoire free'd)

Une autre bonne idée est de ne jamais utiliser des choses comme strcpy, strcatou sprintf- toujours utiliser strncpy, strncatet snprintf. Nous avons écrit nos propres versions de ces derniers aussi bien, pour nous assurer que nous ne rédigeons pas l'extrémité d'un tampon, et ceux - ci ont pris beaucoup de problèmes aussi.

Créé 22/08/2008 à 18:11
source utilisateur

voix
3

Le caractère apparemment aléatoire de la corruption de mémoire ressemble beaucoup à un problème de synchronisation de fil - un bug est reproduit en fonction de la vitesse de la machine. Si des objets (chuncks de mémoire) sont partagées entre les threads et la synchronisation (section critique, mutex, sémaphores, autres) primitives ne sont pas sur la base, il est possible par classe (par objet, par classe) pour arriver à une situation où la classe (partie de la mémoire) est supprimé / libéré lors de l'utilisation, ou utilisé après suppression / libérée.

En guise de test pour cela, vous pouvez ajouter des primitives de synchronisation à chaque classe et de méthode. Cela rendra votre code plus lent parce que de nombreux objets devront attendre les uns des autres, mais si cela élimine la corruption du tas, votre problème tas-corruption deviendra une optimisation de code un.

Créé 25/08/2008 à 20:55
source utilisateur

voix
0

La suggestion de Graeme de malloc personnalisée / gratuit est une bonne idée. Voyez si vous pouvez caractériser un certain modèle de la corruption pour vous donner une poignée pour tirer parti.

Par exemple, si elle est toujours dans un bloc de la même taille (disons 64 octets) puis changez votre malloc / libre paire pour toujours allouer 64 morceaux d'octets dans leur propre page. Lorsque vous libérez un morceau de 64 octets puis définissez les bits de protection de la mémoire sur cette page pour éviter les lectures et wites (en utilisant VirtualQuery). Ensuite, toute personne tentant d'accéder à cette mémoire génère une exception plutôt que corrompre le tas.

Cela ne suppose que le nombre de morceaux en cours 64 octets est modérée ou si vous avez beaucoup de mémoire pour graver dans la boîte!

Créé 02/09/2008 à 05:23
source utilisateur

voix
3

Est - ce dans des conditions de faible mémoire? Dans ce cas , il pourrait être cette nouvelle est de retour NULLplutôt que de jeter std :: bad_alloc. Anciens VC++compilateurs ne mettent pas en œuvre correctement cela. Il y a un article sur les échecs d'allocation de mémoire hérités s'écraser des STLapplications construites avec VC6.

Créé 02/09/2008 à 07:03
source utilisateur

voix
8

Exécutez l'application originale avec ADplus -crash -pn appnename.exe Lorsque la question de la mémoire vous apparaît-up obtiendrez une belle grande décharge.

Vous pouvez analyser la décharge pour comprendre ce que l' emplacement mémoire a été corrompu. Si vous êtes chanceux la mémoire Ecraser est une chaîne unique que vous pouvez savoir où il vient. Si vous n'êtes pas chanceux, vous aurez besoin de creuser en win32tas et comprendre ce que sont les caractéristiques de mémoire Orignal. (tas -x pourrait aider)

Une fois que vous savez ce qui a été sali-up, vous pouvez restreindre l' utilisation AppVerifier avec les paramètres de tas spéciaux. à- dire que vous pouvez spécifier ce que DLLvous surveillez, ou quelle taille allocation à surveiller.

Espérons que cela speedup suffisamment de surveillance pour attraper le coupable.

Dans mon expérience, je ne avais besoin en mode plein verifier tas, mais j'ai passé beaucoup de temps à analyser le vidage sur incident (s) et les sources de navigation.

PS: Vous pouvez utiliser DebugDiag pour analyser les décharges. Il peut indiquer le DLLpropriétaire du tas corrompu, et vous donner d' autres détails utiles.

Créé 16/09/2008 à 08:33
source utilisateur

voix
1

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

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

Cela attire 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 intégrer en mettant #include « debug.h » en haut de chaque fichier RPC, et la définition DEBUG et DEBUG_MEM.

Créé 17/09/2008 à 14:40
source utilisateur

voix
4

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

Pour une analyse statique envisager la compilation avec PREfast ( cl.exe /analyze). Il détecte dépareillées deleteet delete[], les dépassements de mémoire tampon et une foule d'autres problèmes. Soyez prêt, cependant, à patauger dans de nombreux kilo - octets d'avertissement L6, surtout si votre projet n'a toujours L4pas fixé.

PREfast est disponible avec le système de Visual Studio Team et, apparemment , dans le cadre de Windows SDK.

Créé 12/10/2008 à 22:55
source utilisateur

voix
0

Le peu de temps que je devais résoudre un problème similaire. Si le problème persiste, je vous suggère de le faire: surveiller tous les appels à nouveau / supprimer et malloc / calloc / realloc / gratuit. Je fais seul DLL exporter une fonction pour enregistrer tous les appels. Cette fonction de réception paramètre pour identifier votre code source, pointeur vers la zone allouée et le type d'appel d'économie ces informations dans un tableau. Tout paire alloué / libéré est éliminé. A la fin ou après que vous avez besoin de faire un appel à une autre fonction pour créer un rapport pour les données de gauche. Avec cela, vous pouvez identifier les appels mal (nouveau / gratuit ou malloc / supprimer) ou manquants. Si de tout cas de tampon écrasé dans votre code les informations enregistrées peuvent être faux, mais chaque test peut détecter / découvrir / inclure une solution de défaillance identifiée. De nombreuses pistes pour aider à identifier les erreurs. Bonne chance.

Créé 19/12/2008 à 12:52
source utilisateur

voix
0

Pensez-vous que cela est une condition de course? Sont plusieurs threads partagent un tas? Pouvez-vous donner à chaque fil un tas privé avec HeapCreate, ils peuvent courir vite avec HEAP_NO_SERIALIZE. Dans le cas contraire, un tas doit être thread-safe, si vous utilisez la version multi-thread des bibliothèques système.

Créé 30/07/2009 à 14:48
source utilisateur

voix
0

Quelques suggestions. Vous mentionnez les mises en garde copieuses à W4 - je suggère de prendre le temps de corriger votre code pour compiler proprement au niveau d'alerte 4 - cela ira un long chemin à la prévention subtile difficile de trouver des bogues.

Deuxièmement - pour le / analyser l'interrupteur - il ne génère en effet des avertissements copieuses. Pour utiliser ce commutateur dans mon propre projet, ce que je faisais était de créer un nouveau fichier d'en-tête qui a utilisé #pragma avertissement pour désactiver tous les avertissements supplémentaires générés par / analyser. Ensuite, plus bas dans le fichier, je me tourne uniquement sur les avertissements auxquels je tiens. Ensuite, utilisez le / FI commutateur de compilateur pour forcer ce fichier d'en-tête à inclure d'abord dans toutes vos unités de compilation. Cela devrait vous permettre d'utiliser le / analyser commutateur pendant la sortie contrôlant

Créé 03/10/2009 à 17:48
source utilisateur

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more