作为一个安全开发人员离不开调试器,它可以动态显示程序的执行过程,对于解决程序问题有极大的帮助,这些文章记录了我开发一个调试器雏形的过程,希望对你有帮助

我们使用  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的字段的名称可以很快地判断哪个字段与哪种事件关联。
 dwProcessId和dwThreadId分别是触发调试事件的进程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