65 votes

Corruption de tas sous Win32 ; comment la localiser ?

Je travaille sur un multithreading Application C++ qui corrompt le tas. Les outils habituels pour localiser cette corruption semblent être inapplicables. Les anciennes versions (18 mois) du code source présentent le même comportement que la version la plus récente, ce qui signifie que le problème existe depuis longtemps et qu'il n'a tout simplement pas été remarqué. beaucoup des changements de code dans le référentiel.

L'incitation au comportement de plantage est de générer un débit dans ce système - le transfert de données par socket qui est transformé en une représentation interne. Je dispose d'un ensemble de données de test qui provoquent périodiquement des exceptions dans l'application (à différents endroits, pour différentes raisons - y compris l'échec de l'allocation du tas, donc la corruption du tas).

Ce comportement semble lié à la puissance du processeur ou à la bande passante de la mémoire ; plus la machine dispose de l'un ou l'autre de ces éléments, plus il est facile de se planter. La désactivation d'un cœur hyperthreading ou d'un cœur double réduit le taux de corruption (mais ne l'élimine pas). Cela suggère un problème lié au timing.

Maintenant, voici le problème :
Lorsqu'il est exécuté sous un environnement de débogage léger (par exemple Visual Studio 98 / AKA MSVC6 ), la corruption du tas est raisonnablement facile à reproduire - dix ou quinze minutes s'écoulent avant que quelque chose n'échoue de façon épouvantable et que des exceptions, telles que le message alloc; lors de l'exécution sous un environnement de débogage sophistiqué (Rational Purify, VS2008/MSVC9 ou même Microsoft Application Verifier), le système devient limité par la vitesse de la mémoire et ne se bloque pas (Limité par la mémoire : Le CPU ne dépasse pas 50% La lumière du disque n'est pas allumée, le programme va aussi vite qu'il peut, la boîte consomme 1.3G de 2G de RAM). Donc, J'ai le choix entre être capable de reproduire le problème (mais pas d'en identifier la cause) ou être capable d'en identifier la cause ou un problème que je ne peux pas reproduire.

Mes meilleures suppositions actuelles quant à la prochaine étape sont :

  1. Obtenir une boîte follement grunty (pour remplacer la boîte de dev actuelle : 2Gb RAM dans un E6550 Core2 Duo ) ; cela permettra de reproduire le comportement erroné à l'origine du crash lors de l'exécution dans un environnement de débogage puissant ; ou bien
  2. Opérateurs de réécriture new y delete à utiliser VirtualAlloc y VirtualProtect pour marquer la mémoire comme étant en lecture seule dès qu'elle est utilisée. Exécuter sous MSVC6 et que le système d'exploitation attrape le méchant qui écrit dans la mémoire libérée. Oui, c'est un signe de désespoir : qui réécrit dans l'enfer ? new y delete ? ! 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 vient de passer et demande "Stack Overflow ? On a des débordements de pile maintenant ? !?"

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


Mise à jour : équilibrage new[] y delete[] semble avoir fait un grand pas vers la résolution du problème. Au lieu de 15 minutes, l'application dure maintenant environ deux heures avant de planter. On n'en est pas encore là. D'autres suggestions ? La corruption du tas persiste.

Mise à jour : une version construite sous Visual Studio 2008 semble nettement meilleure ; les soupçons actuels reposent sur la STL qui est livrée avec VS98 .


  1. Reproduisez le problème. Dr Watson produira un vidage qui pourrait être utile pour une analyse ultérieure.

J'en prends note, mais je crains que le Dr Watson ne soit pris au dépourvu qu'après coup, et non au moment où le tas de ferraille est piétiné.

Un autre essai pourrait être d'utiliser WinDebug comme outil de débogage, qui est assez puissant tout en étant léger.

J'ai ça en ce moment, encore une fois : pas très utile jusqu'à ce que quelque chose se passe mal. Je veux attraper le vandale sur le fait.

Peut-être ces outils vous permettront-ils au moins de circonscrire 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 compilés et liés avec les drapeaux appropriés.


Mise à jour : Cela a pris 30 secondes. Sélectionnez tous les projets dans le Settings désélectionnez jusqu'à ce que vous trouviez le ou les projets qui n'ont pas les bons paramètres (ils avaient tous les bons paramètres).

29voto

James Devlin Points 6699

Mon premier choix serait un outil dédié au tas de données tel que pageheap.exe .

Réécrire new et delete pourrait être utile, mais cela ne permet pas d'attraper les allocations effectuées par du code de niveau inférieur. Si c'est ce que vous voulez, il est préférable de détourner la méthode low-level alloc API en utilisant Microsoft Detours.

Vérifiez également que vos bibliothèques d'exécution correspondent (release vs. debug, multi-threaded vs. single-threaded, dll vs. static lib), recherchez les mauvaises suppressions (par exemple, delete là où delete [] aurait dû être utilisé), assurez-vous que vous ne mélangez pas vos allocs.

Essayez également de désactiver sélectivement des fils de discussion et voyez si le problème disparaît.

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

10voto

Michal Sznajder Points 6292

J'ai les mêmes problèmes dans mon travail (nous utilisons également des VC6 parfois). Et il n'y a pas de solution facile pour cela. Je n'ai que quelques conseils à vous donner :

  • Essayez avec des vidages automatiques de crash sur la machine de production (voir Process Dumper ). D'après mon expérience, le Dr Watson est pas parfait pour le déversement.
  • Supprimer tout catch(...) de votre code. Ils cachent souvent de graves exceptions de mémoire.
  • Vérifiez Débogage avancé de Windows - il y a beaucoup de bons conseils pour des problèmes comme le vôtre. Je le recommande de tout cœur.
  • Si vous utilisez STL essayez STLPort et vérifié les constructions. Les itérateurs invalides sont un enfer.

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

8voto

Graeme Perrow Points 22249

Nous avons eu de la chance en écrivant nos propres fonctions malloc et free. En production, elles appellent simplement les fonctions standard malloc et free, mais en débogage, elles peuvent faire ce que vous voulez. Nous avons également une simple classe de base qui ne fait rien d'autre que de surcharger les opérateurs new et delete pour utiliser ces fonctions, puis 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 free par les nouveaux malloc et free (n'oubliez pas realloc !), mais à long terme, c'est très utile.

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

  • Suivre les allocations pour trouver les 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 de libération, 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 la libération (pour trouver l'utilisation de la mémoire libérée).

Une autre bonne idée est de jamais utiliser des choses comme strcpy , strcat ou sprintf -- toujours utiliser strncpy , strncat et snprintf . Nous avons également écrit nos propres versions de ces outils, afin de nous assurer que nous n'écrivons pas à la fin d'un tampon, et celles-ci ont permis de résoudre de nombreux problèmes.

8voto

Tal Points 617

Exécutez l'application originale avec ADplus -crash -pn appnename.exe Quand le problème de mémoire apparaîtra, vous aurez une bonne grosse décharge.

Vous pouvez analyser le vidage pour déterminer quel emplacement de la mémoire a été corrompu. Si vous avez de la chance, la mémoire d'écrasement est une chaîne unique dont vous pouvez déterminer l'origine. Si vous n'avez pas de chance, vous devrez creuser dans la section win32 et déterminer quelles étaient les caractéristiques de la mémoire originale. (heap -x peut aider)

Une fois que vous savez ce qui s'est passé, vous pouvez restreindre l'utilisation de l'appverificateur avec des paramètres de tas spéciaux, c'est-à-dire que vous pouvez spécifier ce que l'appverificateur doit faire. DLL que vous surveillez, ou la taille de l'allocation à surveiller.

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

Dans 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) dump(s) de crash et à parcourir les sources.

P.S : Vous pouvez utiliser DebugDiag pour analyser les vidanges. Il peut mettre en évidence DLL possédant le tas corrompu, et vous donner d'autres détails utiles.

4voto

Constantin Points 12185

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

Pour l'analyse statique, envisagez de compiler avec PREfast ( cl.exe /analyze ). Il détecte les delete y delete[] les dépassements de tampon et une foule d'autres problèmes. Préparez-vous cependant à parcourir de nombreux kilo-octets d'avertissement L6, surtout si votre projet a encore des problèmes de sécurité. L4 non fixé.

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X