《一个调试器的实现.docx》由会员分享,可在线阅读,更多相关《一个调试器的实现.docx(127页珍藏版)》请在金锄头文库上搜索。
1、一个调试器的实现(一)调试事件与调试循环前言程序员离不开调试器,它可以动态显示程序的执行过程,对于解决程序问题有极大的帮助。如果你和我一样对调试器的工作原理很感兴趣,那么这一系列文章很适合你,这些文章记录了我开发一个调试器雏形的过程,希望对你有帮助。或许我写的代码很拙劣,还请大家多多见谅!这个调试器使用Visual Studio 2010作为开发工具,是一个控制台程序。为了简化,一切输入输出都使用C+标准库的相关类,而且省略了很多错误检查和处理的过程。启动被调试程序要想对一个程序进行调试,首先要做的当然是启动这个程序,这要使用CreateProcess这个Windows API来完成。例如,下
2、面的代码以记事本作为被调试程序:1#include2#include34intwmain(intargc,wchar_t*argv)56STARTUPINFOsi=0;7si.cb=sizeof(si);89PROCESS_INFORMATIONpi=0;1011if(CreateProcess(12TEXT(C:windowsnotepad.exe),13NULL,14NULL,15NULL,16FALSE,17DEBUG_ONLY_THIS_PROCESS|CREATE_NEW_CONSOLE,18NULL,19NULL,20&si,21&pi)=FALSE)2223std:wcoutTE
3、XT(CreateProcessfailed:)GetLastError()std:endl;24return-1;252627CloseHandle(pi.hThread);28CloseHandle(pi.hProcess);2930return0;31CreateProcess的第六个参数使用了DEBUG_ONLY_THIS_PROCESS,这意味着调用CreateProcess的进程成为了调试器,而它启动的子进程成了被调试的进程。除了DEBUG_ONLY_THIS_PROCESS之外,还可以使用DEBUG_PROCESS,两者的不同在于:DEBUG_PROCESS会调试被调试进程以及它
4、的所有子进程,而DEBUG_ONLY_THIS_PROCESS只调试被调试进程,不调试它的子进程。一般情况下我们只想调试一个进程,所以应使用后者。我建议在第六个参数中加上CREATE_NEW_CONSOLE标记。因为如果被调试程序是一个控制台程序的话,调试器和被调试程序的输出都在同一个控制台窗口内,显得很混乱,加上这个标记之后,被调试程序就会在一个新的控制台窗口中输出信息。如果被调试程序是一个窗口程序,这个标记没有影响。上面的代码仅仅是启动了被调试进程,然后就立即退出了。要注意的是,如果调试器进程结束了,那么被它调试的所有子进程都会随着结束。这就是为什么虽然CreateProcess调用成功了
5、,却看不到记事本窗口。调试循环调试器如何知道被调试进程内部发生了什么呢?是这样的,当一个进程成为被调试进程之后,在完成了某些操作或者发生异常时,它会发送通知给调试器,然后将自身挂起,直到调试器命令它继续执行。这有点像Windows窗口的消息机制。被调试进程发送的通知称为调试事件,DEBUG_EVENT结构体描述了调试事件的内容:1typedefstruct_DEBUG_EVENT2DWORDdwDebugEventCode;3DWORDdwProcessId;4DWORDdwThreadId;5union6EXCEPTION_DEBUG_INFOException;7CREATE_THREAD
6、_DEBUG_INFOCreateThread;8CREATE_PROCESS_DEBUG_INFOCreateProcessInfo;9EXIT_THREAD_DEBUG_INFOExitThread;10EXIT_PROCESS_DEBUG_INFOExitProcess;11LOAD_DLL_DEBUG_INFOLoadDll;12UNLOAD_DLL_DEBUG_INFOUnloadDll;13OUTPUT_DEBUG_STRING_INFODebugString;14RIP_INFORipInfo;15u;16DEBUG_EVENT,17*LPDEBUG_EVENT;dwDebugE
7、ventCode描述了调试事件的类型,总共有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_
8、EVENT被调试进程调用OutputDebugString之类的函数时发送此类调试事件。RIP_EVENT发生系统调试错误时发送此类调试事件。UNLOAD_DLL_DEBUG_EVENT卸载一个DLL模块之后发送此类调试事件。每种调试事件的详细信息通过联合体u来记录,通过u的字段的名称可以很快地判断哪个字段与哪种事件关联。例如CREATE_PROCESS_DEBUG_EVENT调试事件的详细信息由CreateProcessInfo字段来记录。dwProcessId和dwThreadId分别是触发调试事件的进程ID和线程ID。一个调试器可能同时调试多个进程,而每个进程内又可能有多个线程,通过这两
9、个字段就可以知道调试事件是从哪个进程的哪个线程触发的了。本系列文章只考虑单进程单线程的情况,因此这两个字段不会被用到,因为在调用CreateProcess的时候已经获取到这两个值了。调试器通过WaitForDebugEvent函数获取调试事件,通过ContinueDebugEvent继续被调试进程的执行。ContinueDebugEvent有三个参数,第一和第二个参数分别是进程ID和线程ID,表示让指定进程内的指定线程继续执行。通常这是在一个循环中完成的,如下面的代码所示:1voidOnProcessCreated(constCREATE_PROCESS_DEBUG_INFO*);2voidO
10、nThreadCreated(constCREATE_THREAD_DEBUG_INFO*);3voidOnException(constEXCEPTION_DEBUG_INFO*);4voidOnProcessExited(constEXIT_PROCESS_DEBUG_INFO*);5voidOnThreadExited(constEXIT_THREAD_DEBUG_INFO*);6voidOnOutputDebugString(constOUTPUT_DEBUG_STRING_INFO*);7voidOnRipEvent(constRIP_INFO*);8voidOnDllLoaded(
11、constLOAD_DLL_DEBUG_INFO*);9voidOnDllUnloaded(constUNLOAD_DLL_DEBUG_INFO*);1011BOOLwaitEvent=TRUE;12DEBUG_EVENTdebugEvent;13while(waitEvent=TRUE&WaitForDebugEvent(&debugEvent,INFINITE)1415switch(debugEvent.dwDebugEventCode)1617caseCREATE_PROCESS_DEBUG_EVENT:18OnProcessCreated(&debugEvent.u.CreatePro
12、cessInfo);19break;2021caseCREATE_THREAD_DEBUG_EVENT:22OnThreadCreated(&debugEvent.u.CreateThread);23break;2425caseEXCEPTION_DEBUG_EVENT:26OnException(&debugEvent.u.Exception);27break;2829caseEXIT_PROCESS_DEBUG_EVENT:30OnProcessExited(&debugEvent.u.ExitProcess);31waitEvent=FALSE;32break;3334caseEXIT_