Principio de la interfaz de programación de aplicaciones Win32
Win32 tiene varias API para que las utilicen los programadores y que proporcionan el equivalente a un depurador. Se denominan API (o primitivas) de depuración de Win32. Usando estas API, podemos:
Cargar un programa o vincularlo a un programa en ejecución para depurarlo
Obtener la información subyacente del programa depurado, como ID de proceso, dirección de entrada e imagen. dirección base, etc.
Mientras se ejecuta el depurador, puede utilizar la API de depuración para depurar su programa.
Reciba notificaciones cuando se produzcan eventos relacionados con la depuración, como inicio/finalización de proceso/proceso, carga/liberación de DLL, etc.
Modificar el proceso o hilo que se está depurando
En resumen, podemos escribir un depurador simple usando estas API. Como este tema es un poco extenso, lo he dividido en varias partes, de las cuales este tutorial es la primera. En este tutorial, explicaré algunos conceptos básicos y el marco general de la API de depuración de Win32.
Los pasos para utilizar la API de depuración de Win32 son los siguientes:
Crear un proceso o vincularlo a un proceso en ejecución. Este es el primer paso para utilizar la API de depuración de Win32. Dado que nuestro programa actuará como depurador, necesitamos encontrar un programa para depurar. El programa que se está depurando se llama depurado. El depurador se puede obtener de dos maneras:
Creando un proceso de depuración mediante CreateProcess. Para crear un proceso para depurar, se debe especificar el indicador DEBUG_PROCESS. Esta bandera le dice a Windows que queremos depurar el proceso. Windows envía notificaciones a nuestro programa cuando ocurren eventos importantes relacionados con la depuración (eventos de depuración) en el depurador. El depurador se bloqueará inmediatamente, esperando a que nuestro programa esté listo. Si el depurador también crea procesos secundarios, Windows también enviará notificaciones a nuestro programa sobre eventos de depuración en cada proceso secundario. Esta característica suele ser innecesaria. Podemos deshabilitar esta función especificando una combinación de indicadores DEBUG_ONLY_THIS_PROCESS y DEBUG_PROCESS.
También podemos vincularnos a un proceso en ejecución usando el indicador DebugActiveProcess.
Esperar eventos de depuración. Después de adquirir el proceso del depurador, el hilo principal del depurador se suspenderá hasta que nuestro programa llame a WaitForDebugEvent. Esta función es similar a otras funciones WaitForXXX, es decir, bloquea el hilo de llamada hasta que ocurre el evento esperado. Para esta función, espera los eventos de depuración enviados por Windows. Aquí está su definición:
WaitForDebugEvent proto lpDebugEvent:DWORD, dwMillisegundos:DWORD
lpDebugEvent es la dirección de la estructura DEBUG_EVENT que se completará con información sobre el evento de depuración que ocurrió en el depurador.
dwMillisegundos Esta función espera eventos de depuración en milisegundos. Si no se producen eventos de depuración durante este tiempo, se devolverá WaitForDebugEvent a la persona que llama. Por otro lado, si este parámetro se especifica como una constante INFINITA, la función esperará a que ocurra un evento de depuración.
Ahora echemos un vistazo a la estructura DEBUG_EVENT.
Estructura DEBUG_EVENT
dwDebugEventCode dd ?
dwProcessId dd ?
dwThreadId dd ?
u DEBUGSTRUCT <>
DEBUG_EVENT ENDS
dwDebugEventCode Este valor especifica el tipo de evento de depuración en espera de ocurrir.
Dado que pueden ocurrir muchos tipos de eventos, nuestro programa debe verificar este valor para saber qué tipo de evento debe esperar y responder. Los valores posibles para este valor son los siguientes:
Valor Significado
CREATE_PROCESS_DEBUG_EVENT El proceso ha sido creado. Este evento ocurre cuando el depurador acaba de crearse (aún no se está ejecutando) o cuando nuestro programa acaba de vincularse a un proceso en ejecución como DebugActiveProcess. Este es el primer evento que nuestro programa debería recibir.
EXIT_PROCESS_DEBUG_EVENT El proceso salió.
CREATE_THEAD_DEBUG_EVENT Este evento ocurre cuando se crea un nuevo hilo en el proceso deuggee, o cuando nuestro programa se vincula a un proceso en ejecución por primera vez. Tenga en cuenta que esta notificación no se recibe cuando se crea el hilo principal del depurador.
EXIT_THREAD_DEBUG_EVENT El evento depurado ocurre cuando un hilo en depurado sale. Podemos pensar en el hilo principal del depurador como sinónimo de proceso depurado. Entonces, cuando nuestro programa ve el indicador CREATE_PROCESS_DEBUG_EVENT, es el indicador CREATE_THREAD_DEBUG_EVENT para el hilo principal.
El depurado LOAD_DLL_DEBUG_EVENT carga una DLL. Recibiremos este evento cuando el cargador PE rompa el vínculo con la DLL por primera vez. Recibimos este evento cuando el cargador de PE rompe por primera vez el vínculo con la DLL (cuando se llama a CreateProcess para cargar el depurador) y cuando el depurador llama a LoadLibrary.
UNLOAD_DLL_DEBUG_EVENT Evento que ocurre cuando se descarga una DLL del depurador.
EXCEPTION_DEBUG_EVENT Evento que ocurre cuando el depurador encuentra una excepción. Nota: Este evento solo ocurre una vez antes de que el depurador inicie la primera instrucción. La excepción es en realidad una interrupción de depuración (int 3h). Para reanudar el depurador, llame a la función ContinuarDebugEvent con el indicador DBG_CONTINUE. No utilice el indicador DBG_EXCEPTION_NOT_HANDLED o el depurador no se ejecutará en NT (funciona bien en Win98).
OUTPUT_DEBUG_STRING_EVENT Este evento ocurre cuando el depurador llama a la función DebugOutputString para enviar una cadena de mensaje a nuestro programa.
RIP_EVENT Se produjo un error durante la depuración del sistema
dwProcessId y dwThreadId son el ID del proceso y el ID del hilo del proceso donde ocurrió el evento de depuración. Podemos utilizar estos valores como identificadores del proceso o hilo que nos interesa. Recuerde, si usamos CreateProcess para cargar el depurador, aún obtendremos el proceso y el subproceso del depurador en la estructura PROCESS_INFO. Podemos usar estos valores para distinguir si ocurre un evento de depuración en el depurador o en su proceso hijo (cuando no se especifica el indicador DEBUG_ONLY_THIS_PROCESS).
u es una unión que contiene más información para depurar eventos. Según el dwDebugEventCode anterior, puede tener la siguiente estructura:
Explicación de dwDebugEventCode u
CREATE_PROCESS_DEBUG_EVENT CREATE_PROCESS_DEBUG_INFO La estructura se denomina CreateProcessInfo.
Estructura DEBUG_INFO
EXIT_PROCESS_DEBUG_EVENT EXIT_PROCESS_DEBUG_INFO estructura denominada ExitProcess
CREATE_THREAD_DEBUG_EVENT CreateThread denominada estructura CREATE_THREAD_DEBUG_INFO.
EXIT_THREAD_DEBUG_EVENT EXIT_THREAD_DEBUG_EVENT estructura denominada ExitThread
LOAD_DLL_DEBUG_EVENT LOAD_DLL_DEBUG_INFO estructura denominada LoadDll
UNLOAD_DLL_DEBUG_EVENT UNLOAD_DLL_DEBUG_INFO para UnloadDll
EXCEPTION_DEBUG_EVE NT se utiliza EXCEPTION_DEBUG_EVENT para excepción p>
EXCEPTION_DEBUG_EVENT para excepción
UNLOAD_DLL_DEBUG_EVENT EXCEPTION_DEBUG_INFO estructura
OUTPUT_DEBUG_STRING_EVENT OUTPUT_DEBUG_STRING_INFO estructura denominada DebugString
RIP_EV ENT denominada RipInfo estructuras RIP_INFO
No entraré en todos los detalles de estas estructuras en este tutorial, por lo que solo discutiré la estructura CREATE_PROCESS_DEBUG_INFO.
Suponiendo que nuestro programa llama a la función WaitForDebugEvent y regresa, lo primero que debemos hacer es verificar el valor de dwDebugEventCode para ver qué tipo de evento de depuración ocurrió en el depurador. Por ejemplo, si el valor de dwDebugEventCode es CREATE_PROCESS_DEBUG_EVENT, podemos asumir que el miembro de u es CreateProcessInfo y acceder a él mediante u.CreateProcessInfo.
Responder a eventos de depuración en nuestro programa. Cuando WaitForDebugEvent regresa, significa que se ha producido un evento de depuración o que se ha agotado el tiempo de espera del depurador. Por lo tanto, nuestro programa debe verificar el dwDebugEventCode para responder adecuadamente. Es un poco como tratar con mensajes de Windows: depende del usuario seleccionar o ignorar el mensaje.
Continúe ejecutando el depurador. Windows suspende el depurador cuando ocurre un evento de depuración, por lo que debemos continuar ejecutando el depurador después de que finalice el evento de depuración. Llamamos a la función ContinuarDebugEvent para lograr este propósito.
ContinueDebugEvent Prototipo dwProcessId:DWORD, dwThreadId:DWORD, dwContinueStatus:DWORD
Esta función reanudará un subproceso suspendido debido a un evento de depuración.
dwProcessId y dwThreadId son el ID del proceso y el ID del subproceso que se va a restaurar, generalmente obtenidos de los miembros dwProcessId y dwThreadId de la estructura DEBUG_EVENT.
dwContinueStatus muestra cómo el hilo continúa informando eventos de depuración. Hay dos valores posibles: DBG_CONTINUE y DBG_EXCEPTION_NOT_HANDLED. Para la mayoría de los eventos de depuración, estos dos valores son los mismos: el hilo se reanuda.
La única excepción es EXCEPTION_DEBUG_EVENT, que indica que se ha producido una excepción en el subproceso del depurador si informa que se produjo un evento de depuración de excepción. Si se especifica DBG_CONTINUE, el subproceso ignora su propia parte de manejo de excepciones y continúa la ejecución. En este caso, nuestro programa debe verificar y manejar la excepción antes de usar DBG_CONTINUE para reanudar el hilo; de lo contrario, la excepción continuará ocurriendo... Si especificamos el valor DBG_EXCEPTION_NOT_HANDLED, le estamos diciendo a Windows que nuestro programa no maneja excepciones: Windows usará el controlador de excepciones predeterminado del depurador para manejar excepciones.
En resumen, si nuestro programa no considera excepciones y los eventos de depuración apuntan a excepciones en el proceso de depuración, entonces deberíamos llamar a la función ContinuarDebugEvent con el indicador DBG_CONTINUE. De lo contrario, nuestro programa debe llamar a la función ContinuarDebugEvent con DBG_EXCEPTION_NOT_HANDLED. Sin embargo, el indicador DBG_CONTINUE se debe utilizar en los siguientes casos: El primer caso es cuando el valor en el miembro ExceptionCode es EXCEPTION_BREAKPOINT. Evento EXCEPTION_DEBUG_EVENT. Cuando el depurador comienza a ejecutar la primera instrucción, nuestra función recibe un evento de depuración de excepción. De hecho, se trata de una interrupción de depuración (int 3h). Si llamamos a ContinuarDebugEvent con DBG_EXCEPTION_NOT_HANDLED en respuesta a un evento de depuración, Windows NT se negará a ejecutar el depurado (porque no tiene capacidades de manejo de excepciones). Entonces, en este caso, use el indicador DBG_CONTINUE para decirle a Windows que queremos continuar con la ejecución del hilo.
Continúe con los pasos anteriores hasta que salga el depurador. Nuestro programa debe continuar en un bucle infinito, como un bucle de mensajes, hasta que finalice el depurado. El bucle se ve así
. while TRUE
invoca WaitForDebugEvent, addr DebugEvent, INFINITE
.break .if DebugEvent.dwDebugEventCode==EXIT_ PROCESS_DEBUG_EVENT
invoca ContinuarDebugEvent, DebugEvent.dwProcessId, DebugEvent.dwThreadId, DBG_EXCEPTION _NOT_HANDLED
.endw
Es decir, Al iniciar el depurador, nuestro programa no se puede desconectar del depurador hasta que finalice.
Resumamos los pasos nuevamente:
Crear un proceso o vincular nuestro programa a un proceso en ejecución.
Esperar eventos de depuración
Responder a eventos de depuración.
Continuar con la depuración.
Continúe este bucle sin fin hasta que finalice el proceso de depuración
Ejemplo:
Este ejemplo depura un programa Win32 y muestra el identificador del proceso, el ID del proceso y la base de la imagen. dirección y otros datos.
.3.
.386
.modelo plano, llamada estándar
opción mapa de casos: ninguno
incluir \masm32\include\windows.inc
incluir \masm32\include\windows.inc
incluir \kernel32.inc incluir\kernel32.inc
incluir \ masm32\include\comdlg32.
include\mmas32\include\user32.inc
includelib \mmas32\lib \kernel32.lib
includelib \mmas32\lib \comdlg32.lib
includelib \mmas32\lib\user32.lib
.data .data
AppName db "Ejemplo de depuración de Win32 nº 1",0
ofn OPENFILENAME <>
FilterString db "Archivos ejecutables",0, "*.exe",0
db "Todos los archivos",0, " *.*",0,0
ExitProc db "El depurador sale",0
NewThread db "Crea un nuevo hilo",0
EndThread db " Destruir un hilo",0
ProcessInfo db "Identificador de archivo:
db "Identificador de proceso: %lx",0Dh,0Ah
db "Identificador de hilo: %lx",0Dh,0Ah
db "Base de imagen: db "Dirección de inicio:
.data?
buffer db 512 dup(?) < / p>
startinfo STARTUPINFO <>
pi PROCESS_INFORMATION <>
DBEvent DEBUG_EVENT <>
.code
inicio:
mov ofn.lStructSize,sizeofn
mov ofn.lpstrFilter, offset FilterString
mov ofn.lpstrFile, offset buffer
mov ofn. Banderas, OFN_FILEMUSTEXIST o OFN_PATHMUSTEXIST o OFN_LONGNAMES o OFN_EXPLORER o OFN_HIDEREADONLY
invocar GetOpenFileName, ADDR ofn
.if eax==TRUE
invocar GetStartupInfo,addr startinfo p>
invocar CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, NULL, NULL, addr startinfo, addr p
i
. while TRUE
Llamar a WaitForDebugEvent, addr DBEvent, INF addr DBEvent, INFINITE
.if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
Llamar a MessageBox, 0, dirección ExitProc, dirección AppName, MB_OK+MB_ICONINFORMATION
.break .elseif DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
invocar wsprintf, dirección buffer, dirección ProcessInfo, DBEvent. u.CreateProcessInfo.hFile, DBEvent.u.CreateProcessInfo.hProcess, DBEvent.u.CreateProcessInfo.hThread, DBEvent.u.CreateProcessInfo.hThread DBEvent.u.CreateProcessInfo.hProcess, DBEvent.OK+MB_ICONINFORMATION
.elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
.if DBEvent.u.Exception.pExceptionRecord.dwDebugEventCode==CREATE_THREAD_DEBUG_EVENT
.invoke MessageBox,0, dirección NewThread, dirección AppName, MB_OK+ MB_ICONINFORMATION
.elseif DBEvent.dwDebugEventCode==EXIT_THREAD_DEBUG_EVENT
invocar MessageBox,0, dirección EndThread, dirección AppName, MB_OK+MB_ICONINFORMATION
.endif
invocar ContinuarDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
.endw
invocar CloseHandle,pi.hProcess
invocar CloseHandle,pi.hThread
.endif
invocar ExitProcess, 0
fin de inicio
Análisis:
Se completará cuando el programa inicia la estructura OPENFILENAME y llamar a GetOpenFileName permite al usuario seleccionar el programa a depurar.
invocar GetStartupInfo,addr startinfo
invocar CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, DEBUG_PROCESS+ DEBUG_ONLY_THIS_ PROCESS, NULL, NULL, addr startinfo, addr pi
Después de recibir la selección de usuario, llame al cargador CreateProcess. Luego llame a GetStartupInfo para completar la estructura STARTUPINFO con valores predeterminados. Tenga en cuenta que combinamos el indicador DEBUG_PROCESS con el indicador DEBUG_ONLY_THIS_PROCESS para depurar solo el programa y no el proceso secundario.
. mientras que TRUE
invoca WaitForDebugEvent, addr DBEvent, INFINITE
Después de cargar el depurador, llamaremos a WaitForDebugEvent para ingresar a un ciclo de depuración sin fin. WaitForDebugEvent regresa cuando ocurre un evento de depuración en el depurador porque especificamos INFINITE como segundo parámetro. Cuando ocurre un evento de depuración, WaitForDebugEvent regresa y completa la estructura DBEvent.
.if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
invocar MessageBox, 0, dirección ExitProc, dirección AppName, MB_OK+MB_ ICONINFORMATION
.break
Primero verificamos el valor de dwDebugEventCode, si es EXIT_PROCESS_DEBUG_EVENT, el cuadro de mensaje muestra "El depurador sale" y sale del ciclo de depuración.
.elseif DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
invocar wsprintf, addr buffer, addr ProcessInfo, DBEvent.lpBaseOfImage, DBEvent.u.CreateProcessInfo.hProcess, DBEvent.u.CreateProcessInfo.hThread , DBEvent.u.CreateProcessInfo.lpStartAddress
invoca MessageBox,0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION
Si el valor de dwDebugEventCode es CREATE_PROCESS_DEBUG_EVENT, lo haremos El cuadro de mensaje muestra información básica de interés. Esta información proviene de u.CreateProcessInfo. CreateProcessInfo es una estructura de tipo CREATE_PROCESS_DEBUG_INFO.
.elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
.if DBEvent.u.Exception.pExceptionRecord .ExceptionCode==EXCEPTION_BREAKPOINT
invocar ContinuarDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE
.continue
.endif
Si el valor de dwDebugEventCode es EXCEPTION_DEBUG_EVENT, debemos verificar más a fondo el tipo de excepción. Se trata de un conjunto de estructuras anidadas, pero podemos obtener el tipo de excepción del miembro ExceptionCode. Si el valor de ExceptionCode es EXCEPTION_BREAKPOINT y ocurre por primera vez (o ya sabemos que no hay una instrucción int 3h en el depurador), podemos asumir con seguridad que la excepción ocurre cuando el depurador está a punto de ejecutar la primera instrucción. De esta manera podemos llamar a ContinuarDebugEvent con DBG_CONTINUE para continuar la ejecución del depurador. Luego, continuamos esperando a que ocurra el próximo evento de depuración. dwDebugEventCode==EXIT_THREAD_DEBUG_EVENT
Llamar a MessageBox,0, dirección EndThread, dirección AppName, MB_OK+MB_ICON_EVENT
Llamar a MessageBox,0, dirección NewThread, dirección AppName, MB_OK+MB_ICON_EVENT MB_OK+MB_ICONINFORMATION
.endif
Si el valor de dwDebugEventCode es CREATE_THREAD_DEBUG_EVENT o EXIT_THREAD_DEBUG_EVENT, nuestro programa mostrará un cuadro de mensaje.
invocar ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
.endw
Además de EXCEPTION_DEBUG_EVENT discutido anteriormente, también puedes llamar con DBG_EXCEPTION_NOT_HANDLED marque la función ContinuarDebugEvent para reanudar la ejecución depurada.
invocar CloseHandle,pi.hProcess
invocar CloseHandle,pi.hThread
Después de la depuración, salimos del ciclo de depuración y es hora de cerrar el hilo del depurador y identificadores de proceso. Cerrar estos identificadores no significa cerrar el proceso y el hilo. Simplemente significa que ya no los usamos.