相關知識: Windows知識, Windbg指令, Live debug, x86架構
原文 : Debugging Techniques: Take One...Give One
技巧1 : 當應用程式出問題時,對RtlNtstatusToDosError設中斷點
應用程式產生錯誤的時候,有幾個可能性
- 這是應用程式內部功能的bug
- 應用程式(直接/間接)使用Win32的API,得到了一個Error
- 應用程式(直接/間接)使用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(大小寫不用分)下中斷,之後雙引號代表的是遇到中斷點之後所執行的指令,指令間以分號;隔開.中斷之後總共要跑三個指令
- dd @esp+4 l1, l1是輸出格式,不重要.dd address的意思是display double word(印出32bits). 那為什麼要印出@esp+4這個位置呢?因為中斷點下在function進入點,此時尚未push ebp,所以esp指向return address,@取出位置以後,@esp+4這個地方存放的是就是第一個parameter,也就是NTSTATUS code.
- k印出stack/堆疊/棧
- 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
以下是幾個重點
- 第一行是dd @esp+4 l1的結果, l1是輸出格式,不重要.
- 由第一行可知NTSTATUS code = C0000022.
- 第二行以下可知執行的堆疊(過程)
但是如果真的依照原文下去做,會發現一個令人困擾的事情.就是印出的資訊實在是太多了.一來error可能會帶著好幾個error,一瞬間就可以印上個幾頁.二來就算使用者都沒碰鍵盤跟滑鼠,系統卻不是靜止的,底下的暗潮洶湧也是會讓debugger忙著輸出資訊.難道系統無時不刻在出錯嗎?答案是即使NTSTATUS code = 0 (success),RtlNtstatusToDosError也是有可能會被呼叫.
所以底下介紹另外一個Windbg的技巧:conditional break(條件中斷點).假設我們想在以下條件滿足的時候中斷執行
- 執行到NTDLL.dll的RtlNtstatusToDoserror
- 函式的第一個參數(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:
Post a Comment