Construire un socle de test des cas anormaux Windows avec Application Verifier

· Mis à jour le: · · Développement Windows, Investigation de bug, Caméra industrielle, Application Verifier, Tests de cas anormaux, Fuite de handles

Application Verifier est un outil puissant lorsqu’on veut faire remonter par anticipation les anomalies qui surviennent dans le code natif Windows ou à la frontière Win32. En particulier, lorsqu’on veut tester des anomalies de handles, une corruption de tas (heap corruption) ou des failure paths liés à un manque de ressources, il permet de faire apparaître, bien plus tôt, des problèmes que les essais en fonctionnement normal seuls ne révéleraient jamais.

Dans la partie 1, Quand une application de pilotage de caméra industrielle plante soudainement après un mois (partie 1) - Trouver les fuites de handles et concevoir des journaux pour un fonctionnement de longue durée, nous avons présenté un cas où l’investigation d’une application de pilotage ayant planté après un fonctionnement prolongé a révélé qu’une fuite de handles en était la cause. Cela dit, se contenter de renforcer les journaux ne fait que la moitié du travail. Ce qu’on veut vraiment, c’est pouvoir tester par avance si l’on se trouve dans un état où l’on « comprend ce qui s’est passé », même si une erreur de programmation imprévue provoque à l’avenir une fuite de mémoire, une fuite de handles, un échec partiel ou un oubli de libération.

C’est là qu’intervient Application Verifier, l’outil que nous avons utilisé. C’est un outil qui permet d’injecter des vérifications à l’exécution et du fault injection dans le code s’exécutant dans le code natif Windows et à la frontière Win32. Ce qui est particulièrement pratique en pratique, c’est que l’on peut déclencher par anticipation des modes de défaillance ressemblant à un manque de mémoire ou de ressources, sans avoir réellement à épuiser la mémoire de la machine.

Dans cette seconde partie, nous présentons ce qu’est Application Verifier, ce qu’il permet de faire, et comment l’intégrer dans un socle de test des cas anormaux, dans le contexte d’une application de pilotage de caméra industrielle.

Table des matières

  1. La conclusion d’abord (en une phrase)
  2. Qu’est-ce qu’Application Verifier ?
    • 2.1. En une phrase
    • 2.2. Dans quelles situations il est efficace
    • 2.3. Ce qu’on y gagne
  3. Ce qu’Application Verifier permet de faire
    • 3.1. Basics : Handles / Heaps / Locks / Memory / TLS, etc.
    • 3.2. Low Resource Simulation : anticiper un manque de mémoire ou de ressources
    • 3.3. Page Heap et le débogueur
    • 3.4. !avrf / !htrace / journaux
  4. Pourquoi nous l’avons introduit cette fois
    • 4.1. L’objectif n’est pas seulement de « trouver des bugs »
    • 4.2. Provoquer un phénomène proche d’un manque de mémoire
    • 4.3. Vérifier qu’on peut suivre une anomalie de handle quand elle survient
  5. Comment provoquer un phénomène de manque de mémoire ou de ressources
    • 5.1. La logique de Low Resource Simulation
    • 5.2. Ce qu’on peut faire échouer
    • 5.3. Comment l’appliquer en pratique
  6. Comment observer les anomalies de handles
    • 6.1. La vérification Handles
    • 6.2. Voir la pile open / close avec !htrace
    • 6.3. Comment le combiner avec ses propres journaux
  7. Comment construire un socle de test des cas anormaux
    • 7.1. Faire porter l’unité d’exécution sur un harness
    • 7.2. Séparer les menus de test
    • 7.3. Ce qu’il faut collecter
    • 7.4. Critères de réussite
    • 7.5. Points de vigilance
  8. Guide de décision, en un coup d’œil
  9. Résumé
  10. Références

1. La conclusion d’abord (en une phrase)

  • Application Verifier est un outil qui facilite la détection à l’exécution des mauvais usages survenant à la frontière non managée / native de Windows
  • Son intérêt ne se limite pas à « trouver des bugs » : il permet aussi de provoquer par anticipation des cas anormaux qui apparaissent rarement
  • Handles détecte l’utilisation d’un invalid handle, Heaps fait apparaître la heap corruption, et Low Resource Simulation permet le fault injection de situations proches d’un manque de mémoire ou de ressources
  • Confier entièrement à Application Verifier l’enquête sur une fuite d’un EXE résident de longue durée est une mauvaise approche ; il est plus réaliste de le combiner avec ses propres journaux de Handle Count et de cycle de vie des ressources
  • Dans un socle de test des cas anormaux, il est plus facile à lire de séparer un run verifier en fonctionnement normal et un run avec fault injection
  • Même pour tester une DLL, la cible sur laquelle activer Application Verifier est l’EXE de test qui exécute réellement cette DLL

En résumé, Application Verifier est un outil qui fait sortir au grand jour les « bugs sournois » qui vivent autour de la frontière native / Win32 de Windows. Il est particulièrement bien adapté à des univers comme les applications de pilotage d’équipements, où SDK natifs, P/Invoke et API Win32 se mélangent couramment.

2. Qu’est-ce qu’Application Verifier ?

2.1. En une phrase

Application Verifier est un outil de vérification à l’exécution (runtime) pour les applications Windows en mode utilisateur (user-mode). Il surveille comment une application en cours d’exécution utilise les API du système d’exploitation et gère ses ressources, détecte les usages suspects, et permet d’injecter des échecs de façon intentionnelle.

Contrairement à « l’analyse statique » ou aux « tests unitaires », c’est un outil qui observe comment les choses se cassent quand ce chemin de code est réellement emprunté. Il est donc bien adapté pour mettre au jour les failure paths que les tests fonctionnels habituels ne révèlent jamais.

Harness de testApplication de contrôle / wrapper SDKApplication VerifierAPI Win32 / DLL native / ressources OSverifier stopsortie du débogueurjournaux AppVerifierJournal structuré propre

2.2. Dans quelles situations il est efficace

Il est particulièrement efficace dans des situations comme celles-ci.

  • Vous appelez des DLL natives ou un SDK de caméra
  • Vous traversez des frontières P/Invoke ou COM
  • Vous utilisez beaucoup, directement ou indirectement, des handles, des heaps, des locks et de la mémoire virtuelle
  • L’application ne plante presque jamais en fonctionnement normal, mais la gestion du cycle de vie semble fragile sur les chemins anormaux
  • « Renvoie parfois un échec bizarre » apparaît avant « plante »

À l’inverse, ce n’est pas un outil pour suivre un graphe d’objets dans un monde purement managed. Ainsi, même dans une application C#, il apporte beaucoup si la frontière avec un SDK natif ou Win32 est épaisse, mais ce n’est pas l’outil unique pour tout observer d’une fuite de heap purement managed.

2.3. Ce qu’on y gagne

En pratique, les bénéfices se résument grosso modo à ces trois points.

  1. Arrêter tôt les mauvais usages à la frontière native
    • invalid handle
    • heap corruption
    • lock misuse
    • virtual memory API misuse, etc.
  2. Anticiper des modes de défaillance qui n’apparaissent qu’en cas de manque de ressources
    • l’équivalent de malloc échoue de temps en temps
    • CreateEvent ou CreateFile échouent de temps en temps
    • VirtualAlloc échoue
  3. Plus facile à suivre combiné avec un débogueur
    • !avrf
    • !htrace
    • !heap -p -a
    • les journaux de verifier stop

Ce qui pose problème dans les applications de pilotage d’équipements, c’est de « ne pas savoir ce qui s’est passé sur le chemin anormal ». Application Verifier est très efficace pour réduire ce « ne pas savoir ».

3. Ce qu’Application Verifier permet de faire

3.1. Basics : Handles / Heaps / Locks / Memory / TLS, etc.

L’ensemble de base d’Application Verifier s’appelle Basics. Il regroupe les vérifications les plus utilisées en pratique.

Couche Ce qu’elle observe Utilité dans ce contexte
Handles Utilisation d’un invalid handle Vérifier qu’on ne touche pas un handle fermé / corrompu
Heaps Heap corruption Mettre au jour la corruption de buffer et le use-after-free à la frontière d’un SDK natif
Leak Ressources non libérées au moment du unload d’une DLL Tests d’un harness de courte durée, cas incluant un unload
Locks / SRWLock Mauvais usage des locks Vérifier les compétitions entre reconnect et shutdown
Memory Mauvais usage de VirtualAlloc / MapViewOfFile, etc. Vérifier les anomalies autour des gros buffers et de la mémoire partagée
TLS Mauvais usage des API Thread Local Storage Filet de sécurité pour du code natif aux frontières de threads complexes
Threadpool Cohérence des API de threadpool et de l’état des workers Renfort quand callbacks et traitement asynchrone abondent

Le point important est de « stopper l’usage suspect sur-le-champ », plutôt que de « comprendre en relisant après le plantage ». Pour les défauts qui n’apparaissent qu’en fonctionnement de longue durée, cette anticipation change beaucoup la donne.

3.2. Low Resource Simulation : anticiper un manque de mémoire ou de ressources

C’est la partie qui est vraiment pratique dans le travail quotidien. En effet, on peut provoquer des phénomènes proches d’un manque de mémoire ou de ressources sans avoir réellement à épuiser la RAM.

L’idée est simple.

  • Prendre un certain appel d’API
  • Avec une certaine probabilité
  • Le faire échouer volontairement

Cela permet d’emprunter des error paths qui, en temps normal, ne sont pratiquement jamais empruntés.

Concrètement, cela permet de provoquer facilement, intentionnellement, des phénomènes comme ceux-ci.

  • HeapAlloc ou VirtualAlloc échouent
  • CreateFile échoue
  • CreateEvent échoue
  • MapViewOfFile échoue
  • Une allocation de type OLE/COM comme SysAllocString échoue

C’est bien plus facile à gérer que de vraiment chercher à épuiser la mémoire en mettant à mal toute la machine. Qui plus est, il est possible de cibler le fault injection sur une DLL en particulier. Pour des configurations comme les applications de pilotage d’équipements, où un wrapper maison se mélange à un SDK fournisseur, c’est très adapté à la pratique.

3.3. Page Heap et le débogueur

Pour observer une heap corruption, la combinaison Heaps + page heap est puissante. Le full page heap en particulier a l’avantage d’utiliser des guard pages pour s’arrêter facilement au moment même de la corruption.

Cela dit, c’est assez lourd. Plutôt qu’un passage en force de longue durée, il est plus pratique de se restreindre à des scénarios proches de la reproduction et de les exécuter sous débogueur.

Voici donc une répartition réaliste en pratique.

  • D’abord appliquer largement Basics
  • Une fois le heap suspecté, utiliser le full page heap
  • Si c’est trop lourd, redescendre vers le light page heap
  • Pour les essais de longue durée proches de la production, se fier avant tout à ses propres journaux

En définitive, AppVerifier n’est pas une baguette magique, mais un outil dont on change la lame selon la situation.

3.4. !avrf / !htrace / journaux

Application Verifier ne se contente pas d’émettre un stop et de s’arrêter là. Grâce à ses extensions de débogueur et à ses journaux, il devient plus facile de retracer ce qui s’est passé.

  • !avrf
    • Voir la configuration actuelle du verifier et le stop en cours
  • !htrace
    • Voir la pile d’appels d’ouverture / fermeture / référence invalide (open / close / invalid reference) d’un handle
  • !heap -p -a
    • Combiné avec page heap, retracer le bloc de heap corrompu
  • Les journaux AppVerifier
    • Conserver les journaux du moment où un stop survient

Ce qui est particulièrement appréciable, c’est que l’activation de Handles active automatiquement le handle tracing. Cela permet de retracer bien plus facilement, après coup, « où ce handle a été ouvert et où il a été fermé ».

4. Pourquoi nous l’avons introduit cette fois

4.1. L’objectif n’est pas seulement de « trouver des bugs »

Notre objectif cette fois n’était pas simplement de « trouver un bug avec AppVerifier ». Plus concrètement, ce que nous voulions vérifier était le suivant.

  • Si une fuite de ressources se reproduit un jour sur un autre failure path
  • Le contexte reste-t-il bien conservé dans les journaux ?
  • Peut-on suivre l’affaire jusqu’au bout en combinant avec les informations du débogueur ?
  • Évite-t-on de finir dans un état où « on ne sait pas ce qui s’est passé » ?

Autrement dit, nous l’avons utilisé non seulement comme un détecteur, mais aussi comme un test de notre socle d’observation.

4.2. Provoquer un phénomène proche d’un manque de mémoire

Provoquer un véritable manque de mémoire sur une machine de développement habituelle est assez fastidieux. Pire encore, une fois que toute la machine devient instable, c’est le test lui-même qui se retrouve noyé dans le bruit.

Nous avons donc utilisé Low Resource Simulation pour aller dans le sens suivant : faire délibérément passer l’application par les failure paths susceptibles de se produire en cas de manque de mémoire ou de ressources.

Cela permet de répondre plus facilement à des questions comme celles-ci.

  • Si CreateEvent échoue, cameraId et phase restent-ils dans les journaux ?
  • Après une initialisation à moitié terminée, le clean up s’exécute-t-il vraiment ?
  • Si VirtualAlloc échoue, la nouvelle tentative (retry) évite-t-elle de corrompre l’état ?
  • Si CreateFile échoue sur le chemin de sauvegarde, le handle revient-il correctement ?

Ce que nous voulons souligner, c’est que provoquer l’anomalie n’est pas le but en soi ; le but est que le mode de défaillance reste lisible au moment de l’anomalie.

4.3. Vérifier qu’on peut suivre une anomalie de handle quand elle survient

Comme pour la fuite de handles vue dans la partie 1, tout ce qui touche aux handles a tendance à faire diverger l’endroit où ça finit par planter et la véritable cause.

Voici donc ce que nous voulions vérifier.

  • Quand un stop invalid handle survient, peut-on suivre l’open / close avec !htrace ?
  • Est-ce que cela se relie aux resourceId / sessionId / phase de nos propres journaux ?
  • Le handle count revient-il à la normale après l’échec ?
  • Quand le harness est un processus de courte durée, les écarts de fuite sont-ils faciles à lire ?

Une fois qu’on voit jusque-là, on passe d’un simple « un bug est apparu » à la capacité d’aller jusqu’à « quelle responsabilité a vu sa gestion de cycle de vie se casser ».

5. Comment provoquer un phénomène de manque de mémoire ou de ressources

5.1. La logique de Low Resource Simulation

Low Resource Simulation est, en clair, du fault injection. Plutôt que de reconstituer fidèlement un environnement à ressources réduites, l’idée est de mélanger artificiellement les échecs d’API représentatifs qui se produisent en cas de manque de ressources.

Ses cas d’usage sont donc assez clairs.

  • Vérifier le nettoyage sur les failure paths
  • Vérifier la robustesse du retry / reconnect
  • Vérifier une initialisation où succès et échecs partiels se mélangent
  • Vérifier que les journaux subsistent même pour des « échecs qui ne se produisent normalement jamais »

L’astuce ici est de ne pas tout faire échouer dès le départ. Si l’on active tout d’un coup, les journaux explosent et on perd de vue « ce qu’on est en train d’observer ».

5.2. Ce qu’on peut faire échouer

Avec Low Resource Simulation, on peut faire échouer de façon probabiliste les classes d’API suivantes, à titre représentatif.

Classe Exemples Exemple dans une application de pilotage d’équipement
Heap_Alloc Allocation de heap Buffers temporaires, métadonnées d’image, allocations internes du wrapper SDK
Virtual_Alloc Allocation de mémoire virtuelle Frame buffers volumineux, ring buffers
File CreateFile, etc. Ouverture des chemins de sauvegarde et des fichiers de journal
Event CreateEvent, etc. Notification frame ready, synchronisation stop/reconnect
MapView CreateMapView, etc. Mémoire partagée ou fichier mappé en mémoire
Ole_Alloc SysAllocString, etc. Frontière COM / OLE
Wait Famille WaitForXXX Autour des échecs d’attente de synchronisation
Registry Accès au registre Lecture/écriture de paramètres et configuration liée aux pilotes

En pratique, plutôt que d’activer tout en même temps, l’essentiel est de restreindre l’activation aux classes les plus proches du failure path que l’on veut observer cette fois.

5.3. Comment l’appliquer en pratique

Voici, par exemple, à quoi ressemble l’idée en ligne de commande.

appverif /verify CameraHarness.exe
appverif /verify CameraHarness.exe /faults
appverif -enable lowres -for CameraHarness.exe -with heap_alloc=20000 virtual_alloc=20000 file=20000 event=20000
appverif -query lowres -for CameraHarness.exe

La démarche ressemble à ceci.

  1. D’abord exécuter le fonctionnement normal avec Basics seul
  2. Ensuite ajouter Low Resource Simulation et exécuter avec fault injection
  3. Si besoin, n’attribuer une probabilité qu’aux échecs qu’on veut observer, comme file ou event
  4. Si l’on veut ne cibler qu’une DLL en particulier, restreindre l’injection à cette DLL

Le raccourci /faults est pratique, mais à lui seul il est centré sur OLE_ALLOC et HEAP_ALLOC. Pour observer les failure paths de CreateFile ou CreateEvent, il est plus fiable d’écrire explicitement -enable lowres -with file=... event=....

Dans les applications de pilotage d’équipements, il est souvent plus facile à lire de se restreindre au wrapper de la caméra ou à la DLL du chemin de sauvegarde, plutôt que de disperser les fautes sur toute l’application.

Par exemple, on peut construire des scénarios comme ceux-ci.

  • Échec de CreateEvent juste après le début d’un reconnect
  • Échec de CreateFile au début d’une sauvegarde
  • Échec d’allocation d’un buffer temporaire
  • Échec de SysAllocString lors d’une conversion COM
  • Vérification du chemin d’échec des API d’attente

Ce sont des chemins que les tests habituels en fonctionnement normal ne permettent quasiment jamais d’atteindre. C’est précisément pour cela qu’il est utile de les emprunter délibérément.

6. Comment observer les anomalies de handles

6.1. La vérification Handles

Pour tout ce qui touche aux handles, on commence par Handles. Cela facilite la détection de l’usage d’un invalid handle.

Voici les accidents typiques qu’il détecte bien.

  • Réutiliser un handle déjà fermé
  • Transmettre une valeur de handle corrompue
  • Utiliser un handle resté non initialisé à cause d’un échec partiel
  • Un cycle de vie qui se casse et un accès depuis un autre thread

Là où, en fonctionnement de longue durée, on ne verrait qu’« une erreur bizarre apparaît de temps en temps », sous le verifier, cela peut s’arrêter net sur place. Cette anticipation aide énormément.

6.2. Voir la pile open / close avec !htrace

Ce qui rend Handles précieux, c’est qu’il s’entend bien avec le handle tracing.

windbg -xd av -xd ch -xd sov CameraHarness.exe
!avrf
!htrace 0x00000ABC

Ce qu’on veut voir avec !htrace, c’est grosso modo ceci.

  • Où ce handle a été ouvert
  • Où il a été fermé
  • S’il a été référencé comme invalid handle
  • Si les ouvertures s’accumulent plus que prévu

Ce qui rend les fuites de handles et le handle misuse délicats, c’est que l’API qui finit par planter n’est pas la véritable cause. Avec !htrace, on peut retracer l’historique de ce handle de façon très concrète.

6.3. Comment le combiner avec ses propres journaux

Cela dit, Application Verifier seul ne suffit pas. En particulier, mener seul avec lui l’enquête sur une fuite d’un EXE résident de longue durée est assez pénible.

Aussi, en pratique, nous combinons les éléments suivants.

  • Handle Count relevé périodiquement
  • sessionId
  • resourceId
  • phase
  • Journaux de cycle de vie (lifecycle log) du create/open et du close/dispose
  • Dump et sortie du débogueur au moment d’un verifier stop

Avec cela, on peut par exemple suivre l’affaire comme suit.

  1. Le heartbeat montre que la pente du Handle Count est suspecte
  2. Le lifecycle log permet d’isoler la ressource qui a un Create mais pas de Close
  3. Un run du verifier fait apparaître par anticipation l’invalid handle ou le misuse
  4. !htrace montre la pile open / close

Cette combinaison rend le suivi nettement plus facile.

7. Comment construire un socle de test des cas anormaux

7.1. Faire porter l’unité d’exécution sur un harness

Application Verifier ne peut pas être activé après coup sur un processus déjà en cours d’exécution. On configure, puis on lance.

De plus, la configuration reste en place tant qu’on ne la supprime pas explicitement. Aussi, en pratique, il est plus facile de la faire porter sur un EXE harness de test plutôt que sur l’application de production elle-même.

Par exemple, une configuration comme celle-ci.

Scenario RunnerCameraHarness.exeCameraSdkWrapper.dllSDK fournisseurJournal structuréDump / débogueur

Cela apporte les avantages suivants.

  • Faire tourner un scénario par processus
  • Des écarts de fuite faciles à lire
  • Un basculement facile de la configuration AppVerifier entre activé et désactivé
  • La possibilité de tester une DLL via le côté EXE

Voici à quoi ressemblent les commandes.

appverif /verify CameraHarness.exe
appverif /n CameraHarness.exe

Activation avant le lancement, désactivation explicite. En partant du principe du harness pour cela, on réduit aussi les risques d’accident de configuration.

7.2. Séparer les menus de test

Dans un socle de test des cas anormaux, mieux vaut ne pas tout faire en un seul passage. Le découper en environ trois volets rend les résultats plus lisibles.

  1. Fonctionnement normal + Basics
    • N’injecter aucun échec
    • Vérifier qu’aucun verifier stop n’apparaît
  2. Volet fault injection
    • Low Resource Simulation
    • Cibler des échecs sur event / file / heap_alloc / virtual_alloc, etc.
  3. Volet approfondissement du heap
    • Heaps
    • full page heap
    • Reproduire localement sous débogueur

Cette séparation évite de mélanger « est-ce que ça casse en usage normal » et « est-ce que ça ne casse qu’en cas de manque de ressources ».

La présence ou l’absence de fault injection change en particulier considérablement les code paths empruntés. Il vaut donc mieux exécuter à la fois le run sans faute et le run avec faute.

7.3. Ce qu’il faut collecter

Voici, au minimum, ce qu’il faut conserver.

Catégorie Ce qu’on veut
Journaux applicatifs cameraId, sessionId, phase, handleCount, error code
État du process Handle Count, Private Bytes, Thread Count
Informations du débogueur !avrf, !htrace, et !heap -p -a si besoin
Dumps Au moment d’un verifier stop, ou lors d’une terminaison anormale
Journaux AppVerifier Traces des stops, exportées en XML pour agrégation si besoin

Si besoin, les journaux du côté AppVerifier peuvent également être exportés en XML et agrégés. Mais les regarder seuls ne suffit souvent pas à cerner la cause, donc il est plus adapté à la pratique de partir du principe qu’on les lira côte à côte avec ses propres journaux.

Le volume des journaux n’est pas une vertu en soi. Ce qui compte, c’est que la causalité puisse être reconstituée par la suite.

7.4. Critères de réussite

Le critère de réussite « ça n’a pas planté » est également trop faible. Dans ce contexte, il fallait au moins ce qui suit.

  • Aucun verifier stop en fonctionnement normal + Basics
  • Même avec fault injection, les échecs attendus restent dans les journaux
  • Les ressources initialisées à moitié sont correctement nettoyées
  • Après un reconnect / retry, le Handle Count revient près de sa valeur de référence (baseline)
  • Quand un verifier stop survient, on peut le suivre via sessionId / phase / la pile d’appels
  • Aucun échec ne se termine en « on ne sait pas ce qui s’est passé »

Ce qui compte ici, c’est d’évaluer séparément le fait de ne pas casser et le fait de pouvoir suivre l’affaire quand ça casse.

7.5. Points de vigilance

Application Verifier est très pratique, mais ce n’est pas de la magie.

  • Un code path réellement jamais emprunté n’est pas vérifié
  • Le full page heap est lourd
  • Un stop peut aussi survenir du côté d’un SDK tiers
  • Les code paths empruntés diffèrent beaucoup selon qu’il y a ou non du fault injection
  • Ce n’est pas un outil unique pour mener l’enquête sur une fuite de heap purement managed

Sa place se résume donc ainsi.

  • La dérive de longue durée : ses propres journaux et compteurs
  • Le mauvais usage à la frontière native : Application Verifier
  • La reconstruction de la causalité en cas d’anomalie : journal structuré + dump + débogueur

Cette répartition des rôles est la plus adaptée à la pratique.

8. Guide de décision, en un coup d’œil

  • Un invalid handle ou un double close est suspecté
    • Handles + !htrace
  • Une heap corruption / un use-after-free est suspecté
    • Heaps + full page heap + !heap -p -a
  • On veut provoquer un phénomène proche d’un manque de mémoire ou de ressources
    • Low Resource Simulation
  • Ça casse progressivement en fonctionnement de longue durée
    • Commencer par ses propres Handle Count / Private Bytes / lifecycle log
  • On veut tester une DLL
    • Activer Application Verifier sur l’EXE harness qui appelle cette DLL

Tout activer d’un coup dès le départ finit généralement en un brouillard de journaux. Appliquer d’abord la lame la plus proche du failure path qu’on veut observer est bien plus clair.

9. Résumé

La place d’Application Verifier est celle d’un runtime verifier pour la frontière native / Win32 de Windows. Avec Handles / Heaps / Locks / Memory / TLS / Low Resource Simulation et le reste, on peut forcer par anticipation le passage par des failure paths qui apparaissent rarement.

Ce qui a payé dans ce contexte, c’est qu’une anomalie de handle est devenue facile à suivre avec !htrace quand elle survenait, qu’on pouvait provoquer des phénomènes proches d’un manque de mémoire ou de ressources sans mettre à mal toute la machine, et qu’on a pu vérifier si nos propres journaux étaient réellement utiles à ce moment-là.

Côté conduite en pratique, cela donne la répartition suivante : séparer le run fonctionnement normal + Basics du run fault injection, préparer un EXE harness et faire tourner les scénarios via des processus de courte durée. Par-dessus, on combine avec ses propres journaux, les dumps et les informations du débogueur, tout en observant la pente des fuites de longue durée elle-même avec ses propres counters — c’est là toute la répartition des rôles.

Application Verifier est un outil pour « aller à la rencontre » des anomalies rares, plutôt que de « les attendre par hasard ».

Dans les applications de pilotage d’équipements, ne pas casser est important, mais pouvoir expliquer ce qui s’est passé quand ça casse l’est tout autant. En ce sens, nous pensons que c’est un outil résolument adapté à la pratique.

Partie 1 : Quand une application de pilotage de caméra industrielle plante soudainement après un mois (partie 1) - Trouver les fuites de handles et concevoir des journaux pour un fonctionnement de longue durée

10. Références

Articles récents partageant les mêmes étiquettes, pour approfondir des sujets proches.

Ces pages replacent le sujet dans un contexte plus large de services et de décisions.

Ces études de cas présentent une démarche proche d’analyse, de priorisation ou de refonte.

Cet article est directement lié aux services suivants.

Analyse des bugs et des causes

Application Verifier et les socles de test des cas anormaux sont un thème central de notre service d'investigation de bug et d'analyse de cause racine, qui fait progresser la reproduction des défaillances et l'identification de leur cause.

Questions fréquentes

Questions souvent posées lors d’une consultation sur le sujet de cet article.

Qu'est-ce qu'Application Verifier ?
C'est un outil de vérification à l'exécution (runtime) pour les applications Windows en mode utilisateur (user-mode). Il surveille comment une application en cours d'exécution utilise les API du système d'exploitation et gère ses ressources, détecte les usages suspects comme l'utilisation d'un invalid handle ou une heap corruption, et permet d'injecter des échecs de façon intentionnelle. Contrairement à l'analyse statique ou aux tests unitaires, c'est un outil qui observe comment les choses se cassent réellement lorsqu'on emprunte ce chemin de code, ce qui le rend particulièrement adapté pour mettre au jour les failure paths que les tests fonctionnels habituels ne révèlent jamais.
Application Verifier permet-il de reproduire un manque de mémoire ?
Avec Low Resource Simulation, vous pouvez déclencher par anticipation des phénomènes proches d'un manque de mémoire ou de ressources, sans avoir à réellement épuiser la RAM de la machine. Le mécanisme est du fault injection : les appels d'API comme HeapAlloc, VirtualAlloc, CreateFile ou CreateEvent sont volontairement mis en échec avec une certaine probabilité. Il est également possible de cibler l'injection de fautes sur une DLL spécifique, ce qui reste maniable même dans une configuration où vos propres wrappers se mêlent à un SDK fournisseur. Attention cependant : si vous faites tout échouer dès le départ, les journaux deviennent illisibles ; l'astuce consiste à n'activer, en priorité, que les classes de fautes proches du failure path que vous voulez observer.
Application Verifier peut-il servir à enquêter sur une fuite de handles ?
En activant la vérification Handles, vous pouvez détecter un usage invalid handle comme la réutilisation d'un handle déjà fermé, et le handle tracing s'active automatiquement, ce qui permet de suivre avec !htrace la pile d'appels d'ouverture / fermeture (open / close) de ce handle. Cela dit, il n'est pas réaliste de confier entièrement à Application Verifier l'enquête sur une fuite d'un EXE résident de longue durée. La répartition la plus adaptée à la pratique consiste à la combiner avec un enregistrement périodique du Handle Count et vos propres journaux de cycle de vie des ressources : la détection d'une dérive revient à vos propres journaux, et la détection d'un mauvais usage revient au verifier.
Comment utiliser Application Verifier pour tester une DLL ?
La cible sur laquelle activer Application Verifier est l'EXE de test qui exécute réellement cette DLL. Il ne peut pas être activé après coup sur un processus déjà en cours d'exécution ; il faut le configurer avant le lancement. De plus, la configuration reste en place tant qu'elle n'est pas explicitement supprimée, donc il est plus pratique de la faire porter sur un EXE harness de test plutôt que sur l'application de production elle-même. En exécutant un scénario par processus, les écarts de fuite deviennent plus faciles à lire, et il devient également plus simple de basculer la configuration entre activée et désactivée.

Profil de l’auteur

Page de présentation de l’auteur de l’article.

Go Komura

Représentant de KomuraSoft LLC

Spécialisé dans le développement de logiciels Windows, le conseil technique et l’analyse de pannes, notamment pour les systèmes existants et les incidents difficiles à reproduire.

Retour au blog