web-dev-qa-db-fra.com

Comment déboguer les erreurs de corruption de tas?

Je suis en train de déboguer une application C++ multi-thread (native) sous Visual Studio 2008. À des occasions apparemment aléatoires, le message d'erreur "Windows a déclenché un point d'arrêt ..." indique que cela peut être dû à une corruption du tas. Ces erreurs ne planteront pas toujours immédiatement l'application, même si elle risque de tomber en panne peu de temps après.

Le gros problème de ces erreurs est qu’elles ne surviennent que lorsque la corruption s’est réellement produite, ce qui les rend très difficiles à suivre et à déboguer, en particulier sur une application multithread.

  • Quel genre de choses peut causer ces erreurs?

  • Comment puis-je les déboguer?

Des astuces, des outils, des méthodes, des éclaircissements ... sont les bienvenus.

153
Beef

Application Verifier combiné avec Outils de débogage pour Windows est une configuration incroyable. Vous pouvez obtenir les deux dans le cadre du Kit de pilotes Windows ou du Kit de développement logiciel (SDK) Windows plus léger . (Découverte du vérificateur d'applications lors de la recherche d'une question précédente sur un problème de corruption de tas .) J'ai déjà utilisé BoundsChecker et Insure ++ (mentionné dans d'autres réponses), bien que je sois surpris de la quantité de fonctionnalités de l'application. Vérifieur.

Electric Fence (ou "efence"), dmalloc , valgrind , etc., méritent d’être mentionnés, mais la plupart d’entre eux sont beaucoup plus faciles à exécuter que Windows. Valgrind est ridiculement flexible: j'ai débogué un logiciel de serveur volumineux avec de nombreux problèmes de tas l'utilisant.

Lorsque tout le reste échoue, vous pouvez fournir à votre propre opérateur mondial les surcharges new/delete et malloc/calloc/realloc - la façon de le faire varie un peu en fonction du compilateur et de la plate-forme - ce qui représente un investissement minime - mais cela peut porter ses fruits à long terme. La liste des fonctionnalités souhaitables devrait sembler familière chez dmalloc et electricfence, et le livre étonnamment excellent Writing Solid Code :

  • sentry values ​​: prévoyez un peu plus d'espace avant et après chaque allocation, dans le respect des exigences d'alignement maximales; remplir avec des nombres magiques (aide à attraper les débordements et les débordements de tampon, et le pointeur "sauvage" occasionnel)
  • alloc fill: remplit les nouvelles allocations avec une valeur magique non-0 - Visual C++ le fera déjà pour vous dans les versions Debug (aide à intercepter l'utilisation de vars non initialisés)
  • free fill: remplit la mémoire libérée avec une valeur magique non-0, conçue pour déclencher un segfault s'il est déréférencé dans la plupart des cas (aide à détecter les pointeurs qui pendent)
  • delay free: ne restaurez pas la mémoire libérée pendant un moment, laissez-la libre, mais elle ne sera pas disponible (aide à attraper plus de pointeurs en suspens, près de doubles libérations)
  • tracking: il peut parfois être utile de pouvoir enregistrer où une allocation a été faite.

Notez que dans notre système homebrew local (pour une cible intégrée), le suivi est séparé de la plupart des autres éléments, car la surcharge d’exécution est beaucoup plus importante.


Si vous souhaitez davantage de raisons de surcharger ces fonctions/opérateurs d'allocation, jetez un coup d'œil à ma réponse à "Toute raison de surcharger l'opérateur global new et delete?" ; En dehors de son auto-promotion éhontée, elle répertorie d'autres techniques utiles pour le suivi des erreurs de corruption de tas, ainsi que d'autres outils applicables.

122
leander

Vous pouvez détecter un grand nombre de problèmes de corruption de segment en activant Page Heap pour votre application. Pour ce faire, vous devez utiliser le fichier gflags.exe fourni avec Outils de débogage pour Windows

Exécutez Gflags.exe et dans les options du fichier image de votre exécutable, cochez la case "Activer le tas de page".

Maintenant, redémarrez votre fichier EXE et attachez-le à un débogueur. Avec Page Heap activé, l’application entrera dans le débogueur chaque fois qu’une corruption de tas se produit.

34
Canopus
13
Vijay

Pour vraiment ralentir le travail et effectuer beaucoup de vérifications à l'exécution, essayez d'ajouter ce qui suit en haut de votre main() ou équivalent dans Microsoft Visual Studio C++.

_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CRTDBG_CHECK_ALWAYS_DF );
12
Dave Van Wagner

Un conseil rapide que je viens de Détecter l’accès à la mémoire libérée est le suivant:

Si vous voulez localiser l'erreur rapidement, sans vérifier chaque déclaration qui accède à la mémoire bloc, vous pouvez définir le pointeur de la mémoire à une valeur invalide après avoir libéré le bloc:

#ifdef _DEBUG // detect the access to freed memory
#undef free
#define free(p) _free_dbg(p, _NORMAL_BLOCK); *(int*)&p = 0x666;
#endif
8
StackedCrooked

Quel genre de choses peut causer ces erreurs?

Faire des vilaines choses avec de la mémoire, par exemple l'écriture après la fin d'un tampon ou l'écriture dans un tampon après sa libération dans le tas.

Comment puis-je les déboguer?

Utilisez un instrument qui ajoute une vérification automatique des limites à votre exécutable: par exemple, valgrind sous Unix, ou un outil tel que BoundsChecker (Wikipédia suggère également Purify et Insure ++) sous Windows.

Attention, ils ralentiront votre application. Ils risquent donc d'être inutilisables si votre application est une application temps réel.

Un autre outil/outil de débogage possible pourrait être HeapAgent de MicroQuill.

8
ChrisW

Le meilleur outil que j'ai trouvé utile et qui a fonctionné à chaque fois est la révision de code (avec de bons relecteurs de code).

Autre que la révision de code, je voudrais d’abord essayer Page Heap . Page Heap prend quelques secondes à mettre en place et, avec un peu de chance, il pourrait localiser votre problème.

Si vous n’avez pas de chance avec Page Heap, téléchargez Outils de débogage pour Windows chez Microsoft et apprenez à utiliser WinDbg. Désolé, je ne pourrais vous donner une aide plus spécifique, mais le débogage de la corruption de segments multithreads est plus un art que la science. Google pour "corruption de tas WinDbg" et vous devriez trouver de nombreux articles sur le sujet. 

5
Shing Yip

Vous souhaiterez peut-être également vérifier si vous établissez une liaison avec la bibliothèque d'exécution C dynamique ou statique. Si vos fichiers DLL sont liés à la bibliothèque d'exécution C statique, les fichiers DLL ont des segments distincts.

Par conséquent, si vous deviez créer un objet dans une DLL et essayer de le libérer dans une autre DLL, vous obtiendrez le même message que celui affiché ci-dessus. Ce problème est référencé dans une autre question de débordement de pile, Libération de la mémoire allouée dans un autre DLL.

4
dreadpirateryan

Si ces erreurs se produisent de manière aléatoire, la probabilité que vous rencontriez des courses de données est élevée. Veuillez vérifier: modifiez-vous les pointeurs de mémoire partagée de différents threads? Intel Thread Checker peut aider à détecter de tels problèmes dans les programmes multithread.

3
Vladimir Obrizan

Quel type de fonctions d'allocation utilisez-vous? J'ai récemment rencontré une erreur similaire en utilisant les fonctions d'allocation de style Heap *. 

Il s'est avéré que je créais par erreur le tas avec l'option HEAP_NO_SERIALIZE. Cela permet essentiellement aux fonctions de tas de s'exécuter sans sécurité des threads. Il s'agit d'une amélioration des performances s'il est utilisé correctement mais ne devrait jamais l'être si vous utilisez HeapAlloc dans un programme multithread [1]. Je mentionne cela uniquement parce que votre article mentionne que vous avez une application multithread. Si vous utilisez HEAP_NO_SERIALIZE n’importe où, supprimez-le et cela résoudra probablement votre problème. 

[1] Il existe certaines situations où cela est légal, mais cela nécessite de sérialiser les appels vers Heap * et n'est généralement pas le cas pour les programmes multithreads. 

3
JaredPar

En plus de rechercher des outils, envisagez de rechercher un coupable. Y a-t-il un composant que vous utilisez, peut-être pas écrit par vous, qui n'a peut-être pas été conçu ni testé pour fonctionner dans un environnement multithread? Ou simplement celui que vous ne savez pas a fonctionné dans un tel environnement.

La dernière fois que cela m'est arrivé, il s'agissait d'un package natif qui avait été utilisé avec succès depuis des années dans le traitement par lots. Mais c’était la première fois dans cette entreprise qu’elle était utilisée à partir d’un service Web .NET (multithread). C'était tout - ils avaient menti sur le fait que le code était thread-safe.

1
John Saunders

Vous pouvez utiliser les macros VC CRT Heap-Check pour _ _ CrtSetDbgFlag : _ CRTDBG_CHECK_ALWAYS_DF ou _ CRTDBG_CHECK_EVERY_16_DF .. _ CRTDBG_CHECK_EVERY_1024_DF .

0
KindDragon

J'ai eu un problème similaire - et il est apparu assez au hasard. Quelque chose était peut-être corrompu dans les fichiers de construction, mais j'ai fini par le réparer en nettoyant d'abord le projet, puis en le reconstruisant.

Donc, en plus des autres réponses données:

Quel genre de choses peut causer ces erreurs? Quelque chose est corrompu dans le fichier de construction.

Comment puis-je les déboguer? Nettoyage du projet et reconstruction. Si c'est résolu, c'était probablement le problème.

0
Marty

J'aimerais ajouter mon expérience. Au cours des derniers jours, j'ai résolu une instance de cette erreur dans mon application. Dans mon cas particulier, les erreurs dans le code étaient les suivantes:

  • Supprimer des éléments d'une collection STL en effectuant une itération dessus (je pense qu'il existe des indicateurs de débogage dans Visual Studio pour détecter ces problèmes; je les ai détectés lors de la révision du code)
  • Celui-ci est plus complexe, je vais le diviser en étapes:
    • A partir d'un thread C++ natif, rappelez le code managé
    • Dans les terres gérées, appelez Control.Invoke et disposez d'un objet géré qui enveloppe l'objet natif auquel le rappel appartient.
    • Puisque l'objet est toujours vivant dans le thread natif (il restera bloqué dans l'appel de rappel jusqu'à la fin de Control.Invoke). Je devrais préciser que j'utilise boost::thread, alors j'utilise une fonction membre comme fonction de thread.
    • Solution : Utilisez Control.BeginInvoke (mon interface graphique est créée avec Winforms) afin que le thread natif puisse se terminer avant la destruction de l'objet (le rappel a précisément pour but de notifier que le thread s'est terminé et que l'objet peut être détruit).
0
dario_ramos