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.
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:
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:
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.
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.
.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:
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
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.
[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