ApplicationVerifier,
ou comment faire du code zero bug
|
Application Verifier est un outil développé
par Microsoft afin de permettre la vérification
d'un certain nombre de bugs couramment présents
dans toutes applications en développement,
souvent indétectables et très durs à
trouver sans aide. Parmi les principaux bugs, on retrouve
les buffers overrun et underrun (dépassement
de tableau en écriture et plus sournoisement
en lecture) ou encore les accès concurrentiels
par deux threads à un heap de type d'accès
ne l'autorisant pas.
L'interface se compose d'une fenètre
principale. Cette fenètre se décompose
en trois parties. Voici à quoi elle ressemble
:
INSERER FIGURE 1
Le volet de gauche liste les applications
actuellement vérifiées. Le volet de
droite présente les différents types
de vérifications appliquées à
l'application choisi dans le volet de gauche, classées
par catégories. Enfin le volet présent
en bas, indiques les options configurable de la vérification
selectionnée dans le volet de droite.
| Comprendre
le fonctionnement |
Il est indispensable de comprendre comment
fonctionne ApplicationVerifier pour bien l'utiliser.
ApplicationVerifier n'est qu'une interface graphique
permettant de configurer des options de vérifications
qui sont intrinsèque à
l'architecture de Windows (uniquement pour les version
XP, Serveur 2003 et Vista).
Il est donc important de comprendre
qu'une fois les options définies et sauvegardées,
fermer ApplicationVerifier ne stopppera pas les vérifications
configurées. Il faut re-ouvrir ApplicationVerifier
et désactiver les options de vérification
(dans le volet de droite) ou supprimer l'application
de la liste (du volet de gauche).
Vous l'aurez compris, ApplicationVerifier
fonctionne sur tout type d'application, quelle soit
compilée par vous même ou pas, qu'elle
possède des informations de debug ou pas (mais
biensur, sans ces informations ApplicationVerifier
est nettement moins pratique).
Toutes les options configurables par
ApplicationVerifier sont les options de l'outil bas
niveau GFlags ( Global Flags ), disponible dans le
kit de debugging standard de Windows (voir section
"Liens connexes").
Il faut tout d'abord selectionner l'application
à vérifier. Menu "File" ->
"Add application". Choississez un .EXE à
vérifier. Validez. Il se retrouve dans la liste
du volet de gauche. Par défaut, ApplicationVerifier
choisi de vérifier les bugs "basiques"
et coche automatiquement ceux-ci dans le volet de
droite.
Lancer votre application depuis Visual
C++ (pour profiter du debuggeur) et tester votre application
comme bon vous semble pour tester les éventuels
problèmes. Lorsque ApplicationVerifier en trouve
un, il déclenche une
exception pour avertir le debugger d'un problème.
C'est brutal mais très efficace. Le debugger
se voit communiquer des informations qu'ApplicationVerifier
a extrait lors de l'erreur.
Elles se présentent sous cette
forme :
=======================================
VERIFIER STOP 00000013 : pid 0x3C0: First chance
access violation for
current stack trace.
00009070 : Invalid address causing
the exception.
6968AF0B : Code address executing the invalid access.
0012F658 : Exception record.
0012F674 : Context record.
=======================================
This verifier stop is continuable.
After debugging it use `go' to continue.
=======================================
Elles indiquent :
- le code de la vérification
( 00000013 )
- le PID du processus ( 0x3C0 )
- le type de l'exception générée
pour le coup ( First chance access
violation for current stack trace )
Dans le cas d'une exception de code
13 :
- l'adresse mémoire accédée
par l'instruction ( 00009070 )
- l'adresse de l'instruction machine
qui a causé le problème ( 6968AF0B
)
- l'adresse de la structure EXCEPTION_RECORD
( voir la MSDN )
- l'adresse de la structure CONTEXT
( voir la MSDN )
Ce qui nous interesse en premier lieu
c'est le code de vérification ( ici 13 ). Ce
code nous permet de déterminer le problème
détecté par ApplicationVerifier. Pour
obtenir plus d'informations, ouvrez la documentation
de ApplicationVerifier ( menu "Help" ->
"Help" ou F1 ) et sous l'onglet "Index"
se trouve la liste des codes. Un double clic et c'est
parti... A chaque type d'exception générée
sa liste de paramètres.
| Définir
l'action à effectuer en cas de problème
détecté |
Par défaut, lorsque ApplicationVerifier
rencontre un problème à signaler, il
provoque une exception pour que le debugger prenne
la main et que le programmeur puisse debugger immédiatement
l'endroit ou le problème est rencontré.
Vous pouvez configurer quelle action
ApplicationVerifier doit entreprendre en cas de vérification
raté. Dans le volet de droite, faite apparaître
le menu contextuel par un clic droit sur une des catégories.
Cliquez ensuite sur "Verifier stop options".
La fenètre qui apparaît
permet de configurer quelle action entreprendre selon
la nature de la vérification qui a échouée.
Le contenu est suffisament explicite pour ne pas le
détailler ici. Notez cependant les options
"Stop once" et "Not continuable",
vous permettant de passer outre une vérification
plusieurs fois échoué ou au contraire
de stopper net l'application après la levée
d'exception.
INSERER FIGURE 2
| Options
associées aux catégories de vérifications |
Le volet en bas de la fenètre
principale affiche une liste d'options associées
à la catégorie de vérification
selectionnée dans le volet de droite.
Parmi ces options se trouve quelques
options à connaitre, dont une détaillée
dans la section suivante.
| Vérifications
associées au heap du processus courant |
La grande force de ApplicationVerifier
dans son utilisation journalière réside
dans sa capacité à détecter les
buffer overruns/underruns. Le principe qu'il utilise
est simple et efficace mais implique une contrainte
de taille (c'est le cas de le dire !).
Lorsque la catégorie "Heaps"
est activé pour la vérification (volet
de droite), ApplicationVerifier applique le principe
suivant à chaque allocation de mémoire
dynamique effectuée par le programme : chaque
bloc mémoire alloué est alloué
dans une page mémoire physique qui lui est
entièrement réservé. De plus
ApplicationVerifier alloue une page de garde entre
chaque page physique alloué pour un bloc.
Cela signifie qu'une petite allocation
de seulement quelques octets prend quelques Ko en
pratique ( typiquement la taille de page étant
de 4K, on obtient donc 8k minimum par allocation ).
Vous ne rêvez pas, ApplicationVerifier est très
gourmand et la documentation stipule de passer son
fichier de pagination virtuelle à 1Go (le fichier
de swap).
D'ou l'intérêt de certaines
options comme la très fameuse "DeCommit"
( Decommit guard pages ) qui permet de ne pas utiliser
de page intercalée entre chaque page utilisée.
Vous pourrez ainsi gagner (ou ne pas perdre) deux
fois plus de mémoire.
| Ma
pile d'appels est moisie, je suis perdu !! |
Si vous avez une pile d'appels de type..
NNTDLL! 7c911230()
VRFCORE! 00366a17()
VFBASICS! 00392809()
VFBASICS! 003890b9()
VFBASICS! 00388808()
NTDLL! 7c952dcf()
NTDLL! 7c9477da()
NTDLL! 7c91eafa()
..c'est plutot mal parti, mais tout
n'est pas perdu.
Dans ce cas, plusieurs solutions :
- Vous êtes sous VC++ 6 et vous
utilisez le debugger standard. Tentez de forcer
l'execution du programme pour espérer retomber
dans une pile d'appels valide. Désactivez
l'option de stop (le "Not continuable")
sur la
vérification donnée pour pouvoir continuer
l'execution, ou désactivez purement le lancement
d'une exception sur la vérification écouée
pour ne pas perturber l'execution (qui sinon remonte
la pile d'appel avec l'exception générée).
- Vous êtes sous VC++ 7 ou supérieur
ou vous utilisez un debugger de type WinDBG. Reportez
vous à la rubrique "Installer les symboles
de debug des DLL de windows".
- Vous utilisez WinDBG. Si vous finissez
par ne plus avoir le choix, l'adresse mémoire
fautive en accès peut permettre de lancer
la commande de debug "!heap -p -a ACCESS_ADDRESS"
pour tracer l'allocation du bloc de heap contenant
l'adresse, vous permettant d'avancer dans la résolution
du problème. Cette commande nécessite
d'avoir la main sur un debugger avec ligne de commande,
comme WinDBG ou NTSD le permettent (voir la rubrique
"Liens connexes").
(Note : VRFCORE et VFBASICS sont les
modules d'ApplicationVerifier)
| Installer
les symboles de debug des DLL de windows |
Si vous utilisez le debugger standard
de Visual C++ en version 6, passez votre chemin..
les symboles de debug XP (les .PDB) ne sont pas compatibles
avec le debugger de Visual en version 6.
Si vous utilisez le debugger standard
de Visual C++ .NET ou vous utilisez un debugger de
type WinDBG, vous avez deux choix :
- Télécharger l'archive
des symbôles depuis le site de microsoft.
- Configurer votre débugger
pour automatiquement télécharger (et
mettre en cache sur votre disque) les symboles que
vous utilisez.
| Télécharger
et installer les symboles de debug |
- Allez sur la page http://www.microsoft.com/whdc/devtools/debugging/symbolpkg.mspx
- Téléchargez les symboles
des DLL de windows.
- Installez les ou bon vous semble
( C:\WINDOWS\Symbols par défaut ).
- Créez (ou éditez)
la variable d'environnement appelée _NT_SYMBOL_PATH
- La faire pointer sur le répertoire
ou vous avez installé les symboles ( C:\WINDOWS\Symbols
).
| Automatiquement
télécharger les symboles de debug |
- Allez sur la page http://www.microsoft.com/whdc/devtools/debugging/default.mspx
- Téléchargez et installez
le kit de debugging de windows.
- Si vous utilisez le debugger de Visual
C++, allez dans le répertoire d'installation
du kit de debugging ( par défaut C:\Program
Files\Debugging Tools for Windows )
- Copiez le fichier symsrv.dll.
- Allez dans le répertoire
de votre IDE Visual ( par défaut C:\Program
Files\Microsoft Visual Studio .NET 2003\Common7\IDE
pour la version 2003 )
- Coller symsrv.dll ( remplacez l'ancien,
venant par défaut avec le debugger de Visual
).
- Si vous utilisez WinDBG, symsrv.dll
se trouve deja accessible à WinDBG.
- Créez (ou éditez)
la variable d'environnement appelée _NT_SYMBOL_PATH.
- La faire pointer sur le serveur publique
de téléchargement des symboles. Définissez
la variable comme suit :
'SRV*c:\localsymbols*http://msdl.microsoft.com/download/symbols'
où 'c:\localsymbols' est le chemin de votre
répertoire de cache.
Microsoft Application
Verifier :
http://www.microsoft.com/technet/prodtechnol/windows/appcompatibility/appverifier.mspx
Windows Application Compatibility
:
http://www.microsoft.com/technet/desktopdeployment/appcompat/toolkit.mspx
Windows Support Tools ( GFlags
) :
http://technet2.microsoft.com/WindowsServer/en/Library/b6af1963-3b75-42f2-860f-aff9354aefde1033.mspx?mfr=true
Debugging tools for Windows
( WinDbg, NTSD et GFlags ) :
http://www.microsoft.com/whdc/devtools/debugging/default.mspx
www.DebugInfo.com :
http://www.debuginfo.com/
Installation des symboles :
http://www.codeproject.com/useritems/symbols.asp
http://support.microsoft.com/?kbid=311503
Windows Debuggers : WinDBG
tutorial sur www.codeproject.com :
http://www.thecodeproject.com/debug/windbg_part1.asp
http://www.codeproject.com/debug/#General
Ayant plus de symboles, les debuggers
arrivent plus facilement à vous restituer une
pile d'appels complète y compris à retrouver
vos propres symboles. Debugger dans le code assembleur
des DLLs dont vous n'avez pas le source est aussi
plus simple, puisque les debugger place les noms des
fonctions dans lesquelles vous vous trouvez. Vous
devriez voir la différence.
Quelquesoit l'étrangeté
apparente des problèmes qu'il détecte,
l'incohérence de la pile d'appels lors d'une
levée d'exception ou encore l'illusoire persuation
que la ligne de code qui cause une exception est une
ligne "qui ne fait rien" et que "c'est
impossible que ça plante à cet endroit",
gardez à l'esprit qu'ApplicationVerifier ne
se trompe jamais.
|