Windows內核調試器原理淺析(三)
——
NTSTATUS DebugService(
ULONG ServiceClass,
PVOID Arg1,
PVOID Arg2
)
{
NTSTATUS Status;
__asm {
mov eax, ServiceClass
mov ecx, Arg1
mov edx, Arg2
int 0x2d
int 0x3
mov Status, eax
}
return Status;
}
ServiceClass可以是BEAKPOINT_PRINT(0x1)、BREAKPOINT_PROMPT(0x2)、BREAKPOINT_LOAD_SYMBOLS(0x3)、BREAKPOINT_UNLOAD_SYMBOLS(0x4)。為什么后面要跟個int 0x3,M$的說法是為了和int 0x3共享代碼(我沒弄明白啥意思-_-),因為int 0x2d的陷阱處理程序是做些處理后跳到int 0x3的陷阱處理程序中繼續(xù)處理。但事實上對這個int 0x3指令并沒有任何處理,僅僅是把Eip加1跳過它。所以這個int 0x3可以換成任何字節(jié)。
int 0x2d和int 0x3生成的異常記錄結(EXCEPTION_RECORD)ExceptionRecord.ExceptionCode都是STATUS_BREAKPOINT(0x80000003),不同是int 0x2d產生的異常的ExceptionRecord.NumberParameters>0且ExceptionRecord.ExceptionInformation對應相應的ServiceClass比如BREAKPOINT_PRINT等。事實上,在內核調試器被掛接后,處理DbgPrint等發(fā)送字符給內核調試器不再是通過int 0x2d陷阱服務,而是直接發(fā)包。用M$的話說,這樣更安全,因為不用調用KdEnterDebugger和KdExitDebugger。
最后說一下被調試系統(tǒng)和內核調試器之間的通信。被調試系統(tǒng)和內核調試器之間通過串口發(fā)數據包進行通信,Com1的IO端口地址為0x3f8,Com2的IO端口地址為0x2f8。在被調試系統(tǒng)準備要向內核調試器發(fā)包之前先會調用KdEnterDebugger暫停其它處理器的運行并獲取Com端口自旋鎖(當然,這都是對多處理器而言的),并設置端口標志為保存狀態(tài)。發(fā)包結束后調用KdExitDebugger恢復。每個包就象網絡上的數據包一樣,包含包頭和具體內容。包頭的格式如下:
typedef struct _KD_PACKET {
ULONG PacketLeader;
USHORT PacketType;
USHORT ByteCount;
ULONG PacketId;
ULONG Checksum;
} KD_PACKET, *PKD_PACKET;
PacketLeader是四個相同字節(jié)的標識符標識發(fā)來的包,一般的包是0x30303030,控制包是0x69696969,中斷被調試系統(tǒng)的包是0x62626262。每次讀一個字節(jié),連續(xù)讀4次來識別出包。中斷系統(tǒng)的包很特殊,包里數據只有0x62626262。包標識符后是包的大小、類型、包ID、檢測碼等,包頭后面就是跟具體的數據。這點和網絡上傳輸的包很相似。還有一些相似的地方比如每發(fā)一個包給調試器都會收到一個ACK答復包,以確定調試器是否收到。若收到的是一個RESEND包或者很長時間沒收到回應,則會再發(fā)一次。對于向調試器發(fā)送輸出字符串、報告SYMBOL情況等的包都是一接收到ACK包就立刻返回,系統(tǒng)恢復執(zhí)行,系統(tǒng)的表現就是會卡那么短短一下。只有報告狀態(tài)的包才會等待內核調試器的每個控制包并完成對應功能,直到發(fā)來的包包含繼續(xù)執(zhí)行的命令為止。無論發(fā)包還是收包,都會在包的末尾加一個0xaa,表示結束。
現在我們用幾個例子來看看調試流程。
記得我以前問過jiurl為什么WinDBG的單步那么慢(相對softICE),他居然說沒覺得慢?*$&$^$^(&(&(我ft。。?,F在可以理解為什么WinDBG的單步和從操作系統(tǒng)正常執(zhí)行中斷下來為什么那么慢了。單步慢是因為每單步一次除了必要的處理外,還得從串行收發(fā)包,怎么能不慢。中斷系統(tǒng)慢是因為只有等到時鐘中斷發(fā)生執(zhí)行到KeUpdateSystemTime后被調試系統(tǒng)才會接受來自WinDBG的中斷包?,F在我們研究一下為什么在KiDispatchException里不能下斷點卻可以用單步跟蹤KiDispatchException的原因。如果在KiDispatchException中某處下了斷點,執(zhí)行到斷點時系統(tǒng)發(fā)生異常又重新回到KiDispatchException處,再執(zhí)行到int 0x3,如此往復造成了死循環(huán),無法不能恢復原來被斷點int 0x3所修改的代碼。但對于int 0x1,因為它的引起是因為EFLAG寄存中TF位被置位,并且每次都自動被復位,所以系統(tǒng)可以被繼續(xù)執(zhí)行而不會死循環(huán)?,F在我們知道了內部機制,我們就可以調用KdXXX函數實現一個類似WinDBG之類的內核調試器,甚至可以替換KiDebugRoutine(KdpTrap)為自己的函數來自己實現一個功能更強大的調試器,呵呵。
評論