Introduction à WinDBG
Introduction
Visual Studio a beaucoup évolué depuis l’ère de la version 6, mais pas toujours au profit des développeurs C et C++. Après l’âge d’or de tout ce qui était fonctionnalité .Net, on assiste présentement à un retour à l’avant-plan de la programmation C++ avec la cure de rajeunissement qu’a subie le langage depuis la version C++11. Notre IDE de choix rattrape du terrain, mais traîne encore plus ou moins de la patte quand vient le temps de se salir les mains et de trouver la source d’un « vrai » bogue. Le genre de bogue qui ne prend pas le temps de vous avertir poliment avec une classe d’exception pour vous informer de ce qui ne va pas avant de vous éclater au visage. Le genre de bogue qui prend la peine de corrompre le call stack avant de vous donner une chance de le déboguer. Peut-être la valeur du pointeur this a-t-elle été camouflée lors de l’optimisation du programme par le compilateur ?
Quand rien ne va plus et que Visual Studio ne peut plus nous aider, il nous reste un dernier outil qui nous permettra peut-être de nous sauver d’une mort certaine (j’exagère à peine), pourvu qu’on soit prêt à se retrousser les manches : WinDBG.
Ce qu’est WinDBG
WinDBG est une interface graphique par-dessus les débogueurs noyau kd, utilisateur cdb et ntsd, ce qui veut dire qu’il peut tout aussi bien déboguer le code qui s’exécute au niveau du noyau du système d’exploitation (comme les pilotes de périphériques) que les applications lancées par celui-ci.
Puisque ces deux modes d’opération sont plutôt différents dans leur fonctionnement, cet article se concentrera sur le mode d’opération qui a le plus de chances d’être utile pour le commun des programmeurs : le mode utilisateur.
WinDBG est aujourd’hui distribué avec le Windows SDK (version courante : Windows SDK for Windows 8.1). Il est à noter que bien qu’on doive utiliser l’installateur du SDK, il n’est pas nécessaire d’installer le SDK lui-même pour avoir accès aux outils de déboguage distribués avec celui-ci. Il suffit de sélectionner, lors de l’installation, Debugging Tools for Windows ainsi que Application Verifier qui à eux seuls contiennent tout ce que nous avons besoin.
Il existe une version de WinDBG pour les applications compilées pour l’architecture x86 et une autre pour l’architecture x64. Il est à noter qu’une application 64-bit doit être déboguée avec la version x64 de l’application et que bien qu’il soit possible de déboguer une application 32-bit avec la version x64 de WinDBG, cela s’avère plus complexe. Je suggère donc de limiter les sources de maux de tête et d’utiliser le débogueur approprié à la tâche.
Configuration de base
Pour utiliser efficacement WinDBG, il est important de toujours s’assurer qu’une certaine configuration minimale soit effectuée, sans quoi la qualité de l’information révélée par le débogueur sera limitée.
La principale configuration à effectuer est de s’assurer que WinDBG sache où trouver les symboles de l’application à déboguer, les symboles publics de Windows et tout autre symbole qui pourrait contenir de l’information utile à notre problème. Par exemple, si on utilise une librairie externe et qu’on soupçonne qu’elle pourrait être responsable de notre problème, il serait utile d’avoir accès à ses symboles de débogage, s’ils sont disponibles (ce qui est rarement le cas pour les applications propriétaires).
Dans le menu de configuration des symboles (File/Symbol File Path), on ajoute premièrement les symboles de notre application à déboguer en cliquant sur le bouton Browse... et en pointant sur le répertoire contenant les fichiers *.pdb qui nous intéressent. S’il y a plusieurs répertoires d’intérêt, il suffit de répéter l’opération pour chaque dossier, et leur chemin respectif sera rajouté à la liste globale.
Pour ajouter les symboles de Windows, il suffit de pointer sur le serveur public de symboles de Microsoft en ajoutant le texte suivant à la suite de la liste:
srv*c:\symbols*http://msdl.microsoft.com/download/symbols
(ici c:\symbols est un répertoire où les symboles téléchargés seront mis en cache; il est possible d’en choisir un différent).
Différents types de sessions de déboguage
Déboguage en direct (live)
Lorsque l’on veut trouver la source d’un bogue en exécutant directement le programme fautif, il s’agit de débogage en direct. Cette méthode est à privilégier car elle donne accès à un éventail plus étendu de données sur le processus en cours. Cependant, certains bogues sont particulièrement difficiles à reproduire. Soit une composante aléatoire rend la reproduction du problème longue et ardue, soit le problème n’a lieu que sur une machine spécifique à laquelle on n’a pas accès. C’est pourquoi il existe un autre type de session de débogage:
Analyze de crash dump
Lorsqu’un bogue difficile à reproduire arrive, un testeur judicieux saura générer un vidage de la mémoire (mieux connu par son terme anglais : crash dump) de l’application fautive de manière à prendre une photo de l’état du processus pour consultation future. De cette façon, même si on n’a reproduit le problème qu’une seule fois, on peut fouiller l’état du programme à répétition de différentes façons pour essayer de trouver la source de notre problème. Ceci dit, étant donné que ce type de débogage est effectué post-mortem, certains types de données ne sont pas disponibles (toute donnée transitoire n’ayant pas été préalablement enregistrée est perdue, par exemple), ce qui peut rendre l’analyse un peu plus compliquée.
Instructions utiles
À part quelques fonctionnalités de base qui sont disponibles dans son interface graphique (call stack, liste des threads, liste des variables locales, etc...), la très grande majorité des commandes utilisées dans WinDBG sont des commandes textuelles. Par exemple, pour afficher à l’écran le call stack de la thread courante, on peut utiliser kb
.
Voici une liste de plusieurs commandes utiles dans une variété de scénarios différents (nous pourrons expliquer certaines techniques de débogage plus élaborées lors d’un prochain article) :
Débogage unmanaged
Commandes utiles
- !analyze -v
- Méta-commande qui tente de trouver automatiquement la source d’un bogue (fonctionne rarement, mais ça ne coûte pas cher d’essayer)
- !uniqstack
- Affiche le call stack de toutes les threads ayant un call stack unique (cache les doublons)
-
dt -b
! - Force l'interprétation de ce qui est à l’adresse mentionnée comme étant de type symbole, tel que défini dans le binaire module et affiche la valeur de ses membres (par exemple, permet de visualiser un void* comme un CObjet*)
- !teb
- Affiche la structure de données TEB (Thread Environment Block) associée à la thread courante. Utile entre autres pour déterminer les limites du stack de la thread en mémoire (Stack Base, Stack Limit).
-
dds
- (32-bit) ou
-
dps
- (64-bit) Affiche tout le contenu de la mémoire entre adresse1 et adresse2 ainsi que le symbole qui est associé à chaque adresse, le cas échéant.
-
ub
[opt:plage] -
Affiche la plage d’instructions en assembleur précédant une adresse donnée (ex :
ub 0x33333333 L200
affiche les 200 instructions précédant l’adresse 0x33333333). Utile par exemple pour déduire ce qui a été placé sur le stack avant un appel de fonction. - !address -summary
- Affiche un tableau indiquant comment l’espace mémoire du processus est distribué.
- .ecxr
- Si une exception a été lancée et que son contexte est toujours présent en mémoire, ceci affiche le contexte de l’exception (l’état des registres et l’instruction fautive) et place le débogueur dans le même état que lorsque l’exception a été lancée.
Débogage managed
Pour charger les extensions nécessaires au débogage managed :
- sxe ld clr
- Ajoute un breakpoint qui suspendra l’application une fois CLR.dll en mémoire (préalable pour les étapes suivantes)
- .loadby sos clr
- Charge les extensions pour le débogage managed
- .load sosex
- Charge des extensions additionnelles pour le débogage managed (requiert l’installation préalable de ces extensions depuis le site du développeur)
Commandes utiles
- !mk
- Affiche le call stack combiné des appels managed et unmanaged entrelacés
- !dso
- Affiche la liste de tous les objets managed alloués sur le stack courant
-
!do
- Affiche les détails d’un objet managed particulier situé à l’adresse adresse
Conclusion
Il n’y a pas de règles quant aux manières de déboguer avec WinDBG. La réalité est qu’il y a autant de méthodes qu’il y a de programmeurs. Nous visiterons dans un prochain article certaines techniques qui permettent plus rapidement d’arriver à un résultat avec différents types de problèmes (plantages, gel d’application, etc.), mais aucune de ces techniques n’est une garantie de succès. Une seule chose est certaine, plus vous allez utiliser WinDBG, plus vous allez en apprendre sur le fonctionnement interne des applications Windows et du système d’exploitation Windows, ce qui en soit est un succès certain !