程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> Win32調試接口設計與實現淺析

Win32調試接口設計與實現淺析

編輯:關於C++

所謂調試器實際上是一個很寬泛的概念,凡是能夠以某種形式監控其他程序執行過程的程序,都可以泛稱為調試器。在Windows平台上,根據調試器的實現原理大概可以將之分為三類:內核態調試器、用戶態調試器和偽代碼調試器。

內核態調試器直接工作在操作系統內核一級,在硬件與操作系統之間針對系統核心或驅動進行調試,常見的有SoftICE、WinDbg、WDEB386和i386KD等等;用戶態調試器則通過操作系統提供的調試接口,在操作系統和用戶態程序之間針對用戶態程序進行調試,常見的有各種開發環境如VC/Delphi自帶的調試器,OllyDbg等等;偽代碼調試器則使用目標系統自定義的調試接口,調試由用戶態程序支持的腳本語言或虛擬機代碼,常見的如JVM/CLR的調試工具、VB的pcode調試器、Active Script調試器等等。

因為偽代碼調試器跟具體系統實現相關性太強,不具備原理層面的通用性,本系列文章盡量不涉及其內容,以後如果有機會可以再討論一下JVM/CLR/Active Script提供的調試接口;用戶態調試器應用最廣泛,參考資料也較為完整,我會花較大精力和大家探討;核心態調試器則跟操作系統結合較為緊密,加上我也不是太熟悉,只能盡力而為了,呵呵。歡迎大家提出批評指正意見和建議 :)

此外強烈推薦John Robbins在MSDN的Bugslayer專欄,以及其所著的<Debugging Applications>一書(中文版《應用程序調試技術》),此書中對調試器從原理到應用都有很全面的講解。

[1] 用戶態調試器結構初探

用戶態調試器直接使用Win32 API提供的調試接口,遵循Win32的事件驅動的設計思想,其實現思路非常簡單,基本框架偽代碼如下:

以下為引用?lt;/B>
//啟動要調試的進程或掛接調試器到已運行的進程上
CreateProcess(..., DEBUG_PROCESS, ...) or DebugActiveProcess(dwProcessId)
DEBUG_EVENT de;
BOOL bContinue = TRUE;
DWORD dwContinueStatus;
while(bContinue)
{
bContinue = WaitForDebugEvent(&de, INFINITE);
switch(de.dwDebugEventCode)
{
...
default:
{
dwContinueStatus = DBG_CONTINUE;
break;
}
}
ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);
}

在調試器開始調試的時候,會啟動被調試程序的新進程或者掛接(attach)到一個已運行進程上,此時Win32系統會啟動調試接口的服務器端;然後調試器調用WaitForDebugEvent函數等待調試服務器端的調試事件被引發;調試器根據調試事件進行相應的處理;最後調用ContinueDebugEvent函數請求調試服務器繼續執行被調試進程,以等待並處理下一個調試事件。

首先我們大致看看調試接口的服務器端的實現思路:調試服務的服務器端接口實際上是存在於被調試進程的調試端口(Debug Port),此核心對象實現上跟Win32的完成端口類似,都是通過一個核心隊列實現的LPC端口。啟動調試服務器實際上就是掛接Win32的調試子系統到被調試進程,並在被調試進程內構造調試端口。調試器通過調試端口與Win32的調試子系統通訊;調試子系統響應系統操作所引發的調試事件,並通過調試端口將調試事件分發給核心態/用戶態調試器。

建立被調試程序的新進程時,需要在CreateProcess函數的dwCreationFlags參數設置DEBUG_ONLY_THIS_PROCESS或DEBUG_PROCESS標志位,以表示新建的進程需要被調試。CreateProcess函數的調用路徑如下

以下為引用:
CreateProcessA/CreateProcessW (kernel32.dll)
CreateProcessInternalW (kernel32.dll)
NtCreateProcessEx (ntoskrnl.dll)
PspCreateProcess (ntos\ps\create.c:969)

CreateProcessInternalW函數根據傳入的dwCreationFlags參數,決定是否要構造端口核心對象用於調試端口,並設置PEB的相應調試標志;PspCreateProcess會根據傳入參數的調試選項和端口對象句柄,選擇是否創建目標進程的調試端口;如果要創建則將傳入的端口句柄轉換成內核對象引用,保存在被調試程序進程的EPROCESS->DebugPort字段裡。

Win32 API提供的IsDebuggerPresent函數就是通過判斷CreateProcessInternalW函數在PEB中設置的標志位來判斷當前進程是否被調試的。IsDebuggerPresent函數偽代碼如下:

以下為引用:
BOOL IsDebuggerPresent(void)
{
return NtCurrentTeb()->ProcessEnvironmentBlock->BeingDebugged;
}

TEB和PEB的結構可在http://www.ntinternals.net上找到。

不過此種方法很容易被調試器直接修改PEB內存結構所欺騙,故而有另外一種直接通過檢查EPROCESS->DebugPort字段是否被使用,來判斷此進程是否正在被調試的方法。以前水木上也有過幾次討論,如blowfish的《檢測debugger的方法補遺》一文給出的代碼。Windows XP/2003開始由Win32 API提供的 CheckRemoteDebuggerPresent 函數也是使用相同的思路,通過調用 NtQueryInformationProcess 函數查詢調試端口實現的,偽代碼如下:

以下為引用:
BOOL CheckRemoteDebuggerPresent(HANDLE hProcess, PBOOL pbDebuggerPresent)
{
enum PROCESS_INFO_CLASS { ProcessDebugPort = 7 };
if(hProcess && pbDebuggerPresent)
{
HANDLE hPort;
*pbDebuggerPresent = NT_SUCCESS(NtQueryInformationProcess(hProcess, ProcessDebugPort, &hPort, sizeof(hPort), NULL)) ? TRUE : FALSE;
return *pbDebuggerPresent;
}
return FALSE;
}

與直接創建被調試程序的新進程不同,調試已啟動進程的 DebugActiveProcess 函數首先連接到Win32 系統調試服務器的端口上,然後激活當前正在運行的被調試進程的調試端口。DebugActiveProcess的偽代碼如下:

以下為引用:
BOOL DebugActiveProcess(DWORD dwProcessId)
{
if(DbgUiConnectToDbg())
{
HANDLE hProcess = ProcessIdToHandle(dwProcessId);
if(hProcess)
{
DbgUiDebugActiveProcess(hProcess);
NtClose(hProcess);
}
}
return FALSE;
}

DbgUiConnectToDbg函數(ntos\dll\dlluistb.c:27)嘗試連接核心提供的調試子系統端口(名為"\DbgUiApiPort"),如果成功連接會獲得一個端口對象(保存在DbgUiApiPort NtCurrentTeb()->DbgSsReserved[1]),和一個調試狀態轉換的信號燈句柄(保存在DbgStateChangeSemaphore NtCurrentTeb()->DbgSsReserved[0])用於等待調試事件。偽代碼如下:

以下為引用:
#define DbgStateChangeSemaphore (NtCurrentTeb()->DbgSsReserved[0])
#define DbgUiApiPort (NtCurrentTeb()->DbgSsReserved[1])
NTSTATUS DbgUiConnectToDbg( VOID )
{
NTSTATUS st = NtConnectPort(&DbgUiApiPort, L"\\DbgUiApiPort", ..., &DbgStateChangeSemaphore);
if(NT_SUCCESS(st))
{
NtRegisterThreadTerminatePort(DbgUiApiPort);
}
else
{
DbgUiApiPort = NULL;
}
return st;
}

如果連接調試子系統成功,則調用NtRegisterThreadTerminatePort函數(ntos\ps\psdelete.c:1202)將調試端口加入到當前線程控制塊的終止端口列表(ETHREAD->TerminationPortList)中。在線程結束的之前,會激活此列表中的端口,給調試器一個清理的機會。

DbgUiDebugActiveProcess函數完成具體的激活被調試進程的調試服務器的功能。偽代碼如下:

以下為引用:
#define DbgUiApiPort (NtCurrentTeb()->DbgSsReserved[1])
void DbgUiDebugActiveProcess(HANDLE hProcess)
{
return NtDebugActiveProcess(DbgUiApiPort) &&
DbgUiIssueRemoteBreakin(hProcess) &&
DbgUiStopDebugging(hProcess);
}

至於這幾個函數的具體實現,等後面章節詳細分析Win32調試子系統時再詳細講解,呵呵

在被調試進程啟動了調試支持後,調試器調用WaitForDebugEvent函數等待調試事件的發生。此函數實際上是對DbgUiWaitStateChange函數(ntos\dll\dlluistb.c:93)的一個簡單包裝,通過等待DbgUiConnectToDbg函數獲得的調試事件信號燈來完成實際功能。如果成功獲得調試事件,還會通過NtRequestWaitReplyPort函數(ntos\lpc\lpcsend.c:717)向調試服務器通報DbgUiWaitStateChangeApi消息。

在處理完調試事件後,調試器調用的ContinueDebugEvent函數是DbgUiContinue函數的一個簡單包裝,也是使用NtRequestWaitReplyPort函數向調試服務器通報DbgUiContinueApi消息。

在完成調試功能後,WinXP/2003還提供了DebugActiveProcessStop函數停止調試。偽代碼如下:

以下為引用:
BOOL DebugActiveProcessStop(DWORD dwProcessId)
{
HANDLE hProcess = ProcessIdToHandle(dwProcessId);
if(hProcess)
{
CloseAllProcessHandles(dwProcessId);
DbgUiStopDebugging(hProcess);
if(NtClose(hProcess))
return TRUE;
}
return FALSE;
}

DbgUiStopDebugging函數(ntdll.dll)調用ZwRemoveProcessDebug函數(ntoskrnl.exe)關閉指定進程的調試端口,實現上是傳入端口句柄和進程句柄,調用0xC7號系統服務完成最終功能。這個暫時就不深入討論了,就此打住 :P

在了解這些後,對用戶態調試器的實現應該就有了一個框架性的了解:其結構就是一個基於事件的模型,然後通過向調試子系統請求調試事件並完成具體操作。

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