Feed on
Posts
Comments
Article en preparation:

surprise...

(x%)

Une des choses que j’ai appris sur le tas, c’est que debugger est une vrai competence au meme titre que savoir programmer de la 3D ou de l’IA.
La difference fondammentale est que chaque personne qui programme se doit aussi de savoir debugger. C’est donc c’est une competence fort utile et tous les programmeurs ne sont pas du meme niveau lorsqu’il s’agit d’erradiquer des bugs.
On pourrait se demander alors pourquoi on n’aurait pas des programmeurs dont la specialisation soit le debuggage. Une des raisons principale etant que l’auteur du code a un “avantage” non negligeable: il sait ce que le code doit faire.

Le sujet est gigantesque et autant vous le dire tout de suite, je vais pas pouvoir tout traiter dans un seul post (bien que celui-ci soit plutot gros!), aussi je vais principallement aborder ce que je considere comme les bases (mais bon mes bases avec moi sont parfois elevees, donc vous pouvez toujours jetter un coup d’oeil au cas ou, meme si vous savez deja debugger).

J’ai appris a debugger a la dur a Lionhead et je me suis tres vite ameliore dans ce domaine. Mon premier jour a Lionhead fut d’ailleurs plutot sympa. On etait 3 programmeurs a commencer le meme jour et Peter (Molyneux) nous a montre le jeu.
Mais au bout d’un moment, Peter a realise que les villageois ne faisaient pas ce qu’ils devaient et il nous a montre en live comment il debuggait (oui on oublie facilement que Peter est egalement un programmeur).
Il nous a alors appris une de ses regles de base lorsqu’il debug, a savoir de ne pas avoir de prejuge sur ce qui va se passer.
Depuis cette idee ne m’a pas lache et j’y pense presque a chaque fois que je debug en n’essayant de ne pas faire de conclusion trop tot et en verifiant tout sans assumer que ci ou ca va se passer comme ci ou comme ca.

Ma regle d’or a moi est de partir du principe qu’on ne va pas avoir beaucoup d’opportunites de reproduire le bug facilement (et c’est generallement le cas), du coup des que j’en choppe un dans le debugger j’essaye toutes les tactiques possibles pour pas arreter le debugger.
Bien sur ce n’est pas toujours possible, mais la plupart des debutants (voir meme des gens qui se disent experimentes) ont tendance a arreter beaucoup trop tot le debugger en croyant que l’on n’a pas assez d’information, qu’on doit changer des trucs avant de pouvoir chopper le bug ou que le programme a crashe et qu’on peut plus continuer. Ce n’est pas toujours le cas et on verra comment devenir un ninja du debugger :)

Notez que je ne couvrirais que le debuggage sous Windows avec utilisation de Visual Studio .NET 2005 pour un language en C++. En gros, la config de base pour de la programmation de jeu sur PC. Sur console les principes et la logique restent les memes, mais les outils sont plus pourris.

De ton debugger te servir tu apprendras (la base des bases)

Une des bases du debuggage facile consiste a communiquer les informations du programme de facon a ce qu’elles soient analysees plus tard lorsqu’un bug se produit.

On appelle ca generallement des logs et se traduisent le plus souvent sous forme de textes dans lequel on ajoutte pleins d’informations comme les points de vie, la positions des objets ou je ne sais quoi dont on veut verifier l’historique en cas de pepin pour comprendre ce qui a bien pu se passer.

Lorsqu’on debug une application console (mode texte, boite DOS), on a donc coutume d’envoyer des printf et autre stdout pour afficher ces informations. C’est souvent le cas d’applications en ligne de commande pour les outils d’ailleurs.

C’est tres bien, mais les jeux ne sont pas en mode console. Du coup il est plus difficile d’afficher des informations a l’ecran (sans gacher la vue, pourrir les performances ou introduire des bugs dans le code d’affichage…).

Lorsque vous debuggez avec votre debugger, celui-ci va s’attacher au process cree (votre executable) et communiquer avec. C’est sans douleur pour votre application et se fait automatiquement. Votre application peut alors communiquer avec votre debugger et fournir toute sorte d’information utiles. Vous pouvez par exemple envoyer du texte au debugger et celui-ci se retrouvera dans la fenetre d’execution du debugger.

Il vous suffit simplement d’utiliser la fonction OutputDebugString pour un resultat comme celui-la:

debug0.jpg

Certaines personnes preferent utiliser des fichiers a la place (qu’on appelle fichier log ou fichier debug). L’utilisation de fichier est plus laborieuse toutefois. Il vous faudra en outre constamment ouvrir en ajout/ecrire/fermer le fichier pour eviter qu’un crash n’empeche l’ecriture du fichier qui reste dans le tampon de votre disque dur jusqu’a ce que vous fermiez le fichier ou ecriviez suffisamment. Les access disque dur pouvant gravement interferrer avec les performances l’interet de cette methode est donc principallement reservee a la comparaison entre plusieurs executions ou pour lorsque vous n’avez pas de debugger attache (par exemple pour des tests a distance).

De toute facon, dans tous les cas, en tant que bon programmeur, vous avez surement votre propre interface qui log les informations ou vous desirez, le tout habille dans une MACRO pour pouvoir desactiver tous les appels d’un coup.

Pourquoi avoir besoin de desactiver lorsque vous n’utilisez qu’OutputDebugString me direz vous?
He bien, comme cette fonction envoie des messages au debugger que vous soyez en debug ou en release et que le debugger en question est peut etre celui d’un hacker, il est preferrable de ne rien transmettre.
De plus, la plupart des TRC (regles de soumission) sur console vous force a ne pas afficher le moindre message. Les macros offrent une alternative interessante au fait de ne pas perdre de temps CPU (processeur) en effectuant des appels meme s’ils ne font “rien”, genre un appel a Log->Display(“text”) qui ne fait rien a l’interieur.

Un des autres avantages d’utiliser une macro est d’ajoutter de facon automatique des variables du compilateur comme paramettre telle que __FILE__ qui est remplace automatiquement par une chaine de caracteres contenant le nom du fichier et __LINE__ pour un nombre indiquant le numero de ligne ou ce trouve ce mot.
Si vous vous debrouillez bien avec le formattage de votre message dans la fenetre d’affichage pour copier celui de Visual Studio, vous pourrez alors beneficier des facilites de double clique dessus pour vous y amener directement.

debug1.jpg

Vous pouvez chercher dans l’aide les mots commencant par un double sous-tiret, il y en a d’autres utiles tels que __TIME__, __TIMESTAMP__ pour verifier les temps de compilation du fichier cpp…


Des breakpoints, usage tu ferras…

Bon, voila pour les bases de la communication de votre application vers le debugger, mais nous ce qu’on veut vraiment, c’est de pourvoir stopper l’application a un moment donne pour analyser et changer le comportement de l’executable.

On va donc utiliser le debugger comme une telecommande pour controller notre application.
La plupart des programmeurs malheureusement n’utilisent pas tous les boutons de la telecommande meme si ca pourrait leur permettre de debugger plus facilement ou de trouver le bug.
Dans tous les cas, l’exercice commence avec des breakpoints (conditionnel ou pas).

On va zapper step-in et step-out pour avancer une fois que votre application a atteint un breakpoint, car vous avez l’aide pour ca. On va plutot se concentrer sur des trucs que la plupart des programmeurs oublient ou n’utilisent que trop rarement comme la possibilite de changer la ligne d’execution (pourtant tres pratique comme on le verra un peu plus tard).

J’utilise personnellement assez souvent les breakpoints conditionnels sur changement de valeur. Pour cela, il vous suffit de recuperer l’adresse de la variable (un petit & devant le nom de la variable) et de creer un breakpoint sur changement de valeur a cette adresse.

Les breakpoints sont facile a mettre en place lorsqu’on a un debugger attache, mais on peut toujours simuler un breakpoint dans le code directement. Pour ca, on utilise DebugBreak(). C’est par exemple tres pratique pour l’implementation d’une librairie de report d’erreur/truc louche, car assert remplit ce role d’une bien pietre facon.

Assert, a la poubelle tu jetteras…

La librairie fournie pour les assert est clairement de la merde.
Y’a tellement de raison de dire ca que j’en perds presque le courage de toutes les ennoncer (on va dire que c’est la derniere fois que je le fais alors)…

Voila une assert, decrite par la ligne suivante:

assert(lpCmdLine == 0); // ici on dit qu’on est sur que lpCmdLine doit etre egal a 0. Si c’est pas le cas, on a droit a un joli popup a l’execution du code.

debug2.jpg

Comme on n’a pas de texte explicatif autre que la condition, certains utilisent l’operateur && (et) pour former leur fonction comme ceci:

assert(lpCmdLine == 0 && “Ca doit venir de ci ou ca”);

Comme la fonction verifie que la condition soit vraie et qu’une adresse de string est toujours vraie par nature (car non NULL), cela ne vient pas pertuber la fonction et de plus ca affiche le resultat.

debug3.jpg

Mais oubliez tout ca car de toute facon assert n’est pas a la hauteur.

Tout d’abord vous ne pourrez pas construire des textes qui seront affiche dans la boite de dialogue.

Si votre but est de marquer un truc genre “le fichier image: %s n’a pas pu etre charge” vous allez etre decu car vous aurez aucun moyen d’affichager le contenue d’une variable, il en va de meme pour la valeur des codes d’erreur (rendant la chose inutile a qui n’a pas acces a un debugger).

De plus, assert devient silencieuse en mode release, donc soit vous passer des version debug pour tester ou vous oubliez assert.

Certes il y a l’option ignore, mais vous ne pouvez pas ignorer pour toujours la meme assert. Mettez la dans une boucle et vous passerez votre temps a cliquer sur ignore…

Aucune possibilite de savoir si c’est grave ou pas comme assert. Meme si ca peut ne pas vous deranger en tant que programmeur, les testeurs ne sauront pas differencier un warning d’une erreur grave et urgente.

Sans oublier qu’on a aucune possibilite de logger les incidents dans un fichier log.

Bref, pour une vraie librairie d’erreur, vous pouvez toujours jetter un oeil sur la mienne (GIReport).

La fenetre des variables, dominer tu sauras…

Il arrive parfois que la valeur des variables soit mal affichee dans la fenetre d’information du debugger.

Par exemple, vous pouvez avoir un pointer vers des donnees que vous savez faire partie d’un tableau mais le debugger ne les affiche pas.

Par exemple:

int *buffer = (int *)malloc(sizeof(int) * 8);

Lorsqu’un pointer est alloue de cette facon, le debugger ne sait pas que c’est en fait un tableau.
Biensur, vous pouvez demander afficher buffer[0], buffer[1]… mais c’est plutot fastidieux si vous devez afficher les 50 premiers! Certes la fenetre memoire est la pour aider, mais assumons un moment que ce ne sont pas que des int mais une classe complexe avec pleins de champ.

L’astuce consiste a utiliser la ponctuation qui va bien, a savoir une virgule suivit du nombre d’indice que vous voulez voir:

debug4.jpg

Biensur, vous pouvez aussi caster les trucs dans une structure en cas de pointer de type void ou inapproprie:

debug5.jpg

Du language tu te serviras…

Le language comporte parfois des syntaxes qui peuvent plus ou moins aider a debugger.

Trouver les bonnes valeurs se fait parfois pendant des sceances de debuggage. Ca peut etre pour changer une couleur, un effet ou deplacer de quelques pixel un sprite pour qu’il colle bien… bref, peut importe ce que c’est, des fois on se dit qu’il serait pratique de changer une variable pour voir l’effet et qu’une fois la bonne valeur trouvee on en changera pas.

Donc, on peut avoir une variable a laquelle on ajoutte une valeur quelconque.

Par exemple:
x += 34;

Comment changer 34 en 33 une fois l’application lancee? Naturellemnt, vous avez un breakpoint sur cette ligne mais que faire maintenant?

Certes, Edit & Continue permet de changer et de recompiler sans avoir a relancer le programme, encore que ca ne soit pas toujours possible (voir disponible) et ca a ses limites aussi (comme le nombre de fois ou on recompile et ce que ca demande a recompiler pour les changements).
Relancer son application demande du temps lorsqu’on ne parle pas du mal a reproduire le probleme, ou lorsqu’il faut se taper 30 minutes de gameplay pour en voir poindre le bout de son nez.

Passons sur le fait que je n’aime pas les nombres magiques (nombre comme ca, sans etre ratache a une variable ou constante, puis ca pourrait tout aussi bien etre un enum que ca nous arrangerait pas dans ce cas).

Si la variable est un static toutefois, on peut facilement la changer grace au debugger (ca pourrait etre une variable globale mais on ne va pas polluer le domaine global du programme, c’est mieux de limiter a la fonction dans laquelle on en a besoin).

Pour rappel, les static ne sont initialises qu’une fois, donc personne ne touchera cette valeur par la suite a part vous.

static int ajout= 34;
Value += ajout;

Apres, vous pourrez donc mettre un breakpoint, changer la variable ajout autant de fois que vous le desirez dans la fenetre des variables.
Il faudra juste vous rappeler de changer l’initialisation du static une fois la valeur ideale trouvee pour la prochaine fois ou vous lancer l’application.

Au passage, si vous pensez ne pas pouvoir changer la valeur d’une constante dans le debugger, vous vous trompez.

const int Inflexible = 20;

Au lieu de rentrer Inflexible dans la fenetre des variables, entrez son addresse: &Inflexible, puis cliquez sur le petit + pour etendre et changer la valeur.
Si le debugger ne vous laisse toujours pas faire, caster l’adresse de la variable dans un (int *) et ca devrait faire l’affaire :)

Avec la callstack, ami ami tu ferras…

Ok, lors d’un crash il est super important de verifier la callstack car bien souvent le crash vient d’un probleme en amont.

Donc en remontant les appels, on peut voir (souvent) que l’un des pointeurs sur lequel on appel des fonctions est en fait NULL.

Ex: monobjet->FaitCiOuCa(blah);

Il y a fort a parier que le crash arrivera dans la fonction FaitCiOuCa, aussi une des premiere chose a verifier est que l’objet dans lequel on se trouve soit valide.

Il y a une facon tres simple de verifier la validite de l’objet dans lequel on se trouve (a savoir la variable this).

Ajouttez “this” dans la fenetre des variables et verifiiez le pointer __vfptr. S’il est remplit de cochonneries, alors vous avez votre reponse l’objet est invalide et il y a fort a parier qu’avec la callstack vous verrez que le pointer etait soit NULL soit invalide.

La memoire tu maitriseras…

Il est souvent utile de connaitre le code de memoire en mode debug.
Bien souvent les problemes de bases en C++ sont lie a la manipulation de la memoire (pointer NULL, non initialise, variable effacee mais reutilisee…).

Ceux a connaitre par coeur sont 0xcdcdcdcd qui veut dire non initalise et 0xfeeefeee qui veut dire deja efface.

Cela ne marche pas lorsque vous utilisez votre propre allocateur (bien que rien ne vous empeche d’implementer quelquechose de similaire), mais vous pouvez toujours changer l’implementation de votre allocateur un moment (avec un #define) afin de remplacer les allocations/desallocations par malloc et free afin de beneficier de l’implementation gratuite de l’aide au debuggage de la memoire.

Toutefois, en mode release, tout ca disparait (a moins que vous n’ayez modifie votre allocateur) et termine les petits code de debuggage pour la memoire. De plus les variables sont initalisees a 0 lorsqu’on est en debug et a ce qu’il y a dans la memoire a cette position en mode release, ce qui explique generallement que les bugs entre version debug et release soient generallement lies a des problemes d’initialisation de variables (pour les plus simples).

Petit exemple:

debug6.jpg

Pour en savoir plus sur ces codes:
http://www.nobugs.org/developer/win32/debug_crt_heap.html
http://www.codeguru.com/Cpp/W-P/win32/tutorials/article.php/c9535/


Dans le monde de l’assembleur sans peur tu plongeras…

Y’a pas a dire, qu’on l’aime ou pas, debugger avec le code en assembleur est tres utile et pas besoin d’etre un pro en assembleur pour en voir l’interet.
(En release des fois c’est la seule chose dont vous disposerez, mais cela donnera lieu a un article plus avance sur le debuggage.)

Tenez, lorsqu’on debug, on est parfois confronte a l’inevitable fonction de class sur une ligne. A savoir:

float GetValue() {return mValue;};

Bon pas facile de changer la valeur puisque l’objet n’as pas encore ete initalise. On n’a aucune information sur la variable “this” et il faudrait faire un step avec le debugger pour pouvoir avoir les valeurs de la classe pour connaitre mValue, mais la ligne suivante nous renvoie a celui ayant appele GetValue… hum, pas pratique.

En fait, rien de plus simple. Il vous suffit de passer du cote obscure de la force, dans le monde de l’assembleur. Vous n’avez meme pas besoin de comprendre ce qui se passe, la seule chose qu’on a besoin c’est que la classe nous revele ses variables.
Donc, pour se faire:

1. Afficher le code assembleur en plus des sources (CTRL+F11, Go To Disassembly)

debug7.jpg

2. Avancez de quelques instructions afin que la valeur de “this” apparaisse enfin, ce qui nous permet de pouvoir changer mValue ou d’analyser d’autres variables de la classe.

debug8.jpg

Et voila, magie! :)

Si vous crashez, il vous est souvent possible de continuer l’execution, meme si continuer normallement ne vous est pas permis. La encore, un petit tour en assembler va vous sauver la mise.

Cela depend des conventions d’appels biensur, mais la plupart du temps, l’etat de la callstack est sauvee au debut de la fonction puis restauree a la sortie.
Donc si vous crashez au milieu de la fonction, il vous suffit de forcer la prochaine execution a l’endroit ou la callstack est restauree. A savoir vers les dernieres instructions pop avant le ret.

Le tout en image…

debug9.jpg

Outch, on vient de crasher, tres clairement (si vous avez suivit), 0xcdcdcdcd indique que la variable n’as pas ete initialisee.

debug10.jpg

Ok, mais le probleme c’est que vous avez besoin de continuer pour voir si le reste marche.

Hop, un petit tour dans le monde de l’assembleur…

debug11.jpg

On voit bien ou ca crash (la fleche jaune) et ou sont les premiers pop (fleche blanche) avant le ret (retour) de la fonction.
Il nous suffit donc simplement de placer la prochaine instruction sur la fleche blanche et le code peut continuer a s’executer et nous de debugger.

Tres pratique lorsque vous rencontrer un pointer NULL et que vous avez besoin de reproduire la boucle du jeu qui fait ca (sans redemarrer).

Voir double du mieux tu essayeras…

Un des avantages de programmer sur console par rapport au PC est que l’application lancee tourne sur un autre systeme, du coup votre PC a pleins de CPU disponible pour faire ce que bon lui semble. Vous pouvez aussi avoir deux PCs et faire du debuggage a distance sur le deuxieme PC pour le meme effet. De plus les donnees du programmes sont generallement dans le cash du disque dur et les fichiers se rechargeront plus vite.

L’autre avantage est que le debugger ne gache pas la vue de l’application. Car votre application ne va pas rafraichir son affichage avec un breakpoint dans le ventre. Du coup c’est plutot genant lorsqu’on debug de l’affichage.

Mais lorsqu’on a qu’un PC, on peut aussi avoir deux ecrans, pour jouer avec le debugger sur un ecran et voir le resultat image apres image sur l’autre ecran.
Les PC dual core sont aussi tres utils (un core pour l’application, l’autre pour debugger), enfin si votre application n’utilise qu’un thread…

Dual core, dual screen c’est super lorsqu’on a qu’un PC :)

Voila, c’est tout pour aujourd’hui, et meme si debugger s’apprend principallement en debuggant, c’est toujours bien d’avoir deux trois astuces sous le coude pour trouver et eliminer les bugs, meme si le meilleur moyen reste encore de ne pas en creer (regle d’or pour une application multithreaded…) et pour ca, le plus sur moyen reste encore de tester tout le code que vous ecrivez au fur et a mesure des lignes de code que vous rajouttez sans jamais assumer que ca marche avant d’avoir verifie personnellement.

25 Responses to “L’art du debuggage”

  1. on 04 Mar 2007 at 17:44 Stéphane Becker

    Je rajouterais que pour moi la règle de base du bon débugueur c’est “le problème est de ma faute”. J’ai déjà croisé des gens qui partaient du principe qu’ils programmaient sans bugs et que c’était forcément la faute de quelqu’un d’autre (un autre programmeur de l’équipe, le dev du driver de la carte son etc…).

    Sous Visual un truc très utilise aussi c’est d’éditer le fichier autoexp.dat pour facilement inspecter ses structures de données, parce que c’est dingue ce qu’on peut y laisser du temps à déplier toutes ces petites saloperies.

    Pour les assertions, perso je trouve ça très utile dans le cadre de faire de la programmation par contrat. C’est à dire exprimer les préconditions et postconditions de chacune de tes méthodes, et donc fournir de la documentation par effet de bord sur ce qu’attends la méthode. (l’avantage c’est que c’est de la documentation active qui mord). Mais c’est clairement pas à destination de l’utilisateur finale, mais ça n’a jamais été son but.

    Le débogage est aussi l’endroit où on voit le plus rapidement les limites des gens qui ont été formés au Java. Parce que effectivement il faut souvent descendre pas dans les entrailles de la machine.

    Et pour fini, ça mériterait un sujet à lui tout seul, il est toujours important de s’écrire des outils ingame pour rapidement traquer certains problèmes. Par exemple une console avec une commande pour afficher les informations de pathfinder etc…

  2. on 04 Mar 2007 at 18:32 Jeremy Chatelaine

    ??le problème est de ma faute? ha oui 100% d’accord merci de le preciser, ca doit etre ma deuxieme regle, mais c’etait devenu instinctif, j’y pensais plus :) J’ai bien trop souvent entendu dire ca doit etre un bug dans DirectX… a Lionhead le son de cloche etait bien different

    Cool pour autoexp.dat, j’avoue jamais m’en etre servit, je vais regarder ca a l’occasion.

    J’ai du mal m’exprimer pour les assert. Les assertions OK, la validation de paramettre a l’entree de fonction est TRES importante, mais l’implementation de ces assertion via la fonction assert est a eviter.

    Java etant un language d’assistes au niveau memoire, c’est pas etonnant :) surtout que beaucoup de bugs C++ sont lie a un probleme de memoire ou se resolvent avec une comprehension de comment marche la memoire!

    C’est pas une mauvaise idee pour les outils in-game d’aide au debuggage. T’as des screenshots a faire partager de vos productions?

    Merci pour tes retours instructifs Stephane.

  3. on 04 Mar 2007 at 20:39 Stéphane Becker

    Je peux te faire des screenshots, mais il faudra que je te tue après :)

  4. on 04 Mar 2007 at 21:23 Jeremy Chatelaine

    Ils ont interet a valoir le coup alors 😉

  5. on 04 Mar 2007 at 21:35 Yannick

    Faudra aussi que je regarde autoexp.dat. Merci pour l’info Stéphane.

    Aussi, un truc logique: plus un code sera écrit clairement et bien designé plus il sera facile à débugger. Aprés, c’est sûr que bien des bugs sont dû à des problèmes d’allocation/désallocation de mémoire pas toujours évident à trouver.

    Par exemple, j’ai eu une fois un bug mémoire dans un exporter 3ds Max causé par une fonction qui renvoyait le mauvaie matériaux dans un matériau car j’en vais rajouté un nouveau sous Max donc du coup il n’était pas reconnu par l’exporter et j’avais une belle erreur mémoire lors du free(). J’ai mis du temps à le trouver celui-là car çà venait d’une allocation faite au début de l’exporter et ça plantait à la fin… L’exporteur de base ayant été écrit par un programmeur d’un éditeur qui fournissait le moteur.

  6. on 04 Mar 2007 at 21:59 Stéphane Becker

    En fait il y a aussi des grands classiques dont la résolution est assez connu. Par exemple, si un bug n’arrive qu’en release et pas en debug, c’est sans doute une variable non initialisée. Typiquement le genre de truc qu’on apprends pas à la Fac.

  7. on 04 Mar 2007 at 22:00 Jeremy Chatelaine

    Ho lui il fait dans le rechauffe, je l’ai deja dit dans l’article (ok il est long l’article :))

  8. on 04 Mar 2007 at 22:18 Stéphane Becker

    J’ai du m’endormir sur la fin :)

  9. on 05 Mar 2007 at 9:32 rodounet

    Bon article Jeremy !

    Je suis tout à fait d’accord avec Stéphane au niveau des assertions : à utiliser pour les pré/post-conditions (depuis que je le fais, je passe plus de temps à coder qu’à débugger :D).

    Je développe la plupart du temps sous linux, les outils sont pas aussi puissants… bah gdb + emacs, ça vaut toujours pas VS. En attendant que je programme sous windows (sous peu normalement!), je révise les règles de base 😉

  10. on 05 Mar 2007 at 10:17 darjul

    Salut Jérémy,

    Concernant l’autoexp.dat, je suis surpris que tu n’aies jamais mis le nez dedans. C’est un truc de base de debug fondamental, principalement pour les std:string, mais il y a d’autres exemples: si tu peux visionner les structures DirectX, c’est parce que le SDK les ajoute dans l’autoexp.dat à l’installation. Un conseil: faire une sauvegarde avant toute modification. Et ça marche sans redémarrer Visual Studio (il faut seulement redémarrer la session de debug et les modifs sont prises en compte à la volée).

    Un autre truc qu’on peut faire dans le genre que tu cites “buffer,5″ est “msg.message,wm” où msg est un message windows (de type MSG donc). Ca va tout simplement “traduire” vers le define qui correspond. Par exemple, au lieu de 0x0200, il y aura WM_MOUSEMOVE. Très pratique pour la programmation Windows. Il y a plein d’autres astuces dans ce genre, mais le ,wm est celui que j’utilise le plus souvent.

    Un autre truc qu’on ne sait pas souvent: pourquoi rencontre t-on parfois if(0 == var) au lieu du bien plus lisible et logique if(var == 0) ? Au début je me disais “ils font chier à écrire ça comme ça”, surtout que comme par hasard je rencontrais ça surtout dans les exemples du DirectX SDK… En fait c’est pas pour faire chier le monde, c’est pour éviter un des plus vieux et classiques bugs C/C++ (celui qu’on a tous eu au moins une fois et qui est parfois impossible à voir tellement il est évident). if(0 = var) ne compile pas, alors que if(var = 0) compile et… ne fait pas du tout ce que l’on voulait ! Finalement quand j’ai compris ça, je m’y suis mis. C’est une habitude à prendre, oui c’est moins lisible, oui on peut s’en passer, mais oui ça en vaut la peine.

    PS: ne crie pas trop fort que Java est un langage d’assistés, tu risques de te faire entendre :)

  11. on 05 Mar 2007 at 10:17 AnA

    Article assez interressant, dont je ne retiendaris que 2 choses: L’erreur est ailleurs, toujours a l’endroit ou on ne verifie pas, et ou c’est le plus simple pourtant. Et enfin, c’est de ma faute, (en cause 95% du temps) C’est pour ca que ca fait plaisir de trouver des bugs dans les framework ou autre librairies :). (Vivement un article un peu plus avancé sur le debuggage !)

  12. on 05 Mar 2007 at 12:56 BurgerBob

    L’attente valait le coup !
    Très bon article, j’essaierai de me souvenir de toutes ces petites astuces …
    Vivement le prochain, où je vais surement apprendre des trucs qui vont me changer la vie !

  13. on 05 Mar 2007 at 22:04 Jeremy Chatelaine

    @drajul:

    J’avoue ne pas trop m’embeter avec autoexp.dat, meme si je devrais surement. J’utilise Visual Studio 2005 depuis la premiere BETA (2003 je crois bien) et 2005 affiche peinard les std::string, avant je les utilisais pas hehe :)

    Un peu en annexe, on a aussi usertype.dat mais il merde chez moi et je sais pas pourquoi :(, j’ai les boules car pour les shaders c’est vraiment bien.

    Merci pour le ,wm que je connaissais pas! J’ai oublie de parler du ,hr que je connaissais par contre et qui grosso modo fait la meme chose avec les codes HRESULT des fonctions.

    Ha le fameux NULL == myvariable, je le sais j’arrive juste pas a l’appliquer, ca me frustre a lire :)

    PS: je serais ravi de pouvoir faire ce que fait Stephane et d’encadrer une classe de programmeurs… ils apprendraient a vitesse grand V 😉

    @Ana : c’est plutot 99% du temps meme! Et je prefere que ca soit mon bug, car c’est BIEN PLUS DUR de changer les libs des autres (comme DirectX) hehe

    Lorsque je ferrais l’article suivant je parlerai de .map, thread, debugger temps reel… et pourquoi pas comme le suggerait Stephane des aides au debuggage in-game. Mais bon ca sera pas mon prochain post :)

  14. on 06 Mar 2007 at 7:34 darjul

    Jréémy,

    Les std:string marchent parce que (me semble t-il) crosoft les a intégré dans l’autoexp.dat depuis … VS2005 ! En revanche, dès que tu encapsules la std:string pour faire ta propre class string, ça ne marche plus et il faut bidouiller l’autoexp.dat pour “faire suivre” la variable membre. Ca prend une ligne, encore faut-il savoir laquelle et savoir que c’est dans ce fichier là que ça se passe. En général, on utilise pas directement les std:string. Enfin je dis en général, y’a rien qui nous en empêche, mais disons plutôt que c’est ce que j’ai constaté dans plusieurs studios et autres codes source ça et là. L’encapsulation a ses avantages, notamment pour ajouter des tas de fonctions helpers, voire pour pouvoir changer la taille des strings sur 8 ou 16 bits en un define sur tout le projet.

    Le ,hr est pas mal, je l’avais presque oublié, mais faut dire que je l’utilise plus du tout. En fait j’ai une macro du genre DX_DEBUG(hResult) qui fait:

    if(FAILED(hResult))\
    {\
    TRACE(DXGetErrorString(hResult));\
    TRACE(DXGetErrorDescription(hResult));\
    ASSERT(SUCCEEDED(hResult));\
    }

    Du coup quand ça échoue, je sais tout de suite pourquoi, pas besoin de jouer du debugger. Ca marche avec n’importe quel HRESULT issu de DirectX.

  15. on 07 Mar 2007 at 14:10 Séà-?

    Super article (encore une fois :))
    Ma fois y a plein de truc que je connais pas (et oui debug n’est pas un module de cours helas).
    J’attend le prochain avec impatience!

  16. on 07 Mar 2007 at 15:30 MMoi

    Pfiu, j’ai encore appris pas mal de choses, et realise une fois de plus (comme si c’etait necessaire ^_^) qu’il reste bien des choses a decouvrir.

    Merci 😉

  17. on 08 Mar 2007 at 10:07 Gilles

    “PS: je serais ravi de pouvoir faire ce que fait Stephane et d??encadrer une classe de programmeurs? ils apprendraient a vitesse grand V”
    Et bien, déjà rien qu’en lisant ton article..on apprend beaucoup 😉
    Pas mal de petits trucs sympas qu’on connaissait pas, d’autres qu’on connaissait..
    Pour les assert, on a fait des belles macros, mais personne les utilise :(.
    En tout cas, vivement la suite ! A quand le bouquin ? 😉

    @Séà-? : tiens ,t là toi aussi 😀

  18. on 08 Mar 2007 at 11:44 Séà-?

    Oui oui suis la Gille :)

    Un bouquin! C’est pas une mauvaise idée, mais il sera bigrement gros s’il doit donner plein de conseil :)
    Mais avec une couleur bien choisi, il pourrait devenir un nouveau livre de chevet comme le red book!

  19. on 11 Mar 2007 at 16:26 F8Full

    Cool l’article, moi j’aime bien le passage dans le monde magique de l’assembleur… ^^

    Juste une chose, y’a des trucs pour débugguer les shaders sous VS2005 ? Ou même juste avoir du syntax hilightning ? Visual Assist ne le fait pas, je suis obligé d’utiliser FXComposer qui à aussi le bon goût de vérifier la syntaxe.

    En tout cas, merci pour ce blog

  20. on 11 Mar 2007 at 22:20 News | Conquérir le monde !

    […] Gros pavé très intéressant sur le debug en programmation chez Jeremy Chatelaine (Français) […]

  21. on 13 Mar 2007 at 13:53 Séà-?

    F8Full
    Ta bien de la chance moi FXComposer veux pas se lancer sous vista :(

    Sinon si tu a cocher la case “enable Shader debugging” dans le panneau de control de DirectX. met ce code:

    DWORD dwShaderFlags = D3DXFX_NOT_CLONEABLE;
    #if defined( DEBUG ) || defined( _DEBUG )
    dwShaderFlags |= D3DXSHADER_DEBUG;
    #endif
    #ifdef DEBUG_VS
    dwShaderFlags |= D3DXSHADER_FORCE_VS_SOFTWARE_NOOPT;
    #endif
    #ifdef DEBUG_PS
    dwShaderFlags |= D3DXSHADER_FORCE_PS_SOFTWARE_NOOPT;
    #endif

    Et c’eset le 5eme paramètre du create effect.

    J’espere que ça t’aidera, et si une autre personne à plus évolué je suis prenneur aussi!

  22. on 13 Aug 2007 at 0:37 Psyché

    Salut, sympa l’article.
    J’aimerais connaitre ton avis concernant le débogage de niveau 4.Je connais des gens qui codent en warning niveau 3, et dise que le 4 ca sert rien. Qu es ce que tu en penses?
    Merci.

  23. on 13 Aug 2007 at 6:59 Jeremy Chatelaine

    Salut Psyche et content que l’article te plaise.

    Concernant les configuration pour le compilateur, c’est tres clair pour moi:

    – Le plus haut niveau de warning possible
    – Traiter les warning comme des erreurs

    Comme ca, rien ne passe et c’est important de rien laisser lorsqu’on programme en equipe.

    Apres on peut toujours jouer du pragma pour ignorer certaines erreurs (warning)

  24. on 23 Oct 2007 at 22:03 Thomas Carton

    Sous Visual il y a aussi les CRT DEBUG bien utiles pour tracker les leaks…
    Ca m’a sauvé un temps fou et une énergie folle d’avoir un breakpoint au moment de l’allocation mémoire incriminée…
    Et du coup sur console j’ai chopé le reflexe de me faire un test case en debug avec les overrides des alloc, free, new et delete. C’est un peu mon CRT DEBUG du pauvre 😉

  25. on 06 Jul 2011 at 11:10 Semaine #26 | Grokuik

    […] Question virus, si ça vous intéresse, vous pouvez télécharger ce fichier qui fait quelques 518 kb et qui contient le code source du fameux Stuxnet. Et pour les développeurs (futurs ou en herbe), voici un petit guide de l’art du debuggage. […]

Trackback URI | Comments RSS

Laissez un message