程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> 匯編語言 >> 匯編教程:Win32調試API(1)

匯編教程:Win32調試API(1)

編輯:匯編語言

在本教程中,我們將學習Win32提供給開發者的用於調試的原語. 在教程的結尾,我們將學習如何調試一個進程.

理論:

Win32有一些供程序員使用的API,它們提供相當於調試器的功能. 他們被稱作Win32調試API(或原語).利用這些API,我們可以:

加載一個程序或捆綁到一個正在運行的程序上以供調試

獲得被調試的程序的低層信息,例如進程ID,進入地址,映像基址等.

當發生與調試有關的事件時被通知,例如進程/線程的開始/結束, DLL的加載/釋放等.

修改被調試的進程或線程

簡而言之,我們可以用這些API寫一個簡單的調試器.由於這個題目有些過大,我把它分為幾部分,而本教程就是它的第一部分.在本教程中,我將講解一些基本概念及Win32調試API的大致框架.

使用Win32調試API的步驟如下:

創建一個進程或捆綁到一個運行中的進程上. 這是使用Win32調試API的第一步.由於我們的程序要扮演調試器的角色,我們要找一個供調試的程序.一個被調試的程序被稱為debuggee.可以通過以下兩種方式獲得debuggee:

通過CreateProcess創建debuggee進程.為了創建被調試的進程,必須指定DEBUG_PROCESS標志.這一標志告訴Windows我們要調試該進程. 當debuggee中發生重要的與調試有關的事件(調試事件)時,Windows 會向我們的程序發送通知.debuggee會立即掛起以等待我們的程序准備好.如果debuggee還創建了子進程,Windows還會為每個子進程中的調試事件向我們的程序發送通知.這一特性通常是不必要的.我們可以通過指定DEBUG_ONLY_THIS_PROCESS與 DEBUG_PROCESS的組合標志來禁止它.

我們也可以用 DebugActiveProcess標志捆綁到一個運行中的進程上.

等待調試事件. 在獲得了一個debuggee進程後,debuggee的主線程被掛起,這種狀況將持續到我們的程序調用WaitForDebugEvent為止.這個函數和其他的WaitForXXX函數相似,比如說,它阻塞調用線程直到等待的事件發生.對這個函數來說, 它等待由Windows發送的調試事件.下面是它的定義:

WaitForDebugEvent proto lpDebugEvent:DWORD, dwMilliseconds:DWORD

lpDebugEvent is the address of a DEBUG_EVENT這個結構將被填入關於debuggee中發生的調試事件的信息.

dwMilliseconds 該函數等待調試事件的時間,以毫秒為單位.如果這段時間沒有調試事件發生, WaitForDebugEvent返回調用者.另一方面,如果將該參數指定為 INFINITE 常數,函數將一直等待直到調試事件發生.

現在我們看一下DEBUG_EVENT 結構.


DEBUG_EVENT STRUCT
dwDebugEventCode dd ?
dwProcessId dd ?
dwThreadId dd ?
u DEBUGSTRUCT <>
DEBUG_EVENT ENDS

dwDebugEventCode 該值指定了等待發生的調試事件的類型.因為有很多種類型的事件發生,我們的程序要檢查該值,知道要發生事件的類型並做出響應. 該值可能的取值如下:

取值 含義

CREATE_PROCESS_DEBUG_EVENT 進程被創建.當debuggee進程剛被創建(還未運行) 或我們的程序剛以DebugActiveProcess被捆綁到一個運行中的進程時事件發生. 這是我們的程序應該獲得的第一個事件.

EXIT_PROCESS_DEBUG_EVENT 進程退出.

CREATE_THEAD_DEBUG_EVENT 當一個新線程在deuggee進程中創建或我們的程序首次捆綁到運行中的進程時事件發生.要注意的是當debugge的主線程被創建時不會收到該通知.

EXIT_THREAD_DEBUG_EVENT debuggee中的線程退出時事件發生.debugee的主線程退出時不會收到該通知.我們可以認為debuggee的主線程與debugge進程是同義詞. 因此, 當我們的程序看到CREATE_PROCESS_DEBUG_EVENT標志時,對主線程來說,就是CREATE_THREAD_DEBUG_EVENT標志.

LOAD_DLL_DEBUG_EVENT debuggee裝入一個DLL.當PE裝載器第一次分解指向DLL的鏈接時,我們將收到這一事件. (當調用CreateProcess裝入 debuggee時)並且當debuggee調用LoadLibrary時也會發生.

UNLOAD_DLL_DEBUG_EVENT 一個DLL從debuggee中卸載時事件發生.

EXCEPTION_DEBUG_EVENT 在debuggee中發生異常時事件發生. 注意: 該事件僅在debuggee開始它的第一條指令之前發生一次.異常實際上是一個調試中斷(int 3h).如果想恢復debuggee事,以 DBG_CONTINUE 標志調用ContinueDebugEvent 函數. 不要使用DBG_EXCEPTION_NOT_HANDLED 標志否則debuggee會在NT下拒絕運行(Win98下運行得很好).

OUTPUT_DEBUG_STRING_EVENT 當debuggee調用DebugOutputString函數向我們的程序發送消息字符串時該事件發生.

RIP_EVENT 系統調試發生錯誤

dwProcessId 和dwThreadId發生調試事件的進程和線程Id.我們可以用這些值作為我們感興趣的進程或線程的標志符.記住如果我們使用CreateProcess來裝載debuggee,我們仍可在PROCESS_INFO結構中獲得debuggee的進程和線程.我們可以用這些值來區別調試事件是發生在debuggee中還是它的子進程中(當沒有指定 DEBUG_ONLY_THIS_PROCESS 標志時).

u 是一個聯合,包含了調試事件的更多信息.根據上面dwDebugEventCode的不同,它可以是以下結構:

dwDebugEventCode u的解釋

CREATE_PROCESS_DEBUG_EVENT 名為CreateProcessInfo的CREATE_PROCESS_DEBUG_INFO結構

EXIT_PROCESS_DEBUG_EVENT 名為ExitProcess的EXIT_PROCESS_DEBUG_INFO結構

CREATE_THREAD_DEBUG_EVENT 名為CreateThread的CREATE_THREAD_DEBUG_INFO結構

EXIT_THREAD_DEBUG_EVENT 名為ExitThread的EXIT_THREAD_DEBUG_EVENT 結構

LOAD_DLL_DEBUG_EVENT 名為LoadDll的LOAD_DLL_DEBUG_INFO 結構

UNLOAD_DLL_DEBUG_EVENT 名為UnloadDll的UNLOAD_DLL_DEBUG_INFO結構

EXCEPTION_DEBUG_EVENT 名為Exception的EXCEPTION_DEBUG_INFO結構
  OUTPUT_DEBUG_STRING_EVENT 名為DebugString的OUTPUT_DEBUG_STRING_INFO 結構

RIP_EVENT 名為RipInfo的RIP_INFO 結構

我不會在這一個教程裡講所有這些結構的細節,這裡只詳細講一下CREATE_PROCESS_DEBUG_INFO 結構.

假設我們的程序調用了WaitForDebugEvent函數並返回,我們要做的第一件事就是檢查dwDebugEventCode中的值來看debuggee進程中發生了那種類型的調試事件.比如說,如果dwDebugEventCode的值為 CREATE_PROCESS_DEBUG_EVENT,就可認為u的成員為CreateProcessInfo 並用u.CreateProcessInfo來訪問.

在我們的程序中做對調試事件的響應. 當WaitForDebugEvent 返回時,這意味著在debuggee進程中發生了調試事件或者發生了超時.所以我們的程序要檢查dwDebugEventCode 來作出適當的反應.這裡有些象處理Windows消息:由用戶來選擇和忽略消息.

繼續運行debuggee. 當調試事件發生時, Windows掛起了debuggee,所以當我們處理完調試事件,還要讓debuggee繼續運行.調用ContinueDebugEvent 函數來完成這一過程.

ContinueDebugEvent proto dwProcessId:DWORD, dwThreadId:DWORD, dwContinueStatus:DWORD

該函數恢復由於調試事件而掛起的線程.

dwProcessId和dwThreadId是要恢復的線程的進程ID和線程ID,通常這兩個值從 DEBUG_EVENT結構的dwProcessId 和dwThreadId成員獲得.

dwContinueStatus顯示了如何繼續報告調試事件的線程.可能的取值有兩個: DBG_CONTINUE 和DBG_EXCEPTION_NOT_HANDLED. 對大多數調試事件,這兩個值都一樣:恢復線程.唯一的例外是EXCEPTION_DEBUG_EVENT,如果線程報告發生了一個異常調試事件,這意味著在debuggee的線程中發生了一個異常.如果指定了DBG_CONTINUE,線程將忽略它自己的異常處理部分並繼續執行.在這種情況下,我們的程序必須在以DBG_CONTINUE恢復線程之前檢查並處理異常,否則異常將生生不息地不斷發生....如果我們指定了 DBG_EXCEPTION_NOT_HANDLED值,就是告訴Windows我們的程序並不處理異常:Windows將使用debuggee的默認異常處理函數來處理異常.

總而言之,如果我們的程序沒有考慮異常,而調試事件又指向debuggee進程中的一個異常的話,就應調用含DBG_CONTINUE標志的ContinueDebugEvent函數.否則,我們的程序就必須以DBG_EXCEPTION_NOT_HANDLED調用 ContinueDebugEvent.但在下面這種情況下必須使用DBG_CONTINUE標志:第一個在ExceptionCode成員中有值EXCEPTION_BREAKPOINT的 EXCEPTION_DEBUG_EVENT事件.當debuggee開始執行它的第一條指令時,我們的函數將接受到異常調試事件.它事實上是一個調試中斷(int 3h).如果我們以DBG_EXCEPTION_NOT_HANDLED調用ContinueDebugEvent 來響應調試事件, Windows NT會拒絕執行debuggee(因為它沒有異常處理).所以在這種情況下,要用DBG_CONTINUE標志告訴Windows我們希望該線程繼續執行.

繼續上面的步驟循環直到debuggee進程退出. 我們的程序必須在一個很象消息循環的無限循環中直到debuggee結束.該循環大體如下:


.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

就是說,當開始調試程序時,我們的程序不能和debuggee分開直到它結束.

我們再來總結一下這些步驟:

創建一個進程或捆綁我們的程序到運行中的進程上.

等待調試事件

響應調試事件.

繼續執行debuggee.

繼續這一無盡循環直到debuggee進程結束

例子:

這個例子調試一個win32程序並顯示諸如進程句柄,進程Id,映象基址等.


.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
  分析:

程序首先填充OPENFILENAME結構,調用GetOpenFileName讓用戶選擇要調試的程序.


invoke GetStartupInfo,addr startinfo
invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, NULL, NULL, addr startinfo, addr pi

當接收用戶選擇後,調用CreateProcess裝載程序.並調用GetStartupInfo以默認值填充STARTUPINFO結構.注意我們將DEBUG_PROCESS標志與DEBUG_ONLY_THIS_PROCESS標志組合來僅調試這個程序,不包括子進程.


.while TRUE
invoke WaitForDebugEvent, addr DBEvent, INFINITE

在debuggee被裝入後,我們調用WaitForDebugEvent進入無盡的調試循環,WaitForDebugEvent在debuggee中發生調試事件時返回,因為我們指定了INFINITE作為第二個參數.當調試事件發生時, WaitForDebugEvent 返回並填充DBEvent結構.本文來自編程入門網


.if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
invoke MessageBox, 0, addr ExitProc, addr AppName, MB_OK+MB_ICONINFORMATION
.break

我們要先檢查dwDebugEventCode的值, 如果是EXIT_PROCESS_DEBUG_EVENT,用一個消息框顯示"The debuggee exits" 並退出調試循環.


.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
  如果dwDebugEventCode 的值為CREATE_PROCESS_DEBUG_EVENT,我們就在消息框中顯示一些感興趣的底層信息.這些信息從u.CreateProcessInfo獲得. CreateProcessInfo是一個CREATE_PROCESS_DEBUG_INFO類型的結構體.你可以查閱Win32 API獲得它的更多信息e.


.elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
.if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT
invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE
.continue
.endif

如果dwDebugEventCode 的值為EXCEPTION_DEBUG_EVENT,我們就要更進一步檢查異常類型.它是一大堆的結構嵌套,但我們可以從ExceptionCode成員獲得異常類型.如果ExceptionCode的值為 EXCEPTION_BREAKPOINT並且是第一次發生(或者我們已知道deuggee中沒有int 3h指令),我們可以安全地假定在debuggee要執行第一條指令時發生這一異常.在我們完成這些處理後,就可以用 DBG_CONTINUE調用ContinueDebugEvent來繼續執行debuggee.接著我們繼續等待下一個調試事件的發生.


.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

如果dwDebugEventCode 的值為CREATE_THREAD_DEBUG_EVENT或EXIT_THREAD_DEBUG_EVENT, 我們的程序顯示一個消息框.


invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
.endw


  除了上面討論過的 EXCEPTION_DEBUG_EVENT,用DBG_EXCEPTION_NOT_HANDLED標志調用ContinueDebugEvent函數恢復debuggee的執行.


invoke CloseHandle,pi.hProcess
invoke CloseHandle,pi.hThread

當debuggee結束時,我們就跳出了調試循環,這時要關閉 debuggee的線程和進程句柄.關閉這些句柄並不意味著要關閉這些進程和線程.只是說不再用這些句柄罷了.

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved