1. 程式人生 > 實用技巧 >DebugView原理及應用

DebugView原理及應用

  Windows 下編寫核心驅動時經常用到 DbgPrint 函式輸出一些除錯資訊,用來輔助除錯。當正在用 WinDbg 核心除錯時,除錯資訊會輸出到 WinDbg 中。或者利用一些輔助工具也能看到輸出的除錯資訊,比如 Sysinternals公司的 DebugView 工具。本文分析了 Vista 系統上 DbgPrint 系列函式的執行流程,並揭示了 DebugView 工具的實現原理。

DbgPrint函式流程

先看一下 WDK中 DbgPrint 函式的原型:

ULONG

DbgPrint (

IN PCHAR Format, ...

); 

和 printf 的引數一樣,可以格式化字串。

.text:0049E123; ULONG DbgPrint(PCH Format,...)

.text:0049E123 public_DbgPrint

.text:0049E123 _DbgPrint proc near ; CODE XREF:sub_4046B2+11↑p.text:0049E123

.text:0049E123Format =dword ptr 8

.text:0049E123arglist =byte ptr 0Ch

.text:0049E123

.text:0049E123 mov edi, edi

.text:0049E125 push ebp

.text:0049E126 mov ebp, esp

.text:0049E128 push TRUE

.text:0049E12A lea eax, [ebp+arglist]

.text:0049E12D push eax

.text:0049E12E push [ebp+Format]

.text:0049E131 mov ecx, offset ??_C@_00CNPNBAHC@?$AA@FNODOBFM@

.text:0049E136 push 3 ;DPFLTR_INFO_LEVEL = 3

.text:0049E138 push 65h ;DPFLTR_DEFAULT_ID = 101 = 0x65

.text:0049E13A call vDbgPrintExWithPrefixInternal(x,x,x,x,x,x)

.text:0049E13F pop ebp

.text:0049E140 retn

.text:0049E140_DbgPrint endp

從反彙編程式碼來看,DbgPrint函式很簡單,傳遞引數直接呼叫 vDbgPrintExWithPrefixInternal函式。傳遞的 ComponentId為 DPFLTR_DEFAULT_ID,Level 為 DPFLTR_INFO_LEVEL。檢視 DbgPrintEx 函式的文件可以知道這兩個引數的意義。

.text:0046EBF4__stdcall vDbgPrintExWithPrefixInternal(x, x, x, x, x, x) proc near

.text:0046EBF4 ;CODE XREF: _DbgPrintEx+19↑p .text:0046EC11

.text:0046EC17 push [ebp+ulLevel]

.text:0046EC1A push [ebp+ulComponentId]

.text:0046EC1D call NtQueryDebugFilterState(x,x)

.text:0046EC22 test eax, eax

.text:0046EC24 jnz short loc_46EC2D

.text:0046EC26

.text:0046EC26loc_46EC26:

.text:0046EC26 xor eax, eax

.text:0046EC28 jmp _exit

.text:0046EBA8__stdcall NtQueryDebugFilterState(x, x) proc near

.text:0046EBA8

.text:0046EBA8ulComponentId =dword ptr 8

.text:0046EBA8ulLevel =dword ptr 0Ch

.text:0046EBA8

.text:0046EBA8 mov edi, edi

.text:0046EBAA push ebp

.text:0046EBAB mov ebp, esp

.text:0046EBC0 mov ecx, [ebp+ulLevel]

.text:0046EBCC xor eax, eax

.text:0046EBCE inc eax

.text:0046EBCF shl eax, cl

.text:0046EBD1 test _Kd_WIN2000_Mask,eax

.text:0046EBD7 jnz short loc_46EBE8

.text:0046EBD9 mov ecx, _KdComponentTable[edx*4]

.text:0046EBE0 test [ecx], eax

.text:0046EBE2 jnz short loc_46EBE8

.text:0046EBE4 xor eax, eax

.text:0046EBE6 jmp short loc_46EBEB

vDbgPrintExWithPrefixInternal函式首先呼叫 NtQueryDebugFilterState函式檢查 ComponentId和 Level 值判斷當前輸

出是否需要遮蔽。DbgPrint 傳入的值分別是 65h 和 3, 65h 定為的 nt!Kd_DEFAULT_Mask的值和 3 被移位後的 8 比較,從而確定此次輸出是否需要遮蔽。所以 Vista 系統上用 WinDbg 核心除錯時預設看不到 DbgPrint 輸出的除錯字串,可

以用 ednt!Kd_DEFAULT_Mask 0x8 命令或者修改登錄檔開啟 DbgPrint除錯輸出。

.text:0046EC5D push [ebp+ntStatus2]; cbDest

.text:0046EC63 push [ebp+pszDest] ; pszDest

.text:0046EC69 mov ecx, 512 .text:0046EC6E sub ecx, esi

.text:0046EC70 lea edi, [ebp+esi+szBuffer]

.text:0046EC77 call RtlStringCbVPrintfA(x,x,x,x)

.text:0046ECB2 lea ecx, [ebp+szBuffer]

.text:0046ECB8 mov [ebp+asBuffer.Buffer], ecx

.text:0046ECBE mov [ebp+asBuffer.Length], ax

.text:0046ECC5 cmp _KeBugCheckActive,0

.text:0046ECCC jnz short loc_46ED09

.text:0046ECCC

.text:0046ECCE mov esi, _RtlpDebugPrintCallback

.text:0046ECD4 test esi, esi

.text:0046ECD6 jz short loc_46ED09

.text:0046ECD6

.text:0046ECD8 call ds:KeGetCurrentIrql()

.text:0046ECDE mov bl, al

.text:0046ECE0 cmp bl, PROFILE_LEVEL

.text:0046ECE3 jnb short loc_46ECED

.text:0046ECE3

.text:0046ECE5 mov cl, PROFILE_LEVEL

.text:0046ECE7 call ds:KfRaiseIrql(x)

.text:0046ECED

.text:0046ECED loc_46ECED:

.text:0046ECED push [ebp+ulLevel]

.text:0046ECF0 push [ebp+ulComponentId]

.text:0046ECF3 lea eax, [ebp+asBuffer]

.text:0046ECF9 push eax .text:0046ECFA call esi

; DbgPrintCallback(PANSI_STRING pasBuffer, ULONG ulComponentId, ULONG ulLevel);

.text:0046ECFC cmp bl, PROFILE_LEVEL

.text:0046ECFF jnb short loc_46ED09

.text:0046ECFF

.text:0046ED01 mov cl, bl

.text:0046ED03 call ds:KfLowerIrql(x)

如果此次輸出不需要遮蔽,vDbgPrintExWithPrefixInternal繼續執行。呼叫 RtlStringCbVPrintfA函式格式化字串,再判斷是否正在藍屏過程中,然後提高 IRQL呼叫輸出回撥函式。除錯輸出回撥是 Vista的新增功能,XP 中沒有見到。

NTSTATUS DbgSetDebugPrintCallback(PDBGPRINT_CALLBACEpDbgCallback, BOOLEAN bSet)函式用來設定和取消回撥函式,只能設定一個函式,函式地址儲存在RtlpDebugPrintCallback 內部變數中。回撥函式返回後降低 IRQL。

.text:0046ED09 movzx eax, [ebp+asBuffer.Length]

.text:0046ED10 mov [ebp+pszDest],eax

.text:0046ED16 mov eax, [ebp+asBuffer.Buffer]

.text:0046ED1C mov [ebp+var_234],eax

.text:0046ED22 push edi

.text:0046ED23 push ebx

.text:0046ED24 mov eax, 1 ; eax =BREAKPOINT_PRINT

.text:0046ED29 mov ecx, [ebp+var_234];ecx = pszBuffer

.text:0046ED2F mov edx, [ebp+pszDest];edx = ulBufLength

.text:0046ED35 mov ebx, [ebp+ulComponentId]

.text:0046ED38 mov edi, [ebp+ulLevel]

.text:0046ED3B int 2Dh ; Internalroutine for MSDOS (IRET)

.text:0046ED3D int 3 ; Trap toDebugger

vDbgPrintExWithPrefixInternal函式接著執行,通過 int2d 呼叫除錯服務把字串輸出到偵錯程式。int2d 的服務例程為

KiDebugService 函式。

.text:0044737C_KiDebugService: ;DATA XREF: INIT:0070A35C↓o

.text:004473EA inc dword ptr [ebp+68h]

.text:004473EA inc dword ptr [ebp+68h];[_KTRAP_FRAME].Eip

.text:004473ED mov eax, [ebp+44h] ; [_KTRAP_FRAME].Eax

.text:004473F0 mov ecx, [ebp+40h] ; [_KTRAP_FRAME].Ecx

.text:004473F3 mov edx, [ebp+3Ch] ; [_KTRAP_FRAME].Edx .text:004473F6 jmp loc_447527

.text:00447527

.text:00447527 loc_447527: ;CODE XREF: .text:004473F6↑j .text:00447527

.text:00447543 mov esi, ecx

.text:00447545 mov edi, edx

.text:00447547 mov edx, eax

.text:00447549 mov ebx, [ebp+68h];[_KTRAP_FRAME].Eip

.text:0044754C dec ebx

.text:0044754D mov ecx, 3

.text:00447552 mov eax, STATUS_BREAKPOINT

.text:00447557 call CommonDispatchException

.text:00446D70CommonDispatchException proc near

.text:00446D70

.text:00446D70stExceptionRecord= EXCEPTION_RECORD ptr-50h

.text:00446D70

.text:00446D70 sub esp, 50h

.text:00446D73 mov [esp+50h+stExceptionRecord.ExceptionCode],eax

.text:00446D76 xor eax, eax

.text:00446D78 mov [esp+50h+stExceptionRecord.ExceptionFlags],eax

.text:00446D7C mov [esp+50h+stExceptionRecord.ExceptionRecord],eax

.text:00446D80 mov [esp+50h+stExceptionRecord.ExceptionAddress],ebx

.text:00446D84 mov [esp+50h+stExceptionRecord.NumberParameters],ecx

.text:00446D88 cmp ecx, 0

.text:00446D8B jz short loc_446D99

.text:00446D8D lea ebx,

[esp+50h+stExceptionRecord.ExceptionInformation]

.text:00446D91 mov [ebx], edx

.text:00446D93 mov [ebx+4], esi.text:00446D96 mov [ebx+8], edi

.text:00446D99 .text:00446D99

.text:00446DA8 mov eax, [ebp+6Ch]; [_KTRAP_FRAME].SegCs.text:00446DAB

.text:00446DAB loc_446DAB: ; CODE XREF: CommonDispatchException+36↑j .text:00446DAB and eax, 1

.text:00446DAE push 1 ; char

.text:00446DB0 push eax ; int

.text:00446DB1 push ebp ; BugCheckParameter3

.text:00446DB2 push 0 ; int

.text:00446DB4 push ecx ; void *

.text:00446DB5 call KiDispatchException(x,x,x,x,x)

KiDebugService 先構建一個陷阱幀( KTRAP_FRAME ),然後設定引數呼叫 CommonDispatchException, CommonDispatchException會構建一個異常紀錄(EXCEPTION_RECORD),然後呼叫 KiDispatchException 函式走異常處理流程,異常程式碼為 STATUS_BREAKPOINT。從 int2d‐>KiDebugService‐>CommonDispatchException‐>KiDispatchException這個流程一路看下來,會發現 int2d時提供的三個引數存放在異常紀錄的 ExceptionInformation數組裡面,分別是:

ExceptionInformation[0]表示BREAKPOINT_PRINT功能號。

ExceptionInformation[1]表示除錯資訊字串地址。

ExceptionInformation[2]表示除錯資訊字串長度。

進入 KiDispatchException後的程式碼比較複雜,當前只是分析 DbgPrint的流程,其他程式碼暫時不管,只需要知道 KiDispatchException會呼叫 KiDebugRoutine把異常提交給核心除錯引擎處理。當處於核心除錯時,KiDebugRoutine指向

KdpTrap 函式,沒有除錯時,KiDebugRoutine 指向 KdpStub 函式。先來看看 KdpStub 函式。

.text:0042A2DC __stdcall KdpStub(x, x, x, x, x, x) proc near

.text:0042A2DC

.text:0042A2DC TrapFrame = dword ptr 8

.text:0042A2DC ExceptionFrame = dword ptr 0Ch

.text:0042A2DC ExceptionRecord = dword ptr 10h

.text:0042A2DC ContextRecord = dword ptr 14h .text:0042A2DC PreviousMode = dword ptr 18h

.text:0042A2DC bSecondChance = dword ptr 1Ch

.text:0042A2DC

.text:0042A2DC mov edi, edi

.text:0042A2DE push ebp

.text:0042A2DF mov ebp, esp

.text:0042A2E1 push ebx

.text:0042A2E2 push esi

.text:0042A2E2

.text:0042A2E3 mov esi, [ebp+ExceptionRecord]

.text:0042A2E6 xor ebx, ebx

.text:0042A2E8 cmp [esi+EXCEPTION_RECORD.ExceptionCode],

STATUS_BREAKPOINT

.text:0042A2EE jnz short _elseif

.text:0042A2EE

.text:0042A2F0 cmp [esi+EXCEPTION_RECORD.NumberParameters], ebx

.text:0042A2F3 jbe short _elseif

.text:0042A2F3

.text:0042A2F5 mov eax, [esi+EXCEPTION_RECORD.ExceptionInformation]

.text:0042A2F8 cmp eax, BREAKPOINT_LOAD_SYMBOLS

.text:0042A2FB jz short loc_42A30C

.text:0042A2FD cmp eax, BREAKPOINT_UNLOAD_SYMBOLS .text:0042A300 jz short loc_42A30C

.text:0042A302 cmp eax, BREAKPOINT_COMMAND_STRING

.text:0042A305 jz short loc_42A30C

.text:0042A307 cmp eax, BREAKPOINT_PRINT

.text:0042A30A jnz short _elseif

.text:0042A30C

.text:0042A30C mov eax, [ebp+ContextRecord]

.text:0042A30F inc [eax+CONTEXT._Eip]

.text:0042A315 mov al, 1 ; return TRUE;

.text:0042A317 jmp short _exit

KdpStub 先判斷異常程式碼是不是 STATUS_BREAKPOINT(int3 斷點異常也是這個異常程式碼,但第一個引數是BREAKPOINT_BREAK),然後判斷引數個數。對於當前支援的四種除錯服務,包括輸出除錯字串,都是把 eip 加一,跳過 int2d 後面帶的 int3 指令,然後從異常處理中返回,繼續執行。

當正在除錯時,KiDispatchException呼叫的就是 KdpTrap 函式。

PAGEKD:006AB5EB__stdcall KdpTrap(x, x, x, x, x, x) proc near

PAGEKD:006AB6A1loc_6AB6A1: ;CODE XREF: KdpTrap(x,x,x,x,x,x)+3A↑j

PAGEKD:006AB6A1 mov edx, [ebx+CONTEXT._Ebx]

PAGEKD:006AB6A7 lea ecx, [ebp+bReturn]

PAGEKD:006AB6AA push ecx

PAGEKD:006AB6AB push [ebp+ExceptionFrame]

PAGEKD:006AB6AE movzx ecx, word ptr [eax+1Ch];ExceptionInformation[2] PAGEKD:006AB6B2 push [ebp+TrapFrame]

PAGEKD:006AB6B5 push dword ptr [ebp+PreviousMode]

PAGEKD:006AB6B8 push ecx

PAGEKD:006AB6B9 push dword ptr [eax+18h];ExceptionInformation[1]

PAGEKD:006AB6BC mov ecx, [ebx+CONTEXT._Edi]

PAGEKD:006AB6C2 call KdpPrint(x,x,x,x,x,x,x,x)

KdpTrap 也會和 KdpStub 一樣判斷異常程式碼和引數個數,以及除錯服務號,根據除錯服務號的不同調用不同的處理函式。針對 BREAKPOINT_PRINT輸出除錯資訊的情況,呼叫的是 KdpPrint函式。

KdpPrint 也會根據 ComponentId 和 Level 值判斷一下是否需要遮蔽此次輸出。然後判斷特權模式,如果是使用者模式還需要探測字串記憶體,保證可讀。

PAGEKD:006AC921 mov [ebp+asBuffer.Buffer],edi

PAGEKD:006AC924 mov [ebp+asBuffer.Length],bx

PAGEKD:006AC928 lea eax, [ebp+asBuffer]

PAGEKD:006AC92B push eax

PAGEKD:006AC92C call KdpLogDbgPrint(x)

PAGEKD:006AC931 cmp _KdDebuggerNotPresent,0

PAGEKD:006AC938 jnz short loc_6AC984

PAGEKD:006AC93A push [ebp+ExceptionFrame]PAGEKD:006AC93D push [ebp+TrapFrame]

PAGEKD:006AC940 call KdEnterDebugger(x,x)

PAGEKD:006AC945 mov [ebp-20h],al

PAGEKD:006AC948 lea eax, [ebp+asBuffer]

PAGEKD:006AC94B call KdpPrintString(x)

KdpPrint 接著呼叫 KdpLogDbgPrint 在一個迴圈緩衝區裡記錄除錯字串,然後判斷是否掛接了偵錯程式,呼叫

KdpPrintString 輸出除錯字串。

KdpPrintString 構造一個除錯包,通過 KdSendPacket函式傳送給偵錯程式。

DebugView實現原理

上一節詳細介紹了 DbgPrint輸出除錯字串的流程,現在來看看 DebugView工具的實現原理。 在 Vista 系統上,DebugView 設定了除錯輸出回撥函式,從而截獲除錯字串。

kd> dps nt!RtlpDebugPrintCallback L 1

818f41b8 00000000 kd> g

ModLoad: 919ef000 919f2d00 Dbgv.sys

kd> dps nt!RtlpDebugPrintCallback L 1

818f41b8 919efa86 Dbgv+0xa86

在 Vista 以前的系統,比如 2003系統上, DbgPrint 函式呼叫 vDbgPrintExWithPrefixInternal 函式,在 vDbgPrintExWithPrefixInternal 函式裡面不是直接 int2d 呼叫除錯服務,而是通過 DebugPrint 函式再呼叫除錯服務把字串輸出。DebugView 通過 Hook 函式 DebugPrint 截獲除錯字串。

kd> u nt!DebugPrint nt!DebugPrint:

808356b6 8bff mov edi,edi

808356b8 55 push ebp

808356b9 8bec mov ebp,esp

808356bb ff7510 push dword ptr [ebp+10h]

808356be 8b4508 mov eax,dword ptr [ebp+8]

808356c1 ff750c push dword ptr [ebp+0Ch]

808356c4 0fb708 movzx ecx,word ptr [eax]

808356c7 51 push ecx

808356c8 ff7004 push dword ptr [eax+4]

808356cb 6a01 push 1

808356cd e86f460100 call nt!DebugService (80849d41)

808356d2 5d pop ebp 808356d3 c20c00 ret 0Ch kd> g

ModLoad: f6a46000 f6a49d00 Dbgv.sys

kd> u nt!DebugPrint nt!DebugPrint:

808356b6 ff258c7da4f6 jmp dword ptr [Dbgv+0x1d8c (f6a47d8c)]

808356bc 7510 jne nt!DebugPrint+0x18 (808356ce)

808356be 8b4508 mov eax,dword ptr [ebp+8]

808356c1 ff750c push dword ptr [ebp+0Ch]

808356c4 0fb708 movzx ecx,word ptr [eax]

808356c7 51 push ecx

808356c8 ff7004 push dword ptr [eax+4]

808356cb 6a01 push 1 kd> dps 0xf6a47d8c L 1 f6a47d8c f6a469ac Dbgv+0x9ac

開發程式除錯時不可避免的,一起在資源裡面我放過一個“trace.h”的一個檔案,可以自動建立視窗顯示除錯資訊,但是這個程式在高速執行的時候,除錯介面容易崩潰。

當然,寫log日誌,只最常見的除錯方法。

vc可以debug方法,在F5除錯程式,邊執行,在輸出視窗可以變便是TRACE或除錯資訊。

這裡簡單講一下 DebugView,具體可以參考其幫助文件;

1:DebugView的獲取:http://technet.microsoft.com/en-us/sysinternals/bb842062.aspx;

2:DebugView作用:

  在debug版的exe直接執行的時候,DebugView.exe可以獲取除錯資訊;


  DebugView可以將顯示的資訊儲存問log檔案;


  還有其它的功能,可以檢視幫助文件;

3:DebugView無需安裝,直接執行就可以了;