ApplicationVerifier, ou comment faire du code zero bug


    Présentation

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").

    Comment procéder

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.
    Liens connexes

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

    Conclusion

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.


Copyright 2006 Alexis PAUTROT