Win32 提供一組完整的除錯原語,讓開發者能撰寫自己的除錯器。本系列第一篇將系統性介紹 Win32 除錯 API 的核心概念、事件模型、基本流程,並實作一個可顯示程序基礎資訊的簡易除錯器。
簡言之:這些 API 提供了打造除錯器的所有基礎塊。
CreateProcess(..., DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS, ...):建立新程序並除錯。DEBUG_ONLY_THIS_PROCESS 可避免同時除錯子程序。DebugActiveProcess(dwProcessId):附著至已執行中的程序。WaitForDebugEvent(&DebugEvent, INFINITE) 阻塞等待。事件資訊填入 DEBUG_EVENT 結構。dwDebugEventCode 判斷事件類型,依 u 聯合體的對應成員取得詳細資訊。ContinueDebugEvent(dwProcessId, dwThreadId, dwContinueStatus)。dwContinueStatus 通常為 DBG_CONTINUE(忽略例外)或 DBG_EXCEPTION_NOT_HANDLED(交由目標自身例外處理機制)。EXIT_PROCESS_DEBUG_EVENT。DEBUG_EVENT STRUCT
dwDebugEventCode dd ? ; 事件類型
dwProcessId dd ? ; 事件所屬程序 ID
dwThreadId dd ? ; 事件所屬執行緒 ID
u DEBUGSTRUCT <> ; 聯合體,依事件類型解釋
DEBUG_EVENT ENDS
| 事件代碼 | 意義 | 對應聯合體成員 |
|---|---|---|
| CREATE_PROCESS_DEBUG_EVENT | 程序建立(第一個事件) | u.CreateProcessInfo (CREATE_PROCESS_DEBUG_INFO) |
| EXIT_PROCESS_DEBUG_EVENT | 程序結束 | u.ExitProcess (EXIT_PROCESS_DEBUG_INFO) |
| CREATE_THREAD_DEBUG_EVENT | 執行緒建立 | u.CreateThread (CREATE_THREAD_DEBUG_INFO) |
| EXIT_THREAD_DEBUG_EVENT | 執行緒結束 | u.ExitThread (EXIT_THREAD_DEBUG_INFO) |
| LOAD_DLL_DEBUG_EVENT | DLL 載入 | u.LoadDll (LOAD_DLL_DEBUG_INFO) |
| UNLOAD_DLL_DEBUG_EVENT | DLL 卸載 | u.UnloadDll (UNLOAD_DLL_DEBUG_INFO) |
| EXCEPTION_DEBUG_EVENT | 例外發生 | u.Exception (EXCEPTION_DEBUG_INFO) |
| OUTPUT_DEBUG_STRING_EVENT | 目標呼叫 OutputDebugString | u.DebugString (OUTPUT_DEBUG_STRING_INFO) |
| RIP_EVENT | 系統除錯錯誤 | u.RipInfo (RIP_INFO) |
此結構包含:hFile(檔案控制代碼)、hProcess、hThread、lpBaseOfImage(映像基址)、lpStartAddress(入口點位址)等關鍵資訊,是除錯器啟動時最重要的資料來源。
ContinueDebugEvent proto dwProcessId:DWORD, dwThreadId:DWORD, dwContinueStatus:DWORD
EXCEPTION_BREAKPOINT(程式入口點前的 INT 3),否則 Windows NT 會拒絕讓目標執行。.while TRUE
invoke WaitForDebugEvent, addr DebugEvent, INFINITE
.break .if DebugEvent.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT
; 此處處理各類事件...
invoke ContinueDebugEvent, DebugEvent.dwProcessId, DebugEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
.endw
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib
includelib \masm32\lib\user32.lib
.data
AppName db "Win32 Debug Example no.1",0
ofn OPENFILENAME <>
FilterString db "Executable Files",0,"*.exe",0
db "All Files",0,"*.*",0,0
ExitProc db "The debuggee exits",0
NewThread db "A new thread is created",0
EndThread db "A thread is destroyed",0
ProcessInfo db "File Handle: %lx ",0dh,0Ah
db "Process Handle: %lx",0Dh,0Ah
db "Thread Handle: %lx",0Dh,0Ah
db "Image Base: %lx",0Dh,0Ah
db "Start Address: %lx",0
.data?
buffer db 512 dup(?)
startinfo STARTUPINFO <>
pi PROCESS_INFORMATION <>
DBEvent DEBUG_EVENT <>
.code
start:
; 開啟檔案對話框選擇目標程式
mov ofn.lStructSize, sizeof ofn
mov ofn.lpstrFilter, offset FilterString
mov ofn.lpstrFile, offset buffer
mov ofn.nMaxFile, 512
mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
invoke GetStartupInfo, addr startinfo
invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, \
DEBUG_PROCESS + DEBUG_ONLY_THIS_PROCESS, NULL, NULL, \
addr startinfo, addr pi
.while TRUE
invoke WaitForDebugEvent, addr DBEvent, INFINITE
.if DBEvent.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT
invoke MessageBox, 0, addr ExitProc, addr AppName, MB_OK+MB_ICONINFORMATION
.break
.elseif DBEvent.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT
invoke wsprintf, addr buffer, addr ProcessInfo, \
DBEvent.u.CreateProcessInfo.hFile, \
DBEvent.u.CreateProcessInfo.hProcess, \
DBEvent.u.CreateProcessInfo.hThread, \
DBEvent.u.CreateProcessInfo.lpBaseOfImage, \
DBEvent.u.CreateProcessInfo.lpStartAddress
invoke MessageBox, 0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION
.elseif DBEvent.dwDebugEventCode == EXCEPTION_DEBUG_EVENT
.if DBEvent.u.Exception.pExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT
invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE
.continue
.endif
.elseif DBEvent.dwDebugEventCode == CREATE_THREAD_DEBUG_EVENT
invoke MessageBox, 0, addr NewThread, addr AppName, MB_OK+MB_ICONINFORMATION
.elseif DBEvent.dwDebugEventCode == EXIT_THREAD_DEBUG_EVENT
invoke MessageBox, 0, addr EndThread, addr AppName, MB_OK+MB_ICONINFORMATION
.endif
invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
.endw
invoke CloseHandle, pi.hProcess
invoke CloseHandle, pi.hThread
.endif
invoke ExitProcess, 0
end start
GetOpenFileName 讓使用者選目標 EXE,CreateProcess 攜帶 DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS 建立除錯目標。WaitForDebugEvent 以 INFINITE 參數無限期等待事件。u.CreateProcessInfo 取得檔案/程序/執行緒控制代碼、映像基址、入口點,格式化顯示。EXCEPTION_BREAKPOINT 時以 DBG_CONTINUE 讓程式跨過 INT 3 開始真正執行。DBG_EXCEPTION_NOT_HANDLED 交回目標自行處理。Win32 除錯 API 的核心是一個「等待事件 → 處理 → 繼續」的無限迴圈。掌握 DEBUG_EVENT 結構、各事件類型對應的資料成員、以及 ContinueDebugEvent 的兩種狀態碼用法,即可建構出功能完整的除錯器骨架。下一篇將進入「修改目標程序」的進階主題:記憶體讀寫與執行緒上下文操作。