DbgPrint 函數流程分析

DbgPrint 函數流程分析

前言

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

 

========== 小喂

2007‐12‐01

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章