首页 > 代码库 > 调试器开发实例_调试器框架设计

调试器开发实例_调试器框架设计

 

作为一个安全开发人员离不开调试器,它可以动态显示程序的执行过程,对于解决程序问题有极大的帮助,这些文章记录了我开发一个调试器雏形的过程,希望对你有帮助。或许我写的代码很拙劣,还请大家多多见谅!

我们使用  Microsoft Visual Studio 6.0 VC编译器来作为我们的开发工具
想对一个程序进行调试,首先要做的当然是启动这个程序,这要使用CreateProcess这个Windows API来完成。
例如: 

 1 // LilisiDebug.cpp : Defines the entry point for the console application. 2 // 3  4 #include "stdafx.h" 5 #include <windows.h> 6  7 int main(int argc, char* argv[]) 8 { 9     STARTUPINFO si = { 0 };10     si.cb = sizeof(si);    11     PROCESS_INFORMATION pi = { 0 };12 13     BOOL bRet = CreateProcess(TEXT("C:\\windows\\system32\\calc.exe"),14                               NULL,15                               NULL,16                               NULL,17                               FALSE,18                               DEBUG_ONLY_THIS_PROCESS | CREATE_NEW_CONSOLE,19                               NULL,20                               NULL,21                               &si,22                               &pi);23         24         if ( bRet == FALSE) 25         {26             27             printf("Open Debug Process Error!");28             return -1;29         }        30         CloseHandle(pi.hThread);31         CloseHandle(pi.hProcess);        32         return 0;33 }

CreateProcess的第六个参数使用了DEBUG_ONLY_THIS_PROCESS,这意味着调用CreateProcess的进程成为了调试器,而它启动的子进程成了被调试的进程。
在第六个参数之中也可以加上CREATE_NEW_CONSOLE ,他的作用在于如果被调试程序是一个控制台程序的话,调试器和被调试程序的输出都在同一个控制台窗口内,显得很混乱,加上这个标记之后,被调试程序就会在一个新的控制台窗口中输出信息。如果被调试程序是一个窗口程序,这个标记没有影响。(个人推荐) 

以上的代码仅仅是启动了被调试进程,然后就立即退出了。要注意的是,如果调试器进程结束了,那么被它调试的所有子进程都会随着结束。这就是为什么虽然CreateProcess调用成功了,却看不到计算器窗口的原因。

那么调试器如何知道被调试进程内部发生了什么?其实当一个进程成为被调试进程之后,在完成了某些操作或者发生异常时,它会发送通知给调试器,然后将自身挂起,直到调试器命令它继续执行。所以接下来我们开始调用
调试循环事件对被调试程序进行事件的处理。
被调试进程发送的通知称为调试事件,在
DEBUG_EVENT结构体中详细描述了调试事件的内容: 

 

 1 typedef struct _DEBUG_EVENT  2 { 3    DWORD dwDebugEventCode; 4    DWORD dwProcessId; 5    DWORD dwThreadId; 6    union { 7      EXCEPTION_DEBUG_INFO Exception; 8      CREATE_THREAD_DEBUG_INFO CreateThread; 9      CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;10      EXIT_THREAD_DEBUG_INFO ExitThread;11      EXIT_PROCESS_DEBUG_INFO ExitProcess;12      LOAD_DLL_DEBUG_INFO LoadDll;13      UNLOAD_DLL_DEBUG_INFO UnloadDll;14      OUTPUT_DEBUG_STRING_INFO DebugString;15      RIP_INFO RipInfo;16    } u;17  } DEBUG_EVENT,  

其结构体中的dwDebugEventCode描述了调试事件的类型,总共有9类调试事件:

 

 

CREATE_PROCESS_DEBUG_EVENT

创建进程的时候会来到该调试事件.

CREATE_THREAD_DEBUG_EVENT

创建一个线程之后发送此类调试事件.

EXCEPTION_DEBUG_EVENT

发生异常时发送此类调试事件.

EXIT_PROCESS_DEBUG_EVENT

进程结束后发送此类调试事件.

EXIT_THREAD_DEBUG_EVENT

一个线程结束后发送此类调试事件.

LOAD_DLL_DEBUG_EVENT

装载一个DLL模块之后发送此类调试事件.

OUTPUT_DEBUG_STRING_EVENT

被调试进程调用OutputDebugString之类的函数时发送此类调试事件.

RIP_EVENT

发生系统调试错误时发送此类调试事件.

UNLOAD_DLL_DEBUG_EVENT

卸载一个DLL模块之后发送此类调试事件.

 

而在结构体中的u联合体来记录每种调试事件,通过u的字段的名称可以很快地判断哪个字段与哪种事件关联。
 dwProcessIddwThreadId分别是触发调试事件的进程ID和线程ID
 调试器则通过WaitForDebugEvent,通过ContinueDebugEvent继续被调试进程的执行。 

以下代码摘要于微软帮助文档MSDN 

 

 1 DEBUG_EVENT DebugEv;                   // debugging event information  2 DWORD dwContinueStatus = DBG_CONTINUE; // exception continuation  3 for(;;)  4 {  5  6 // Wait for a debugging event to occur. The second parameter indicates  7 // that the function does not return until a debugging event occurs.  8  9     WaitForDebugEvent(&DebugEv, INFINITE); 10 11 // Process the debugging event code. 12 switch (DebugEv.dwDebugEventCode) 13     { 14         case EXCEPTION_DEBUG_EVENT: 15         // Process the exception code. When handling 16         // exceptions, remember to set the continuation 17         // status parameter (dwContinueStatus). This value 18         // is used by the ContinueDebugEvent function. 19 20             switch (DebugEv.u.Exception.ExceptionRecord.ExceptionCode) 21             { 22                 case EXCEPTION_ACCESS_VIOLATION: 23                 // First chance: Pass this on to the system. 24                 // Last chance: Display an appropriate error. 25 26                 case EXCEPTION_BREAKPOINT: 27                 // First chance: Display the current 28                 // instruction and register values. 29 30                 case EXCEPTION_DATATYPE_MISALIGNMENT: 31                 // First chance: Pass this on to the system. 32                 // Last chance: Display an appropriate error. 33 34                 case EXCEPTION_SINGLE_STEP: 35                 // First chance: Update the display of the 36                 // current instruction and register values. 37 38                 case DBG_CONTROL_C: 39                 // First chance: Pass this on to the system. 40                 // Last chance: Display an appropriate error. 41 42                 // Handle other exceptions. 43             } 44         case CREATE_THREAD_DEBUG_EVENT: 45         // As needed, examine or change the thread‘s registers 46         // with the GetThreadContext and SetThreadContext functions; 47         // and suspend and resume thread execution with the 48         // SuspendThread and ResumeThread functions. 49 50         case CREATE_PROCESS_DEBUG_EVENT: 51         // As needed, examine or change the registers of the 52         // process‘s initial thread with the GetThreadContext and 53         // SetThreadContext functions; read from and write to the 54         // process‘s virtual memory with the ReadProcessMemory and 55         // WriteProcessMemory functions; and suspend and resume 56         // thread execution with the SuspendThread and ResumeThread 57         // functions. 58 59         case EXIT_THREAD_DEBUG_EVENT: 60         // Display the thread‘s exit code. 61 62         case EXIT_PROCESS_DEBUG_EVENT: 63         // Display the process‘s exit code. 64 65         case LOAD_DLL_DEBUG_EVENT: 66         // Read the debugging information included in the newly 67         // loaded DLL. 68 69         case UNLOAD_DLL_DEBUG_EVENT: 70         // Display a message that the DLL has been unloaded. 71 72         case OUTPUT_DEBUG_STRING_EVENT: 73         // Display the output debugging string. 74         }     75     // Resume executing the thread that reported the debugging event.  76        ContinueDebugEvent(DebugEv.dwProcessId, 77                    DebugEv.dwThreadId, dwContinueStatus);78 } 

这样一个循环就是所谓的调试循环。要注意这里是如何退出循环的:引入一个BOOL类型的bRetEvent变量,在处理
EXIT_PROCESS_DE BUG_EVENT