很多朋友都夢想有自己的Debugger程序,今天我們就來自己制作一個。作為一個Debugger程序,其最基本的功能框架其實就是完成2件事情:
? 啟動目標(biāo)程序。
? 實時監(jiān)控目標(biāo)程序的運行,并做出相應(yīng)的應(yīng)對。
我們要打造自己的Debugger程序,實際上也只需要完成這兩個功能就可以了。當(dāng)然,要完成這兩個特定的功能,我們不可能從頭開始造輪子,要首先看看操作系統(tǒng)給我們提供了什么樣的基礎(chǔ)設(shè)施:
由于我們是在Windows平臺上工作,自然離不開微軟公司提供的文檔大全——MSDN。翻開MSDN,定位到“Debugging and Error Handling”,一些最基本的Windows Debug信息都在這里面。不過與其他欄目相比,這個欄目的信息明顯顯得單薄許多——也許越是底層、強大的技術(shù),微軟越不想公開吧。
初步瀏覽之后,我們可以確定,對于我們的Debugger而言,最重要的Debug API有如下幾個:
? CreateProcess —— 用于創(chuàng)建被調(diào)試進程
? WaitForDebugEvent —— Debug Loop(調(diào)試循環(huán))的主要構(gòu)成函數(shù)
? ContinueDebugEvent —— 用于構(gòu)成Debug Loop
? GetThreadContext —— 得到被調(diào)試進程的寄存器信息
? SetThreadContext —— 設(shè)置被調(diào)試進程的寄存器信息
? ReadProcessMemory —— 得到被調(diào)試進程的內(nèi)存內(nèi)容
? WriteProcessMemory —— 設(shè)置被調(diào)試進程的內(nèi)存內(nèi)容
最重要的數(shù)據(jù)結(jié)構(gòu)有如下幾個:
? CONTEXT —— 寄存器結(jié)構(gòu)
? STARTUPINFO —— Start信息
? PROCESS_INFORMATION —— 進程相關(guān)信息
? DEBUG_EVENT —— Debug Event(調(diào)試事件)結(jié)構(gòu)
可以說,我們的Debugger程序就是利用這幾個API函數(shù)結(jié)合下面的幾個數(shù)據(jù)結(jié)構(gòu),完成我們指定的功能。那么下面就讓我們先來看看這幾個API和數(shù)據(jù)結(jié)構(gòu)的具體含義:
?
Debug API解析
在這里,我們將對上面所述的幾個Debug調(diào)試API做一個檢閱式的考察,大概介紹一下每個API的應(yīng)用領(lǐng)域。而將這些API應(yīng)用到具體實踐中去,將會在下一部分“實例解析”中,給出詳細的說明。
1.CreateProcess。
函數(shù)原型:BOOL CreateProcess(
LPCTSTR lpApplicationName, // 要創(chuàng)建的進程模塊名
LPTSTR lpCommandLine, // 命令行字符串
LPSECURITY_ATTRIBUTES lpProcessAttributes, // 進程安全屬性
LPSECURITY_ATTRIBUTES lpThreadAttributes, // 線程安全屬性
BOOL bInheritHandles, // 句柄繼承選項
DWORD dwCreationFlags, // 進程創(chuàng)建選項
LPVOID lpEnvironment, // 進程環(huán)境塊數(shù)據(jù)指針
LPCTSTR lpCurrentDirectory, // 當(dāng)前目錄名
LPSTARTUPINFO lpStartupInfo, // 啟動信息
LPPROCESS_INFORMATION lpProcessInformation // 進程信息
);
函數(shù)解析:該函數(shù)是Windows平臺提供的最基本的創(chuàng)建進程的函數(shù)。每當(dāng)我們雙擊一個EXE可執(zhí)行文件,Windows內(nèi)核就會自動調(diào)用該函數(shù)創(chuàng)建我們雙擊的文件所對應(yīng)的進程。該函數(shù)中,最重要的參數(shù)有三個:一個是進程模塊名,指明了要創(chuàng)建哪個進程;一個是進程創(chuàng)建選項,指明了要如何創(chuàng)建目標(biāo)進程;對于Debugger程序而言,最常用的創(chuàng)建選項就是:DEBUG_PROCESS|DEBUG_ONLY_THIS_PROCESS。最后還有一個就是進程信息,我們調(diào)用CreateProcess創(chuàng)建了進程以后,Windows會將新創(chuàng)建的進程的相關(guān)信息全部放到ProcessInfo信息塊中,我們在Debug Loop調(diào)試循環(huán)中使用進程信息塊中的數(shù)據(jù)與目標(biāo)進程交互,監(jiān)視和控制目標(biāo)進程的動作。
2.WaitForDebugEvent。
函數(shù)原型:BOOL WaitForDebugEvent(
LPDEBUG_EVENT lpDebugEvent, // Debug Event(調(diào)試事件指針)
DWORD dwMilliseconds // 超時設(shè)置
);
函數(shù)解析:該函數(shù)構(gòu)成了Debug Loop調(diào)試循環(huán)的主體,一個Debugger程序在創(chuàng)建出目標(biāo)進程后,一般都會緊接著循環(huán)調(diào)用該函數(shù)等待目標(biāo)進程的各種調(diào)試信息,這個循環(huán)調(diào)用WaitForDebugEvent的過程,我們就稱之為Debug Loop調(diào)試循環(huán)。調(diào)試循環(huán)是所有Debugger程序的主體部分,Debugger幾乎所有的監(jiān)視、控制、調(diào)整的功能都是在調(diào)試循環(huán)內(nèi)完成的。一般來說,此處的超時設(shè)置都設(shè)置為-1也就是無窮等待下去,該函數(shù)是非阻塞函數(shù),在沒有Debug Event發(fā)生,處于等待的過程中,僅消耗極其微小的系統(tǒng)資源。
3.ContinueDebugEvent。
函數(shù)原型:BOOL ContinueDebugEvent(
DWORD dwProcessId, // 目標(biāo)進程ID
DWORD dwThreadId, // 目標(biāo)線程ID
DWORD dwContinueStatus // 線程繼續(xù)的標(biāo)志
);
函數(shù)解析:該函數(shù)主要用于Debugger在Debug Loop調(diào)試循環(huán)中,處理完Debug Event,通知目標(biāo)進/線程繼續(xù)運作。通常情況下,目標(biāo)進程ID和目標(biāo)線程ID這兩個參數(shù),都是CreateProcess調(diào)用后,ProcessInfo結(jié)構(gòu)中所包含的信息。該函數(shù)通過目標(biāo)進程/線程ID來唯一標(biāo)識目標(biāo)進/線程,并且通過設(shè)置不同的ContinueStatus來通知目標(biāo)進/線程繼續(xù)運行的動作。最主要的ContinueStatus有兩種選擇:一個是DBG_CONTINUE,表明調(diào)試事件已經(jīng)被Debugger處理完畢,目標(biāo)進/線程可以照常繼續(xù)運行;另一個是DBG_EXCEPTION_NOT_HANDLED,表明Debugger并未處理該調(diào)試事件,目標(biāo)進程收到該標(biāo)志位后,將會將調(diào)試事件沿著Windows異常調(diào)用鏈繼續(xù)往下發(fā)送。直至該調(diào)試事件被處理完為止——當(dāng)然,如果目標(biāo)進程發(fā)出的Debug Event沒有任何調(diào)試器能夠處理,那最后Windows只有祭出自己的殺手锏:應(yīng)用程序XXX異常,即將被關(guān)閉。
3.GetThreadContext & SetThreadContext。
函數(shù)原型:BOOL GetThreadContext(
HANDLE hThread, // 目標(biāo)線程句柄
LPCONTEXT lpContext // CONTEXT結(jié)構(gòu)
);
BOOL SetThreadContext(
HANDLE hThread, // 目標(biāo)線程句柄
CONST CONTEXT *lpContext // CONTEXT結(jié)構(gòu)
);
函數(shù)解析:這兩個函數(shù)分別用來得到和設(shè)置目標(biāo)線程的寄存器內(nèi)容。請注意,在Windows操作系統(tǒng)中,操作系統(tǒng)調(diào)度的最小單位粒度是線程而不是進程,所以,籠統(tǒng)地說:設(shè)置某進程的寄存器內(nèi)容是錯誤的,因為一個進程可能對應(yīng)多個線程。因此在和寄存器打交道的時候,一定要指明是哪個線程所對應(yīng)的寄存器。該函數(shù)參數(shù)中,由hThread參數(shù)也就是線程句柄參數(shù)指定目標(biāo)線程,該參數(shù)的來源也通常是CreateProcess調(diào)用后所得到的ProcessInfomation中的hThread成員。而CONTEXT結(jié)構(gòu)則是根據(jù)所在機器硬件平臺的不同而有不同的定義。Windows操作系統(tǒng)在Intel、MIPS、Alpha和PowerPC平臺上,各有不同的CONTEXT定義,每種定義都忠實而完整地反映出了目標(biāo)CPU的寄存器分布情況。不過雖然CONTEXT結(jié)構(gòu)在不同的CPU平臺上有不同的表現(xiàn)形式,但是最基本的Intel X86架構(gòu)在各個CPU上面的表現(xiàn)都是相同的,因此,只要Debugger代碼未牽涉到各個CPU非常Specifc的細節(jié),還是可以跨CPU平臺使用的。
4.ReadProcessMemory & WriteProcessMemory。
函數(shù)原型:BOOL ReadProcessMemory(
HANDLE hProcess, // 進程句柄
LPCVOID lpBaseAddress, // 欲讀取內(nèi)存基地址
LPVOID lpBuffer, // 數(shù)據(jù)緩沖區(qū)指針
SIZE_T nSize, // 欲讀取的內(nèi)存內(nèi)容長度
SIZE_T * lpNumberOfBytesRead // 實際讀取的內(nèi)存內(nèi)容長度
);
BOOL WriteProcessMemory(
HANDLE hProcess, // 進程句柄
LPVOID lpBaseAddress, // 欲寫入內(nèi)存基地址
LPCVOID lpBuffer, // 數(shù)據(jù)緩沖區(qū)指針
SIZE_T nSize, // 欲寫入的內(nèi)存內(nèi)容長度
SIZE_T * lpNumberOfBytesWritten // 實際寫入的內(nèi)存內(nèi)容長度
);
函數(shù)解析:這兩個函數(shù)分別用于“讀取”和“寫入”目標(biāo)進程的內(nèi)存地址空間。與寄存器操作不同,Windows對內(nèi)存的分配粒度是以進程為單位的。由于自Intel 386以后,所有的Intel x86系列CPU都采用的保護模式,因此在保護模式下,Windows為每一個應(yīng)用程序——也就是每一個進程都虛擬出一個擁有4GB大小內(nèi)存的“虛擬機器”, 隸屬于該進程的所有線程都共享這4GB的地址空間。因此,與上面寄存器操作不同,讀取、寫入內(nèi)存操作時,我們需要的是進程的句柄——當(dāng)然,該句柄也來源于CreateProcess后得到的ProcessInfomation結(jié)構(gòu)。有了進程句柄以后,我們還需要一個基地址和一個長度參數(shù)來確定我們的Debugger程序所需要讀取的內(nèi)存范圍。當(dāng)然,此處的基地址數(shù)值上應(yīng)該對應(yīng)于Windows虛擬出來的那4GB的平坦地址空間內(nèi)的地址——也就是經(jīng)過了段選擇和頁選擇過程后的地址數(shù)值。
DEBUG_EVENT全面剖析
在整個調(diào)試循環(huán)中,Debugger和目標(biāo)進程之間調(diào)試信息的交互完全是通過調(diào)用WaitForDebugEvent時,傳遞的DEBUG_EVENT結(jié)構(gòu)參數(shù)。該結(jié)構(gòu)的定義初看起來似乎很簡單:
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;
不過就是一個union聯(lián)合域加上三個普通的信息數(shù)據(jù)。可細細推敲之下,這個結(jié)構(gòu)所實現(xiàn)的功能卻絕對不簡單——讓我們做一個最簡單的思考,作為Debugger,至少應(yīng)該能夠收到:
? 目標(biāo)進程啟動
? 目標(biāo)進程發(fā)生異常
? 目標(biāo)進程退出
這三個最基本的調(diào)試信息。而且每個信息所對應(yīng)的信息類型應(yīng)該都不太一樣,比如:
? 目標(biāo)進程啟動時,我們需要的是目標(biāo)進程的模塊名字,權(quán)限設(shè)置等啟動信息
? 目標(biāo)進程異常時,我們應(yīng)該能夠知道異常的地址,異常的原因(這里面分類又很多),異常的嚴(yán)重程度
? 目標(biāo)進程退出時,我們應(yīng)該能夠知道進程的退出值,用來確定進程是否屬于正常退出
上面所列出的,還只是一些最基本的要素而已,如果要構(gòu)成整個Windows Debug API的數(shù)據(jù)交互層,那情況還要復(fù)雜得多。將這么多錯綜復(fù)雜的信息全部用一個結(jié)構(gòu)表示,其難度可想而知。
微軟公司在這里選擇的方法是通過dwDebugEventCode標(biāo)識最基本信息,然后通過union聯(lián)合域?qū)⒏鞣N信息全部包羅進去的方法。這種做法的優(yōu)點是在WaitForDebugEvent調(diào)用時,只需要傳遞一個統(tǒng)一的結(jié)構(gòu)參數(shù)即可;缺點就是Debug_Event結(jié)構(gòu)內(nèi)部信息的異常復(fù)雜,給程序設(shè)計帶來不小的麻煩。
我們在使用Debug_Event結(jié)構(gòu)時,首先要得到dwDebugEventCode的值,從而確定union聯(lián)合域內(nèi)是什么內(nèi)容。兩者之間的對應(yīng)關(guān)系如下表所示:
dwDebugEventCode的值
Union聯(lián)合域類型 調(diào)試信息
EXCEPTION_DEBUG_EVENT EXCEPTION_DEBUG_INFO
應(yīng)用程序發(fā)生異常
CREATE_THREAD_DEBUG_EVENT CREATE_THREAD_DEBUG_INFO
線程創(chuàng)建
CREATE_PROCESS_DEBUG_EVENT CREATE_PROCESS_DEBUG_INFO
進程創(chuàng)建
EXIT_THREAD_DEBUG_EVENT EXIT_THREAD_DEBUG_INFO
線程退出
EXIT_PROCESS_DEBUG_EVENT EXIT_PROCESS_DEBUG_INFO
進程退出
LOAD_DLL_DEBUG_EVENT LOAD_DLL_DEBUG_INFO
Dll載入
UNLOAD_DLL_DEBUG_EVENT UNLOAD_DLL_DEBUG_INFO
Dll卸載
OUTPUT_DEBUG_STRING_EVENT OUTPUT_DEBUG_STRING_INFO
輸出Debug字符串
RIP_EVENT RIP_INFO 系統(tǒng)調(diào)試錯誤
上表僅僅是Debug_Event結(jié)構(gòu)數(shù)據(jù)解析的第一層,當(dāng)union聯(lián)合域取得各個不同的值時,這些第二層的數(shù)據(jù)結(jié)構(gòu)并不比這一層簡單多少——不過幸虧大部分的時間我們解析Debug_Event結(jié)構(gòu)只需要解析到第二層就可以了。
一般情況下,我們最感興趣的DebugEventCode就是EXCEPTION_DEBUG_EVENT,而不巧的是,EXCEPTION_DEBUG_EVENT所對應(yīng)的EXCEPTION_DEBUG_INFO結(jié)構(gòu)也是所有union聯(lián)合域結(jié)構(gòu)中最復(fù)雜的一個,因此,有必要在此再對EXCEPTION_DEBUG_INFO這個二級數(shù)據(jù)結(jié)構(gòu)作進一步的詳細說明:
typedef struct _EXCEPTION_DEBUG_INFO {
EXCEPTION_RECORD ExceptionRecord;
DWORD dwFirstChance;
} EXCEPTION_DEBUG_INFO, *LPEXCEPTION_DEBUG_INFO;
? dwFirstChange:如果為0,表示是該異常從前未被處理過,也就是當(dāng)前我們的Debugger程序處于Windows異常處理鏈條的頭部。
? ExceptionRecord:該結(jié)構(gòu)內(nèi)部的信息是EXCEPTION_DEBUG_INFO結(jié)構(gòu)的真實存儲地點。因此,繼續(xù)剖析該結(jié)構(gòu)。
typedef struct _EXCEPTION_RECORD {
DWORD ExceptionCode;
DWORD ExceptionFlags;
struct _EXCEPTION_RECORD *ExceptionRecord;
PVOID ExceptionAddress;
DWORD NumberParameters;
ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD, *PEXCEPTION_RECORD;
首先:要注意到,該結(jié)構(gòu)內(nèi)部有一個類型為EXCEPTION_RECORD*的數(shù)據(jù)成員,這是C語言里經(jīng)典的串聯(lián)數(shù)據(jù)的方式:用一個指針域連接各個數(shù)據(jù)成員,可以構(gòu)成經(jīng)典的“鏈表”數(shù)據(jù)結(jié)構(gòu)(英文里管這種做法叫做Chain)。
其次:ExceptionCode成員標(biāo)識了該結(jié)構(gòu)所代表的EXCEPTION_RECORD的類型,Windows內(nèi)部定義了如下20種異常行為:
值 含義
EXCEPTION_ACCESS_VIOLATION 存取越界
EXCEPTION_ARRAY_BOUNDS_EXCEEDED 由硬件監(jiān)測到的數(shù)組訪問越界
EXCEPTION_BREAKPOINT 觸發(fā)斷點
EXCEPTION_DATATYPE_MISALIGNMENT 數(shù)據(jù)未對齊
EXCEPTION_FLT_DENORMAL_OPERAND 浮點操作數(shù)范圍越界(太小或太大)
EXCEPTION_FLT_DIVIDE_BY_ZERO 浮點操作除數(shù)為0
EXCEPTION_FLT_INEXACT_RESULT 浮點運算結(jié)果不能用小數(shù)正常表示.
EXCEPTION_FLT_INVALID_OPERATION 其他未知的浮點數(shù)錯誤
EXCEPTION_FLT_OVERFLOW 浮點操作太大溢出
EXCEPTION_FLT_STACK_CHECK 浮點棧溢出.
EXCEPTION_FLT_UNDERFLOW 浮點操作太小溢出
EXCEPTION_ILLEGAL_INSTRUCTION 執(zhí)行非法指令
EXCEPTION_IN_PAGE_ERROR 存取未存在的內(nèi)存頁
EXCEPTION_INT_DIVIDE_BY_ZERO 除數(shù)為0
EXCEPTION_INT_OVERFLOW 整數(shù)操作,最高位溢出
EXCEPTION_INVALID_DISPOSITION 錯誤的異常處理程序地址
EXCEPTION_NONCONTINUABLE_EXCEPTION 遇上不能繼續(xù)執(zhí)行的異常
EXCEPTION_PRIV_INSTRUCTION 當(dāng)前模式下,不能執(zhí)行該指令
EXCEPTION_SINGLE_STEP 單步跟蹤斷點觸發(fā)
EXCEPTION_STACK_OVERFLOW 線程的棧空間溢出
一般而言,我們的Debugger程序處理得最多的異常情況就是EXCEPTION_BREAKPOINT和EXCEPTION_SINGLE_STEP。例如:我們需要在目標(biāo)進程運行到0x00400000地址的時候?qū)δ繕?biāo)進程進行一些操作,那么我們只要設(shè)法讓目標(biāo)程序運行到0x00400000的時候,向我們的Debugger程序發(fā)出異常信號,那么我們的Debugger程序就能收到該信號,并進而通過前面介紹的Set/GetThreadContext和Read/WriteProcessMemory函數(shù)對目標(biāo)進程進行控制操作。
一個最初步的Debugger框架
既然上面所述的內(nèi)容現(xiàn)在已經(jīng)足夠我們寫出一個最基本的Debugger了,那么我們就來“實戰(zhàn)演習(xí)”一番,先寫出一個最基本的Debugger程序,該程序完成的功能異常簡單:利用CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS作為標(biāo)志,創(chuàng)建一個進程,并讓目標(biāo)進程能夠照常地運作起來。
首先跳入我們腦海的代碼大概如下所示:
::CreateProcess (_T("Msg.exe"), NULL, NULL, NULL, NULL, DEBUG_PROCESS|DEBUG_ONLY_THIS_PROCESS,
NULL, NULL, &sif, &pi) ;
//這個下面就是大名鼎鼎的Debug框架!
do
{
::WaitForDebugEvent (&DBEvent, INFINITE) ;
dwState = DBG_EXCEPTION_NOT_HANDLED ;
case EXIT_PROCESS_DEBUG_EVENT :
{
STOP = TRUE ;
break ;
}
if (!STOP)
{
::ContinueDebugEvent(pi.dwProcessId, pi.dwThreadId, dwState) ;
}
} while (!STOP) ;
編譯,雙擊,很不幸!Windows給了我們
為什么會出現(xiàn)上面這種錯誤情況?微軟的文檔里面并沒有對Debugger具體應(yīng)該如何工作做出詳細的說明。不過通過跟蹤上面的程序,我們發(fā)現(xiàn)每次Msg.exe目標(biāo)應(yīng)用程序啟動前,都會向我們的Debugger程序發(fā)送一個EXCEPTION_BREAKPOINT的斷點信號,而我們的Debug Loop并沒有對該信號進行處理。現(xiàn)在我們加入對該信號的處理過程,使我們的Debugger不返回DBG_EXCEPTION_NOT_HANDLED而返回DBG_CONTINUE,具體到代碼中,就是在:
case EXIT_PROCESS_DEBUG_EVENT :
這一句之前,加入:
case EXCEPTION_DEBUG_EVENT:
{
switch (DBEvent.u.Exception.ExceptionRecord.ExceptionCode)
{
case EXCEPTION_BREAKPOINT:
{
dwState = DBG_CONTINUE ;
break ;
}
}
break ;
}
編譯、鏈接,試運行,一切OK。Msg.exe很正常地彈出了MessageBox,
從C到C++
從上面那個最簡單的調(diào)試框架代碼不難看出,每當(dāng)我們要增加一個處理判斷的異常類型,都要在日益龐大復(fù)雜的switch – case代碼中增加新的選擇路線,程序的代碼很容易就變得臃腫不堪且難以維護。而實際上整個代碼的工作骨架并沒有發(fā)生實質(zhì)性的改變,變化的僅僅是針對各種不同的異常情況我們所需要的不同處理子過程——這不禁讓我們想起了Template設(shè)計模式——我們可以將在Debug Loop中分解Debug_Event的代碼放進框架代碼中,然后在框架代碼中調(diào)用不同的hook虛函數(shù),這樣,我們要擴展自己的Debugger功能時,只需要從已有的debug_base基類繼承,重寫hook虛方法即可。
隨本文附帶的Debug_Base.h中有debug_base類,該類就實現(xiàn)了如上所述的Debugger Template模板。該類最簡單的使用如下所示:
Debugger::debug_base debugger ;
debugger.run_debug_loop(std::tstring(TEXT("Msg.exe"))) ;
以上這兩段代碼,就實現(xiàn)了上一節(jié)所提到的那個最簡單的調(diào)試器所完成的功能——載入被調(diào)試程序并且照常運行。
Debug_base提供有如下幾個hook虛函數(shù):
hook函數(shù)名 函數(shù)作用
handle_first_exception Windows內(nèi)核首次發(fā)送EXCEPTION_BREAKPOINT時的處理子過程
handle_exception_breakpoint 調(diào)試過程中EXCEPTION_BREAKPOINT的處理子過程
handle_single_step 調(diào)試過程中EXCEPTION_SINGLE_STEP的處理子過程
handle_process_create 進程創(chuàng)建處理子過程
handle_process_exit 進程退出處理子過程
handle_thread_create 線程創(chuàng)建處理子過程
handle_thread_exit 線程推出處理子過程
handle_dll_load Dll載入時得到調(diào)用
handle_dll_unload Dll卸載的時候得到調(diào)用
handle_debug_wstring 被調(diào)試程序調(diào)用OutputDebugString得到調(diào)用
實際的編程過程中,我們只需要從debug_base派生出自己的新類,并重寫其中需要的虛函數(shù)即可編譯得到自己的Debugger!
例如:我們想讓我們的Debugger具有如下功能:
? 在程序入口點處中斷,提示已經(jīng)到達入口點
? 截獲被調(diào)試程序的Debug字符串,并輸出
那么我們可以這樣設(shè)計我們的新類:
class MyDebugger : public Debugger::debug_base
{
virtual void handle_first_exception(const PROCESS_INFORMATION& pi)
{
::MessageBox(NULL, TEXT("首次中斷"), TEXT("Debugger Worked"), NULL) ;
return ;
}
virtual void handle_debug_wstring(std::wstring& debug_wstring)
{
::MessageBox(NULL, ATL::CW2T(debug_wstring.c_str()), TEXT("Debug String"), NULL) ;
return ;
}
} ;
而主程序并不需要做任何改變,該Debugger運行后,可以接受被調(diào)試程序的調(diào)試信息,并輸出:
結(jié)束語
Windows借助于Debug API,為我們提供了一個功能全面的Ring 3級別的調(diào)試平臺,但是由于文檔資料、示例代碼的缺乏,迄今為止,Debug API的應(yīng)用面還很窄。其實如果能真正把握住動態(tài)調(diào)試的精髓,利用Debug API打造一個自己的專有調(diào)試器調(diào)試一些很特殊的程序是能收到非常好的效果的。可以說,任何一款脫殼機或者是內(nèi)存注冊機都只不過是Debug API的一個具體應(yīng)用。
當(dāng)然,由于篇幅所限,本文也不可能更加詳盡深入地探索Windows Debugger的世界,我們的debug_base類還很初步——甚至還不具備在程序運行時增加調(diào)試斷點的功能,不過只要理解了Debugger的工作原理,結(jié)合MicroSoft和Intel的一些底層的編程資料,各種功能都可以慢慢添加、完善。希望讀者朋友在學(xué)習(xí)逆向工程的過程中,注意基礎(chǔ)知識的積累、整理、消化,不斷地提升自己對計算機系統(tǒng)的理解水平,最終才能修成一代宗師,打造出made in china的Soft-ice和OllyDbg!