En la primera parte de esta serie de artículos aprendió a instalar Windbg y a configurar un aspecto básico, los símbolos de depuración. En este artículo veremos cómo configurar otros aspectos menores de Windbg, pero que le pueden resultar necesarios, y depuraremos la primera aplicación en modo usuario.
Configurar la ruta de código fuente
Si está depurando una aplicación que no haya desarrollado, es probable que no disponga del código fuente de la misma; sin embargo, si estuviera depurando una aplicación de la que dispone el código fuente, es muy buena idea configurar Windbg para que sea capaz de usar toda esa información y mostrar la ejecución paso a paso en formato de instrucciones de código fuente, no instrucciones máquina. Para configurar este aspecto, despliegue el menú File, Source File Path (o pulse Ctrl+P). También puede usar el comando .srcpath <Ruta_código_fuente>
Para que el depurador muestre instrucciones de código fuente y no instrucciones máquina es necesario además que los símbolos de depuración contengan información acerca del fichero fuente y número de línea que se corresponde con un determinado símbolo. Estos símbolos, denominados normalmente símbolos privados, no suelen estar a disposición de los usuarios ajenos a la empresa creadora del software en cuestión.
Configurar la ruta de los módulos
En la mayoría de ocasiones, Windbg es capaz por sí solo de encontrar las imágenes de los ejecutables implicados en una sesión de depuración. En determinados casos, como por ejemplo cuando se está depurando un volcado de memoria pequeño o minidump (la depuración de volcados de memoria se tratará en un posterior artículo), es necesario indicarle al depurador dónde debe buscar los módulos (.exe, .dll, .sys, etc.). Para ello, abra File, Image File Path (o pulse Ctrl+I). Como siempre, si desea lograr esto mediante un comando de Windbg, ejecute .exepath <Ruta_ejecutables>
Ejecutar una aplicación dentro del depurador
Ya está en condiciones de depurar su primera aplicación en Windbg y para ello vamos a ejecutar la calculadora de Windows dentro de Windbg.
Abra File, Open Executable (o pulse Ctrl+E) y busque el ejecutable Calc.exe dentro del directorio \Windows\system32\. En este caso deje desmarcada la casilla Debug child processes also, que sirve para depurar no solo el proceso principal que cree ese ejecutable sino sus procesos hijos también. Haga clic sobre Abrir.
La pantalla de Windbg se rellenará con un texto similar al siguiente:
Microsoft (R) Windows Debugger Version 6.11.0001.404 X86
Copyright (c) Microsoft Corporation. All rights reserved.
CommandLine: C:\WINDOWS\system32\calc.exe
Symbol search path is: SRV*c:\websymbols*http://msdl.microsoft.com/download/symbols
Executable search path is:
ModLoad: 01000000 0101f000 calc.exe
ModLoad: 7c910000 7c9c5000 ntdll.dll
ModLoad: 7c800000 7c903000 C:\WINDOWS\system32\kernel32.dll
ModLoad: 7e6a0000 7eec1000 C:\WINDOWS\system32\SHELL32.dll
ModLoad: 77da0000 77e4c000 C:\WINDOWS\system32\ADVAPI32.dll
ModLoad: 77e50000 77ee2000 C:\WINDOWS\system32\RPCRT4.dll
ModLoad: 77fc0000 77fd1000 C:\WINDOWS\system32\Secur32.dll
ModLoad: 77ef0000 77f39000 C:\WINDOWS\system32\GDI32.dll
ModLoad: 7e390000 7e421000 C:\WINDOWS\system32\USER32.dll
ModLoad: 77be0000 77c38000 C:\WINDOWS\system32\msvcrt.dll
ModLoad: 77f40000 77fb6000 C:\WINDOWS\system32\SHLWAPI.dll
(23c.24c): Break instruction exception - code 80000003 (first chance)
eax=001a1eb4 ebx=7ffdb000 ecx=00000007 edx=00000080 esi=001a1f48 edi=001a1eb4
eip=7c91120e esp=0007fb20 ebp=0007fc94 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
ntdll!DbgBreakPoint:
7c91120e cc int 3
Analicemos detalladamente toda esta información:
La línea CommandLine nos está indicando el archivo que vamos a ejecutar dentro de Windbg, en este caso la calculadora de Windows. A continuación se muestra la ruta establecida para los símbolos y la ruta de búsqueda de módulos ejecutables, que en este caso está vacía porque no la hemos establecido desde la opción File, Image File Path. Seguidamente el depurador ha registrado información sobre la carga de una serie de módulos de los que depende la calculadora de Windows. Para ver las dependencias de un módulo yo recomiendo usar la herramienta Dependency Walker (http://www.dependencywalker.com/).
La siguiente línea, (23c.24c): Break instruction exception - code 80000003 (first chance), da pie a comentar bastantes cosas:
La sintaxis (23c.24c) la va a encontrar no pocas veces en Windbg. Hace referencia a un hilo dentro de un proceso. 23c es el identificador de proceso (PID), en formato hexadecimal. En este caso es el PID del proceso Calc.exe. 24c hace referencia al identificador de hilo (TID), en formato hexadecimal también. La excepción recibida en este caso es "Break instruction" (STATUS_BREAKPOINT), identificada por el código hexadecimal 0x80000003. Vamos a ver qué ocurre "por lo bajo" para explicar por qué ha ocurrido esto:
Cuando hemos seleccionado el ejecutable Calc.exe y hemos hecho clic sobre Abrir, Windbg ha llamado a la API CreateProcess con el sexto parámetro establecido a DEBUG_PROCESS. Esto permite crear un proceso e indicarle a Windows que deseamos depurarlo. Pero depurar un proceso requiere de algo más. Requiere que el programa depurador reciba una serie de eventos procedentes de la ejecución de la aplicación y los muestre al usuario o los trate de manera conveniente. Lo que ocurre durante una sesión de depuración se puede entender como un bucle en el cual el depurador espera eventos de depuración mediante la API WaitForDebugEvent, los trata de manera conveniente, y continua la ejecución del programa mediante la API ContinueDebugEvent. Este bucle se repite indefinidamente hasta que o bien la aplicación que está siendo depurada desaparece de la memoria, o bien finaliza correctamente su ejecución, o bien desacoplamos el depurador mediante el comando Debug, Detach Debugee.
La frase (first chance) hace referencia al comportamiento de las excepciones. Cuando una aplicación genera una excepción y está siendo depurada, el depurador recibe una excepción de primera oportunidad (first chance) y le da la opción de tratarla. En este caso, el depurador trata la excepción STATUS_BREAKPOINT deteniendo la ejecución de la aplicación permitiendo que el usuario introduzca comandos o examine el estado de la imagen de memoria del proceso y el estado del procesador. Si el depurador no capturase la excepción, se comprueba si el código de la aplicación la capturara. Este procedimiento implica verificar en la pila de ejecución si hay algún código manejador de excepciones, desde el marco de activación más reciente hasta al más antiguo. Este procedimiento recibe técnicamente el nombre de stack unwinding. No se preocupe, en posteriores artículos se va a tratar la estructura de datos conocida como pila, pues es de capital importancia a la hora de entender buena parte de la información que nos proporcione Windbg. En caso de que el código no capturase la excepción, se lanza al depurador una excepción de segunda oportunidad (second chance) y se vuelve a comprobar si el código captura la excepción.
Lo que aparece a continuación en la pantalla de Windbg es el estado de los registros del procesador. Para entender esta parte de la salida es necesario disponer de ciertos conocimientos específicos de la máquina que está siendo depurada. En este caso se trata de la arquitectura x86, por lo que la referencia obligada es este documento: http://download.intel.com/design/PentiumII/manuals/24319102.PDF. Cuando veamos algunos casos de estudio verá cómo aplicar la información sobre los registros de la máquina a un caso real.
Para finalizar nos aparece la última instrucción ejecutada en el procesador antes de que se pasara el control al depurador. En este caso se trata de una instrucción dentro de la función DbgBreakPoint dentro del módulo Ntdll.dll. La instrucción en cuestión es la instrucción int 3, identificada mediante el código hexadecimal CC. Esta instrucción lanza una interrupción software. El parámetro 3 indica que ejecutaremos la posición 3 del vector de interrupciones y básicamente se enviará la excepción STATUS_BREAKPOINT al depurador, para que haga con ella lo que considere oportuno. La dirección de memoria que contiene esa instrucción es la 7c91120e (en este ejemplo particular).
Para concluir, puede configurar Windbg para que no informe de ciertos eventos (tales como la finalización de un proceso, la carga de una DLL, etc.). Para ello puede ir al menú Debug, Event Filters.

Si desea hacer uso de la línea de comandos de Windbg, el comando en cuestión es sx y sus derivados sxe, sxd, sxi, sxn. En la documentación de Windbg (Help, About) puede obtener información adicional sobre el uso de estos comandos.
En este artículo hemos aprendido a configurar Windbg para comenzar una sesión de depuración y hemos ejecutado nuestra primera aplicación dentro del depurador. También hemos aprendido qué quiere decir la información que aparece en pantalla y en qué consiste "por lo bajo" la depuración de aplicaciones y el manejo de excepciones. En la siguiente parte veremos algunos comandos para examinar el estado de la máquina y del proceso que está siendo depurado. Se detallarán también algunos aspectos de la arquitectura interna de Windows que son importantes para saber realmente lo que está pasando.
Un problema que le ocurre a no poca gente que usa Windows XP consiste en que al intentar abrir un fichero con extensión MSC (Services.msc, Diskmgmt.msc, etc.) el sistema muestra el siguiente mensaje de error:
MMC no puede abrir el archivo C:\WINDOWS\system32\services.msc.
Puede ser que el archivo no exista, no sea una consola de MMC o fue creado por una versión más reciente de MMC. También puede ser que no tiene suficientes derechos de acceso para abrir el archivo.
El propio mensaje nos explica las posibles causas del problema, pero ciertamente la información que ofrece es algo vaga e inespecífica. El principal problema con el que se encuentra el usuario al recibir este mensaje de error es que, al tratarse de un mensaje de error genérico, es posible que la causa del problema que está experimentando no quede recogida en el texto del mensaje de error.
¿Cómo actuar en casos como este?
Hay muchas formas de actuar ante un mensaje de error de este estilo. Una manera consiste en observar si hay algún tipo de LOG que registre información detallada sobre el error (lo primero que debemos buscar es el código de error exacto). Es bastante probable que, de existir la posibilidad de registrar en un fichero LOG la información correspondiente a un mensaje de error, esta opción deba activarse explícitamente a través del registro del sistema operativo, o mediante algún otro método avanzado similar. En ocasiones, como último recurso para obtener información detallada sobre un mensaje de error tendremos que instalar una versión de depuración del sistema operativo o aplicación (versión checked). Las versiones checked son versiones que incorporan información detallada de depuración (trazas, aserciones, etc.) que son de mucha utilidad a los programadores de aplicaciones. De hecho, para adquirir la versión checked de un sistema operativo de Microsoft debe ser suscriptor de MSDN. Las versiones de depuración de los Service Packs sí están disponibles públicamente desde el Centro de descargas de Microsoft. Por ejemplo, este es el enlace para descargar la versión de depuración del SP3 de Windows XP.
En este artículo se va a describir un método menos complicado que puede sacarnos del atolladero en bastantes casos. De hecho, nos va a sacar del atolladero en el problema concreto que se describe más arriba.
En primer lugar, instale la última versión de Debugging Tools for Windows y configure el depurador Windbg apropiadamente. Para saber cómo hacer esto, siga los pasos de este tutorial.
A continuación, vamos a ejecutar la aplicación desde dentro del depurador. Para ello, haga clic sobre File, Open Executable. En la caja de texto Nombre, escriba lo siguiente: %SystemRoot%\system32\mmc.exe En la caja de texto Arguments, escriba lo siguiente: Services.msc (o el nombre del fichero MSC que no es capaz de abrir). Pulse Abrir.
Asegúrese de que Windbg le informe de que la información sobre los símbolos es correcta y pulse F5 (Go).
Cuando se muestre el mensaje de error en pantalla, active la ventana de Windbg y pulse Ctrl+Pausa o haga clic sobre Debug, Break.
Ahora tendrá que examinar la pila de ejecución de todos y cada uno de los hilos activos de la aplicación para ver si encuentra el código de error como parámetro de alguna de las funciones que allí aparezcan. Para cambiar de hilo, puede hacerlo gráficamente desde el menú View, Processes and Threads. Seleccione con un simple clic el hilo que quiera examinar (quedará marcado en negrita) e introduzca en la caja de texto de Windbg el comando kb para mostrar la pila de ejecución. En mi caso, esta es la pila de ejecución del hilo responsable de abrir el fichero MSC:
0:000> kb
ChildEBP RetAddr Args to Child
0007f030 7c91df4a 7c809590 00000002 0007f05c ntdll!KiFastSystemCallRet
0007f034 7c809590 00000002 0007f05c 00000001 ntdll!ZwWaitForMultipleObjects+0xc
0007f0d0 7e3995f9 00000002 0007f0f8 00000000 kernel32!WaitForMultipleObjectsEx+0x12c
0007f12c 6c6d4b52 00000001 0007f160 ffffffff USER32!RealMsgWaitForMultipleObjectsEx+0x13e
0007f14c 6c6d4cbd 000024ff ffffffff 00000000 DUSER!CoreSC::Wait+0x3a
0007f170 6c6d4eb9 000024ff 00000000 0007f19c DUSER!CoreSC::WaitMessage+0x40
0007f180 7e3d8e33 000024ff 00000000 00000064 DUSER!MphWaitMessageEx+0x22
0007f19c 7c91e473 0007f1ac 00000008 000024ff USER32!__ClientWaitMessageExMPH+0x1e
0007f1b0 7e399418 7e3a770a 00000000 00000000 ntdll!KiUserCallbackDispatcher+0x13
0007f1e8 7e3a49c4 000301b4 00000000 00000001 USER32!NtUserWaitMessage+0xc
0007f210 7e3ba956 7e390000 000c44f8 00000000 USER32!InternalDialogBox+0xd0
0007f4d0 7e3ba2bc 0007f62c 00000001 00000000 USER32!SoftModalMessageBox+0x938
0007f620 7e3e63fd 0007f62c 00000028 00000000 USER32!MessageBoxWorker+0x2ba
0007f678 7e3d0853 00000000 00e76008 00bafeb0 USER32!MessageBoxTimeoutW+0x7a
0007f698 7e3e6579 00000000 00e76008 00bafeb0 USER32!MessageBoxExW+0x1b
0007f6b4 4754f5fe 00000000 00e76008 00bafeb0 USER32!MessageBoxW+0x45
0007f6f0 0107a216 00e76008 00002010 00e86458 mmcbase!MMCErrorBox+0x7f
0007f740 01058ce9 00000003 80040154 00000000 mmc!DisplayFileOpenError+0x191
0007f794 0104093c 0007f748 00000004 00e6e018 mmc!CAMCDoc::OnOpenDocument+0x90
0007f7e0 5f81424a 0007fc14 00000001 00038988 mmc!CAMCMultiDocTemplate::OpenDocumentFile+0x21f
Marcada en negrita está la parte importante de la traza, el error en cuestión (0x80040154). ¿Qué quiere decir ese código de error? El propio Windbg nos lo puede decir:
0:001> !error 80040154
Error code: (HRESULT) 0x80040154 (2147746132) - Clase no registrada
Un error de clase no registrada. Para sabér qué clase es la afectada, puede usar Process Monitor (http://technet.microsoft.com/en-us/sysinternals/bb896645.aspx), monitorizar el sistema mientras intenta volver a abrir el archivo MSC y analizar con calma los resultados NOT FOUND que hagan referencia al Registro del sistema, o bien puede seguir las pautas de este artículo referido a Explorer.exe.
Para no hacerle perder el tiempo, le diré que en este caso la clase afectada está en la librería Msxml3.dll y se puede volver a registrar con el comando regsvr32 msxml3.dll.
Otro código de error que se puede encontrar es el código 0x2 (archivo no encontrado). En este caso lo ideal es hacer uso de Process Monitor (http://technet.microsoft.com/en-us/sysinternals/bb896645.aspx), monitorizar el sistema mientras intenta volver a abrir el archivo MSC y analizar con calma los resultados NOT FOUND que hagan referencia, en esta ocasión, al sistema de archivos. Un caso que he analizado tenía como culpable al virus Vundo, aunque en general podría tratarse de cualquier otro tipo de malware.
Una duda que quizá tenga es, ¿cómo distinguir en la salida del comando kb lo que es un código de error de lo que no? En general yo suelo descartar en primer lugar lo que seguro sé que no es un código de error. Por ejemplo, en la traza de pila del ejemplo puede ver que 7e3a770a es un valor que está cerca de las direcciones de retorno de cada una de las funciones, por lo que probablemente no sea un código de error. Los valores 00bafeb0 y similares, es decir, un número hexadecimal no muy alto, suelen ser punteros a estructuras de datos, por lo que tampoco suelen ser códigos de error. Los valores muy bajos (00000002), o que comiencen por 8 (80040154), sí suelen ser códigos de error. Por supuesto, en este caso el nombre de la función mmc!DisplayFileOpenError+0x191, nos da la pista fundamental.
Espero que este artículo les ayude a investigar a fondo los mensajes de error en Windows en los que no aparece de manera explícita el código de error. En futuros artículos verá cómo atacar un problema similar pero esta vez causado por una excepción de aplicación, o bien por una versión incompatible de una DLL en el sistema, usando para ello la aplicación Dependency Walker.