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