Exemple 12 : Utilisation de la vérification du tas de page pour rechercher un bogue

La série de commandes suivante montre comment utiliser les fonctionnalités de vérification du tas de page de GFlags et du débogueur NTSD pour détecter une erreur dans l’utilisation de la mémoire du tas. Dans cet exemple, le programmeur soupçonne qu’une application fictive, pheap-buggy.exe, a une erreur de tas et procède à une série de tests pour identifier l’erreur.

Pour plus d’informations sur NTSD, consultez Débogage à l’aide de CDB et NTSD.

Étape 1 : Activer la vérification du tas de pages standard

La commande suivante active la vérification standard du tas de pages pour les pheap-buggy.exe :

gflags /p /enable pheap-buggy.exe

Étape 2 : Vérifier que le tas de page est activé

La commande suivante répertorie les fichiers image pour lesquels la vérification du tas de pages est activée :

gflags /p

En réponse, GFlags affiche la liste de programmes suivante. Dans cet affichage, les traces indiquent la vérification du tas de page standard, et les traces complètes indiquent la vérification du tas de page complète. Dans ce cas, pheap-buggy.exe est répertorié avec des traces, indiquant que la vérification du tas de page standard est activée, comme prévu.

pheap-buggy.exe: page heap enabled with flags (traces )

Étape 3 : Exécuter le débogueur

La commande suivante exécute la fonction CorruptAfterEnd de pheap-buggy.exe dans NTSD avec les paramètres -g (ignorer le point d’arrêt initial) et -x (définir l’arrêt de la deuxième chance sur les exceptions de violation d’accès) :

ntsd -g -x pheap-buggy CorruptAfterEnd

En cas d’échec de l’application, NTSD génère l’affichage suivant, ce qui indique qu’il a détecté une erreur dans pheap-buggy.exe :

===========================================================
VERIFIER STOP 00000008: pid 0xAA0: corrupted suffix pattern

        00C81000 : Heap handle 
        00D81EB0 : Heap block 
        00000100 : Block size 
#         00000000 :
===========================================================

Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=00d81eb0 ecx=77f7e257 edx=0006fa18 esi=00000008 edi=00c81000
eip=77f7e098 esp=0006fc48 ebp=0006fc5c iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
ntdll!DbgBreakPoint:
77f7e098 cc               int     3

Les informations d’en-tête incluent l’adresse du tas avec le bloc endommagé (00C81000 : Handle de tas), l’adresse du bloc endommagé (00D81EB0 : Bloc de tas) et la taille de l’allocation (00000100 : Taille du bloc).

Le message « Modèle de suffixe endommagé » indique que l’application a violé le modèle d’intégrité des données que GFlags a inséré après la fin de l’allocation de tas pheap-buggy.exe.

Étape 4 : Afficher la pile des appels

À l’étape suivante, utilisez les adresses signalées par NTSD pour localiser la fonction à l’origine de l’erreur. Les deux commandes suivantes activent le numéro de ligne dans le débogueur et affichent la pile des appels avec les numéros de ligne.

C:\>.lines

Line number information will be loaded 

C:\>kb

ChildEBP RetAddr  Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
0006fc5c 77f9e6dd 00000008 77f9e3e8 00c81000 ntdll!DbgBreakPoint
0006fcd8 77f9f3c8 00c81000 00000004 00d81eb0 ntdll!RtlpNtEnumerateSubKey+0x2879
0006fcfc 77f9f5bb 00c81000 01001002 00000010 ntdll!RtlpNtEnumerateSubKey+0x3564
0006fd4c 77fa261e 00c80000 01001002 00d81eb0 ntdll!RtlpNtEnumerateSubKey+0x3757
0006fdc0 77fc0dc2 00c80000 01001002 00d81eb0 ntdll!RtlpNtEnumerateSubKey+0x67ba
0006fe78 77fbd87b 00c80000 01001002 00d81eb0 ntdll!RtlSizeHeap+0x16a8
0006ff24 010013a4 00c80000 01001002 00d81eb0 ntdll!RtlFreeHeap+0x69
0006ff3c 01001450 00000000 00000001 0006ffc0 pheap-buggy!TestCorruptAfterEnd+0x2b [d:\nttest\base\testsrc\kernel\rtl\pageheap\pheap-buggy.cxx @ 185]
0006ff4c 0100157f 00000002 00c65a68 00c631d8 pheap-buggy!main+0xa9 [d:\nttest\base\testsrc\kernel\rtl\pageheap\pheap-buggy.cxx @ 69]
0006ffc0 77de43fe 00000000 00000001 7ffdf000 pheap-buggy!mainCRTStartup+0xe3 [crtexe.c @ 349]
0006fff0 00000000 0100149c 00000000 78746341 kernel32!DosPathToSessionPathA+0x204

Par conséquent, le débogueur affiche la pile des appels pour pheap-buggy.exe avec des numéros de ligne. L’affichage de la pile des appels indique que l’erreur s’est produite lorsque la fonction TestCorruptAfterEnd dans pheap-buggy.exe tenté de libérer une allocation à 0x00c80000 en appelant HeapFree, une redirection vers RtlFreeHeap.

La cause la plus probable de cette erreur est que le programme a écrit après la fin de la mémoire tampon qu’il a allouée dans cette fonction.

Étape 5 : Activer la vérification du tas en pleine page

Contrairement à la vérification de tas de pages standard, la vérification de tas de page complète peut détecter l’utilisation incorrecte de cette mémoire tampon de tas dès qu’elle se produit. La commande suivante active la vérification du tas de page complète pour pheap-buggy.exe :

gflags /p /enable pheap-buggy.exe /full

Étape 6 : Vérifier que le tas de pages entières est activé

La commande suivante répertorie les programmes pour lesquels la vérification du tas de page est activée :

gflags /p

En réponse, GFlags affiche la liste de programmes suivante. Dans cet affichage, les traces indiquent la vérification du tas de page standard, et les traces complètes indiquent la vérification du tas de page complète. Dans ce cas, pheap-buggy.exe est répertorié avec des traces complètes, indiquant que la vérification du tas de page complète est activée, comme prévu.

pheap-buggy.exe: page heap enabled with flags (full traces )

Étape 7 : Réexécuter le débogueur

La commande suivante exécute la fonction CorruptAfterEnd de pheap-buggy.exe dans le débogueur NTSD avec les paramètres -g (ignorer le point d’arrêt initial) et -x (définir l’arrêt de la deuxième chance sur les exceptions de violation d’accès) :

ntsd -g -x pheap-buggy CorruptAfterEnd

En cas d’échec de l’application, NTSD génère l’affichage suivant, ce qui indique qu’il a détecté une erreur dans pheap-buggy.exe :

Page heap: process 0x5BC created heap @ 00880000 (00980000, flags 0x3)
ModLoad: 77db0000 77e8c000   kernel32.dll
ModLoad: 78000000 78046000   MSVCRT.dll
Page heap: process 0x5BC created heap @ 00B60000 (00C60000, flags 0x3)
Page heap: process 0x5BC created heap @ 00C80000 (00D80000, flags 0x3)
Access violation - code c0000005 (first chance)
Access violation - code c0000005 (!!! second chance !!!)
eax=00c86f00 ebx=00000000 ecx=77fbd80f edx=00c85000 esi=00c80000 edi=00c16fd0
eip=01001398 esp=0006ff2c ebp=0006ff4c iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000206
pheap-buggy!TestCorruptAfterEnd+1f:
01001398 889801010000     mov     [eax+0x101],bl          ds:0023:00c87001=??

Avec la vérification du tas de page complète activée, le débogueur s’arrête en cas de violation d’accès. Pour trouver l’emplacement précis de la violation d’accès, activez le dumping du numéro de ligne et affichez la trace de la pile des appels.

La trace de la pile des appels numérotée s’affiche comme suit :

ChildEBP RetAddr  Args to Child
0006ff3c 01001450 00000000 00000001 0006ffc0 pheap-buggy!TestCorruptAfterEnd+0x1f [d:\nttest\base\testsrc\kernel\rtl\pageheap\pheap-buggy.cxx @ 184]
0006ff4c 0100157f 00000002 00c16fd0 00b70eb0 pheap-buggy!main+0xa9 [d:\nttest\base\testsrc\kernel\rtl\pageheap\pheap-buggy.cxx @ 69]
0006ffc0 77de43fe 00000000 00000001 7ffdf000 pheap-buggy!mainCRTStartup+0xe3 [crtexe.c @ 349]
WARNING: Stack unwind information not available. Following frames may be wrong.
0006fff0 00000000 0100149c 00000000 78746341 kernel32!DosPathToSessionPathA+0x204

La trace de la pile indique que le problème se produit à la ligne 184 de pheap-buggy.exe. Étant donné que la vérification du tas de page complète est activée, la pile des appels démarre dans le code du programme, et non dans une DLL système. Par conséquent, la violation a été interceptée là où elle s’est produite, plutôt que lorsque le bloc de tas a été libéré.

Étape 8 : Localiser l’erreur dans le code

Une inspection rapide révèle la cause du problème : le programme tente d’écrire dans le 257e octet (0x101) d’une mémoire tampon de 256 octets (0x100), une erreur off-by-one courante.

*((PCHAR)Block + 0x100) = 0;