8
votes

Comment chasser une heisenbug

Récemment, nous avons reçu un rapport de bogue de l'un de nos utilisateurs: quelque chose à l'écran n'a pas été affiché de manière incorrecte dans notre logiciel. En quelque sorte, nous ne pouvions pas reproduire cela dans notre environnement de développement (Delphi 2007).

Après une autre étude, il apparaît que ce bogue seul se manifeste lorsque "Optimisation du code" est activé sur . .

Y a-t-il des personnes ici avec une expérience dans la chasse à la chasse à une telle Heisenbug ? Toute constructions spécifiques ou bogues de codage qui causent généralement une telle question dans le logiciel Delphi? Tous les endroits que vous commenceriez à chercher?

Je vais aussi commencer tout simplement à déboguer dans le tout à la manière habituelle, mais les conseils spécifiques aux bugs liés à l'optimisation (*) seraient plus que les bienvenus!

(*) Remarque: je ne veux pas dire que le bug est causé par l'optimiseur; Je pense que c'est beaucoup plus probable que certains constructions bonky dans le code sont en quelque sorte poussés "sur le bord" par l'optimiseur.

Mise à jour

Il semble que le bogue se résume à un enregistrement étant complètement initialisé avec des zéros lorsqu'il n'y a pas d'optimisation de code, et le même enregistrement contenant des données aléatoires lorsqu'il est est optimisation. Dans ce cas, les données aléatoires semblent provoquer un type d'énumération contiennent des données non valides (à ma grande surprise!).

Solution

La solution s'est avérée impliquer une variable d'enregistrement locale unialisée quelque part en profondeur dans le code. Apparemment, sans optimisation, l'enregistrement était réinitialisé (tas de tas?), Et avec optimisation tourné sur , l'enregistrement était rempli de la poubelle habituelle. Merci à vous tous pour vos contributions --- J'ai beaucoup appris sur le chemin!


9 commentaires

"Quelque chose à l'écran a été affiché de manière incorrecte" ne donne à personne beaucoup à qui continuer ... Je n'ai jamais vu l'optimisation altérer l'exécution de quelque manière que ce soit le fonctionnement du programme et j'utilise Delphi depuis la libération de V1. . Peut-être certains détails sur ce que le code fait en réalité au point que la sortie d'écran est affichée aiderait?


Voir aussi: Stackoverflow.com/ Questions / 132116 / ...


@Ken: Je ne pense pas que les spécificités de ce bogue soient très intéressantes. La cause pourrait être n'importe où: dans la couche logique de l'entreprise, dans la logique de vue ... Je pense que la mémoire de la mémoire unialisée pourrait être.


Pourquoi êtes-vous surpris de constater qu'un enregistrement complet de données aléatoires provoque une variable de valeur non valide? Ordures dans, ordures.


@ROB: En fonction de la manière dont l'enregistrement est attribué, il peut être initialisé de ne pas être complètement mis à zéro. D'où ma réponse avec la suggestion sur les variables d'enregistrement locales. Onno doit indiquer quel type de stockage utilise les enregistrements.


@ROB: Bien sûr que vous avez absolument raison, mais je n'aurais pas cessé d'une variable définie de contenir des valeurs hors de portée de l'énum de constitution et de diverses constructions de langue en boucle avec heureusement sur ces valeurs hors de portée :) Quoi qu'il en soit, dès que j'ai écrasé ce bogue, je posterai plus de détails.


@onnodb: le type Enum n'est rien d'autre qu'un octet / mot / entier (en fonction de la minénumsize $). Et sans l'initialisation, il peut contenir des données qui s'adaptent à la gamme entière mais non dans la plage d'ENUM. La même chose est pour les ensembles. Un ensemble peut comporter jusqu'à 32 octets. Et sans initialisation, les bits peuvent être définis pour des "éléments" qui ne sont pas des éléments.


Comment chasser un heisenbug? Cherchez-la avec des couches et cherchez-la avec soin, poursuivez-la avec des fourches et de l'espoir. Menacez sa vie avec une part de chemin de fer et le charme avec des sourires et du savon. ;)


@Andreas: Bien sûr, mais je m'attendrais à ce qu'un "car-in" -Loop ignore tous les bits dans le masque de bit qui sont hors de portée de la variable de boucle Enum. De toute façon. Les bugs définitivement dans mon code, pas de question 'Bout qui: o)


10 Réponses :


12
votes

Les bugs de ce formulaire sont causés par un accès à la mémoire non valide (lecture de données non inticialisées, la lecture de la fin d'un tampon ...) ou des conditions de course de fil.

Le premier sera affecté par les optimisations provoquant la réaménagement de la disposition de données en mémoire et / ou éventuellement par code de débogage qui met en place une mémoire nouvellement attribuée à une certaine valeur; provoquant le code incorrect de "travailler accidentellement".

Ce dernier sera affecté en raison des horaires changeant entre les niveaux d'optimisation. Le premier est généralement beaucoup plus probable.

Si vous avez une manière automatisée de faire de la mémoire fraîchement allouée, être remplie de valeur constante avant de passer au programme, et cela fait disparaître l'accident ou devenir reproductible dans la construction de débogage, cela donnera un bon point commencer à poursuivre des choses.


2 commentaires

HM, oui, je pense que c'est la mémoire ininitialisée, alors (c'est une application à une seule-filetée). Je vais garder un œil sur cela tout en débogage du code.


J'ai vu ce genre de bug avec des variables non assemblées ou un réarrangement de mémoire de mémoire avec C / C ++ au fil des ans. J'ai également vu de nombreux types de bugs de synchronisation affectés par les niveaux d'optimisation, pas seulement des problèmes de threads. Par exemple, j'ai vu du code qui a écrit à un port série ou à une connexion réseau, a fait autre chose depuis un certain temps, puis a essayé de lire une réponse sans permettre la possibilité que la réponse ne soit pas encore là. Cela a bien fonctionné lorsque le code n'a pas été optimisé et a échoué lorsque le code a été optimisé.



2
votes

Surtout dans des langues purement autochtones, comme Delphi, vous devriez être plus que prudent de ne pas abuser de la liberté de ne rien faire. IOW: Une chose, j'ai vu que quelqu'un copie la définition d'une classe (par exemple de la section de mise en œuvre de RTL ou VCL) dans son propre code puis de lancer des instances de la classe d'origine à sa copie. Maintenant, après la mise à niveau de la bibliothèque où est venu la classe d'origine, vous pourriez ressentir toutes sortes de choses étranges. Comme sauter dans les mauvaises méthodes ou les bufferverflows.

Il y a aussi l'habitude d'utiliser entier signé en tant que pointeurs et vice-versa. (Au lieu de cardinal) Cela fonctionne parfaitement bien tant que votre processus n'a que 2 Go d'espace d'adressage. Mais démarrez avec le commutateur / 3 Go et vous verrez beaucoup d'applications qui commencent à agir fou. Ceux qui ont fait l'hypothèse de "pointeur = entier signé" au moins quelque part. Votre client utilise une fenêtre de 64 bits? Les chances sont, il pourrait avoir un espace d'adressage plus grand pour les applications 32bits. Assez difficile de déboguer avec un tel système de test disponible.

Alors, il y a des conditions de course. Comme avoir 2 threads, où l'on est très, très lent. Donc, vous supposez instinctivement que ce sera toujours le dernier et il n'y a donc aucun code qui gère le scénario où «Capt Slow» se termine en premier. Les changements dans les technologies sous-jacentes peuvent rendre ces hypothèses très mauvaises, très rapidement. Jetez un coup d'œil à la prochaine race de stockage de serveurs Super-Mega-Fast basé sur Flash. Systèmes pouvant lire et écrire des gigaoctets par seconde. Les applications qui supposent que les trucs d'IO sont nettement plus lents que certains calculs sur les valeurs en mémoire échoueront facilement sur ce type de stockage rapide.

Je pourrais continuer et sur, mais je dois courir maintenant ... Acclamations


1 commentaires

Aucun de ces problèmes ne pourrait être la cause de ce bogue (je suis sûr que la base de code fait l'un de ce casting de type funky; tous les systèmes du client sont 32 bits; c'est une application à une seule-filetée), mais j'ai appris Quelques choses de votre réponse, merci!



2
votes

L'optimisation du code ne signifie pas nécessairement que les symboles de débogage doivent être laissés de côté. Faites une construction de débogage avec optimisation du code, vous pouvez toujours déboguer le programme et peut-être que l'erreur se produit maintenant.


1 commentaires

Bon point, j'ai oublié de mentionner ça. Néanmoins, je me demandais quels types de code pouvaient causer un tel bogue - c'est un programme important, tout indice quant à ce que la cause pourrait être, sont les bienvenues: o)



6
votes

pourrait très bien être un problème de mémoire VS Enregistrement: vous programmez bien en fonction de la persistance de la mémoire après un.
Je recommanderais d'exécuter votre application avec FastMM4 en mode de débogage complet pour être sûr de votre gestion de la mémoire.
Un autre outil (non gratuit) qui peut être très utile dans un cas comme celui-ci est Eurekalog.

Une autre chose que j'ai vue: un crash avec les registres FPU étant bâclées lorsque vous appelez du code extérieur (DLL, COM ...) pendant que le débogueur tout était correct.


0 commentaires

1
votes

Dans de tels problèmes, je conseille toujours à utiliser des logfiles.

Question: pouvez-vous en quelque sorte déterminer l'affichage incorrect dans le code SOURCECODE?

Sinon, ma réponse ne vous aidera pas.

Si oui, vérifiez l'incorrecté et dès que vous le trouverez, videz la pile à un logfile. (Voir Post mortem Débogage pour plus de détails sur le dumping et la résymétrie de la pile).

Si vous voyez que certaines données ont été corrompues, mais que vous ne savez pas comment et ensuite cela se passe, extraire une fonction qui fait un tel test de validité (avec la journalisation si elle a échoué) et appelez cette fonction de plus en plus d'endroits sur Exécution du programme (c'est-à-dire après chaque appel de menu). Si vous réitérez une telle approche à quelques reprises, vous avez de bonnes chances de trouver le problème.


1 commentaires

Les fichiers journaux sont géniaux, de même qu'une fenêtre de trace. J'utilise les deux.



2
votes

Une chose facile à faire est d'allumer l'avertissement de compilateur et l'indice, le projet de reconstruction, puis fixer toutes les avertissements / astuces

acclamations


1 commentaires

Les avertissements compilateurs et les astuces sont toujours allumés dans notre projet, mais oui, c'est généralement un bon conseil!



1
votes

est-ce une variable locale dans une procédure ou une fonction?

Si tel est le cas, cela vit sur la pile et contiendra des ordures. En fonction du chemin d'exécution et des paramètres du compilateur, la poubelle changera potentiellement votre logique "sur le bord".

- Jeroen


0 commentaires

2
votes

Si le code Business Delphi, avec des composants Dataaware, etc., le suivi pourrait ne pas s'appliquer.

Je suis cependant en train d'écrire un code de vision de la machine qui est un peu calculal. La plupart des unit sont basés sur la console. Je suis également impliqué avec FPC et au fil des ans, j'ai beaucoup testé avec FPC. Partiellement hors du passe-temps, partiellement dans des situations désespérées où je voulais une hunch.

Quelques astuces standard que j'ai essayées (une utilité décroissante)

  1. Utilisez -gv et valcrind Le code (pratiquement, cela signifie que des applications sont nécessaires pour exécuter sous Linux / FreeBSD. Mais pour le code de calcul et les unitest qui peuvent être faisables)
  2. Compilez à l'aide de FPC param -gt (= trash local vars, randomiser les Vares locaux sur la procédure init)
  3. Modifier le tasManager pour randomiser les données des blocs qu'il propose (également postuler au code Delphi)
  4. Essayez la vérification de la gamme / des débordements de FPC et de la vérification du compilation.
  5. Exécutez sur un Mac Mini (PowerPC) ou Win64. En raison de règles et de mises en page totalement différentes, il peut attraper de jolies choses funky.

    Les 2 et 3 ensemble vous permettent de trouver la plupart, sinon tous les problèmes d'initialisation.

    Essayez de trouver des indices, puis retournez à Delphi et recherchez plus de détails, de débogage, etc.

    Je me rends compte que ce n'est pas facile. J'ai beaucoup d'expérience de la FPC et je n'ai pas eu à tout trouver de zéro pour ces cas. Cela vaut toujours la peine d'essayer, et pourrait être une motivation de démarrer la configuration de systèmes non visuels et des unitest FPC compatible et la plate-forme indépendante. La plupart de ces travaux seront nécessaires de toute façon, en voyant la feuille de route Delphes.


0 commentaires

0
votes

Compte tenu de votre description du problème, je pense que vous avez eu des données ininitialisées que vous avez éloignées sans l'optimiseur mais qui a explosé avec l'optimisation.


0 commentaires

3
votes

Un enregistrement contenant différentes données selon différents paramètres du compilateur me dit une chose: que l'enregistrement n'est pas explicitement initialisé.

Vous constaterez peut-être que le réglage du drapeau d'optimisation du compilateur n'est qu'un facteur qui pourrait affecter le contenu de cet enregistrement - avec des structures de données non initialisées, la seule chose que vous pouvez compter sur est que vous ne pouvez pas compter sur la première Contenu de la structure.

en termes simples:

  • Les données de membre de la classe sont initialisées (à zéro) pour de nouvelles instances de la classe

  • variables locales (dans les fonctions et procédures) et les variables d'unités ne sont pas initialisées, sauf dans quelques cas spécifiques: références d'interface, tableaux dynamiques et chaînes et je pense (mais devriez vérifier) ​​les enregistrements s'ils contiennent un ou Plus de champs de ces types qui seraient initialisés (chaînes, références d'interface, etc.).

    La question telle que indiquée est maintenant un peu trompeuse car il semble que vous ayez trouvé votre "heisenberg" assez facilement assez facilement. Maintenant, la question est de savoir comment traiter avec elle et la réponse est simplement d'initialiser explicitement votre dossier afin que vous ne dépendez pas de tout comportement ou effet secondaire du compilateur en prenant en charge parfois cela pour vous et parfois non. < / p>


0 commentaires