web-dev-qa-db-fra.com

Le programme ne plante que lors de la création de la version - comment déboguer?

J'ai un problème de type "Schroedinger's Cat" - mon programme (en fait, la suite de tests de mon programme, mais un programme tout de même) se bloque, mais uniquement lorsqu'il est intégré au mode de publication et uniquement lorsqu'il est lancé à partir de la ligne de commande . Par le biais du débogage des hommes des cavernes (c’est-à-dire de mauvais messages printf () partout), j’ai déterminé la méthode de test où le code plante, mais malheureusement, le crash semble se produire dans un destructeur, car les derniers messages de trace que je vois sont dans d'autres destructeurs qui s'exécutent proprement.

Lorsque je tente d'exécuter ce programme dans Visual Studio, il ne se bloque pas. Il en va de même lors du lancement à partir de WinDbg.exe. Le crash ne survient que lors du lancement à partir de la ligne de commande. Cela se produit sous Windows Vista, d'ailleurs, et malheureusement, je n'ai pas accès à un ordinateur XP pour le test.

Ce serait vraiment bien si je pouvais demander à Windows d’imprimer une trace de pile ou quelque chose autre que de simplement terminer le programme comme s’il était sorti proprement. Quelqu'un at-il des conseils sur la façon dont je pourrais obtenir des informations plus utiles ici et, espérons-le, corriger ce bogue?

Edit: Le problème est en effet causé par un tableau hors limites, que je décris plus en détail dans cet article . Merci à tous pour votre aide dans la recherche de ce problème!

83
Nik Reiman

Dans 100% des cas que j'ai vus ou dont j'ai entendu parler, lorsqu'un programme C ou C++ s'exécutait correctement dans le débogueur mais échouait lorsqu'il était exécuté à l'extérieur, la cause était en train d'écrire après la fin d'un tableau local de fonctions. (Le débogueur met plus sur la pile, de sorte que vous êtes moins susceptible d'écraser quelque chose d'important.)

117
James Curran

Quand j’ai rencontré des problèmes comme celui-ci auparavant, c’était généralement dû à l’initialisation des variables. En mode débogage, les variables et les pointeurs sont automatiquement initialisés à zéro, mais pas en mode de libération. Par conséquent, si vous avez un code comme celui-ci

int* p;
....
if (p == 0) { // do stuff }

En mode débogage, le code dans if n’est pas exécuté, mais en mode de publication, p contient une valeur indéfinie, dont la probabilité est 0, le code est donc souvent exécuté et provoque un crash.

Je voudrais vérifier votre code pour les variables non initialisées. Cela peut également s'appliquer au contenu des tableaux.

48
David Dibben

Aucune réponse à ce jour n'a tenté de donner une vue d'ensemble sérieuse sur les techniques disponibles pour le débogage des applications de publication: 

  1. Les versions Release et Debug se comportent différemment pour de nombreuses raisons.Voici un excellent aperçu. Chacune de ces différences peut provoquer un bogue dans la version Release qui n'existe pas dans la version Debug.

  2. La présence d'un débogueur peut également modifier le comportement d'un programme, à la fois pour les versions release et debug. Voir cette réponse. En bref, au moins le débogueur Visual Studio utilise automatiquement le tas de débogage lorsqu'il est attaché à un programme. Vous pouvez désactiver le segment de débogage à l'aide de la variable d'environnement _NO_DEBUG_HEAP. Vous pouvez spécifier cela dans les propriétés de votre ordinateur ou dans les paramètres du projet dans Visual Studio. Cela pourrait rendre le crash reproductible avec le débogueur attaché. 

    Plus d'informations sur le débogage de la corruption de segment ici.

  3. Si la solution précédente ne fonctionne pas, vous devez intercepter l'exception non gérée et attacher un débogueur post-mortem lors de l'incident. Vous pouvez utiliser par exemple WinDbg pour cela, détails sur les débogueurs post-mortem disponibles et leur installation sur MSDN

  4. Vous pouvez améliorer votre code de traitement des exceptions. S'il s'agit d'une application de production, vous devez:

    une. Installer un gestionnaire de terminaison personnalisé à l'aide de std::set_terminate 

    Si vous souhaitez déboguer ce problème localement, vous pouvez exécuter une boucle sans fin à l'intérieur du gestionnaire de terminaison et envoyer du texte à la console pour vous informer que std::terminate a été appelé. Ensuite, attachez le débogueur et vérifiez la pile d'appels. Ou vous imprimez la trace de la pile comme décrit dans cette réponse.

    Dans une application de production, vous souhaiterez peut-être envoyer un rapport d'erreur à la maison, idéalement avec un petit vidage de la mémoire qui vous permettra d'analyser le problème comme décrit ici.

    b. Utilisez le mécanisme de gestion des exceptions structuré de Microsoft qui vous permet d'intercepter les exceptions matérielles et logicielles. Voir MSDN . Vous pouvez protéger des parties de votre code à l'aide de SEH et utiliser la même approche qu'en a) pour résoudre le problème. SEH fournit plus d'informations sur l'exception qui s'est produite et que vous pouvez utiliser lors de l'envoi d'un rapport d'erreur à partir d'une application de production.

19
Sebastian

Les choses à surveiller:

Dépassements de tableau - le débogueur de Visual Studio insère un rembourrage pouvant empêcher les plantages.

Conditions de concurrence - plusieurs threads sont-ils impliqués si, dans l'affirmative, une condition de concurrence excessive apparaît uniquement lorsqu'une application est exécutée directement.

Liaison - votre version est-elle en train de générer les bonnes bibliothèques?.

Choses à essayer:

Minidump - vraiment facile à utiliser (il suffit de le regarder dans msdn) vous donnera un vidage complet sur incident pour chaque thread. Vous venez de charger la sortie dans Visual Studio et tout se passe comme si vous étiez en train de déboguer au moment du crash.

15
morechilli

Vous pouvez définir WinDbg comme débogueur post-mortem. Cela lancera le débogueur et l'attachera au processus lorsque l'incident se produira. Pour installer WinDbg pour le débogage post-mortem, utilisez l'option/I (notez qu'il s'agit de en majuscule):

windbg /I

Plus de détails ici .

Quant à la cause, il s’agit très probablement d’une variable unitialisée, comme le suggèrent les autres réponses.

12
Franci Penov

Après de nombreuses heures de débogage, j'ai finalement trouvé la cause du problème, qui était bien dû à un débordement de mémoire tampon, causant une différence d'octet unique:

char *end = static_cast<char*>(attr->data) + attr->dataSize;

Ceci est une erreur fencepost (erreur off-by-one) et a été corrigé par:

char *end = static_cast<char*>(attr->data) + attr->dataSize - 1;

La chose étrange était, j'ai mis plusieurs appels à _CrtCheckMemory () autour de différentes parties de mon code, et ils ont toujours retourné 1. J'ai pu trouver la source du problème en mettant "return false"; appels dans le cas test, puis détermination éventuelle par tâtonnement où la faute a été commise.

Merci à tous pour vos commentaires - j'ai beaucoup appris sur windbg.exe aujourd'hui! :)

9
Nik Reiman

Même si vous avez construit votre exe comme une version de sortie, vous pouvez toujours générer des fichiers PDB (programme de base de données) qui vous permettront d’empiler la trace et d'effectuer un nombre limité d'inspections de variables . pour créer les fichiers PDB. Activez-le et reliez-le. Ensuite, essayez d’exécuter en premier à partir de IDE pour voir si vous rencontrez le problème. Si c'est le cas, alors tant mieux, vous êtes prêt à regarder. Sinon, en exécutant à partir de la ligne de commande, vous pouvez effectuer l'une des deux opérations suivantes:

  1. Exécutez EXE et, avant le crash, effectuez une opération Attacher au processus (menu Outils de Visual Studio).
  2. Après le crash, sélectionnez l'option pour lancer le débogueur.

Lorsque vous êtes invité à désigner des fichiers PDB, parcourez-les pour les rechercher. Si les fichiers PDB ont été placés dans le même dossier de sortie que vos fichiers EXE ou DLL, ils seront probablement repris automatiquement.

Les PDB fournissent un lien vers la source avec suffisamment d’informations sur les symboles pour permettre de voir les traces de la pile, les variables, etc. apparaissent dans des registres ou les choses se passent dans un ordre différent de celui auquel vous vous attendez.

NB: Je suppose un environnement Windows/Visual Studio ici.

7
Greg Whitfield

De tels plantages sont presque toujours causés car un IDE définira généralement le contenu de la variable non initialisée sur zéros, null ou toute autre valeur "raisonnable", alors que lors de l'exécution en mode natif, vous obtiendrez tous les déchets aléatoires choisis par le système up.

Votre erreur est donc presque certainement que vous utilisez quelque chose comme si vous utilisiez un pointeur avant qu'il ne soit correctement initialisé et que vous vous en tiriez dans le IDE car il ne pointe aucun danger - ou la valeur est gérée par votre vérification d'erreur - mais en mode release, cela fait quelque chose de méchant.

3
Cruachan

Afin de créer un vidage sur incident que vous pouvez analyser:

  1. Générez des fichiers pdb pour votre code.
  2. Vous rebase pour avoir votre exe et dll chargé dans la même adresse.
  3. Activer le débogueur post mortem tel que Dr. Watson
  4. Vérifiez l'adresse des incidents en utilisant un outil tel que crash Finder .

Vous devez également consulter les outils dans Outils de débogage pour Windows . Vous pouvez surveiller l’application et voir toutes les exceptions de la première chance antérieures à votre exception.

J'espère que ça aide...

3
Yuval Peled

Une fois, j'ai eu un problème lorsque l'application s'est comportée de la même façon que la vôtre. Il s’est avéré qu’il s’agissait d’un vilain tampon saturé en sprintf. Naturellement, cela fonctionnait lorsqu'il était exécuté avec un débogueur attaché. Ce que j’ai fait, c’est d’installer un filtre d’exception non géré ( SetUnhandledExceptionFilter ) dans lequel j’ai simplement bloqué à l’infini (en utilisant WaitForSingleObject sur un pseudo-pseudonyme avec un délai INFINITE).

Donc, vous pourriez quelque chose comme:

 long __stdcall MonFiltre (EXCEPTION_POINTERS *) 
 {
 HANDLE hEvt = :: CreateEventW (0,1,0,0); 
 si (hEvt) 
 {
 if (WAIT_FAILED == :: WaitForSingleObject (hEvt, INFINITE)) 
 {
 // échec du journal 
 } 
 } 

} 
 // quelque part dans votre wmain/WinMain: 
 SetUnhandledExceptionFilter (MyFilter); 

J'ai ensuite attaché le débogueur après que le bogue se soit manifesté (le programme d'interface graphique a cessé de répondre) .

Ensuite, vous pouvez soit prendre un cliché et l'utiliser plus tard:

. dump/ma path_to_dump_file

Ou déboguer tout de suite. La méthode la plus simple consiste à déterminer où le contexte du processeur a été enregistré par le mécanisme de traitement des exceptions du moteur d'exécution:

s-d esp Intervalle 1003f

La commande effectuera une recherche dans l’espace adresse de la pile pour les enregistrements CONTEXT fournissant la longueur de la recherche. J'utilise habituellement quelque chose comme 'l? 10000'. Remarque: n'utilisez pas de nombres trop grands, car l'enregistrement que vous recherchez est généralement proche du cadre de filtre d'exception non géré . 1003f est la combinaison d'indicateurs (je crois que cela correspond à CONTEXT_FULL) utilisés pour capturer l'état du processeur ..__ Votre recherche ressemblerait à ceci:

0: 000> s-d esp l1000 1003f
0012c160 0001003f 00000000 00000000 00000000? ...............

Une fois les résultats obtenus, utilisez l’adresse de la commande cxr:

. cxr 0012c160

Cela vous mènera à ce nouveau CONTEXTE, exactement au moment du crash (vous obtiendrez exactement la trace de la pile au moment du crash de votre application) . De plus, utilisez:

. exr -1

pour savoir exactement quelle exception s'est produite.

J'espère que ça aide.

2
deemok

Cela se produit parfois parce que vous avez encapsulé une opération importante dans la macro "assert". Comme vous le savez peut-être, "assert" n'évalue les expressions qu'en mode débogage.

Vista SP1 possède en réalité un générateur de vidages de mémoire très agréable intégré au système. Malheureusement, il n'est pas activé par défaut!

Voir cet article: http://msdn.Microsoft.com/en-us/library/bb787181(VS.85).aspx

L'avantage de cette approche est qu'aucun logiciel supplémentaire ne doit être installé sur le système affecté. Saisissez-le et déchirez-le, bébé!

1
nick

D'après mon expérience, ce sont pour la plupart des problèmes de corruption de mémoire.

Par exemple :

char a[8];
memset(&a[0], 0, 16);

: /*use array a doing some thing */

il est très possible d'être normal en mode débogage quand on exécute le code.

Mais dans la publication, cela pourrait/pourrait être un crash.

Pour moi, fouiller la mémoire hors d’usage est trop pénible.

Utilisez des outils tels que Détecteur de fuite visuel (Windows) ou Valgrind (Linux) sont plus judicieux.

1
Gaiger Chen

J'ai vu beaucoup de bonnes réponses. Cependant, aucun ne m'a aidé. Dans mon cas, les instructions SSE avec la mémoire unaligned ont été mal utilisées. Examinez votre bibliothèque mathématique (si vous en utilisez une) et essayez de désactiver le support SIMD, de recompiler et de reproduire le crash.

Exemple:

Un projet inclut mathfu et utilise les classes avec le vecteur STL: std :: vector <mathfu :: vec2>. Un tel usage provoquera probablement un blocage au moment de la construction de l'item mathfu :: vec2 car l'allocateur par défaut de la STL ne garantit pas l'alignement requis de 16 octets. Dans ce cas, pour prouver l’idée, on peut définir #define MATHFU_COMPILE_WITHOUT_SIMD_SUPPORT 1 avant chaque inclusion du mathfu , recompiler dans la configuration de la version et relancer la vérification.

Les configurations Debug et RelWithDebInfo ont bien fonctionné pour mon projet, mais pas celles de Release. Ce comportement est probablement dû au fait que le débogueur traite les demandes d'allocation/désallocation et effectue une comptabilité de la mémoire pour vérifier et vérifier les accès à la mémoire.

J'ai vécu la situation dans les environnements Visual Studio 2015 et 2017.

1
Vlad Serhiienko

En ce qui concerne vos problèmes d’obtention d’informations de diagnostic, avez-vous essayé d’utiliser adplus.vbs comme alternative à WinDbg.exe? Pour attacher à un processus en cours, utilisez

adplus.vbs -crash -p <process_id>

Ou pour démarrer l'application dans le cas où le crash se produit rapidement:

adplus.vbs -crash -sc your_app.exe

Des informations complètes sur adplus.vbs sont disponibles à l’adresse: http://support.Microsoft.com/kb/286350

1
DocMax

Ntdll.dll avec le débogueur attaché

Une petite différence entre le lancement d’un programme à partir de IDE ou de WinDbg et son lancement à partir de la ligne de commande/du bureau est que lors du lancement avec un débogueur attaché (c.-à-d. IDE ou WinDbg), ntdll.dll utilise mise en œuvre de tas différente qui effectue un peu de validation sur l’allocation/libération de mémoire. 

Vous pouvez lire des informations pertinentes dans un point d'arrêt utilisateur inattendu dans ntdll.dll . Un des outils qui pourrait vous aider à identifier le problème est PageHeap.exe

Analyse de crash

Vous n'avez pas écrit ce qu'est le "crash" que vous vivez. Une fois que le programme se bloque et vous propose d'envoyer les informations d'erreur à Microsoft, vous devriez pouvoir cliquer sur les informations techniques et vérifier au moins le code d'exception. Vous pouvez même, avec quelques efforts, effectuer une analyse post mortem (voir Heisenbug: le programme WinApi se bloque sur certains ordinateurs) pour les instructions)

1
Suma

Un excellent moyen de déboguer une erreur de ce type consiste à activer les optimisations pour votre génération de débogage.

1
Mgill404

Vous pouvez exécuter votre logiciel avec les indicateurs globaux activés (regardez dans Outils de débogage pour Windows). Cela aidera très souvent à résoudre le problème.

0
Marcin Gil

Essayez d’utiliser _CrtCheckMemory () pour voir l’état de la mémoire allouée dans . Si tout se passe bien, _CrtCheckMemory retourneTRUE, sinonFALSE.

0
Vhaerun

Le débogage des versions de publication peut être un problème en raison d'optimisations modifiant l'ordre dans lequel les lignes de votre code semblent être exécutées. Cela peut vraiment devenir déroutant!

Une technique pour au moins réduire le problème consiste à utiliser MessageBox () pour afficher des instructions rapides indiquant la partie du programme que votre code a ("Démarrage de Foo ()", "Démarrage de Foo2 ()"); commencez à les placer au sommet des fonctions de votre code que vous soupçonnez (que faisiez-vous au moment où il s'est écrasé?). Lorsque vous pouvez déterminer quelle fonction, modifiez les zones de message en blocs de code ou même en lignes individuelles dans cette fonction jusqu'à ce que vous la réduisiez à quelques lignes. Vous pouvez ensuite commencer à imprimer la valeur des variables pour voir dans quel état elles se trouvent au moment de leur panne.

0
JTeagle

Voici un cas que j'ai eu que quelqu'un pourrait trouver instructif. Il s'est seulement écrasé dans la version dans Qt Creator - pas dans le débogage. J'utilisais des fichiers .ini (car je préfère les applications pouvant être copiées sur d'autres lecteurs, par opposition à celles qui perdent leurs paramètres si le registre est corrompu). Cela s'applique à toutes les applications qui stockent leurs paramètres dans l'arborescence des applications. Si les versions de débogage et de publication se trouvent dans des répertoires différents, vous pouvez également définir un paramètre différent entre elles. J'avais préférence enregistré dans un qui n'a pas été vérifié dans l'autre. Il s'est avéré être la source de mon crash. C'est une bonne chose que je l'ai trouvée.

Je n'aime pas le dire, mais je n'ai diagnostiqué le crash que dans MS Visual Studio Community Edition; après avoir installé VS, laissé mon application planter dans Qt Creator et choisi de l’ouvrir dans Visual Studio debugger. Bien que mon application Qt ne contienne aucune information sur les symboles, il s'avère que les bibliothèques Qt en possédaient. Cela m'a conduit à la ligne incriminée; puisque je pouvais voir quelle méthode était appelée. (Néanmoins, je pense que Qt est un cadre LGPL pratique, puissant et multiplate-forme.)

0
CodeLurker

Quelque chose de semblable m'est arrivé une fois avec GCC. Il s’est avéré que l’optimisation était trop agressive et n’était activée que lors de la création de la version finale et non pendant le processus de développement.

Eh bien, à vrai dire, c'était ma faute, pas celle de gcc, car je n'avais pas remarqué que mon code reposait sur le fait que cette optimisation particulière n'aurait pas été effectuée.

Il m'a fallu beaucoup de temps pour le retrouver et je n'y suis arrivé que parce que j'avais demandé à un groupe de discussion et que quelqu'un m'y avait fait penser. Alors, permettez-moi de vous rendre la pareille au cas où cela vous arriverait également.

0
Remo.D

Demandez à votre programme de générer un mini-vidage lorsque l'exception se produit, puis ouvrez-le dans un débogueur (par exemple, dans WinDbg). Les fonctions clés à regarder: MiniDumpWriteDump, SetUnhandledExceptionFilter

0
mikhailitsky

J'ai trouvé ceci cet article utile pour votre scénario. ISTR les options du compilateur étaient un peu dépassées. Examinez les options de votre projet Visual Studio pour voir comment générer des fichiers pdb pour votre version, etc.

0
fizzer

Il est suspect que cela se produise à l'extérieur du débogueur et non à l'intérieur; l'exécution dans le débogueur ne change pas normalement le comportement de l'application. Je vérifierais les différences d’environnement entre la console et l’EDI. Aussi, bien sûr, compilez la version sans optimisations et avec les informations de débogage, et voyez si cela affecte le comportement. Enfin, jetez un œil aux outils de débogage post-mortem suggérés par d’autres personnes. Vous pouvez généralement en obtenir des indices.

0
Nick