上一章講解了如何用調試API來開啟一個被偵錯工具,並給出了一個簡單的例子,這一章將祥細講解調試訊息包含的內容. 類似於訊息處理中的訊息結構TMessage一樣,調試事件也有自己特定的事件結構,那就是TDebugEvent, TDebugEvent在Delphi中的定義為: TDebugEvent = _DEBUG_EVENT; _DEBUG_EVENT = record dwDebugEventCode: DWORD; dwProcessId: DWORD; dwThreadId: DWORD; case Integer of 0: (Exception: TExceptionDebugInfo); 1: (CreateThread: TCreateThreadDebugInfo); 2: (CreateProcessInfo: TCreateProcessDebugInfo); 3: (ExitThread: TExitThreadDebugInfo); 4: (ExitProcess: TExitThreadDebugInfo); 5: (LoadDll: TLoadDLLDebugInfo); 6: (UnloadDll: TUnloadDLLDebugInfo); 7: (DebugString: TOutputDebugStringInfo); 8: (RipInfo: TRIPInfo); end; 這個結構很複雜,包含了三個基本類型和一個等位型別的資料.dwProcessId和dwThreadId指明了產生調試事件的進程和線程的ID,dwDebugEventCode指明了產生了何種調試事件,可能的取值如下表(摘自<<Win32組譯工具設計>>): 1.CREATE_PROCESS_DEBUG_EVENT:進程被建立.當被調試進程剛被建立(還未運行) 或我們的程式剛以DebugActiveProcess捆綁到一個運行中的進程時該事件發生. 這是我們的程式應該獲得的第一個事件. 2.EXIT_PROCESS_DEBUG_EVENT:被調試進程退出時產生此訊息. 3.CREATE_THEAD_DEBUG_EVENT:當一個新線程在被調試進程中建立或我們的程式首次捆綁到運行中的進程時該事件發生.要注意的是當被調試進程的主線程被建立時不會收到該通知. 4.EXIT_THREAD_DEBUG_EVENT:被調試進程中的線程退出時事件發生.被調試進程的主線程退出時不會收到該通知.我們可以認為被調試進程的主線程與被調試進程是同義字. 因此, 當我們的程式看到CREATE_PROCESS_DEBUG_EVENT標誌時,對主線程來說,就是CREATE_THREAD_DEBUG_EVENT標誌. 5.LOAD_DLL_DEBUG_EVENT:被調試進程裝入一個DLL.當PE裝載器第一次分解指向DLL的連結時,我們將收到這一事件. (當調用CreateProcess裝入 被調試進程時)並且當被調試進程調用LoadLibrary時也會發生. 6.UNLOAD_DLL_DEBUG_EVENT:一個DLL從被調試進程中卸載時此事件發生. 7.EXCEPTION_DEBUG_EVENT:在被調試進程中發生異常時事件發生.異常實際上是一個調試中斷(int 3h).如果想恢複被調試進程事,以 DBG_CONTINUE 標誌調用ContinueDebugEvent 函數. 不要使用DBG_EXCEPTION_NOT_HANDLED 標誌否則被調試進程會在NT下拒絕運行(Win98下運行得很好). 8.OUTPUT_DEBUG_STRING_EVENT:當被調試進程調用DebugOutputString函數向我們的程式發送訊息字串時該事件發生. 9.RIP_EVENT:系統調試發生錯誤. 根據dwDebugEventCode的不同,應調用聯合中相應的結構來獲得相關的調試資訊.例如我們有個名為Debug的TDebguEvent的結構,在調用WaitForDebugEvent(Debug, INFINITE)後接收到調試資訊時,並且dwDebugEventCode的值為CREATE_PROCESS_DEBUG_EVENT,我們就可以通過仿問Debug.CreateProcessInfo.hProcess來獲得剛建立的被調試進程的進程控制代碼. 下面將祥細講解TDebugEvent結構中可能包括的每個結構的含義.因為沒有相關的祥細資料,大部分結果是靠測試所得,如有錯漏敬請指正. 一.CreateProcessInfo結構: 對應的調試訊息CREATE_PROCESS_DEBUG_EVENT.CreateProcessInfo.hFile:被調試進程的EXE檔案被映射到記憶體中的記憶體檔案對應控制代碼,可以通過開啟這個控制代碼(用OpenFileMapping和MapViewOfFile)來讀取此EXE檔案的相關資訊.如引入引出表等.CreateProcessInfo.hProcess:被調試進程的進程控制代碼,如果要使用ReadProcessMemory和WriteProcessMemory等函數來修改被調試進程,就需要用到這個控制代碼,可以用一個變數儲存起來供以後使用.CreateProcessInfo.hThread:主線程控制代碼.CreateProcessInfo.lpBaseOfImage:可執行檔被裝載到虛似地址空間中的基址.CreateProcessInfo.dwDebugInfoFileOffset:調試資訊在可執行檔中的位移地址(一般為0,即沒有調試資訊).CreateProcessInfo.nDebugInfoSize:調試資訊的長度.CreateProcessInfo.lpThreadLocalBase:主線程基址.CreateProcessInfo.lpStartAddress:主線程的線程函數地址.CreateProcessInfo.lpImageName:檔案映像名,注意這是一個RVA地址(相對虛擬位址).CreateProcessInfo.fUnicode:如果此值大於0,則lpImageName指向的檔案名稱為UNICODE碼. 二.ExitProcess結構: 對應的調試訊息EXIT_PROCESS_DEBUG_EVENT.ExitProcess.dwExitCode:即被偵錯工具調用ExitProcess函數時傳入的結束代碼. 三.CreateThread結構: 對應的調試訊息CREATE_THEAD_DEBUG_EVENT.CreateThread.hThread:建立線程的控制代碼.線程控制代碼,如果以後會涉及到對線程的操作,如掛起線程等,則可以用一個TList來儲存進程ID(TDebugEvent.dwThreadId)和相對應的控制代碼.再在其它調試事件發生時,根據dwThreadId得到線程控制代碼.CreateThread.lpThreadLocalBase:新線程的基址.CreateThread.lpStartAddress:新線程的線程函數地址. 四.ExitThread結構: 對應的調試訊息EXIT_THREAD_DEBUG_EVENT.ExitThread.dwExitCode:即退出的線程調用ExitThread函數時傳入的結束代碼. 五.LoadDll結構: 對應的調試訊息LOAD_DLL_DEBUG_EVENT.LoadDll.hFile:被載入的DLL檔案對應到記憶體中的記憶體檔案對應控制代碼,可以通過開啟這個控制代碼來讀取此DLL檔案的相關資訊.LoadDll.lpBaseOfDll:DLL檔案被裝載到虛似地址空間中的基址.這個地址加上DLL檔案引出的函數的地址,就是這個函數在記憶體中的地址.LoadDll.dwDebugInfoFileOffset:調試資訊在DLL檔案中的位移地址.LoadDll.nDebugInfoSize:調試資訊的長度.LoadDll.lpImageName:DLL檔案名稱的地址,是一個RVA地.LoadDll.fUnicode:如果此值大於0,則lpImageName指向的檔案名稱為UNICODE碼. 六.UnLoadDll結構: 對應的調試訊息UNLOAD_DLL_DEBUG_EVENT.UnloadDll.lpBaseOfDll:卸載的DLL檔案的基址,可以通過在處理LOAD_DLL_DEBUG_EVENT訊息中儲存DLL資訊和對應的基址的方法,來得到卸載的DLL資訊. 七.Exception結構: 對應的調試訊息EXCEPTION_DEBUG_EVENT.Exception.ExceptionRecord: 這是一個TExceptionRecord結構,裡麵包含了被偵錯工具產生的中斷或異常的代碼,產生的中斷或異常的地址等資訊. 八.DebugString結構: 對應的調試訊息OUTPUT_DEBUG_STRING_EVENT.DebugString.lpDebugStringData:被調試進程調用DebugOutputString函數發送的訊息字串的地址.DebugString.nDebugStringLength:被調試進程調用DebugOutputString函數發送的訊息字串的長度.DebugString.fUnicode:如果此值大於0,則訊息字串為UNICODE碼. 九.RipInfo結構: 對應的調試訊息RIP_EVENT.RipInfo.dwError:錯誤碼.RipInfo.dwType:錯誤類型. 瞭解了以上的知識,我們就可以在調試器中監視這些調試訊息,並獲得我們感興趣的資訊.但這僅僅實現了對被調試偵錯工具的監視.下一章將講解如何修改被偵錯工具. 附:一個監視目標程式啟動,載入DLL,退出的例子,並示範了如何讀取RVA地址,獲得載入的DLL檔案名稱的方法.請到下面的地址下載.
http://qxccccc.8u8.com/debug.rar