Qu'est-ce qui a été difficile à trouver? Comment l'avez-vous retrouvé?
Pas assez proche pour fermer mais voir aussi
https://stackoverflow.com/questions/175854/what-is-the-funniest-bug-youve-ever-experienced
Un analyseur jpeg, fonctionnant sur une caméra de surveillance, qui s'est écrasé chaque fois que le PDG de l'entreprise entrait dans la pièce.
Erreur 100% reproductible.
Je ne plaisante pas!
C'est pourquoi:
Pour vous qui ne savez pas grand-chose sur la compression JPEG - l'image est en quelque sorte décomposée en une matrice de petits blocs qui sont ensuite encodés en utilisant la magie, etc.
L'analyseur s'est étouffé lorsque le PDG est entré dans la pièce, car il avait toujours une chemise avec un motif carré, ce qui a déclenché un cas particulier de contraste et d'algorithmes de limite de bloc.
Vraiment classique.
Cela ne m'est pas arrivé, mais un ami m'en a parlé.
Il devait déboguer une application qui plantait très rarement. Il n'échouerait que le mercredi - en septembre - après le 9. Oui, 362 jours de l'année, ça allait, et trois jours par an, ça plantait immédiatement.
Il formaterait une date comme "mercredi 22 septembre 2008", mais le tampon était un caractère trop court - donc cela ne causerait un problème que si vous aviez un DOM à 2 chiffres un jour avec le nom le plus long du mois avec le nom le plus long.
Cela nécessite de connaître un peu l'assembleur Z-8000, que j'expliquerai au fur et à mesure.
Je travaillais sur un système embarqué (dans l'assembleur Z-8000). Une autre division de l'entreprise construisait un système différent sur la même plate-forme et avait écrit une bibliothèque de fonctions, que j'utilisais également dans mon projet. Le bug était que chaque fois que j'appelais une fonction, le programme se bloquait. J'ai vérifié toutes mes entrées; ils allaient bien. Cela devait être un bogue dans la bibliothèque - sauf que la bibliothèque avait été utilisée (et fonctionnait bien) dans des milliers de sites POS à travers le pays.
Désormais, les CPU Z-8000 ont 16 registres 16 bits, R0, R1, R2 ... R15, qui peuvent également être adressés comme 8 registres 32 bits, nommés RR0, RR2, RR4..RR14 etc. La bibliothèque a été écrite à partir de zéro, refactorisant un tas d'anciennes bibliothèques. Il était très propre et suivait des normes de programmation strictes. Au début de chaque fonction, chaque registre qui serait utilisé dans la fonction a été poussé sur la pile pour conserver sa valeur. Tout était propre et bien rangé - ils étaient parfaits.
Néanmoins, j'ai étudié la liste des assembleurs pour la bibliothèque, et j'ai remarqué quelque chose d'étrange à propos de cette fonction --- Au début de la fonction, il y avait Push RR0/Push RR2 et à la fin avait POP RR2/POP R0. Maintenant, si vous ne suivez pas cela, il a poussé 4 valeurs sur la pile au début, mais n'en a supprimé que 3 à la fin. C'est une recette pour un désastre. Il y avait une valeur inconnue en haut de la pile où l'adresse de retour devait être. La fonction ne pouvait pas fonctionner.
Sauf, permettez-moi de vous le rappeler, que cela fonctionnait. Il était appelé des milliers de fois par jour sur des milliers de machines. Cela ne pouvait pas fonctionner.
Après un certain débogage (ce qui n'était pas facile dans l'assembleur sur un système embarqué avec les outils du milieu des années 80), il plantait toujours au retour, car la mauvaise valeur l'envoyait à une adresse aléatoire. Évidemment, j'ai dû déboguer l'application de travail, pour comprendre pourquoi elle n'avait pas échoué.
Eh bien, rappelez-vous que la bibliothèque était très bonne pour préserver les valeurs dans les registres, donc une fois que vous avez mis une valeur dans le registre, elle y est restée. R1 avait 0000 en elle. Il y aurait toujours 0000 quand cette fonction était appelée. Le bug a donc laissé 0000 sur la pile. Ainsi, lorsque la fonction est retournée, elle sautera à l'adresse 0000, qui se trouve être un RET, qui supprimera la valeur suivante (l'adresse de retour correcte) de la pile et sautera à cela. Les données masquaient parfaitement le bug.
Bien sûr, dans mon application, j'avais une valeur différente dans R1, donc ça s'est juste écrasé ....
C'était sous Linux mais aurait pu arriver sur pratiquement n'importe quel OS. Maintenant, la plupart d'entre vous connaissent probablement l'API socket BSD. Nous l'utilisons avec plaisir année après année, et cela fonctionne.
Nous travaillions sur une application massivement parallèle qui aurait de nombreux sockets ouverts. Pour tester son fonctionnement, nous avions une équipe de test qui ouvrirait des centaines et parfois plus d'un millier de connexions pour le transfert de données. Avec les numéros de canaux les plus élevés, notre application commencerait à montrer un comportement étrange. Parfois, cela venait de s'écraser. L'autre fois, nous avons eu des erreurs qui ne pouvaient tout simplement pas être vraies (par exemple, accept () renvoyant le même descripteur de fichier lors des appels suivants, ce qui a bien sûr entraîné le chaos.)
Nous pouvions voir dans les fichiers journaux que quelque chose s'était mal passé, mais c'était incroyablement difficile à localiser. Les tests avec Rational Purify ont révélé que tout allait bien. Mais quelque chose n'allait pas. Nous y avons travaillé pendant des jours et sommes devenus de plus en plus frustrés. C'était un showblocker parce que le test déjà négocié causerait des ravages dans l'application.
Comme l'erreur ne s'est produite que dans des situations de charge élevée, j'ai revérifié tout ce que nous avons fait avec des sockets. Nous n'avions jamais testé de cas de charge élevée dans Purify, car cela n'était pas possible dans une situation aussi gourmande en mémoire.
Enfin (et heureusement), je me suis souvenu que le nombre massif de sockets pouvait être un problème avec select () qui attend les changements d'état sur les sockets (peut lire/peut écrire/erreur). Effectivement, notre application a commencé à faire des ravages exactement au moment où elle a atteint le socket avec le descripteur 1025. Le problème est que select () fonctionne avec les paramètres de champ de bits. Les champs de bits sont remplis par des macros FD_SET () et des amis qui NE VÉRIFIENT PAS LEURS PARAMÈTRES POUR LA VALIDITÉ.
Donc, chaque fois que nous avons plus de 1024 descripteurs (chaque système d'exploitation a sa propre limite, les noyaux Linux Vanilla en ont 1024, la valeur réelle est définie comme FD_SETSIZE), la macro FD_SET écraserait volontiers son champ de bits et écrirait des ordures dans la prochaine structure en mémoire.
J'ai remplacé tous les appels select () par poll () qui est une alternative bien conçue à l'appel arcane select (), et les situations de charge élevée n'ont jamais été un problème par la suite. Nous avons eu de la chance car toute la manipulation des sockets se faisait dans une seule classe de framework où 15 minutes de travail pouvaient résoudre le problème. Cela aurait été bien pire si les appels select () avaient été saupoudrés sur tout le code.
Leçons apprises:
même si une fonction API a 25 ans et que tout le monde l'utilise, elle peut avoir des coins sombres que vous ne connaissez pas encore
les écritures de mémoire non contrôlées dans les macros API sont EVIL
un outil de débogage comme Purify ne peut pas aider dans toutes les situations, en particulier lorsque beaucoup de mémoire est utilisée
Ayez toujours un cadre pour votre application si possible. Son utilisation augmente non seulement la portabilité, mais aide également en cas de bugs de l'API
de nombreuses applications utilisent select () sans penser à la limite de socket. Je suis donc presque sûr que vous pouvez provoquer des bugs dans BEAUCOUP de logiciels populaires en utilisant simplement de nombreux sockets. Heureusement, la plupart des applications n'auront jamais plus de 1024 sockets.
Au lieu d'avoir une API sécurisée, les développeurs de systèmes d'exploitation aiment rejeter la faute sur le développeur. La page de manuel Linux select () indique
"Le comportement de ces macros n'est pas défini si une valeur de descripteur est inférieure à zéro ou supérieure ou égale à FD_SETSIZE, qui est normalement au moins égal au nombre maximal de descripteurs pris en charge par le système."
C'est trompeur. Linux peut ouvrir plus de 1024 sockets. Et le comportement est absolument bien défini: l'utilisation de valeurs inattendues ruinera l'application en cours d'exécution. Au lieu de rendre les macros résistantes aux valeurs illégales, les développeurs écrasent simplement les autres structures. FD_SET est implémenté en tant qu'assemblage en ligne (!) Dans les en-têtes Linux et sera évalué en une seule instruction d'écriture d'assembleur. Pas la moindre vérification des limites qui se passe nulle part.
Pour tester votre propre application, vous pouvez gonfler artificiellement le nombre de descripteurs utilisés en ouvrant par programme des fichiers ou des sockets FD_SETSIZE directement après main () puis en exécutant votre application.
Thorsten79
Le mien était un problème matériel ...
À l'époque, j'ai utilisé une DEC VaxStation avec un grand moniteur CRT de 21 ". Nous avons déménagé dans un laboratoire dans notre nouveau bâtiment et installé deux VaxStations dans les coins opposés de la pièce. À la mise sous tension, mon moniteur a vacillé comme une discothèque (oui, c'était les années 80), mais pas l'autre moniteur.
D'accord, échangez les moniteurs. L'autre moniteur (maintenant connecté à ma VaxStation) vacilla, et mon ancien moniteur (déplacé à travers la pièce) ne le fit pas.
Je me suis souvenu que les moniteurs CRT étaient sensibles aux champs magnétiques. En fait, ils étaient très sensibles aux champs magnétiques alternatifs à 60 Hz. J'ai immédiatement soupçonné que quelque chose dans ma zone de travail générait un champ magnétique altérant de 60 Hz.
Au début, je soupçonnais quelque chose dans ma zone de travail. Malheureusement, le moniteur scintillait toujours, même lorsque tous les autres équipements étaient éteints et débranchés. À ce moment-là, j'ai commencé à soupçonner quelque chose dans le bâtiment.
Pour tester cette théorie, nous avons converti la VaxStation et son moniteur de 85 lb en un système portable. Nous avons placé l'ensemble du système sur un chariot roulant et l'avons connecté à une rallonge de construction orange de 100 pieds. Le plan était d'utiliser cette configuration en tant que mesureur de champ portable, afin de localiser l'équipement en cause.
Faire tourner le moniteur nous a complètement déroutés. Le moniteur scintilla dans exactement la moitié de la pièce, mais pas de l'autre côté. La pièce avait la forme d'un carré, avec des portes dans des coins opposés, et le moniteur scintillait d'un côté d'une ligne de diagnostic reliant les portes, mais pas de l'autre. La pièce était entourée des quatre côtés par des couloirs. Nous avons poussé le moniteur dans les couloirs et le scintillement s'est arrêté. En fait, nous avons découvert que le scintillement ne se produisait que dans une moitié de forme triangulaire de la pièce, et nulle part ailleurs.
Après une période de confusion totale, je me suis souvenu que la pièce avait un système d'éclairage au plafond à deux voies, avec des interrupteurs d'éclairage à chaque porte. À ce moment, j'ai réalisé ce qui n'allait pas.
J'ai déplacé le moniteur dans la moitié de la pièce avec le problème et j'ai éteint les plafonniers. Le scintillement s'est arrêté. Quand j'ai allumé les lumières, le scintillement a repris. Allumer ou éteindre les lumières à partir de l'un ou l'autre des interrupteurs d'éclairage, allumer ou éteindre le scintillement dans la moitié de la pièce.
Le problème a été causé par une personne coupant les coins lorsqu'elle a câblé les plafonniers. Lors du câblage d'un interrupteur bidirectionnel sur un circuit d'éclairage, vous passez une paire de fils entre les contacts du commutateur SPDT et un fil unique du commun sur un interrupteur, à travers les lumières, et au commun sur l'autre interrupteur.
Normalement, ces fils sont regroupés. Ils partent en groupe d'un coffret électrique, courent vers le plafonnier suspendu et se dirigent vers l'autre coffret. L'idée clé est que tous les fils conducteurs de courant sont regroupés.
Lorsque le bâtiment a été câblé, le fil unique entre les interrupteurs et la lumière a été acheminé à travers le plafond, mais les fils voyageant entre les interrupteurs ont été acheminés à travers les murs.
Si tous les fils étaient proches et parallèles les uns aux autres, le champ magnétique généré par le courant dans un fil a été annulé par le champ magnétique généré par le courant égal et opposé dans un fil voisin. Malheureusement, la façon dont les lumières étaient réellement câblées signifiait que la moitié de la pièce se trouvait essentiellement à l'intérieur d'un grand primaire de transformateur à un tour. Lorsque les lumières étaient allumées, le courant circulait en boucle et le mauvais moniteur était essentiellement assis à l'intérieur d'un grand électro-aimant.
Morale de l'histoire: les lignes chaudes et neutres de votre câblage d'alimentation CA sont côte à côte pour une bonne raison.
Maintenant, tout ce que j'avais à faire était d'expliquer à la direction pourquoi ils devaient recâbler une partie de leur nouveau bâtiment ...
Un bug où vous rencontrez du code, et après l'avoir étudié, vous concluez: "Il n'y a aucun moyen que cela aurait pu fonctionner!" et soudain, il cesse de fonctionner même s'il fonctionnait toujours auparavant.
L'un des produits que j'ai aidé à créer dans mon travail fonctionnait sur un site client depuis plusieurs mois, collectant et enregistrant avec bonheur chaque événement reçu dans une base de données SQL Server. Il a très bien fonctionné pendant environ 6 mois, collectant environ 35 millions d'enregistrements.
Un jour, notre client nous a demandé pourquoi la base de données n'avait pas été mise à jour depuis près de deux semaines. Après une enquête plus approfondie, nous avons constaté que la connexion à la base de données qui effectuait les insertions n'avait pas pu être renvoyée depuis l'appel ODBC. Heureusement, le thread qui effectue l'enregistrement a été séparé du reste des threads, permettant à tout mais le fil d'enregistrement pour continuer à fonctionner correctement pendant près de deux semaines!
Nous avons essayé pendant plusieurs semaines de reproduire le problème sur n'importe quelle machine autre que celle-ci. Nous n'avons jamais pu reproduire le problème. Malheureusement, plusieurs de nos autres produits ont alors commencé à échouer à peu près de la même manière, aucun d'entre eux n'ayant leurs threads de base de données séparés du reste de leurs fonctionnalités, provoquant le blocage de l'application entière, qui a ensuite dû être redémarrée à la main chaque fois qu'ils écrasé.
Des semaines d'enquête se sont transformées en plusieurs mois et nous avons toujours eu les mêmes symptômes: blocages complets ODBC dans toutes les applications où nous avons utilisé une base de données. À ce stade, nos produits sont criblés d'informations de débogage et de moyens de déterminer ce qui s'est mal passé et où, même au point que certains produits détectent le blocage, collectent des informations, nous envoient les résultats par e-mail, puis redémarrent.
En travaillant sur le serveur un jour, en collectant toujours des informations de débogage des applications pendant leur plantage, en essayant de comprendre ce qui se passait, le serveur BSoD sur moi. Lorsque le serveur est revenu en ligne, j'ai ouvert le minidump dans WinDbg pour comprendre quel était le pilote incriminé. J'ai obtenu le nom du fichier et je l'ai retracé jusqu'au fichier réel. Après avoir examiné les informations de version dans le fichier, j'ai compris qu'elles faisaient partie de la suite antivirus McAfee installée sur l'ordinateur.
Nous avons désactivé l'antivirus et n'avons eu aucun problème depuis !!
Je veux juste signaler un bug assez courant et désagréable qui peut se produire en ce moment dans la zone google:
collage de code et infâme infâme
C'est alors que vous copiez collez du code avec un moins dedans, au lieu d'un ASCII caractère trait d'union-moins (' - ') .
Plus, moins (U + 2212), trait d'union-moins (U + 002D)
Maintenant, même si le moins est censé être rendu plus long que le trait d'union moins, sur certains éditeurs (ou sur une fenêtre du shell DOS), en fonction du jeu de caractères utilisé, il est en fait rendu sous la forme d'un signe trait d'union `` - '' normal.
Et ... vous pouvez passer des heures à essayer de comprendre pourquoi ce code ne se compile pas, en supprimant chaque ligne une par une, jusqu'à ce que vous trouviez la cause réelle!
Ce n'est peut-être pas le bug le plus dur, mais assez frustrant;)
(Merci ShreevatsaR pour avoir repéré l'inversion dans mon message d'origine - voir les commentaires)
La première était que notre produit publié présentait un bogue, mais lorsque j'ai essayé de déboguer le problème, il ne s'est pas produit. Je pensais que c'était une chose "release vs debug" au début - mais même quand j'ai compilé le code en mode release, je n'ai pas pu reproduire le problème. Je suis allé voir si un autre développeur pouvait reproduire le problème. Nan. Après beaucoup d'investigations (produisant une liste mixte de code d'assemblage/code C) de la sortie du programme et parcourant le code d'assemblage du produit sorti (beurk!), J'ai trouvé la ligne incriminée. Mais la ligne me semblait très bien! J'ai ensuite dû rechercher ce que les instructions d'assemblage faisaient - et bien sûr, la mauvaise instruction d'assemblage se trouvait dans l'exécutable publié. Ensuite, j'ai vérifié l'exécutable produit par mon environnement de génération - il contenait les instructions d'assemblage correctes. Il s'est avéré que la machine de construction était en quelque sorte corrompue et produisait un mauvais code d'assemblage pour une seule instruction pour cette application. Tout le reste (y compris les versions précédentes de notre produit) a produit un code identique à celui des autres machines des développeurs. Après avoir montré mes recherches au gestionnaire de logiciels, nous avons rapidement reconstruit notre machine de génération.
Quelque part au fond des entrailles d'une application en réseau se trouvait la ligne (simplifiée):
if (socket = accept() == 0)
return false;
//code using the socket()
Que s'est-il passé lorsque l'appel a réussi? socket
a été défini sur 1. Que fait send()
quand on lui donne un 1? (comme dans:
send(socket, "mystring", 7);
Il imprime dans stdout
... ce que j'ai trouvé après 4 heures de me demander pourquoi, avec toutes mes printf()
s supprimées, mon application imprimait dans la fenêtre du terminal au lieu d'envoyer les données sur le réseau.
Avec FORTRAN sur un mini-ordinateur Data General dans les années 80, nous avons eu un cas où le compilateur faisait en sorte qu'une constante 1 (un) soit traitée comme 0 (zéro). Cela s'est produit parce qu'un ancien code passait une constante de valeur 1 à une fonction qui déclarait la variable comme paramètre FORTRAN, ce qui signifiait qu'elle était (supposée être) immuable. En raison d'un défaut dans le code, nous avons effectué une affectation à la variable de paramètre et le compilateur a joyeusement changé les données dans l'emplacement de mémoire qu'il a utilisé pour une constante de 1 à 0.
Beaucoup de fonctions non liées plus tard, nous avions du code qui faisait une comparaison avec la valeur littérale 1 et le test échouait. Je me souviens avoir regardé ce code le plus longtemps dans le débogueur. J'imprimerais la valeur de la variable, ce serait 1 mais le test 'if (foo .EQ. 1)' échouerait. Il m'a fallu beaucoup de temps avant de penser à demander au débogueur d'imprimer ce qu'il pensait être la valeur 1. Il a ensuite fallu beaucoup de cheveux pour remonter dans le code pour savoir quand la constante 1 est devenue 0.
Pas très dur, mais j'ai beaucoup ri quand on l'a découvert.
Lorsque je maintenais un système de traitement des commandes 24/7 pour une boutique en ligne, un client s'est plaint que sa commande était "tronquée". Il a affirmé que même si la commande qu'il avait passée contenait en fait N positions, le système acceptait beaucoup moins de positions sans aucun avertissement.
Après avoir retracé le flux des commandes dans le système, les faits suivants ont été révélés. Il y avait une procédure stockée chargée de stocker les articles de commande dans la base de données. Il a accepté une liste d'éléments de commande sous forme de chaîne, qui codait la liste de (product-id, quantity, price)
des triplets comme celui-ci:
"<12345, 3, 19,99> <56452, 1, 8,99> <26586, 2, 12,99>"
Maintenant, l'auteur de la procédure stockée était trop intelligent pour recourir à quelque chose comme l'analyse et la boucle ordinaires. Il a donc directement transformé la chaîne en instruction SQL multi-insert en remplaçant "<"
avec "insert into ... values ("
et ">"
avec ");"
. Ce qui était très bien et dandy, si seulement il ne stockait pas la chaîne résultante dans une variable varchar (8000)!
Ce qui s'est passé, c'est que son "insert ...; insert ...;"
a été tronqué au 8000e caractère et pour cet ordre particulier, la coupure a eu la chance d'avoir lieu entre insert
s, de sorte que le SQL tronqué est resté syntaxiquement correct.
Plus tard, j'ai découvert que l'auteur de sp était mon patron.
J'ai eu un bug dans un jeu de console qui ne s'est produit qu'après que vous vous êtes battu et a remporté une longue bataille de boss, puis seulement environ 1 fois sur 5. Lorsqu'il se déclenchait, le matériel était à 100% coincé et incapable de parler au monde extérieur du tout.
C'était le bug le plus timide que j'aie jamais rencontré; modifier, automatiser, instrumenter ou déboguer le boss-battle cacherait le bug (et bien sûr, je devrais faire 10 à 20 runs pour déterminer que le bug avait été caché).
À la fin, j'ai trouvé le problème (un truc de cache/DMA/course d'interruption) en lisant le code encore et encore pendant 2-3 jours.
C'est de retour quand je pensais que le C++ et les montres numériques étaient plutôt soignés ...
J'ai la réputation de pouvoir résoudre les fuites de mémoire difficiles. Une autre équipe a eu une fuite qu'elle n'a pas pu retrouver. Ils m'ont demandé d'enquêter.
Dans ce cas, il s'agissait d'objets COM. Au cœur du système se trouvait un composant qui donnait de nombreux petits objets COM sinueux qui avaient tous la même apparence. Chacun a été remis à de nombreux clients différents, chacun étant responsable de faire AddRef()
et Release()
le même nombre de fois.
Il n'y avait aucun moyen de calculer automatiquement qui avait appelé chaque AddRef
et s'ils avaient Release
d.
J'ai passé quelques jours dans le débogueur, écrivant des adresses hexadécimales sur de petits morceaux de papier. Mon bureau en était couvert. Enfin, j'ai trouvé le coupable. L'équipe qui m'a demandé de l'aide était très reconnaissante.
Le lendemain, je suis passé à une langue GC'd. *
(* Pas vraiment vrai, mais ce serait une bonne fin à l'histoire.)
En testant de nouvelles fonctionnalités que j'avais récemment ajoutées à une application de trading, j'ai remarqué que le code pour afficher les résultats d'un certain type de trading ne fonctionnerait jamais correctement. Après avoir regardé le système de contrôle des sources, il était évident que ce bug existait depuis au moins un an, et j'ai été étonné qu'aucun des commerçants ne l'ait jamais repéré.
Après avoir laissé perplexe pendant un certain temps et vérifié auprès d'un collègue, j'ai corrigé le bogue et j'ai continué à tester mes nouvelles fonctionnalités. Environ 3 minutes plus tard, mon téléphone a sonné. À l'autre bout de la ligne se trouvait un trader furieux qui se plaignait que l'un de ses métiers ne s'affichait pas correctement.
Après une enquête plus approfondie, j'ai réalisé que le commerçant avait été touché avec exactement le même bug que j'avais remarqué dans le code 3 minutes plus tôt. Ce bug traînait depuis un an, attendant juste qu'un développeur vienne le repérer pour qu'il puisse frapper pour de vrai.
Ceci est un bon exemple d'un type de bogue connu sous le nom de Schroedinbug . Alors que la plupart d'entre nous ont entendu parler de ces entités particulières, c'est un sentiment étrange lorsque vous en rencontrez réellement une dans la nature.
Bryan Cantrill de Sun Microsystems a présenté un excellent exposé technique de Google sur un bogue qu'il a détecté à l'aide d'un outil qu'il a aidé à développer appelé dtrace.
Le The Tech Talk est drôle, geek, informatif et très impressionnant (et long , environ 78 minutes).
Je ne donnerai pas de spoilers ici sur ce qu'était le bug, mais il commence à révéler le coupable vers 53h00.
Lorsque le lapin de compagnie du client a rongé à mi-chemin le câble Ethernet. Oui. C'était mauvais.
Les deux bogues les plus difficiles qui me viennent à l'esprit étaient tous deux dans le même type de logiciel, un seul dans la version Web et un dans la version Windows.
Ce produit est un visualiseur/éditeur de plan d'étage. La version Web a un front-end flash qui charge les données au format SVG. Maintenant, cela fonctionnait bien, seulement parfois le navigateur se bloquait. Uniquement sur quelques dessins, et uniquement lorsque vous avez déplacé la souris un peu sur le dessin. J'ai réduit le problème à une seule couche de dessin, contenant 1,5 Mo de données SVG. Si je ne prenais qu'une sous-section des données, n'importe quelle sous-section, le blocage ne se produisait pas. Finalement, il m'est apparu que le problème était probablement qu'il y avait plusieurs sections différentes dans le fichier qui en combinaison causaient le bogue. Effectivement, après avoir supprimé au hasard des sections de la couche et testé le bogue, j'ai trouvé la combinaison offensante d'instructions de dessin. J'ai écrit une solution de contournement dans le générateur SVG, et le bogue a été corrigé sans changer une ligne d'actionscript.
Dans le même produit côté Windows, écrit en Delphi, nous avons eu un problème comparable. Ici, le produit prend des fichiers DXF autocad, les importe dans un format de dessin interne et les rend dans un moteur de dessin personnalisé. Cette routine d'importation n'est pas particulièrement efficace (elle utilise beaucoup de copie de sous-chaîne), mais elle fait le travail. Seulement dans ce cas, ce n'était pas le cas. Un fichier de 5 mégaoctets est généralement importé en 20 secondes, mais sur un fichier, cela a pris 20 minutes, car l'empreinte mémoire montait à un gigaoctet ou plus. Au début, cela ressemblait à une fuite de mémoire typique, mais les outils de fuite de mémoire l'ont signalé propre, et l'inspection manuelle du code n'a rien révélé non plus. Le problème s'est avéré être un bogue dans l'allocateur de mémoire de Delphi 5. Dans certaines conditions, que ce fichier particulier recréait dûment, il serait sujet à une grave fragmentation de la mémoire. Le système continuerait d'essayer d'allouer de grandes chaînes et ne trouverait nulle part où les placer, sauf au-dessus du bloc de mémoire alloué le plus élevé. L'intégration d'une nouvelle bibliothèque d'allocation de mémoire a corrigé le bogue, sans modifier une ligne de code d'importation.
En y repensant, les bugs les plus difficiles semblent être ceux dont la correction implique de changer une partie du système différente de celle où le problème se produit.
Eu un bug sur une plate-forme avec un très mauvais débogueur d'appareil. Nous aurions un plantage sur l'appareil si nous ajoutions un printf au code. Il se bloquerait alors à un endroit différent de celui de l'imprimé. Si nous avons déplacé le printf, le le crash se déplacerait ou disparaîtrait. En fait, si nous modifions ce code en réorganisant certaines instructions simples, le crash se produirait dans certains cas sans rapport avec le code que nous avons modifié.
Il s'avère qu'il y avait un bogue dans le relocator pour notre plate-forme. le relocator n'était pas à zéro en initialisant la section ZI mais en utilisant plutôt la table de relocalisation pour initialiser les valeurs. Donc, à chaque fois que la table de relocalisation change dans le binaire, le bogue se déplace. Donc, simplement ajouter un printf changerait la table de relocalisation pour le bogue.
Cela m'est arrivé au moment où je travaillais dans une boutique informatique.
Un client est venu un jour en boutique et nous a dit que son tout nouvel ordinateur fonctionnait bien le soir et la nuit, mais qu'il ne fonctionnait pas du tout le midi ou en fin de matinée. Le problème était que le pointeur de la souris ne bouge pas à ce moment-là.
La première chose que nous avons faite a été de changer sa souris par une nouvelle, mais le problème n'était pas résolu. Bien sûr, les deux souris travaillaient sur le magasin sans faute.
Après plusieurs essais, nous avons constaté que le problème venait de cette marque et de ce modèle de souris. Le poste de travail du client était proche d'une très grande fenêtre et, à midi, la souris était sous la lumière directe du soleil. Son plastique était si mince que dans ces circonstances, il devenait translucide et la lumière du soleil empêchait la roue optomécanique de fonctionner: |
Mon équipe a hérité d'une application Web C++ multi-thread basée sur CGI. La plate-forme principale était Windows; une plate-forme secondaire éloignée était Solaris avec des threads Posix. La stabilité sur Solaris était un désastre, pour une raison quelconque. Nous avons eu diverses personnes qui ont examiné le problème pendant plus d'un an, de temps en temps (principalement éteint), tandis que notre personnel de vente a réussi à pousser la version Windows.
Le symptôme était une stabilité pathétique: un large éventail de plantages du système avec peu de rime ou de raison. L'application a utilisé à la fois Corba et un protocole local. Un développeur est même allé jusqu'à supprimer l'intégralité du sous-système Corba comme une mesure désespérée: pas de chance.
Enfin, un développeur senior et original s'est interrogé à haute voix sur une idée. Nous l'avons étudié et avons finalement trouvé le problème: sur Solaris, il y avait un paramètre de compilation (ou d'exécution?) Pour ajuster la taille de la pile pour l'exécutable. Il a été mal réglé: beaucoup trop petit. Ainsi, l'application manquait de pile et imprimait des traces de pile qui étaient des harengs rouges totaux.
Ce fut un vrai cauchemar.
Leçons apprises:
Le message d'Adam Liss ci-dessus parlant du projet sur lequel nous avons tous les deux travaillé, m'a rappelé un bug amusant avec lequel je devais faire face. En fait, ce n'était pas un bug, mais nous y reviendrons dans une minute.
Résumé de l'application au cas où vous n'avez pas encore vu le message d'Adam: logiciel d'automatisation de la force de vente ... sur les ordinateurs portables ... fin de journée, ils ont composé ... pour se synchroniser avec la base de données Mother.
Un utilisateur s'est plaint que chaque fois qu'il tentait de se connecter, l'application se bloquait. Les gens du support client sont passés par toutes leurs astuces de diagnostic généralement par téléphone, et ils n'ont rien trouvé. Donc, ils ont dû céder à l'ultime: avoir l'utilisateur FedEx l'ordinateur portable dans nos bureaux. (C'était très important, car la base de données locale de chaque ordinateur portable était personnalisée pour l'utilisateur, donc un nouvel ordinateur portable devait être préparé, expédié à l'utilisateur pour qu'il puisse l'utiliser pendant que nous travaillions sur son original, puis nous devions échanger et demandez-lui enfin de synchroniser les données sur le premier ordinateur portable d'origine).
Ainsi, lorsque l'ordinateur portable est arrivé, il m'a été donné de comprendre le problème. Maintenant, la synchronisation impliquait de connecter la ligne téléphonique au modem interne, d'aller à la page "Communication" de notre application et de sélectionner un numéro de téléphone dans une liste déroulante (avec le dernier numéro utilisé présélectionné). Les numéros dans le DDL faisaient partie de la personnalisation et étaient essentiellement le numéro du bureau, le numéro du bureau préfixé de "+1", le numéro du bureau préfixé de "9 ," au cas où ils seraient appeler depuis un hôtel, etc.
Donc, je clique sur l'icône "COMM" et j'appuie sur Retour. Il a composé un numéro, s'est connecté à un modem - puis s'est immédiatement écrasé. Je me suis fatigué plusieurs fois. Répétabilité à 100%.
Ainsi, un accroché une portée de données entre l'ordinateur portable et la ligne téléphonique, et regarda les données traversant la ligne. Cela avait l'air plutôt étrange ... La partie la plus étrange était que je pouvais le lire!
L'utilisateur avait apparemment voulu utiliser son ordinateur portable pour se connecter à un système BBS local, et donc, changer la configuration de l'application pour utiliser le numéro de téléphone du BBS au lieu de celui de l'entreprise. Notre application attendait notre protocole binaire propriétaire - pas de longs flux de texte ASCII. Tampons débordés - KaBoom!
Le fait qu'un problème de numérotation ait commencé immédiatement après avoir changé le numéro de téléphone pourrait donner à l'utilisateur moyen un indice qu'il s'agissait du cause du problème, mais ce type ne l'a jamais mentionné.
J'ai fixé le numéro de téléphone et l'ai renvoyé à l'équipe de support, avec une note élisant le gars "l'utilisateur Bonehead de la semaine". (*)
(*) OkOkOk ... Il y a probablement une très bonne chance que ce qui s'est réellement passé en ce que l'enfant du gars, voyant son père composer tous les soirs, ait pensé que c'est aussi comme ça que vous appelez chez BBS, et a changé le numéro de téléphone parfois quand il était seul à la maison avec l'ordinateur portable. Quand il s'est écrasé, il n'a pas voulu admettre qu'il avait touché l'ordinateur portable, encore moins l'avoir cassé; alors il l'a juste mis de côté et n'en a parlé à personne.
C'était lors de ma thèse de diplôme. J'écrivais un programme pour simuler l'effet d'un laser à haute intensité sur un hélium atom using FORTRAN.
Un essai a fonctionné comme ceci:
Celles-ci devraient être constantes au total, mais elles ne l'étaient pas. Ils ont fait toutes sortes de choses étranges.
Après avoir débogué pendant deux semaines, je suis devenu fou sur la journalisation et journalisé chaque variable à chaque étape de la simulation, y compris les constantes.
De cette façon, j'ai découvert que j'avais écrit à la fin d'un tableau, ce qui a changé une constante!
Un ami a dit qu'il avait une fois changé le littéral 2 avec une telle erreur.
J'ai entendu parler d'un bug classique au lycée; un terminal auquel vous ne pouviez vous connecter que si vous étiez assis sur la chaise devant lui. (Il rejetterait votre mot de passe si vous étiez debout.)
Il s'est reproduit de manière assez fiable pour la plupart des gens; vous pouvez vous asseoir sur la chaise, vous connecter, vous déconnecter ... mais si vous vous levez, vous êtes refusé à chaque fois.
Finalement, il s'est avéré qu'un imbécile avait échangé quelques touches adjacentes du clavier, E/R et C/V IIRC, et lorsque vous vous êtes assis, vous avez tapé au clavier et vous êtes entré, mais quand vous vous êtes levé, vous avez dû chasser ' n picorer, donc vous avez regardé les étiquettes incorrects et échoué.
Un blocage dans mon premier programme multi-thread!
Il était très difficile de le trouver car cela s'est produit dans un pool de threads. Parfois, un fil dans la piscine se bloquait mais les autres fonctionnaient toujours. Étant donné que la taille de la piscine était beaucoup plus grande que nécessaire, il a fallu une semaine ou deux pour remarquer le premier symptôme: l'application était complètement bloquée.
Fondamentalement, tout ce qui implique des threads.
J'ai occupé un poste dans une entreprise dans laquelle j'avais la distinction douteuse d'être l'une des seules personnes suffisamment à l'aise avec le filetage pour déboguer des problèmes désagréables. L'horreur. Vous devriez avoir à obtenir une sorte de certification avant d'être autorisé à écrire du code threadé.
J'ai passé des heures à des jours à déboguer un certain nombre de choses qui ont fini par être réparables avec seulement quelques caractères.
Quelques exemples divers:
ffmpeg a cette habitude désagréable de produire un avertissement concernant le "recadrage des cerveaux" (se référant à un cas où les valeurs de recadrage dans le cours d'eau sont> = 16) lorsque les valeurs de recadrage dans le cours d'eau étaient en fait parfaitement valides. Je l'ai corrigé en ajoutant trois caractères: "h->".
x264 avait un bug où, dans des cas extrêmement rares (un sur un million d'images) avec certaines options, il produisait un bloc aléatoire de couleur complètement incorrecte. J'ai corrigé le bug en ajoutant la lettre "O" à deux endroits dans le code. Il s'est avéré que j'avais mal orthographié le nom d'un #define dans un commit précédent.
Mon premier "vrai" travail a été pour une entreprise qui a écrit un logiciel d'automatisation des forces de vente client-serveur. Nos clients ont exécuté l'application client sur leurs ordinateurs portables (15 livres) et, à la fin de la journée, ils se sont connectés à nos serveurs Unix pour se synchroniser avec la base de données Mother. Après une série de plaintes, nous avons constaté qu'un nombre astronomique d'appels diminuait au tout début, lors de l'authentification.
Après des semaines de débogage, nous avons découvert que l'authentification toujours échouait si l'appel entrant était répondu par un processus getty sur le serveur dont l'ID de processus contenait un numéro pair suivi immédiatement d'un 9. Il s'avère que l'authentification était un schéma homebrew qui dépendait d'une représentation sous forme de chaîne de 8 caractères du PID; un bogue a provoqué un PID incriminé de planter le getty, qui a réapparu avec un nouveau PID. Le deuxième ou le troisième appel a généralement trouvé un PID acceptable, et la recomposition automatique a rendu inutile l'intervention des clients, donc cela n'a pas été considéré comme un problème important jusqu'à ce que les factures de téléphone arrivent à la fin du mois.
Le "correctif" (ahem) consistait à convertir le PID en une chaîne représentant sa valeur en octal plutôt qu'en décimal, ce qui rend impossible de contenir un 9 et inutile de résoudre le problème sous-jacent.
Notre interface réseau, une carte ATM compatible DMA, fournirait très occasionnellement des données corrompues dans les paquets reçus. Le CRC AAL5 avait vérifié comme étant correct lorsque le paquet venait du fil, mais les données DMAd en mémoire seraient incorrectes. La somme de contrôle TCP l'attraperait généralement, mais à l'époque grisante de l'ATM, les gens étaient enthousiastes à l'idée d'exécuter des applications natives directement sur AAL5, sans passer par TCP/IP. Nous avons finalement remarqué que la corruption uniquement s'est produite sur certains modèles du poste de travail du vendeur (qui doit rester anonyme), pas sur d'autres.
En calculant le CRC dans le logiciel du pilote, nous avons pu détecter les paquets corrompus, au prix d'un énorme impact sur les performances. En essayant de déboguer, nous avons remarqué que si nous stockions simplement le paquet pendant un certain temps et revenions plus tard, la corruption des données se corrigerait comme par magie. Le contenu du paquet serait bien, et si le conducteur calculait le CRC une deuxième fois, il irait bien.
Nous avions trouvé un bogue dans le cache de données d'un processeur d'expédition. Le cache de ce processeur n'était pas cohérent avec DMA, ce qui obligeait le logiciel à le vider explicitement au bon moment. Le bogue était que, parfois, le cache ne vidait pas son contenu lorsqu'on lui demandait de le faire.
Bien que je ne me souvienne pas d'un cas spécifique, la catégorie la plus difficile est celle des bogues qui ne se manifestent qu'après que le système a fonctionné pendant des heures ou des jours, et lorsqu'il tombe en panne, ne laisse que peu ou pas de trace de ce qui a causé le crash. Ce qui les rend particulièrement mauvais, c'est que peu importe à quel point vous pensez avoir raisonné la cause et appliqué la solution appropriée pour y remédier, vous devrez attendre encore quelques heures ou jours pour avoir la moindre confiance que vous 'ai vraiment réussi.
Le bug le plus difficile que j'ai jamais eu à résoudre était celui que j'avais moi-même soulevé - j'ai contracté en tant que testeur pour une grande entreprise de télécommunications, testant le produit d'une autre entreprise. Plusieurs années plus tard, j'avais un contrat avec l'autre société et la première chose qu'ils m'ont donnée était les bugs que j'avais moi-même soulevés.
Il s'agissait d'une condition de concurrence critique du noyau dans un système d'exploitation embarqué écrit en assembleur 6809 et BCPL. L'environnement de débogage consistait en un printf spécial qui écrivait sur un périphérique série; pas de fantaisie IDE stuff dans cette configuration.
Il a fallu un certain temps pour corriger, mais ce fut une énorme augmentation de la satisfaction quand je l'ai finalement déniché.
Dans CS435 de retour à Purdue, nous avons dû écrire un raytracer pour notre projet final. Tout ce que je produisais avait une forte teinte orange, mais je pouvais voir chacun des objets de ma scène. J'ai finalement abandonné et l'ai soumis tel quel, et j'ai demandé au professeur de regarder mon code pour trouver le bug, et quand il ne l'a pas trouvé, j'ai passé la majeure partie de l'été à creuser pour trouver ce qui n'allait pas.
Enfoui profondément dans le code, dans le cadre d'une fonction de calcul des couleurs, j'ai finalement réalisé que je divisais un entier et le passais à une fonction OpenGL qui attendait une valeur flottante. L'un des composants de couleur était juste assez bas dans la majeure partie de la scène pour être arrondi à 0, provoquant la teinte orange. Le lancer sur un flotteur à un seul endroit (avant la division) a corrigé le bug.
Vérifiez toujours vos entrées et les types attendus.
C'était un crash de violation d'accès.
à partir du vidage sur incident, je pouvais seulement comprendre qu'un paramètre de la pile d'appels était corrompu.
La raison était ce code :
n = strlen(p->s) - 1;
if (p->s[n] == '\n')
p->s[n] = '\0';
si la longueur de la chaîne était 0 et que le paramètre de la pile ci-dessus se trouve à l'adresse 0x0Axxxxxxx
==> corruption de pile
Heureusement, ce code était assez proche de l'emplacement de crash actuall, donc parcourir le code source (laid) était le moyen de trouver le culrpit
Je travaille pour un grand collège communautaire et nous sommes passés de Blackboard à Moodle l'année dernière. Moodle utilise la nomenclature des "cours" et des "groupes". Un cours pourrait être Microeconomics ECO-150, par exemple, et les groupes sont ce que nous appellerions des sections (OL1, OL2, 01, 14, W09 comme exemples).
Quoi qu'il en soit, nous sommes primitifs. Nous n'avons même pas LDAP. Tout est constitué de fichiers texte, de feuilles de calcul Excel et de Gd bases de données Microsoft Access. Mon travail consiste à créer une application Web qui prend tout ce qui précède en entrée et produit encore plus de fichiers texte que ce qui peut ensuite être téléchargé dans Moodle pour créer des cours, des groupes dans les cours et les utilisateurs et placer les utilisateurs dans les cours et les groupes. L'ensemble de la configuration est positivement byzantin, avec environ 17 étapes individuelles qui doivent être effectuées dans l'ordre. Mais la chose fonctionne et remplace un processus qui prenait auparavant des jours pendant la période la plus occupée du semestre.
Mais il y avait un problème. Parfois, nous obtenions ce que j'appelais des "groupes fous". Ainsi, au lieu de créer un cours avec 4 groupes de 20 étudiants chacun, cela créerait un cours avec 80 groupes de 1 étudiant chacun. Le pire, il n'y a aucun moyen, par programmation, d'entrer dans cpanel (auquel je n'ai pas accès) pour supprimer un groupe une fois qu'il est créé. Il s'agit d'un processus manuel qui prend environ 5 clics sur les boutons. Donc, chaque fois qu'un cours avec Crazy Groups était créé, je devais soit supprimer le cours, ce qui est préférable mais pas une option si l'enseignant avait déjà commencé à mettre du contenu dans le cours, ou je devais passer une heure de façon répétitive en suivant le même schéma: Sélectionnez le groupe, affichez le groupe, modifiez le groupe, supprimez le groupe. Voulez-vous vraiment supprimer le groupe? Oui, bon sang!
Et il n'y avait aucun moyen de savoir si des groupes fous s'étaient produits à moins que vous n'ouvriez manuellement chaque cours et que vous regardiez (avec des centaines de cours) ou jusqu'à ce que vous receviez une plainte. Crazy Groups semblait se produire de manière aléatoire et Google et les forums Moodle n'étaient d'aucune aide, il semble que tout le monde utilise cette chose appelée LDAP ou une base de données REAL afin qu'ils n'aient jamais rencontré le problème.
Enfin, après je ne sais pas combien de temps pour enquêter et supprimer plus de groupes fous que je ne veux l'admettre, je l'ai compris. C'était un bug dans Moodle pas mon code! Cela ne m'a pas donné un peu de plaisir. Vous voyez que la façon de créer un groupe est simplement d'essayer d'inscrire quelqu'un dans le groupe et si le groupe n'existe pas déjà, Moodle le crée. Et cela a bien fonctionné pour les groupes nommés OL1 ou W12 ou même SugarCandyMountain mais si vous avez essayé de créer un groupe avec un numéro comme nom, dites 01 ou 14 C'EST à ce moment-là que des groupes fous se produiraient. Moodle ne compare pas correctement les nombres sous forme de chaînes. Peu importe le nombre de groupes nommés 01 dans un cours, il pensera toujours que le groupe n'existe pas encore et le créera donc. Voilà comment vous vous retrouvez avec 80 groupes avec 1 personne dans chacun.
Fier de ma découverte, je suis allé sur le forum Moodle et j'ai publié mes résultats avec les étapes pour reproduire le problème à volonté. C'était il y a environ un an et le problème existe toujours à l'intérieur de Moodle à ma connaissance, personne ne semble motivé à le résoudre car personne d'autre que nous primitifs n'utilise l'inscription de fichier texte. Ma solution, simplement pour m'assurer que tous les noms de nos groupes contiennent au moins 1 caractère non numérique. Les groupes fous ont disparu pour toujours au moins pour nous, mais je pense à ce gars qui travaille dans un collège communautaire de la Mongolie extérieure qui vient de télécharger l'équivalent d'un semestre de cours et est sur le point d'avoir un réveil brutal. Au moins cette fois, Google peut l'aider parce que je lui ai écrit ce message dans une bouteille sur les marées du cyberespace.
Grâce à un éclair d'inspiration, cela n'a pas pris trop de temps à retrouver, mais c'était quand même un peu étrange. Petite application, uniquement utilisée par d'autres personnes du service informatique. Il se connecte à son tour à tous les PC de bureau du domaine. Beaucoup sont désactivés et la connexion met AGES à expirer, elle s'exécute donc sur le pool de threads. Il analyse simplement AD et met en file d'attente des milliers d'éléments de travail dans le pool de threads. Tout fonctionnait bien. Quelques années plus tard, je parlais à un autre membre du personnel qui utilise réellement cette applaudissement et il a mentionné que cela rendait le PC inutilisable. Pendant qu'il s'exécutait, essayer d'ouvrir des pages Web ou parcourir un lecteur réseau prendrait quelques minutes, ou tout simplement ne se produirait jamais.
le problème s'est avéré être la limite tcp à moitié ouverte de XP. Les PC d'origine étaient à double processeur, donc .NET alloue 50 (ou 100, pas sûr) threads au pool, pas de problème. Nous avons maintenant un processeur dual core dual core, nous avons maintenant plus de threads dans le pool de threads que vous ne pouvez avoir de connexions à moitié ouvertes, de sorte que d'autres activités réseau deviennent impossibles pendant que l'application est en cours d'exécution.
Il est maintenant corrigé, il envoie un ping aux machines avant d'essayer de se connecter à elles, donc le délai est beaucoup plus court et utilise un petit nombre fixe de threads pour effectuer le travail réel.
Conçu une fois un système multithread en temps réel (frémissement) qui interrogeait les images de plusieurs caméras de surveillance réseau et faisait toutes sortes de magie sur les images.
Le bogue a simplement fait planter le système, certaines sections critiques étant bien sûr maltraitées. Je ne savais pas comment déclencher l'échec directement, mais j'ai dû attendre qu'il se produise, ce qui était environ une fois en trois ou quatre jours (cotes: environ 1 sur 15000000 à 30 ips).
J'ai dû préparer tout ce que je pouvais, déboguer les messages de sortie salissant le code, les outils de trace, les outils de débogage à distance sur la caméra et la liste continue. Ensuite, j'ai juste dû attendre deux ou trois jours et espérer attraper toutes les informations pour localiser le mutex défaillant ou autre chose. Il a fallu quatre de ces essais avant de le retrouver, quatre semaines!. Encore une course et j'aurais dépassé le délai client.
Juste avant qu'Internet ne se propage, nous travaillions sur une application de banque à domicile basée sur un modem (la première en Amérique du Nord).
Trois jours avant la sortie, nous étions (presque) dans les temps et prévoyions d'utiliser le temps restant pour tester de manière exhaustive le système. Nous avions un plan de test, et le suivant sur la liste était les communications par modem.
À peu près à ce moment-là, notre client s'est précipité pour vouloir une mise à niveau de dernière minute. Bien sûr, j'étais complètement contre cela, mais j'ai été rejeté. Nous avons brûlé l'huile de minuit pendant trois jours en ajoutant la chose stupide et l'avons fait fonctionner à la date de sortie. Nous avons respecté la date limite et livré plus de 2000 disquettes aux clients.
Le lendemain de la sortie, je suis revenu à mon calendrier de test et j'ai recommencé à tester le module de communication modem. À ma grande surprise, j'ai trouvé que le modem ne parviendrait pas à se connecter de manière aléatoire. À peu près à ce moment-là, nos téléphones ont commencé à sonner, les clients en colère ne pouvant pas utiliser leur application.
Après beaucoup de coups de dents et de cheveux, j'ai retracé le problème jusqu'à l'initialisation du port série. Un programmeur junior avait commenté une écriture dans l'un des registres de contrôle. Le registre est resté non initialisé, et il y avait environ 10% de chances qu'il contienne une valeur non valide - selon la configuration de l'utilisateur et les applications qu'il avait exécutées auparavant.
Interrogé à ce sujet, le programmeur a affirmé que cela fonctionnait sur sa machine.
Nous avons donc dû re-graver ces 2000+ disquettes, et traquer chaque client pour les rappeler. Pas une chose amusante à faire, surtout avec une équipe déjà épuisée.
Nous avons pris un gros coup sur celui-là. Notre client a affirmé que parce que c'était notre bug, nous devrions avoir à absorber le coût du rappel. Notre calendrier pour la prochaine version a été reporté d'un mois. Et notre relation avec le client a été ternie.
De nos jours, je suis beaucoup moins flexible avec les ajouts de dernière minute et j'essaie de mieux communiquer avec mon équipe.
Dans un jeu sur lequel je travaillais, un Sprite particulier ne s'afficherait plus en mode Release, mais fonctionnait bien en mode Debug, et seulement dans une édition particulière. Un autre programmeur a essayé de trouver ce bug pendant 2 jours, puis est parti en vacances. Il s'est retrouvé sur mes épaules pour essayer de trouver le bug ~ 5 heures avant sa sortie.
Depuis que la version Debug a fonctionné, j'ai dû déboguer avec la version Release. Visual Studio prend en charge certains débogages dans la version Release, mais vous ne pouvez pas vous fier à tout ce que le débogueur vous dit d'être correct (en particulier avec les paramètres d'optimisation agressifs que nous utilisions). Par conséquent, j'ai dû parcourir des listes de demi-code et des listes de demi-assembleur, regardant parfois des objets directement dans le vidage hexadécimal au lieu de dans la vue du débogueur bien formatée.
Après avoir passé un certain temps à m'assurer que tous les appels de tirage corrects étaient effectués, j'ai découvert que la couleur du matériau du Sprite était incorrecte - elle était censée être orange à pleine opacité, mais était plutôt définie sur noir et complètement transparente. La couleur a été récupérée à partir d'une palette résidant dans un tableau const dans notre classe EditionManager. Il a été initialement configuré comme la bonne couleur orange, mais lorsque la couleur réelle a été récupérée du code de dessin Sprite, c'était à nouveau ce noir transparent. J'ai défini un point d'arrêt mémoire, qui a été déclenché dans le constructeur EditionManager. Une écriture dans un tableau différent a entraîné la modification de la valeur dans le tableau de palettes.
Il s'avère que l'autre programmeur a changé une énumération essentielle du système:
enum {
EDITION_A = 0,
EDITION_B,
//EDITION_DEMO,
EDITION_MAX,
EDITION_DEMO,
};
Il met EDITION_DEMO
juste après EDITION_MAX
, et le tableau en cours d'écriture a été indexé avec EDITION_DEMO
donc il a débordé dans la palette et y a défini les mauvaises valeurs. Cependant, je ne pouvais pas changer l'énumération, car les numéros d'édition ne pouvaient plus changer (ils étaient utilisés dans la transmission binaire). J'ai donc fini par faire un EDITION_REAL_MAX
entrée dans l'énumération et en l'utilisant comme taille de tableau.
Une application multi-thread où l'exécution en débogage est très bien mais dès que vous exécutez en version cela tourne mal à cause d'un timing légèrement différent. Même l'ajout d'appels Console.WriteLine à la sortie de débogage de base du produit a provoqué suffisamment de changement de timing pour qu'il fonctionne et ne montre pas le problème. Outil par semaine pour trouver et corriger quelques lignes de code qui devaient être modifiées.
il y a longtemps, j'ai écrit un langage orienté objet en utilisant C et une bibliothèque de formulaires (basée sur les caractères); chaque formulaire était un objet, les formulaires pouvaient contenir des sous-formulaires, etc. L'application de facturation complexe écrite à l'aide de cela fonctionnerait correctement pendant environ 20 minutes, puis des caractères aléatoires aléatoires apparaîtraient de temps en temps à l'écran. Après quelques minutes supplémentaires d'utilisation de l'application, la machine redémarrerait, se bloquerait ou quelque chose de radical.
cela s'est avéré être une mauvaise désallocation résultant d'une délégation mal dirigée dans le moteur de traitement des messages; les messages mal acheminés étaient délégués dans l'arborescence de confinement lorsque nous manquions de superclasses, et parfois les objets parents avaient des méthodes avec le même nom, donc cela semblait fonctionner la plupart du temps. Le reste du temps, il désallouait un petit tampon (8 octets environ) dans le mauvais contexte. Le pointeur mal désalloué était en fait une mémoire morte utilisée par un compteur intermédiaire pour une autre opération, donc sa valeur avait tendance à converger vers zéro après le temps.
oui, le mauvais pointeur traverserait la zone de la carte mémoire de l'écran en route vers la page zéro, où il a finalement écrasé un vecteur d'interruption et tué le PC
c'était bien avant les outils de débogage modernes, donc comprendre ce qui se passait a pris quelques semaines ...
Pas un des miens, mais un collègue d'un ancien lieu de travail a passé 3 jours à déboguer son contrôle d'éditeur de pop-up JavaScript (il y a longtemps, avant les joies des frameworks), pour constater qu'il manquait un seul point-virgule à mi-chemin l'un de ses énormes fichiers de base.
Nous l'avons surnommé "le point-virgule le plus cher du monde", mais je suis sûr qu'il y a eu bien pire à travers l'histoire!
Une course entre la méthode ToString de la classe OracleDecimal d'Oracle (qui P/Invoque la version native de la même fonctionnalité) et le garbage collector provoqué par un appel GC.KeepAlive manquant qui peut provoquer OracleDecimal.ToString () pour renvoyer essentiellement indésirable arbitraire si son espace de mémoire arrive à être écrasé avant la fin de l'appel.
J'ai écrit un rapport de bug détaillé et je n'ai jamais entendu parler, pour autant que je sache, cela est toujours là. J'ai même eu un harnais de test qui n'a fait que créer de nouvelles représentations OracleDecimal du numéro 1, appeler ToString sur eux et comparer le résultat avec "1". Il échouerait tous les dix millions de fois environ avec un charabia fou (nombres énormes, nombres négatifs et même chaînes de caractères indésirables alphanumériques).
Soyez prudent avec vos appels P/Invoke! Il est légal pour le garbage collector .NET de collecter votre instance pendant qu'un appel à une méthode d'instance sur cette instance est toujours en attente, tant que la méthode d'instance a fini d'utiliser la référence this
.
Reflector est une bouée de sauvetage absolue pour des trucs comme ça.
Une boîte s'était écrasée sur le site d'un gros client, et nous avons dû nous connecter via une session WebX à l'ordinateur d'un informaticien, qui était connecté à notre boîte. J'ai fouillé pendant environ une heure, saisissant des traces de pile, enregistrant des vidages, des statistiques, des compteurs et vidant des sections de mémoire qui semblaient pertinentes.
Leurs informaticiens m'ont ensuite envoyé par courriel une transcription de ma session, et je me suis mis au travail.
Après quelques heures, je l'avais retracée à un tableau de structures qui contenaient des métadonnées de paquets suivies de données de paquets. L'une des métadonnées du paquet était corrompue et il semblait qu'elle avait été remplacée par quelques octets de données de paquet. Bugzilla n'avait aucune trace de quelque chose de similaire.
En fouillant dans le code, j'ai vérifié toutes les choses évidentes. Le code qui copiait les données des paquets dans le tampon était méticuleux pour ne pas dépasser ses limites: le tampon était la taille MTU de l'interface et la routine de copie vérifiait que les données ne dépassaient pas la taille MTU. Mes vidages de mémoire m'ont permis de valider que, oui, foo-> bar était en effet de 4 lorsque le crash s'est produit. Rien n'a été ajouté. Rien n'était faux d'une manière qui aurait dû causer le problème. Il y avait ce qui ressemblait à 16 octets de données par paquets dans l'en-tête suivant.
Quelques jours plus tard, j'ai commencé à vérifier tout ce à quoi je pouvais penser.
J'ai remarqué que la longueur du tampon de données était correcte. Autrement dit, le nombre d'octets depuis le début des données jusqu'à la fin des données était un MTU, même si l'en-tête suivant a commencé à MTU-16.
Lorsque ces structures ont été malloc'd, des pointeurs vers chaque élément ont été placés dans un tableau, et j'avais vidé ce tableau. J'ai commencé à mesurer la distance entre ces pointeurs. 6888 ... 6888 ... 6888 ... 6872 ... 6904 ... 6880 ... 6880 ...
Attends quoi?
J'ai commencé à regarder les pointeurs internes et les décalages dans les deux structures. Tout s'est ajouté. Cela ressemblait à ma seule mauvaise structure - celle qui avait été partiellement encombrée - n'était que de 16 octets trop tôt en mémoire.
La routine d'allocation a regroupé ces gars comme un morceau, puis les a découpés en boucle:
for (i = 0; i < NUM_ELEMS; i++) {
array[i] = &head[i*sizeof(foo)];
}
(avec des tolérances pour l'alignement, etc.).
Lorsque le tableau a été rempli, la valeur de mon pointeur corrompu doit avoir été lue comme 0x8a112 8 ac au lieu de 0x8a112 9 ac.
Je suis arrivé à la conclusion que j'avais été victime d'une erreur de mémoire de 1 bit lors de l'allocation (je sais, je sais! Je ne le croyais pas non plus, mais nous les avions déjà vus sur ce matériel - valeurs NULL qui ont été lus comme 0x00800000). En tout cas, j'ai réussi à convaincre mon patron et mes collègues qu'il n'y avait pas d'autre explication raisonnable, et que mon explication expliquait exactement ce que nous voyions.
Alors, boîte RMA'd.
C'est un peu hors sujet (c'est pourquoi je l'ai fait communauté).
Mais The Bug par Ellen Ullman est un fantastique livre de fiction sur ce sujet.
Quand j'ai commencé dans l'entreprise pour laquelle je travaille, j'ai fait beaucoup de RCR pour apprendre les produits.
Ce produit intégré écrit dans l'assemblage HC11 avait une fonctionnalité qui se produisait toutes les huit heures. Il s'avère que l'interruption qui a décrémenté la valeur s'est déclenchée pendant le code qui vérifiait le compteur. Giflé quelques CLI/STI autour du code et c'était bien. Je l'ai retrouvé en piratant l'événement pour qu'il se produise deux fois par seconde plutôt que toutes les huit heures.
La leçon que j'ai apprise de ceci était lors du débogage de code qui échoue rarement, je devrais d'abord vérifier les variables utilisées par les interruptions.
Je fréquente actuellement l'université et le bug le plus difficile que j'ai rencontré provenait d'un cours de programmation là-bas. Au cours des deux semestres précédents, nous avons simplement écrit tout notre propre code. Mais pour le troisième semestre, le professeur et TA écriraient la moitié du code, et nous devions écrire l'autre moitié. C'était pour nous aider à apprendre à lire le code.
Notre première mission pour ce semestre a été d'écrire un programme qui simule le fractionnement des gènes d'ADN. Fondamentalement, nous devions simplement trouver une sous-chaîne dans une plus grande et traiter les résultats. Apparemment, le professeur et TA étaient tous les deux occupés cette semaine-là et nous ont donné leur moitié du code sans avoir terminé leur propre mise en œuvre complète. Ils n'avaient pas eu le temps d'écrire l'autre moitié pour agir comme solution. Leur moitié compilerait, mais sans une solution complète codée, il n'y avait aucun moyen pour eux de la tester. On nous a dit de ne pas modifier le code du professeur. Tout le monde dans la classe avait exactement le même bug, mais nous pensions tous que nous faisions tous la même erreur.
Le programme engloutissait des gigaoctets de mémoire, puis s'épuisait et tombait en panne. Nous (les étudiants) avons tous supposé que notre moitié du code devait contenir une fuite de mémoire obscure. Tout le monde dans la classe a parcouru le code pendant deux semaines et l'a exécuté à travers un débogueur encore et encore. Notre fichier d'entrée était une chaîne de 5,7 Mo et nous y trouvions des centaines de sous-chaînes et les stockions. Le code du professeur/TA l'a utilisé.
myString = myString.substr(0,pos);
Vous voyez le problème? Lorsque vous affectez une variable chaîne à sa propre sous-chaîne, la mémoire n'est pas réallouée. C'est une petite quantité d'informations que personne (pas même le professeur ou TA) ne connaissait. Ainsi, myString avait 5,7 Mo de mémoire allouée uniquement pour contenir quelques octets de données réelles. Cela a été répété des centaines de fois; d'où l'utilisation massive de la mémoire. J'ai passé deux semaines sur ce problème. J'ai passé la première semaine à vérifier mon propre code pour les fuites de mémoire. Dans ma frustration, j'ai finalement conclu que la moitié du professeur/TA devait avoir la fuite, alors j'ai passé la deuxième semaine à vérifier leur code. Mais même alors, ça m'a pris tellement de temps à trouver car ce n'était pas techniquement une fuite. Toutes les allocations ont finalement été libérées et le programme a bien fonctionné lorsque nos données d'entrée n'étaient que d'une dizaine de kilo-octets. La seule raison pour laquelle je l'ai trouvé, c'est parce que j'ai envoyé un psycho fou et j'ai décidé d'analyser chaque dernière variable; même les trucs jetables temporaires. Je passais également beaucoup de temps à vérifier combien de caractères la chaîne avait réellement, pas combien était alloué. J'ai supposé que la classe de cordes s'en occupait. Voici la solution, un changement d'une ligne qui a corrigé des semaines de frustration et m'a valu un A sur la tâche pour trouver/corriger le code de l'enseignant.
myString.substr(0,pos).swap(myString);
La méthode swap force une réallocation.
Une application basée sur une base de données héritée (avec seulement une partie de la source disponible) s'est plantée lorsqu'un utilisateur particulier a accédé à une certaine fonctionnalité d'inventaire. Cela fonctionnait parfaitement pour tous les autres utilisateurs. Le profil utilisateur non? Nan. Lors de la connexion en tant qu'utilisateur différent (même en tant qu'administrateur), le même utilisateur avait le même problème.
Problème d'ordinateur? Nan. Le même utilisateur, un PC différent (sous sa connexion ou toute autre connexion) s'est toujours écrasé.
Le problème: lors de la connexion au programme, un écran d'accueil de copyright pouvait être fermé en cliquant sur le "X" pour fermer la fenêtre ou en appuyant sur n'importe quelle touche. Lors de la connexion, cet utilisateur a toujours cliqué sur le "X" où les autres utilisateurs ont toujours appuyé sur une touche. Cela a entraîné une fuite de mémoire, mais uniquement lors de l'accès à la recherche d'inventaire.
Correction: ne cliquez pas sur le X.
cela peut sembler drôle, mais quand j'apprenais, j'ai passé un après-midi entier à essayer de comprendre pourquoi une déclaration if était toujours vraie, j'ai utilisé = au lieu de ==: j'ai tout réécrit deux fois sur un autre ordinateur :)
Un blocage dans un Java Application serveur. Mais pas un simple blocage avec deux threads. J'ai retrouvé un blocage impliquant huit threads. Le thread 1 attend le thread 2 qui attend le thread 3, etc., et enfin le thread 8 attend le thread 1.
Il m'a fallu environ une journée entière pour comprendre ce qui se passait, puis seulement 15 minutes pour le réparer. J'utilise Eclipse pour surveiller environ 40 threads jusqu'à ce que je découvre l'impasse.
En Python, j'avais un fil faisant quelque chose comme ceci:
while True:
with some_mutex:
...
clock.tick(60)
clock.tick(60)
suspend le thread afin qu'il ne s'exécute pas plus de 60 fois par seconde. Le problème était que la plupart du temps, le programme ne montrait qu'un écran noir. Si je l'ai laissé fonctionner pendant un certain temps, il a finalement montré l'écran de jeu.
C'est parce que le thread faisait la pause tout en maintenant le mutex. Ainsi, il a rarement laissé d'autres threads acquérir le mutex. Cela peut sembler évident ici, mais il m'a fallu deux jours pour le comprendre. La solution consiste simplement à supprimer un niveau de retrait:
while True:
with some_mutex:
...
clock.tick(60)
Une fois, j'ai eu un bogue dans une application .NET qui provoquerait le crash du CLR - oui, le CLR se terminerait avec un résultat non nul et il n'y aurait pas d'informations de débogage.
J'ai poivré le code avec des messages de trace de la console en essayant de trouver où était le problème (l'erreur se produirait au démarrage) et j'ai finalement trouvé les quelques lignes à l'origine du problème. J'ai essayé d'isoler le problème, mais chaque fois que je l'ai fait, le cas isolé fonctionnait!
À la fin, j'ai changé le code de:
int value = obj.CalculateSomething();
à
int value;
value = obj.CalculateSomething();
Ne me demandez pas pourquoi, mais cela a fonctionné.
DevExpress XPO parle à une base de données Oracle qui plante brutalement (comme dans: le programme se ferme silencieusement) si le chemin du répertoire dans lequel l'application est installée ne contient pas au moins un espace et que le dictionnaire de données que XPO recherche n'est pas 100% correctement placé dans le base de données.
Problème décrit ici .
Je peux vous dire ceci: j'étais>> près de pleurer quand nous avons compris comment contourner le problème. Je ne sais toujours pas quelle est la cause réelle, réelle, du problème, mais notre produit ne va pas prendre en charge Oracle dans la future version, donc je ne donne plus de ... plus.
Le bogue le plus difficile devrait être quand un programmeur sort dans un journal "General Error!". Après avoir parcouru le code, il était éparpillé partout avec le texte "Erreur générale!". Essayez de clouer celui-ci.
Au moins, écrire une macro pour produire __LINE__ ou __FUNCTION__ aurait été un peu plus utile à ajouter à la sortie de débogage.
Il y avait un code qui fixe une date d'expiration à la date actuelle plus un an en ajoutant 1 à l'année en cours et en gardant le jour et le mois identiques. Cela a échoué le 29 février 2008 car la base de données a refusé d'accepter le 29 février 2009 !!
Je ne sais pas si cela peut être "dur", mais c'était un code étrange qui a été réécrit immédiatement, bien sûr!
Je ne suis pas sûr que ce soit le plus difficile, mais il y a plusieurs années, j'avais un programme Java qui utilisait XMLEncoder
pour enregistrer/charger une classe particulière. Pour une raison quelconque, la classe ne fonctionnait pas correctement. J'ai fait une simple recherche binaire d'erreur et j'ai découvert que l'erreur se produisait après un appel de fonction mais avant un autre appel, ce qui aurait dû être impossible. 2 heures plus tard, je ne l'avais pas compris, mais au moment où je a fait une pause (et s'en allait), j'ai réalisé le problème. Il s'est avéré que XMLEncoder
créait une instance de la classe construite par défaut au lieu que la classe et la référence à la classe se réfèrent au même objet Donc, alors que je pensais que les deux appels de fonction étaient tous deux sur des membres de la même instance d'une classe particulière, l'un était en fait sur une copie construite par défaut.
C'était difficile à trouver car je savais ils étaient tous les deux des références à la même classe.
J'ai eu un bug avec un programme de synchronisation personnalisé une fois. Il a utilisé l'horodatage des fichiers/dossiers pour comparer ce qui a été modifié pour synchroniser les données d'une clé flash vers un partage réseau dans Windows, avec une intégrité supplémentaire et une logique métier intégrées.
Un jour, un opérateur a signalé que sa synchronisation prenait une éternité ... après avoir examiné les journaux, pour une raison quelconque, le logiciel pensait que chaque fichier sur la clé (ou le serveur) avait 3 heures de plus qu'il ne devrait, rafraîchissant les 8 concerts de données! J'utilisais l'UTC, comment diable cela pourrait-il être?
Il s'avère que cet opérateur particulier a en effet défini son fuseau horaire sur l'heure du Pacifique au lieu de l'heure de l'Est, ce qui ne devrait pas avoir lieu, car tout le code utilisait l'UTC - bon dieu, qu'est-ce que cela pourrait être?! Cela a fonctionné lors du test sur mon système local ... qu'est-ce qui donne?
À ce stade, nous avons demandé à tous les opérateurs de s'assurer que leurs ordinateurs portables étaient réglés sur l'heure de l'Est avant de se synchroniser, et le bug est resté dans la file d'attente jusqu'à ce que nous ayons plus de temps pour enquêter.
Puis, octobre est arrivé et BOOM! Le temps de l'heure d'été! Que diable!? Maintenant, tout le monde se plaignait que la synchronisation prenait une éternité! Doit être réparé et rapide!
Je l'ai retrouvé en modifiant le cas de test pour exécuter un bâton au lieu de mon disque dur local, et bien sûr, il a échoué ... ouf, doit une chose de bâton de mémoire - attendez une seconde, est-il formaté en FAT32 ... AH HA! FAT32 utilise l'heure locale lors de l'enregistrement de l'horodatage d'un fichier!
http://msdn.Microsoft.com/en-us/library/ms724290 (VS.85) .aspx
Ainsi, le logiciel a été réécrit de sorte que lors de l'écriture sur un support FAT32, nous l'avons programmé sur UTC ...
Il y a des années, j'ai passé plusieurs jours à essayer de retrouver et de corriger un petit bogue dans dbx, le débogueur textuel sous AIX. Je ne me souviens pas du bug exact. Ce qui a rendu la tâche difficile, c'est que j'utilisais le dbx installé pour déboguer la version de développement du dbx sur laquelle je travaillais. C'était très difficile de savoir où j'étais. Plus d'une fois, je me suis préparé à partir pour la journée et j'ai quitté dbx deux fois (la version dev et la version installée) seulement pour voir que j'étais toujours en cours d'exécution à l'intérieur de dbx, parfois deux ou plusieurs niveaux "profonds" .
-
bmb
Un crash méchant dans une application graphique écrite en Turbo Pascal. Trois jours et plus avant que je découvre, par une seule étape dans le débogueur, au niveau du code machine, sur un code simple et évidemment correct, que je mettais un entier 16 bits sur la pile d'appels pour une fonction qui attend 32 bits (ou certains un tel décalage)
Maintenant, je suis sage, bien que les compilateurs modernes ne permettent plus ce genre de problème.
C'était un petit bug dans Rhino (interpréteur Javascript en Java) qui faisait échouer un script. C'était difficile parce que je savais peu de choses sur le fonctionnement de l'interprète, mais j'ai dû intervenir pour corriger le bogue le plus rapidement possible, dans l'intérêt d'un autre projet.
J'ai d'abord trouvé quel appel au Javascript échouait, donc je pouvais reproduire le problème. J'ai parcouru l'interpréteur en cours d'exécution en mode débogage, initialement assez perdu, mais en apprenant lentement comment cela fonctionnait. (La lecture des documents m'a aidé un peu.) J'ai ajouté printlns/logging à des points que je pensais être pertinents.
J'ai différencié le fichier journal (nettoyé) d'un cycle de travail d'un cycle de rupture, pour voir à quel moment ils ont commencé à diverger. En réexécutant et en ajoutant de nombreux points d'arrêt, j'ai trouvé mon chemin vers la chaîne d'événements qui ont conduit à l'échec. Quelque part, il y avait une ligne de code qui, si elle était écrite légèrement différemment, résolvait le problème! (C'était quelque chose de très simple, comme nextNode () devrait retourner null au lieu d'IndexOutOfBounds.)
Deux semaines après cela, j'ai réalisé que mon correctif cassait les scripts dans certaines autres situations, et j'ai changé la ligne pour bien fonctionner dans tous les cas.
J'étais dans un environnement inconnu. J'ai donc essayé beaucoup de choses différentes, jusqu'à ce que l'une d'entre elles fonctionne, ou du moins ait aidé à progresser/à comprendre. Cela ne a pris un certain temps, mais j'ai été heureux d'y arriver à la fin!
Si je recommençais maintenant, je chercherais le canal IRC (pas seulement sa liste de diffusion) du projet, pour poser quelques questions polies et chercher des pointeurs.
Nous avions un serveur RMI fonctionnant sur une invite DOS. Quelqu'un a "sélectionné" la fenêtre - ce qui a interrompu le processus.
Le correctif était assez simple ... appuyez sur Entrée.
Ce fut une journée assez angoissante ...
Il y avait un bogue sur une plate-forme avec un très mauvais débogueur d'appareil. Nous obtiendrions un plantage sur l'appareil si nous ajoutions un printf au code. Il se bloquerait alors à un endroit différent de celui de l'imprimé. Si nous déplacions le printf, le crash se déplacerait ou disparaîtrait. En fait, si nous modifions ce code en réorganisant certaines instructions simples, le plantage se produirait dans certains cas sans rapport avec le code que nous avons modifié.
Cela ressemble à un classique Heisenbug . Dès que vous le reconnaissez, vous recherchez immédiatement des variables non initialisées ou une corbeille de limite de pile.
Un Heisenbug où la principale difficulté n'était pas de réaliser que ce n'était pas du tout mon bug.
Le problème était une interface API. L'appel à n'importe quelle fonction réelle (par opposition à la configuration) avait une très forte probabilité de planter avec une violation de protection. Une seule étape dans la fonction (dans la mesure du possible, cela provoquerait une interruption et vous ne pourriez pas retracer ce point - c'était de retour lorsque vous utilisiez des interruptions pour parler au système) produisait la sortie correcte, pas de plantage.
Après une longue recherche en vain de ce que je faisais mal, j'ai finalement fouillé les routines RTL pour essayer de comprendre ce que je faisais mal. Ce que je faisais de mal, c'était de croire que les routines fonctionnaient - toutes les routines qui bombardaient manipulaient un pointeur en mode réel avec un type de pointeur en mode protégé. À moins que la valeur du segment en mode réel ne soit valide en mode protégé, cela a explosé.
Cependant, quelque chose à propos de la manipulation du programme par le débogueur a provoqué un fonctionnement correct lors d'une seule étape, je n'ai jamais pris la peine de comprendre pourquoi.
En deux mots: fuites de mémoire.
Une violation de mémoire de tas dans un contrôle d'édition de texte que j'ai utilisé. Après plusieurs mois (...) à le chercher, j'ai trouvé la solution fonctionnant avec un autre programmeur, débogage par les pairs du problème. Cet exemple m'a convaincu de la valeur du travail en équipe et Agile en général. En savoir plus à ce sujet sur mon blog
Je corrige le bug de quelqu'un avec le code ci-dessous:
private void foo(Bar bar) {
bar = new Bar();
bar.setXXX(yyy);
}
Il s'attendait à ce que bar
soit changé en dehors de foo
!
J'avais un morceau de code Delphi qui exécutait une longue routine de traitement mettant à jour une barre de progression au fur et à mesure. Le code a bien fonctionné en Delphi 1 16 bits, mais lorsque nous avons mis à niveau vers delphi 2, un processus qui prenait 2 minutes a soudainement pris environ une heure.
Après des semaines à séparer la routine, il s'avère que c'est la ligne qui a mis à jour la barre de progression qui a causé le problème, pour chaque itération, nous vérifions le nombre d'enregistrements à l'aide de table1.recordcount, dans delphi 1, cela a bien fonctionné, mais il semble que dans les versions ultérieures de delphi appelant table.recordcount sur une table dbase prend une copie de la table compte les enregistrements et renvoie le montant, l'appelant à chaque itération de notre progression entraînait le téléchargement de la table à partir du réseau à chaque itération et comptage. La solution consistait à compter les enregistrements avant le début du traitement et à stocker le montant dans une variable.
Il a fallu des âges pour trouver mais s'est avéré si simple.
Un crash se produisant dans une DLL, chargée à partir d'un service. Déclenché par l'arrêt du système.
Le bug était simple à corriger, mais il a fallu environ une semaine - et beaucoup de frustration - pour le localiser.
Il y en a quelques-uns dont je me souviens, la plupart causés par moi :). Presque chacun d'entre eux avait besoin de beaucoup de grattage de tête.
Je faisais partie d'un projet Java (client riche), le code Java fonctionnait bien sur les versions Vanilla ou les nouvelles machines sans problème, mais lorsqu'il était installé sur les ordinateurs portables de présentation, il a soudainement cessé de fonctionner et a commencé à lancer stackdump. Une enquête plus approfondie a montré que le code reposait sur une DLL personnalisée en conflit avec cygwin. Ce n'est pas la fin de l'histoire, nous étions censés l'installer sur 5 autres machines et devinez quoi, sur l'une des machines, il s'est de nouveau écrasé! Cette fois, le coupable était le jvm, le code que nous avons donné était destiné à être construit avec Sun microsystems jdk et la machine avait le jvm d'ibm.
Une autre instance dont je me souviens a à voir avec un code de gestionnaire d'événements personnalisé, le code a été testé et vérifié à l'unité, enfin lorsque j'ai supprimé les instructions print (), BOOM !!. Lorsque nous avons débogué, le code fonctionnait parfaitement, s'ajoutant à nos obligations. J'ai dû recourir à la méditation zen (une sieste sur le bureau) et il s'est produit qu'il pourrait y avoir une anamoly temporelle! L'événement que nous déléguions déclenchait la fonction avant même que la condition ne soit définie, les instructions d'impression et le mode de débogage donnaient suffisamment de temps pour que la condition soit définie et fonctionnait donc correctement. Un soupir de soulagement et quelques remaniements ont résolu le problème.
Un beau jour, j'ai décidé que certains des objets de domaine nécessaires pour implémenter l'interface Clonable, les choses allaient bien. Au bout de quelques semaines, nous avons constaté que l'application commençait à se comporter bizarrement. Devine quoi? nous ajoutions ces copies superficielles aux classes de collection et les méthodes remove () n'effaçaient pas réellement le contenu correctement (en raison de références en double pointant vers le même objet). Cela a provoqué une révision sérieuse du modèle et quelques sourcils levés.
le bug le plus dur que j'ai jamais eu n'a pas été causé par moi, bien qu'il ait provoqué le crash de mon code! c'était TurboPascal sur DOS. Le compilateur TurboPascal a eu une mise à niveau mineure et tout à coup mon binaire a commencé à planter. s'est avéré que dans la nouvelle version, la mémoire était allouée à partir des limites de segment uniquement. bien sûr, mon programme n'a jamais vérifié de telles choses parce que pourquoi? comment un programmeur saurait-il de telles choses? quelqu'un des anciens groupes d'intérêt spéciaux de compuserve a publié cet indice et la solution de contournement:
comme les segments faisaient 4 mots, le correctif consistait à toujours faire un mod (4) pour calculer la taille de la mémoire à allouer.
Délais d'attente SQL Server inexpliqués et blocage intermittent
Nous avons eu un problème où nos utilisateurs expiraient sans raison apparemment. J'ai surveillé SQL Server pendant un certain temps et j'ai constaté que de temps en temps il y avait beaucoup de blocage. Je dois donc trouver la cause de cela et y remédier.
S'il y avait un blocage, il devait y avoir des verrous exclusifs quelque part dans la chaîne des appels de proc stockés…. Droite?
J'ai parcouru la liste complète des procs stockés qui ont été appelés et tous les procs, fonctions et vues stockés suivants. Parfois, cette hiérarchie était profonde et même récursive.
Je cherchais des instructions UPDATE ou INSERT…. Il n'y en avait pas (sauf sur les tables temporaires qui n'avaient que la portée du proc stocké, donc elles ne comptaient pas.)
Lors de recherches supplémentaires, j'ai découvert que le verrouillage était dû aux éléments suivants:
A. Si vous utilisez un SELECT INTO pour créer votre table temporaire, SQL Sever place des verrous sur les objets système. Ce qui suit était dans notre proc getUserPrivileges:
--get all permissions for the specified user
select permissionLocationId,
permissionId,
siteNodeHierarchyPermissionId,
contactDescr as contactName,
l.locationId, description, siteNodeId, roleId
into #tmpPLoc
from vw_PermissionLocationUsers vplu
inner join vw_ContactAllTypes vcat on vplu.contactId = vcat.contactId
inner join Location l on vplu.locationId = l.locationId
where isSelected = 1 and
contactStatusId = 1 and
vplu.contactId = @contactId
Le proc getUserPrivileges est appelé avec chaque demande de page (il se trouve dans les pages de base.) Il n'a pas été mis en cache comme vous pouvez vous y attendre. Cela ne lui ressemble pas, mais le SQL ci-dessus fait référence à 23 tables dans les clauses FROM ou JOIN. Aucun de ces tableaux n'a l'indication "avec (nolock)", donc cela prend plus de temps que prévu. Si je supprime la clause WHERE pour avoir une idée du nombre de lignes impliquées, elle retourne 159 710 lignes et prend 3 à 5 secondes pour s'exécuter (après des heures sans personne d'autre sur le serveur).
Donc, si ce proc stocké ne peut être exécuté qu'un par un à cause du verrou, et qu'il est appelé une fois par page, et qu'il contient les verrous sur les tables système pendant la durée de la création de la table select et temp, vous pouvez voir comment cela pourrait affecter les performances de l'application entière.
Le correctif serait le suivant: 1. Utilisez la mise en cache au niveau de la session afin qu'elle ne soit appelée qu'une fois par session. 2. Remplacez SELECT INTO par du code qui crée la table à l'aide d'instructions DDL Transact-SQL standard, puis utilisez INSERT INTO pour remplir la table. 3. Mettez "avec (nolock)" sur tout ce qui est impliqué dans cet appel.
B. Si le proc stocké getUserPrivileges n'a pas eu assez de problèmes pour vous, alors permettez-moi d'ajouter: il est probablement recompilé à chaque appel. SQL Server acquiert donc un verrou COMPILE à chaque appel.
La raison pour laquelle elle est recompilée est que la table temporaire est créée et que de nombreuses lignes en sont supprimées (si un @locationId ou @permissionLocationId est passé). Cela entraînera la recompilation du proc stocké sur le SELECT qui suit (oui, au milieu de l'exécution du proc stocké.) Dans d'autres procs, j'ai remarqué une instruction DECLARE CURSOR dont l'instruction SELECT fait référence à une table temporaire - cela forcera un recompiler aussi.
Pour plus d'informations sur la recompilation, voir: http://support.Microsoft.com/kb/243586/en-us
Le correctif serait: 1. Encore une fois, frappez ce proc stocké beaucoup moins de fois en utilisant la mise en cache. 2. Ayez le filtrage @locationId ou @permissionLocationId appliqué dans la clause WHERE pendant la création de la table. 3. Remplacez les tables temporaires par des variables de table - elles entraînent moins de recompilations.
Si les choses ne fonctionnent pas comme vous vous y attendez, vous pouvez passer beaucoup de temps à regarder quelque chose sans que chacun ne trouve ce qui ne va pas.
J'ai désinstallé PHP une fois. Manuellement. Beaucoup de bugs corrigés d'un seul coup ...