[Win32]一個偵錯程式的實現-------除錯事件與除錯迴圈
前言
程式設計師離不開偵錯程式,它可以動態顯示程式的執行過程,對於解決程式問題有極大的幫助。如果你和我一樣對偵錯程式的工作原理很感興趣,那麼這一系列文章很適合你,這些文章記錄了我開發一個偵錯程式雛形的過程,希望對你有幫助。或許我寫的程式碼很拙劣,還請大家多多見諒!
這個偵錯程式使用Visual Studio 2010作為開發工具,是一個控制檯程式。為了簡化,一切輸入輸出都使用C++標準庫的相關類,而且省略了很多錯誤檢查和處理的過程。
啟動被除錯程式
要想對一個程式進行除錯,首先要做的當然是啟動這個程式,這要使用CreateProcess這個Windows API來完成。例如,下面的程式碼以記事本作為被除錯程式:
#include <Windows.h> #include <iostream> int wmain(int argc, wchar_t** argv) { STARTUPINFO si = { 0 }; si.cb = sizeof(si); PROCESS_INFORMATION pi = { 0 }; if (CreateProcess( TEXT("C:\\windows\\notepad.exe"), NULL, NULL, NULL, FALSE, DEBUG_ONLY_THIS_PROCESS | CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi) == FALSE) { std::wcout << TEXT("CreateProcess failed:") << GetLastError() << std::endl; return -1; } CloseHandle(pi.hThread); CloseHandle(pi.hProcess); return 0; }
CreateProcess的第六個引數使用了DEBUG_ONLY_THIS_PROCESS,這意味著呼叫CreateProcess的程序成為了偵錯程式,而它啟動的子程序成了被除錯的程序。除了DEBUG_ONLY_THIS_PROCESS之外,還可以使用DEBUG_PROCESS,兩者的不同在於:DEBUG_PROCESS會除錯被除錯程序以及它的所有子程序,而DEBUG_ONLY_THIS_PROCESS只調試被除錯程序,不除錯它的子程序。一般情況下我們只想除錯一個程序,所以應使用後者。
我建議在第六個引數中加上CREATE_NEW_CONSOLE標記。因為如果被除錯程式是一個控制檯程式的話,偵錯程式和被除錯程式的輸出都在同一個控制檯視窗內,顯得很混亂,加上這個標記之後,被除錯程式就會在一個新的控制檯視窗中輸出資訊。如果被除錯程式是一個視窗程式,這個標記沒有影響。
上面的程式碼僅僅是啟動了被除錯程序,然後就立即退出了。要注意的是,如果偵錯程式程序結束了,那麼被它除錯的所有子程序都會隨著結束。這就是為什麼雖然CreateProcess呼叫成功了,卻看不到記事本視窗。
除錯迴圈
偵錯程式如何知道被除錯程序內部發生了什麼呢?是這樣的,當一個程序成為被除錯程序之後,在完成了某些操作或者發生異常時,它會發送通知給偵錯程式,然後將自身掛起,直到偵錯程式命令它繼續執行。這有點像Windows視窗的訊息機制。
被除錯程序傳送的通知稱為除錯事件,DEBUG_EVENT結構體描述了除錯事件的內容:
typedef struct _DEBUG_EVENT {
DWORD dwDebugEventCode;
DWORD dwProcessId;
DWORD dwThreadId;
union {
EXCEPTION_DEBUG_INFO Exception;
CREATE_THREAD_DEBUG_INFO CreateThread;
CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;
EXIT_THREAD_DEBUG_INFO ExitThread;
EXIT_PROCESS_DEBUG_INFO ExitProcess;
LOAD_DLL_DEBUG_INFO LoadDll;
UNLOAD_DLL_DEBUG_INFO UnloadDll;
OUTPUT_DEBUG_STRING_INFO DebugString;
RIP_INFO RipInfo;
} u;
} DEBUG_EVENT,
*LPDEBUG_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的欄位的名稱可以很快地判斷哪個欄位與哪種事件關聯。例如CREATE_PROCESS_DEBUG_EVENT除錯事件的詳細資訊由CreateProcessInfo欄位來記錄。
dwProcessId和dwThreadId分別是觸發除錯事件的程序ID和執行緒ID。一個偵錯程式可能同時除錯多個程序,而每個程序內又可能有多個執行緒,通過這兩個欄位就可以知道除錯事件是從哪個程序的哪個執行緒觸發的了。本系列文章只考慮單程序單執行緒的情況,因此這兩個欄位不會被用到,因為在呼叫CreateProcess的時候已經獲取到這兩個值了。
偵錯程式通過WaitForDebugEvent函式獲取除錯事件,通過ContinueDebugEvent繼續被除錯程序的執行。ContinueDebugEvent有三個引數,第一和第二個引數分別是程序ID和執行緒ID,表示讓指定程序內的指定執行緒繼續執行。通常這是在一個迴圈中完成的,如下面的程式碼所示:
void OnProcessCreated(const CREATE_PROCESS_DEBUG_INFO*);
void OnThreadCreated(const CREATE_THREAD_DEBUG_INFO*);
void OnException(const EXCEPTION_DEBUG_INFO*);
void OnProcessExited(const EXIT_PROCESS_DEBUG_INFO*);
void OnThreadExited(const EXIT_THREAD_DEBUG_INFO*);
void OnOutputDebugString(const OUTPUT_DEBUG_STRING_INFO*);
void OnRipEvent(const RIP_INFO*);
void OnDllLoaded(const LOAD_DLL_DEBUG_INFO*);
void OnDllUnloaded(const UNLOAD_DLL_DEBUG_INFO*);
BOOL waitEvent = TRUE;
DEBUG_EVENT debugEvent;
while (waitEvent == TRUE && WaitForDebugEvent(&debugEvent, INFINITE)) {
switch (debugEvent.dwDebugEventCode) {
case CREATE_PROCESS_DEBUG_EVENT:
OnProcessCreated(&debugEvent.u.CreateProcessInfo);
break;
case CREATE_THREAD_DEBUG_EVENT:
OnThreadCreated(&debugEvent.u.CreateThread);
break;
case EXCEPTION_DEBUG_EVENT:
OnException(&debugEvent.u.Exception);
break;
case EXIT_PROCESS_DEBUG_EVENT:
OnProcessExited(&debugEvent.u.ExitProcess);
waitEvent = FALSE;
break;
case EXIT_THREAD_DEBUG_EVENT:
OnThreadExited(&debugEvent.u.ExitThread);
break;
case LOAD_DLL_DEBUG_EVENT:
OnDllLoaded(&debugEvent.u.LoadDll);
break;
case UNLOAD_DLL_DEBUG_EVENT:
OnDllUnloaded(&debugEvent.u.UnloadDll);
break;
case OUTPUT_DEBUG_STRING_EVENT:
OnOutputDebugString(&debugEvent.u.DebugString);
break;
case RIP_EVENT:
OnRipEvent(&debugEvent.u.RipInfo);
break;
default:
std::wcout << TEXT("Unknown debug event.") << std::endl;
break;
}
if (waitEvent == TRUE) {
ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, DBG_CONTINUE);
}
else {
break;
}
}
這樣一個迴圈就是所謂的除錯迴圈。要注意這裡是如何退出迴圈的:引入一個BOOL型別的waitEvent變數,在處理EXIT_PROCESS_DEBUG_EVENT之後將它的值改成FALSE。之所以要這樣處理,是因為在被除錯程序結束之後仍然可以呼叫WaitForDebugEvent函式等待除錯事件,這樣就會陷入無限的等待之中,導致偵錯程式程序無法結束。
示例程式碼
示例程式碼將上面兩段程式碼結合起來,並實現了上述的OnProcessCreated等除錯事件處理函式,實現過程僅僅是輸出提示資訊。當然,對除錯事件的處理遠遠不只這麼簡單,雖然你可以選擇忽略某些除錯事件,但有些除錯事件是必須進行處理的,這部分內容將放到下一篇文章中進行講解。
作者:Zplutor 出處:http://www.cnblogs.com/zplutor/ 本文版權歸作者和部落格園共有,歡迎轉載。但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線,否則保留追究法律責任的權利。