Friday, February 27, 2009

Nt Insider : 常用的Windbg技巧(1)

相關知識: Windows知識, Windbg指令, Live debug, x86架構

原文 : Debugging Techniques: Take One...Give One

技巧1 : 當應用程式出問題時,對RtlNtstatusToDosError設中斷點

應用程式產生錯誤的時候,有幾個可能性

  1. 這是應用程式內部功能的bug
  2. 應用程式(直接/間接)使用Win32的API,得到了一個Error
  3. 應用程式(直接/間接)使用Win32的API,雖然沒有發生Error,但是拿到的不是預期的結果

     本文要討論的技巧適用於狀況2.當應用程式直接或間接的使用了Win32 API並且得到了一個Error,NTDLL.dll的RtlNtstatusToDosError會被呼叫,此函式負責把Kernel回傳的NTSTATUS code (ex. 0xC0000005)轉換成相對應的System error code.所以我們可以對RtlNtstatusToDosError設置一個中斷點,以便錯誤發生時可以偵錯.

這裡舉一個例子,我們用notepad去儲存一個read-only的檔案.先看本文的dump:

   1: 0: kd> !process 0 0
   2: **** NT ACTIVE PROCESS DUMP ****
   3: --
   4: PROCESS 819645d0  SessionId: 0  Cid: 0250    Peb: 7ffdf000  ParentCid: 01ac
   5:     DirBase: 02080200  ObjectTable: e10f3200  HandleCount:  90.
   6:     Image: notepad.exe
   7:  0: kd> .process /r /p 819645d0
   8: Implicit process is now 819645d0
   9: .cache forcedecodeuser done
  10: Loading User Symbols

     上面首先列出目前系統下所有的process(!process 0 0),然後切換context到notepad之下(.process /r /p 819645d0).接著要來下中斷點摟.

   1: 0: kd> bp ntdll!rtlntstatustodoserror "dd @esp+4 l1; k; g"
   2: 0: kd> g
    這裡對ntdll的RtlNtstatusToDoserror(大小寫不用分)下中斷,之後雙引號代表的是遇到中斷點之後所執行的指令,指令間以分號;隔開.中斷之後總共要跑三個指令
  1. dd @esp+4 l1, l1是輸出格式,不重要.dd address的意思是display double word(印出32bits). 那為什麼要印出@esp+4這個位置呢?因為中斷點下在function進入點,此時尚未push ebp,所以esp指向return address,@取出位置以後,@esp+4這個地方存放的是就是第一個parameter,也就是NTSTATUS code.
  2. k印出stack/堆疊/棧
  3. g繼續執行,換言之debugger印出上述資訊以後,繼續執行

依照原文,當使用notepad去存一個read-only的檔案時,會發現Windbg 輸出下面的stack.

   1: 0007fa38  c0000022
   2: ChildEBP RetAddr  
   3: 0007fa30 7c80937a ntdll!RtlNtStatusToDosError
   4: 0007fa40 7c81109a kernel32!BaseSetLastNTError+0xf
   5: 0007fab0 01004ede kernel32!CreateFileW+0x390
   6: 0007faec 01002c2f NOTEPAD!SaveFile+0x30
   7: 0007fd6c 01003927 NOTEPAD!NPCommand+0xa8
   8: 0007fd90 77d48734 NOTEPAD!NPWndProc+0x4fe
   9: 0007fdbc 77d48816 USER32!InternalCallWinProc+0x28
  10: 0007fe24 77d4b4c0 USER32!UserCallWinProcCheckWow+0x150
  11: 0007fe78 77d4b50c USER32!DispatchClientMessage+0xa3
  12: 0007fea0 7c90eae3 USER32!__fnDWORD+0x24
  13: 0007fea0 80500a60 ntdll!KiUserCallbackDispatcher+0x13
  14: f7ee3b04 805a0771 nt!KiCallUserMode+0x4
  15: f7ee3b60 bf813e47 nt!KeUserModeCallback+0x87
  16: f7ee3be4 bf813fdd win32k!SfnDWORD+0xa8
  17: f7ee3c2c bf8141cf win32k!xxxSendMessageToClient+0x176
  18: f7ee3c78 bf80f5b8 win32k!xxxSendMessageTimeout+0x1a6
  19: f7ee3c9c bf8e14ac win32k!xxxSendMessage+0x1b
  20: f7ee3ce4 bf8e12a5 win32k!xxxTranslateAccelerator+0x264
  21: f7ee3d50 8054060c win32k!NtUserTranslateAccelerator+0x85
  22: f7ee3d50 7c90eb94 nt!KiFastCallEntry+0xfc


以下是幾個重點

  1. 第一行是dd @esp+4 l1的結果, l1是輸出格式,不重要.
  2. 由第一行可知NTSTATUS code = C0000022.
  3. 第二行以下可知執行的堆疊(過程)

     但是如果真的依照原文下去做,會發現一個令人困擾的事情.就是印出的資訊實在是太多了.一來error可能會帶著好幾個error,一瞬間就可以印上個幾頁.二來就算使用者都沒碰鍵盤跟滑鼠,系統卻不是靜止的,底下的暗潮洶湧也是會讓debugger忙著輸出資訊.難道系統無時不刻在出錯嗎?答案是即使NTSTATUS code = 0 (success),RtlNtstatusToDosError也是有可能會被呼叫.

     所以底下介紹另外一個Windbg的技巧:conditional break(條件中斷點).假設我們想在以下條件滿足的時候中斷執行

  1. 執行到NTDLL.dll的RtlNtstatusToDoserror
  2. 函式的第一個參數(NTSTATUS code)小於0,(請參考NTSTATUS的格式,當最高位的兩個bit為1時代表錯誤發生)

先來看看完整的條件中斷指令:

bp ntdll!rtlntstatustodoserror ".if(poi(@esp+4)<0){dd @esp+4 l1;k}.else{g}"

     前半段bp ntdll!rtlntstatustodoserror安置了一個中斷點在函式入口.後面用括弧包起來的指令就是中斷時要執行的指令

.if(poi(@esp+4)<0){dd @esp+4 l1;k}.else{g}

     這裡就是條件中斷點的精華. .if(中斷的條件){中斷時執行之指令}.else{g} g代表繼續執行.中斷的條件是poi(@esp+4)<0,小於0以及@esp+4先前已經解釋過.poi(address)代表取出該位置的值,也就是NTSTATUS code.這樣一來,就可以在真正產生錯誤的時候,印出需要的資訊(錯誤碼以及堆疊內容)然後中斷執行.
(待續)

No comments: