Tutorial 28: API de Depuración de Win32 Parte 1

En este tutorial, aprenderás que ofrece Win32 a los desarrolladores interesados en las primitivas de depuración. Sabrás como depurar un proceso cuando hayas finalizado con este tutorial.
Bajar el ejemplo.

Teoría:

Win32 tiene varias funciones en su API que permiten a los programadores usar algunas de las potencialidades de un depurador. Son llamadas las Apis de depuración de Win32 o primitivas. Con ellas puedes:

En pocas palabras, puedes escribir el código de un depurador sencillo con estas APIs. Como este tema es amplio, lo he dividido en varias partes: este tutorial será la primera. Explicaré los conceptos básicos y daré un marco general para usar las APIs de depuración de Win32 en este tutorial.

Los pasos al usar las APIs de depuración de Win32 son:

  1. Crear un proceso o enganchar un proceso en curso. Este es el primer paso al usar las APIs de depuración de Win32. Como tu programa va actuar como un depurador, necesitas un programa a depurar. El programa que está siendo depurado será llamado un "depurando" [debuggee]. Puedes adquirir un "depurando" [debugee] de dos maneras:
  2. Esperar por eventos de depuración. Después de que tu programa ha adquirido un proceso para su depuración, el hilo primario del proceso en depuración es suspendido hasta que tu programa llame a WaitForDebugEvent. Esta función trabaja como otras funciones WaitForXXX,, es decir, bloquea el hilo que llama hasta que ocurre el evento por el cual se espera. En este caso, espera por eventos de depuración a ser enviados por Windows. Veamos su definición:

    WaitForDebugEvent proto lpDebugEvent:DWORD, dwMilliseconds:DWORD

    lpDebugEvent es la dirección de una estructura DEBUG_EVENT que será llenada con información sobre el evento de depuración que ocurre dentro del depurando.

    dwMilliseconds es el lapso de tiempo en milisegundos que esta función esperará hasta que ocurra el evento de depuración. Si este período caduca y no ocurre ningún evento de depuración, WaitForDebugEvent regresa al programa que ha hecho la llamada. Pero si especificas la constante INFINITE en este argumento, la función no regresará hasta que ocurra un evento de depuración.

    Ahora examinemos con más detalles la estructura DEBUG_EVENT.

    DEBUG_EVENT STRUCT
       dwDebugEventCode dd ?
       dwProcessId dd ?
       dwThreadId dd ?
       u DEBUGSTRUCT <>
    DEBUG_EVENT ENDS

    dwDebugEventCode contiene el valor que especifica qué tipo de evento de depuración ocurre. En pocas palabras, pueden haber muchos tipos de eventos, tu programa necesita chequear el valor en este campo para conocer qué tipo de evento ocurre y responder apropiadamente. Los valores posibles son:

  3. Valor Significado
    CREATE_PROCESS_DEBUG_EVENT Un proceso ha sido creado. Este evento será enviado cuando el proceso en depuración es creado (y todavía no está correindo) o cuando tu programa se anexe a un proceso con DebugActiveProcess. Este es el primer evento que recibirá tu programa.
    EXIT_PROCESS_DEBUG_EVENT Un proceso termina.
    CREATE_THREAD_DEBUG_EVENT Se ha creado un nuevo hilo en el proceso en depuración o tu programa se anexa a un proceso que ya está corriendo. Nota que no recibirás esta notificación cuando el hilo primario del proceso en depuración sea creado.
    EXIT_THREAD_DEBUG_EVENT Termina un hilo en el proceso en depuración. Tu programa no recibirá este evento para el hilo primario. En pocas palabras, puedes pensar en el hilo primario del proceso en depuración como un equivalente del mismo proceso en depuración. Así que, cuando tu programa ve CREATE_PROCESS_DEBUG_EVENT, es realmente el CREATE_THREAD_DEBUG_EVENT del hilo primario.
    LOAD_DLL_DEBUG_EVENT El proceso en depuración carga una DLL. Recibirás este evento cuando el cargador del PE resuelva primero los enlaces a las DLLs (llamas a CreateProcess para cargar el depurando) y cuando el proceso en depuración llama a LoadLibrary.
    UNLOAD_DLL_DEBUG_EVENT Una DLL es descargada del proceso en depuración.
    EXCEPTION_DEBUG_EVENT Ocurre una excepción en el proceso en depuración. Importante: Este evento ocurrirá una vez justo antes de que el proceso en depuración comience a ejecutar su primera instrucción. La excepción realmente es una ruptura de depuración [a debug break] (int 3h). Cuando quieres resumir el proceso en depuración, llamas a ContinueDebugEvent con la bandera DBG_CONTINUE. No uses la bandera DBG_EXCEPTION_NOT_HANDLED sino el proceso en depuración rehusará correr bao NT (en Win98, trabaja bien).
    OUTPUT_DEBUG_STRING_EVENT Este evento es generado cuando el proceso en depuración llama a la función DebugOutputString para eviar una cadena de caracteres con un mensaje a tu programa.
    RIP_EVENT Ocurre un error en el sistema al depurar...

    dwProcessId y dwThreadId son los id del proceso y del hilo del proceso donde ocurre el evento de depuración. Puedes usar estos valores como identificadores del proceso/hilo en el cual estás interesado. Recuerda que si usas CreateProcess para cargar el proceso en depuración, también obtienes los IDs del proceso y del hilo del proceso en depuración en la estructura PROCESS_INFO. Puedes usar estos valores para diferenciar entre los eventos de depuración que ocurren en el proceso en depuración y su proceso hijo (en caso de que no hayas especificado la bandera DEBUG_ONLY_THIS_PROCESS).

    u es una union que contiene más información sobre el proceso en depuración. Puede ser una de las siguientes estructuras dependiendo del valor de dwDebugEventCode arriba.

    valor en dwDebugEventCode Interpretación de u
    CREATE_PROCESS_DEBUG_EVENT Una estructura CREATE_PROCESS_DEBUG_INFO llamada CreateProcessInfo
    EXIT_PROCESS_DEBUG_EVENT Una estructura EXIT_PROCESS_DEBUG_INFO llamada ExitProcess
    CREATE_THREAD_DEBUG_EVENT Una estructura CREATE_THREAD_DEBUG_INFO llamada CreateThread
    EXIT_THREAD_DEBUG_EVENT Una estructura EXIT_THREAD_DEBUG_EVENT llamada ExitThread
    LOAD_DLL_DEBUG_EVENT Una estructura LOAD_DLL_DEBUG_INFO llamada LoadDll
    UNLOAD_DLL_DEBUG_EVENT Una estructura UNLOAD_DLL_DEBUG_INFO llamada UnloadDll
    EXCEPTION_DEBUG_EVENT Una estructura EXCEPTION_DEBUG_INFO llamada Exception
    OUTPUT_DEBUG_STRING_EVENT Una estructura OUTPUT_DEBUG_STRING_INFO llamada DebugString
    RIP_EVENT A RIP_INFO llamada RipInfo

    En este tutorial no entraré en detalles sobre todas las estructuras, aquí sólo será cubierta la estructura CREATE_PROCESS_DEBUG_INFO.

    Asumiendo que nuestro programa llama a WaitForDebugEvent y regresa . Lo primero que deberíamos hacer es examinar dwDebugEventCode para ver qué tipo de evento de depuración ocurrió en el proceso en depuración. Por ejemplo, si el valor en dwDebugEventCode es CREATE_PROCESS_DEBUG_EVENT, puedes interpretar el miembro en u como CreateProcessInfo y acceder a él con u.CreateProcessInfo.

  4. Hacer lo que quieras hacer con tu programa para ese evento de depuración. Cuando regresa WaitForDebugEvent, signifca que acaba de ocurrir un evento de depuración en el depurando o ha ocurrido una pausa. Tu programa necesita examinar el valor en dwDebugEventCode con el fin de reaccionar apropiadamente al evento. En este sentido, es como procesar mensajes de Windows: eliges manejar algunos e ignorar otros.
  5. Dejar que el proceso en depuración continúe la ejecución. Cuando ocurre un evento de depuración, Windows suspende el proceso en depuración. Cuando hayas terminado la manipulación del evento, necesitas poner en movimiento el proceso en depuración de nuevo. Haces esto llamando a la función ContinueDebugEvent.

    ContinueDebugEvent proto dwProcessId:DWORD, dwThreadId:DWORD, dwContinueStatus:DWORD

    Esta función resume el hilo que fue suspendido previamante porque ocurrió un evento de depuración.
    dwProcessId y dwThreadId los IDs de proceso y de hilo del hilo que será resumido. Usualmente tomas estos dos valores de los miembros dwProcessId y dwThreadId de la estructura DEBUG_EVENT.
    dwContinueStatus especifica cómo continuar el hilo que reportó el evento de depuración. Hay dos valores posibles: DBG_CONTINUE y DBG_EXCEPTION_NOT_HANDLED. Para los otros eventos de depuración, esos dos valores hacen lo mismo: resumen el hilo. La excepción es el EXCEPTION_DEBUG_EVENT. Si el hilo reporta un evento de depuración excepción, significa que ocurrió una excepción en el hilo del proceso en depuración. Si especificas DBG_CONTINUE, el hilo ignorará su manipulación de la excepción y continuará con la ejecución. En este escenario, tu programa debe examinar y resolver la excepción misma antes de resumir el hilo con DBG_CONTINUE sino la excepción ocurrirá una vez más, una vez más.... Si especificas DBG_EXCEPTION_NOT_HANDLED, tu programa está diciendo a Windows que no manejará la excepción: Windows usaría el manejador de excepción por defecto del proceso en depuración para manejar la excepción.
    En conclusión, si el evento de depuración refiere a una excepción en el proceso en depuración, deberías llamar a ContinueDebugEvent con la bandera
    DBG_CONTINUE si tu programa ya removió la causa de la excepción. De otra manera, tu programa debe llamar a ContinueDebugEvent con la bendera DBG_EXCEPTION_NOT_HANDLED. Excepto en un caso en el que siempre debes usar la bandera DBG_CONTINUE: el primer EXCEPTION_DEBUG_EVENT que tiene el valor EXCEPTION_BREAKPOINT en el miembro ExceptionCode. Cuando el proceso en depuración vaya a ejecutar su primera instrucción, tu programa recibirá el evento de depración exepción. Realmente es un quiebre de depuración [debug break] (int 3h). Si respondes llamando a ContinueDebugEvent con la bandera DBG_EXCEPTION_NOT_HANDLED, Windows NT reusará correr el proceso en depuración (porque nada cuida de él). Siempre debes usar la bandera DBG_CONTINUE en este caso para decir a Windows que quieres que el hilo continúe.

  6. Continuar este ciclo en un bucle infinito hasta que termine el proceso en depuración. Tu programa debe estar en un bucle infinito muy parecido al bucle de mensajes hasta que el proceso en depuración termine. El bucle tiene más o menos el siguiente aspecto:

    .while TRUE
        invoke WaitForDebugEvent, addr DebugEvent, INFINITE
       .break .if DebugEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
       <Handle the debug events>
       invoke ContinueDebugEvent, DebugEvent.dwProcessId, DebugEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
    .endw

    Aquí está el truco: una vez que empiezas a depurar un programa, ya no puedes desprenderte del proceso en depuración hasta que termine.

Resumamos los pasos de nuevo:

  1. Crear un procesos o anexar tu programa a un proceso que esté corriendo.
  2. Esperar por los eventos de depuración
  3. Hacer lo que tu programa quiere hacer en respuesta al evento en depuración.
  4. Dejar que el proceso en depuración continúe su ejecución.
  5. Continuar este ciclo en un bucle infinito hasta que el proceso en depuración termina

Ejemplo:

Este ejemplo depura un programa win32 y muestra información importante tal como el manejador del proceso, el Id del proceso, la base de la imagen , etc.

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib
includelib \masm32\lib\user32.lib
.data
AppName db "Win32 Debug Example no.1",0
ofn OPENFILENAME <>
FilterString db "Executable Files",0,"*.exe",0
             db "All Files",0,"*.*",0,0
ExitProc db "The debuggee exits",0
NewThread db "A new thread is created",0
EndThread db "A thread is destroyed",0
ProcessInfo db "File Handle: %lx ",0dh,0Ah
            db "Process Handle: %lx",0Dh,0Ah
            db "Thread Handle: %lx",0Dh,0Ah
            db "Image Base: %lx",0Dh,0Ah
            db "Start Address: %lx",0
.data?
buffer db 512 dup(?)
startinfo STARTUPINFO <>
pi PROCESS_INFORMATION <>
DBEvent DEBUG_EVENT <>
.code
start:
mov ofn.lStructSize,sizeof ofn
mov ofn.lpstrFilter, offset FilterString
mov ofn.lpstrFile, offset buffer
mov ofn.nMaxFile,512
mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
invoke GetStartupInfo,addr startinfo
invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, NULL, NULL, addr startinfo, addr pi
.while TRUE
   invoke WaitForDebugEvent, addr DBEvent, INFINITE
   .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
       invoke MessageBox, 0, addr ExitProc, addr AppName, MB_OK+MB_ICONINFORMATION
       .break
   .elseif DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
       invoke wsprintf, addr buffer, addr ProcessInfo, DBEvent.u.CreateProcessInfo.hFile, DBEvent.u.CreateProcessInfo.hProcess, DBEvent.u.CreateProcessInfo.hThread, DBEvent.u.CreateProcessInfo.lpBaseOfImage, DBEvent.u.CreateProcessInfo.lpStartAddress
       invoke MessageBox,0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION    
   .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
       .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT
          invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE
         .continue
       .endif
   .elseif DBEvent.dwDebugEventCode==CREATE_THREAD_DEBUG_EVENT
       invoke MessageBox,0, addr NewThread, addr AppName, MB_OK+MB_ICONINFORMATION
   .elseif DBEvent.dwDebugEventCode==EXIT_THREAD_DEBUG_EVENT
       invoke MessageBox,0, addr EndThread, addr AppName, MB_OK+MB_ICONINFORMATION
   .endif
   invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
.endw
invoke CloseHandle,pi.hProcess
invoke CloseHandle,pi.hThread
.endif
invoke ExitProcess, 0
end start

Análisis:

El programa llena la estructura OPENFILENAME y luego llama a GetOpenFileName para pernitir que el usuario elija un programa para su depuración.

invoke GetStartupInfo,addr startinfo
invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, NULL, NULL, addr startinfo, addr pi

Cuando el usuario elige uno, llama a CreateProcess para cargar el programa. Llama a GetStartupInfo para llenar la estructura STARTUPINFO con sus valores por defecto. Nota que usamos la bandera DEBUG_PROCESS combinada con DEBUG_ONLY_THIS_PROCESS con el fin de depurar solamente este programa, sin incluir sus procesos hijos.

.while TRUE
   invoke WaitForDebugEvent, addr DBEvent, INFINITE

Cuando es cargado el proceso en depuración, introducimos el bucle infinito de depuración, llamando a WaitForDebugEvent. WaitForDebugEvent no regresará hasta que ocurra un evento de depuración en el 'proceso en depuración' porque especificamos INFINITE como su segundo parámetro. Cuando ocurre un evento de depuracion, WaitForDebugEvent regresa y DBEvent es llenada con información sobre el evento de depuración.

   .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
       invoke MessageBox, 0, addr ExitProc, addr AppName, MB_OK+MB_ICONINFORMATION
       .break

Primero chequeamos el valor en dwDebugEventCode. Si es EXIT_PROCESS_DEBUG_EVENT, desplegamos una caja de mensaje que dice "The debuggee exits" [El evento de depuración ha culminado] y luego salimos del bucle de depuración.

   .elseif DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
       invoke wsprintf, addr buffer, addr ProcessInfo, DBEvent.u.CreateProcessInfo.hFile, DBEvent.u.CreateProcessInfo.hProcess, DBEvent.u.CreateProcessInfo.hThread, DBEvent.u.CreateProcessInfo.lpBaseOfImage, DBEvent.u.CreateProcessInfo.lpStartAddress
       invoke MessageBox,0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION    

Si el valor en dwDebugEventCode es CREATE_PROCESS_DEBUG_EVENT, entonces desplegamos cierta información interesante sobre el proceso en depuración en una caja de mensaje. Obtenemos esa información a partir de u.CreateProcessInfo. CreateProcessInfo es una estructura del tipo CREATE_PROCESS_DEBUG_INFO. Puedes obtener más info sobre esta estructura en la referencia de la API de Win32.

   .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
       .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT
          invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE
         .continue
       .endif

Si el valor en dwDebugEventCode es EXCEPTION_DEBUG_EVENT, debemos chequear luego por el tipo exacto de excepción. Es una línea larga de referencia de estructura anidada pero puedes obtener el tipo de excepción del miembro ExceptionCode. Si el valor en ExceptionCode es EXCEPTION_BREAKPOINT y ocurre por primera vez (o si estamos seguros de que el proceso en depuración no tiene incrustado int 3h), podemos asumir con seguridad que esta excepción ocurrió cuando el proceso en depuración iba a ejecutar la primera instrucción. Cuando hayamos hecho lo que ibamos a hacer con el procesamiento, debemos llamar a l ContinueDebugEvent con la bandera DBG_CONTINUE para dejar que corra de nuevo el proceso en depuración. Luego volvemos a esperar el siguiente evento de depuración.

   .elseif DBEvent.dwDebugEventCode==CREATE_THREAD_DEBUG_EVENT
       invoke MessageBox,0, addr NewThread, addr AppName, MB_OK+MB_ICONINFORMATION
   .elseif DBEvent.dwDebugEventCode==EXIT_THREAD_DEBUG_EVENT
       invoke MessageBox,0, addr EndThread, addr AppName, MB_OK+MB_ICONINFORMATION
   .endif

Si el valor en dwDebugEventCode es CREATE_THREAD_DEBUG_EVENT o EXIT_THREAD_DEBUG_EVENT, desplegamos una caja de mensaje que diga eso.

   invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
.endw

Excepto para el caso EXCEPTION_DEBUG_EVENT de arriba, llamamos a ContinueDebugEvent con la bandera DBG_EXCEPTION_NOT_HANDLED para resumir el proceso en depuración.

invoke CloseHandle,pi.hProcess
invoke CloseHandle,pi.hThread

Cuando termina el proceso en depuración, estamos fuera del bucle de depuración y debemos cerrar los manejadores del proceso y del hilo del proceso en depuración. Cerrar los manejadores no significa que estamos matando el proceso/hilo. Sólo significa que no queremos usar más esos manejadores para referir al proceso/hilo.


Index

Next

[Iczelion's Win32 Assembly Homepage]

n u M I T_o r's   Programming Page

Este tutorial, original de Iczelion, ha sido traducido por:   n u M I T_o r

 

www.000webhost.com