Ejemplo 12: Uso de la comprobación del montón de páginas para buscar un error

En la siguiente serie de comandos se muestra cómo usar las características de comprobación del montón de páginas de GFlags y el depurador NTSD para detectar un error en el uso de memoria del montón. En este ejemplo, el programador sospecha que una aplicación ficticia, pheap-buggy.exe, tiene un error de montón y continúa a través de una serie de pruebas para identificar el error.

Para obtener más información sobre NTSD, consulte Depuración mediante CDB y NTSD.

Paso 1: Habilitar la comprobación del montón de páginas estándar

El siguiente comando habilita la comprobación del montón de páginas estándar para pheap-buggy.exe:

gflags /p /enable pheap-buggy.exe

Paso 2: Comprobar que el montón de páginas está habilitado

El siguiente comando muestra los archivos de imagen para los que está habilitada la comprobación del montón de páginas:

gflags /p

En respuesta, GFlags muestra la siguiente lista de programas. En esta pantalla, los seguimientos indican la comprobación del montón de páginas estándar y los seguimientos completos indican la comprobación del montón de página completa. En este caso, pheap-buggy.exe aparece con seguimientos, lo que indica que la comprobación del montón de páginas estándar está habilitada, según lo previsto.

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

Paso 3: Ejecutar el depurador

El siguiente comando ejecuta la función CorruptAfterEnd de pheap-buggy.exe en NTSD con los parámetros -g (omitir el punto de interrupción inicial) y -x (establecer la interrupción de segunda oportunidad en las excepciones de infracción de acceso):

ntsd -g -x pheap-buggy CorruptAfterEnd

Cuando se produce un error en la aplicación, NTSD genera la siguiente pantalla, lo que indica que detectó un error en 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

La información de encabezado incluye la dirección del montón con el bloque dañado (00C81000 : identificador del montón), la dirección del bloque dañado (00D81EB0 : bloque montón) y el tamaño de la asignación (00000100 : tamaño de bloque).

El mensaje "patrón de sufijo dañado" indica que la aplicación infringió el patrón de integridad de datos que GFlags insertó después del final de la pheap-buggy.exe asignación del montón.

Paso 4: Mostrar la pila de llamadas

En el paso siguiente, use las direcciones que NTSD notificó para buscar la función que provocó el error. Los dos comandos siguientes activan el volcado de números de línea en el depurador y muestran la pila de llamadas con números de línea.

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

Como resultado, el depurador muestra la pila de llamadas para pheap-buggy.exe con números de línea. La pantalla de pila de llamadas muestra que el error se produjo cuando la función TestCorruptAfterEnd en pheap-buggy.exe intentó liberar una asignación en 0x00c80000 llamando a HeapFree, un redireccionamiento a RtlFreeHeap.

La causa más probable de este error es que el programa escribió más allá del final del búfer que asignó en esta función.

Paso 5: Habilitar la comprobación del montón de página completa

A diferencia de la comprobación del montón de páginas estándar, la comprobación del montón de página completa puede detectar el uso incorrecto de este búfer de montón en cuanto se produzca. El siguiente comando habilita la comprobación del montón de página completa para pheap-buggy.exe:

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

Paso 6: Comprobar que el montón de página completa está habilitado

El siguiente comando muestra los programas para los que está habilitada la comprobación del montón de páginas:

gflags /p

En respuesta, GFlags muestra la siguiente lista de programas. En esta pantalla, los seguimientos indican la comprobación del montón de páginas estándar y los seguimientos completos indican la comprobación del montón de página completa. En este caso, pheap-buggy.exe aparece con seguimientos completos, lo que indica que la comprobación del montón de página completa está habilitada, según lo previsto.

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

Paso 7: Volver a ejecutar el depurador

El siguiente comando ejecuta la función CorruptAfterEnd de pheap-buggy.exe en el depurador NTSD con los parámetros -g (omitir el punto de interrupción inicial) y -x (establecer interrupción de segunda oportunidad en excepciones de infracción de acceso):

ntsd -g -x pheap-buggy CorruptAfterEnd

Cuando se produce un error en la aplicación, NTSD genera la siguiente pantalla, lo que indica que detectó un error en 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=??

Con la comprobación del montón de página completa habilitada, el depurador se interrumpe en una infracción de acceso. Para encontrar la ubicación precisa de la infracción de acceso, active el volcado de números de línea y muestre el seguimiento de la pila de llamadas.

El seguimiento de la pila de llamadas numerada aparece de la siguiente manera:

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

El seguimiento de la pila muestra que el problema se produce en la línea 184 de pheap-buggy.exe. Dado que la comprobación del montón de página completa está habilitada, la pila de llamadas se inicia en el código del programa, no en un archivo DLL del sistema. Como resultado, la infracción se detectó donde ocurrió, en lugar de cuando se liberó el bloque del montón.

Paso 8: Buscar el error en el código

Una inspección rápida revela la causa del problema: el programa intenta escribir en el byte 257 (0x101) de un búfer de 256 bytes (0x100), un error común fuera de uno.

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