WINDOWS調試工具很強大,但是學習使用它們並不容易。特別對於驅動開發者使用的WinDbg和KD這兩個內核調試器(CDB和NTSD是用戶態調試器)。
本教程的目標是給予一個已經有其他調試工具使用經驗的開發者足夠信息,使其能通過參考WINDOWS調試工具的幫助文件進行內核調試。
本文將假定開發者熟悉一般WINDOWS操作系統和進程的建立過程。
本文的重點是集成內核模式和用戶態模式的圖形化調試器WinDbg。KD在腳本和自動化調試中更有用,並且在資深程序員中擁有一定地位,但是本教程將集中討論WinDbg,
只會偶爾提到KD。
本文討論的是WindowsNT 4.0,Windows2000或以后的版本,而且目標電腦的處理器基於X86架構。對於64位平台,將不會特別提及。
總之,本教程由簡單介紹調試器的安裝開始,大體分成2部分,基礎知識和選擇技術。基礎知識包括基本調試命令和常用調試命令。選擇技術是其他命令和在很多情況下都
有用的調查方法。后者並不是調查象deadlocks, memory corruption或者resource leaks的唯一方法。第一次閱讀本教程,你可能會跳過選擇技術。你可以停止閱讀本教程
而轉向微軟調試器討論組,也可以通過調試器的反饋e-mai解決更多的問題。
取得最新版的調試器,並且有規律的更新它。這里並沒有誇大最新版的價值,因為調試器會經常改進和修復錯誤。你將能在下面網址下載:
http://www.microsoft.com/whdc/devtools/debugging/default.mspx.
調試器有使用null-modemcable 或者1394cable連接兩台電腦的安裝方案 。本教程不分析單操作系統的本地調試(即在調試器運行的電腦上進行分析)。
3台電腦(目標電腦,調試服務器,調試客戶端)的調試將會被簡要的討論。
在主機調試軟件(WinDbg或者KD)和目標操作系統之間,是一個協同處理的調試過程。每一部分都必須做些什么。更明確地,WinDbg 不是作為一個“管理操作系統”,
象客戶和一個真正操作系統那樣運行目標。WinDbg是一個調試軟件,象目標操作系統的合作伙伴那樣知道它在調試過程中的角色。在這種關系中,WinDbg從目標接收信息,
並且向目標發送信息。這是一種有效的通信機制。
serialprotocol是調試器與目標系統之間可靠的通信機制。你能通過null-modem cable使用COM端口連接主機和目標機器。另一個可供選擇的通信機制是1394。
在調試工具的幫助文件中的“Configuring Software on the Target Computer.” 主題有關於它們的描述。
假設你的主機使用WIN2K或以上的版本。主機的操作系統可以不同於目標電腦的操作系統。主機可以在你平常進行開發,維護或者故障診斷的地方。它應該與網絡連接,
如果你希望訪問symbol和source服務器(請看symbols和source)。
從命令提示窗口中,改變當前的目錄到WINDOWS調試工具的安裝目錄。這是windbg.exe 和kd.exe 所在的位置。輸入windbg,按下Enter。你將會看到:
在這里,你能重排你的窗口。下面的例子包括可移動的窗口。打開組合窗口並移到屏幕上方,單擊“Command”標題欄並拖動它的窗口離開主框架。然后收縮主框架,
你可以使用鍵擊代替直接使用菜單或者按鈕。
然后使用FileàKernel Debug 以得到一個協議窗口,選擇1394和channel1。到這里,你的桌面會象下圖一樣:
在KernelDebugging窗口中,點OK。
現在你已經准備好在主機和目標之間建立連接。在目標機器以其中一個調試入口啟動WINDOWS。立即回到主機系統,用鼠標激活WinDbg 的命令窗口,
按下CTRL+BREAK 。不久之后,你會看到:
現在不必擔心關於symbols的信息。你已經將WinDbg連接到WIN 2003。你現在很忙!
你需要明白一件細小卻至關重要的事:在命令窗口的底部顯示“kd>”提示符。這代表WinDbg 已經准別好接受命令。如果沒有提示符顯示,這時WinDbg將不能處理命令,
盡管你輸入的任何命令都將會被保存在緩沖區域並盡可能快的運行。你必須等待“kd>”出現,以確定WinDbg已經作好響應的准備。
因為有時它正在忙於做某些你看不見的事(例如從目標取得信息,該信息可能很龐大)。缺少“kd>”是WinDbg處於繁忙狀態的唯一線索。
另一個可能是WinDbg試圖解析symbol並且時間超過了你的預期。不幸地,WinDbg偶爾會等待一個永遠不會響應的目標連接(可能boot.ini配置得不好,或者選擇了錯誤的選項)。在等待足夠時間之后,你必須決定采取激烈的措施例如按下 CTRL+BREAK,或者停止WinDbg重新開始。
現在你很可能渴望開始調試,但仍然有一些東西你必須去做,因為它們將會很好的改善你的調試體驗。
首先確認WinDbg能找到你感興趣模塊的symbols。Symbols指出一個二進制命令與聲明之間的聯系和什么變量正在被轉移。換句話說,就是Symbols表。
如果你在建立模塊的地方,那么你將擁有有效的symbols和source文件。但是如果你需要單步調試其他很早以前建立代碼呢?或者,在那種情況下,
如果你的代碼不在它被建立的地方呢?
明確的設置symbols所在的地方,使用.sympath命令。在命令窗口中中斷(CTRL-BREAK)然后輸入:
.sympathSRV*<DownstreamStore>*http://msdl.microsoft.com/download/symbols
以便告訴WinDbg在Microsoft公開的symbols服務器上查找symbols。讓WinDbg使用該服務以及在本地保存一份已下載的symbols。例如,在D:\DebugSymbols,你應該這么做:
.sympathSRV*d:\DebugSymbols*http://msdl.microsoft.com/download/symbols
你偶爾會在symbols服務器上獲取symbols時遇到一些故障。在這個情況下,使用!sym noisy 命令以獲得關於WinDbg嘗試獲取symbols的更多信息。
然后使用 !lmi 查看WinDbg知道多少關於ntoskrnl的信息。然后嘗試取得ntoskrnl的symbols,使用.reload /f。因而:
kd> !sym noisy
noisy mode - symbol prompts on
kd>!lmi nt
LoadedModule Info: [nt]
Module: ntoskrnl
Base Address: 80a02000
Image Name: ntoskrnl.exe
Machine Type: 332 (I386)
Time Stamp: 3e80048b Mon Mar 24 23:26:032003
Size: 4d8000
CheckSum: 3f6f03
Characteristics:10e
DebugData Dirs: Type Size VA Pointer
CODEVIEW 25, ee00, e600 RSDS - GUID:(0xec9b7590, 0xd1bb, 0x47a6, 0xa6, 0xd5, 0x38, 0x35, 0x38, 0xc2, 0xb3, 0x1a)
Age: 1, Pdb: ntoskrnl.pdb
Image Type: MEMORY - Image read successfully from loadedmemory.
Symbol Type: EXPORT -PDB not found
Load Report: exportsymbols
在WINDOWS調試工具幫助文件中,有關於這里使用的命令及其語法的描述。
輸出symbols通常很大。WINDOWS調試工具包括一個symbol服務器,以便連接到Microsoft的網絡服務器保存這些公開的symbol。添加這些到你的symbol路徑,
然后加載它們:
kd>.sympath SRV*d:\DebugSymbols*http://msdl.microsoft.com/download/symbols
Symbol search path is: SRV*d:\ DebugSymbols*http://msdl.microsoft.com/download/symbols
kd>.reload /f nt
SYMSRV: \\symbols\symbols\ntoskrnl.pdb\EC9B7590D1BB47A6A6D5383538C2B31A1\file.ptr
SYMSRV: ntoskrnl.pdb from \\symbols\symbols: 9620480bytes copied
DBGHELP:nt - public symbols
d:\DebugSymbols\ntoskrnl.pdb\EC9B7590D1BB47A6A6D5383538C2B31A1\ntoskrnl.pdb
kd>!lmi nt
LoadedModule Info: [nt]
Module: ntoskrnl
Base Address: 80a02000
Image Name: ntoskrnl.exe
Machine Type: 332 (I386)
Time Stamp: 3e80048b Mon Mar 24 23:26:032003
Size: 4d8000
CheckSum: 3f6f03
Characteristics:10e
DebugData Dirs: Type Size VA Pointer
CODEVIEW 25, ee00, e600 RSDS - GUID:(0xec9b7590, 0xd1bb, 0x47a6, 0xa6, 0xd5, 0x38, 0x35, 0x38, 0xc2, 0xb3, 0x1a)
Age: 1, Pdb: ntoskrnl.pdb
Image Type: MEMORY - Image read successfully from loadedmemory.
Symbol Type: PDB -Symbols loaded successfullyfrom symbol server.
d:\DebugSymbols\ntoskrnl.pdb\EC9B7590D1BB47A6A6D5383538C2B31A1\ntoskrnl.pdb
Compiler: C - front end [13.10 bld 2179]- back end [13.10 bld 2190]
Load Report: public symbols
d:\DebugSymbols\ntoskrnl.pdb\EC9B7590D1BB47A6A6D5383538C2B31A1\ntoskrnl.pdb
symbols只會給你一些信息,而不會提供源代碼。在最簡單的情況下,在它們被建立的時候,source文件便在同一個地方(該位置包括2進制文件和symbol文件)。
但是在大多數情況下,你不能在那里找到它們(它們可能被移走了),你必須指定在哪里能找到它們。這時,你需要一個源路徑,例如,
.srcpath e:\Win2003SP1
它的意思是:想要source文件,請查看e:\Win2003SP1目錄。
另一個解決方案是命名一個source服務器,如果你有:
.srcpath\\MySrcServer
如果你曾經在獲取source文件時遇到麻煩,使用.srcnoisy 1以取得更多關於調試器查找它們的信息。
目前你還不能開始調試,除非你已經准備好打很多字。很多設置都被保存在workspace中。所以你應該使用FileàSave 保存在workspace里面,例如,你將它保存為kernel1394Win2003。在這之后,你希望以這個workspace的設置啟動WinDbg:
windbg -Wkernel1394Win2003 -k 1394:channel=1
–W指定一個workspace,而–k給出通信方式(祥見WINDOWS調試工具幫助文件中的“WinDbg Command-Line Options”)。注意:在WinDbg或者KD中,
你應該小心區分命令行可選項的大小寫。
為了讓事情變得簡單,你可以在桌面建立快捷方式,以使用特定的workspace啟動WinDbg,例如,使用1394連接:
上述文件中的內容:
cd /d "d:\ProgramFiles\Debugging Tools for Windows"
start windbg.exe -y SRV*d:\DebugSymbols*http://msdl.microsoft.com/download/symbols -W kernel1394Win2003
第一行將切換到WINDOWS調試工具的安裝目錄下面,確認調試器模塊能在那里被找到。第二行啟動WinDbg,指定symbo路徑(-y)和workspace (-W)。
使用示例驅動IoCtl練習,這將會幫助你熟悉WinDbg。你能在WINDDK和它的后續產品,WDK中找到。安裝它,你便能在src\general\Ioctl子目錄下找到該驅動。
IoCtl的優點在於它是示例,而且是一個“legacy”驅動,由服務管理器(SCM)加載,而不是即插即用的一部分(這里並不關心PnP的輸入和輸出)。
你應該建立用戶態程序(ioctlapp.exe),並在前者被加載之后建立內核態驅動程序(sioctl.sys)。
這里有些重要的事需要明白。在優化代碼方面,建立程序的處理十分靈巧,優化會導致代碼移動(當然,原邏輯會被保留),並且將一些變量單獨保存在寄存器中。
為了確保更簡單的調試體驗,你應該在建立窗口或者源代碼文件中使用這些編譯指令建立一個調試版本:
MSC_OPTIMIZATION=/Od
(這是“Oh d”而不是“zerod.”)
有時上述的情況會引起內部函數的一些問題,例如memcmp。如果你碰上這個問題,嘗試 :
MSC_OPTIMIZATION=/Odi
請明白阻止優化對於生成正式版產品來說,並不是一個好選擇。使用上述的指令,你將不能建立或者測試正式版。盡管如此,這對於測試未經優化的版本來說,
是不錯的練習。一旦你熟悉代碼,排除簡單的錯誤,正式產品便能得到提升。如果你需要處理已優化的代碼,你將會在“處理優化代碼”找到相關幫助。
在IoCtl 的DriverEntry設置斷點。在啟動驅動之前,中斷在WinDbg的命令窗口,輸入:
busioctl!DriverEntry
bu (“Breakpoint Unresolved”)命令將會延遲斷點的設置時間,直到該模塊被加載;也就是說WinDbg會探測“DriverEntry”。如果沒有什么需要做,按下F5(你也可以輸入g, “Go”)
接下來,復制ioctlapp.exe和sioctl.sys到目標系統,例如C:\Temp\IOCTL,以管理員權限登陸系統,在命令窗口中,切換到C:\Temp\IOCTL目錄下。
(你不需要在WinDbg中將此路徑設置為symbol路徑和source路徑。)在同樣的命令窗口,輸入ioctlapp按下Enter,在WinDbg中,你會看到:
如圖,程序停在斷點之后,!lmi 命令顯示WinDbg從DDK中取得symbols。時間信息象你期望的一樣,本地symbol文件也符合你的要求。
依賴於你的排列方案,它並不明顯,當前窗口能被其他窗口隱藏,但是你能在某個地方使用源代碼窗口(按鍵順序‘alt-Keypad *’ ― 不用按單引號― 將會把窗口置前):
斷點被設置,即運行停止的地方會以粉紅色標記(WINDOWS調試工具幫助文件把它稱為紫色)。當運行進IoCreateDevice(運行控制 描述如何熟練運用):
這里你能看到原始斷點(高亮為紅色,現在控制將停止在這里),你能看到當前聲明被標記為深藍色。
在調試session中,這是一個“測試驅動”。這是一些基本的調試操作。
命令來自幾個系列:簡單的(未修飾的),一些從句號(“.”)開始,一些從驚嘆號(“!”)開始。WINDOWS調試工具幫助文件將它們分別描述為commands,
meta-commands and extension commands。以現在的效果來看,這些系列非常接近。
在運行中產生中斷,是調試器的功能之一。這是一些實現方法。
· 在操作系統啟動時中斷
為了在操作系統啟動時盡早中斷,請確認WinDbg 已經連接,重新按CTRL-ALT-K直到你看到:
在下次啟動時,在ntoskrnl加載之后的一小段時間,這時所有驅動還沒有被加載,操作系統將會掛起,而WinDbg將會取得控制權。在系統引導時間,
你可能會希望為驅動程序定義斷點,這就是時機。
· 普通斷點
最簡單的設置斷點的方法就是通過bp (“Breakpoint”)命令。例如:
bp MyDriver!xyz
bp f89adeaa
第一行,這個斷點設在模塊中的一個名字(<module>!<name>);第二行,它被設置在一個給出的地址。當運行到其中一個斷點時,操作系統就會掛起,並且把控制權交給WinDbg。(你可以在“尋找名字”看看如何為第二個命令取得地址。)
注意:第一個命令的語法假定操作系統已經加載該模塊,以及在symbol文件或者外部名定義有足夠可用信息關於識別xyz。如果不能在模塊中找到xyz,
調試器會這么告訴你這些。
· 延遲斷點
說到驅動程序沒有被加載,你最初的哪個斷點,使用bu(見上述開始調試示例驅動)設置的是一個“可延遲的”斷點。Bu命令的參數是一個模塊及它里面的名字,例如:
bu sioctl!SioctlDeviceControl
SioctlDeviceControl是一個入口點,或者其他在模塊sioctl.sys中的名字。這個形式假定當模塊被加載,足夠有用的信息識別SioctlDeviceControl以便斷點能夠設置。
(如果模塊已經加載名字被找到,那么斷點將會立即被設置)。如果操作系統找不到SioctlDeviceControl,調試器會提示,另外將不會在SioctlDeviceControl處掛起。
延遲斷點的一個有用的特性便是它對modules!names操作。相比之下,一般斷點對地址或者立即將modules!names解釋為地址。
延遲斷點的另一個特性便是在引導的過程中會被記住(這不會影響明確地址的斷點)。然而,延遲斷點的另外一個特性使得即使關聯模塊被卸載,它仍然會被保留。
相同情況下,一般斷點將會被移除。
· 另外一個設置一般斷點的方法是通過source窗口。返回sioctl.sys。當你中斷於DriverEntry,,你能向下滾動窗口到你希望停止地方,將光標移動到該行代碼,按下F9:
紅色的那一行便是通過F9設置的斷點。
· 你可以使用bl(“Breakpoint List”)查看所有已設置的斷點:
kd> bl
0e [d:\winddk\3790\src\general\ioctl\sys\sioctl.c @ 123] 0001 (0001) SIoctl!DriverEntry
1e [d:\winddk\3790\src\general\ioctl\sys\sioctl.c @ 338] 0001 (0001)Sioctl!SioctlDeviceControl+0x103
注意兩件事:每個斷點都有一個號碼並且顯示出斷點狀態,“e”是“enabled”,而“d”是“disabled”。
· 假設你希望臨時停止使用某個斷點。bd (“DisableBreakpoint”) 將會完成它。你只需指定斷點號碼:
kd> bd 1
kd> bl
0e [d:\winddk\3790\src\general\ioctl\sys\sioctl.c @ 123] 0001 (0001) SIoctl!DriverEntry
1 d[d:\winddk\3790\src\general\ioctl\sys\sioctl.c @ 338] 0001 (0001)SIoctl!SioctlDeviceControl+0x103
· 相似的方法,永久移除斷點號碼,使用bc 1 (“ClearBreakpoint”)。現在該斷點將會從斷點列表中消除。
· 然而,有時在操作系統或者驅動程序中,斷點會被設置在一些頻繁被激活的地方,你可能希望將它應用在一些環境或者條件操作,以便斷點只在該情況下生效。
這是基本格式:
bp SIoctl!SioctlDeviceControl+0x103 "j (@@(Irp)=0xffb5c4f8) ''; 'g'"
它的意思是:只有Irp=地址0xFFB5C4F8時才中斷;如果條件不符合,繼續運行。
更深入的探討上述命令,並不是斷點本身的狀態。更准確的說,斷點有一個操作項目(在雙引號標記中);在該項目中,j (“Execute IF/ELSE”)命令是一個條件操作。
J 的函數運行於TRUE|FALSE項目(在單引號標記中)。如上述一樣,TRUE項目(第一)為空,以便當斷點激活和符合TRUE的條件出現時,
WinDbg除了掛起程序之外不會做其他的事。如果符合FALSE的條件出現,由於使用了g命令,程序講會繼續運行。一個或者其他操作會被完成,這依賴於實際情況。
思考這個比上述更詳細的命令:
bp SIoctl!SioctlDeviceControl+0x103 "j (@@(Irp)=0xffb5c4f8) '.echo Found theinteresting IRP' ; '.echo Skipping an IRP of no interest; g' "
這里TRUE項目給出信息並停止。FALSE項目給出信息並繼續(這個信息很有用,WinDbg計算出條件為FALSE,並且默默地繼續)。
有時要注意:下面斷點,EAX被檢測(你能在寄存器中找到關於它們的處理方法),不會象你想的那樣工作:
bp SIoctl!SioctlDeviceControl+0x103 "j (@eax=0xffb5c4f8) '.echoHere!' ; '.echo Skipping; g' "
原因是可能會將寄存器的值擴充到64位再計算,例如,擴充到0xFFFFFFFF`FFB5C4F8,這將不會與0x00000000`FFB5C4F8匹配。
這導致只有32位的最高位為1和一些其他條件(例如,一個32位寄存器)才適用。在WINDOWS調試工具幫助文件中的“SignExtension”有更詳盡的資料
(也可以看看“Setting a Conditional Breakpoint”)。
斷點可能包含一些條件式,附帶或不附帶條件操作。其中一個條件是激發“one-shot”:斷點只激活一次(激活之后便清除)。假如你只對第一次激活感興趣,
對於那些使用頻繁的代碼,這很便利。
bp /1 SIoctl!SioctlDeviceControl+0x103
另外一個有用的條件式測試一個進程或者線程:
bp /p 0x81234000 SIoctl!SioctlDeviceControl+0x103
bp /t 0xff234000 SIoctl!SioctlDeviceControl+0x103
它們分別代表,僅當進程塊(EPROCESS)在0x81234000,才在指定的地方停止,以及僅當線程塊(ETHREAD)在0xFF234000時才在指定地方停止。
該條件式能被組合為:
bp /1 /C 4 /p0x81234000 SIoctl!SioctlDeviceControl+0x103
這代表,當call stack深度大於4(這里的大寫C很重要,因為“c”代表“少於”)和進程塊在0x81234000時中斷。
· 另外一種不同類型的斷點,需要指定訪問方式。例如:
ba w40xffb5c4f8+0x18+0x4
正如你所看到的,這個地址來自IRP,它的偏移0x18+0x4處即它的IoStatus.Information 成員。所以當某程序企圖更新IRP中IoStatus.Information的這4個字節時,
斷點會被激活。這種斷點被稱為數據斷點(因為它們由數據訪問觸發)或者處理器斷點(因為它們由處理器執行,而不是調試器自己)。
在驅動程序之中使用變量提供參數,如進程地址。你或許同意那是很容易的一件事。然而,你需要理解一些調試器的表達式。
調試器有兩種評價表達式的方法,參考“MASM” (MicrosoftMacro Assembler)和“C++”。引用WINDOWS調試工具幫助文件中的“MASMExpressions vs. C++ Expressions”:
在MASM的表達式中,任何符號的數值都在內存地址中。在C++表達式中,變量中的數值是它的真實數值,而不是它的地址。
閱讀再閱讀這部分,這將會節省你更多的時間。
一條表達式將會使用MASM,或者C++,或者它們的混合形式計算。
簡要說明:
1. 默認表達式類型是MASM.
2. 你能使用.expr 改變默認類型(詳見WINDOWS調試工具幫助文件)。
3. 某些命令總是使用C++的方式求值。
4. 一個特殊的表達式(或表達式的一部分)的賦值能通過前綴“@@”改成與一般表達式相反的方向。
這個摘要相當棘手,你應該參考WINDOWS調試工具幫助文件中的“Evaluating Expressions”。現在,這里有一些例子,給你一些關於賦值是如何工作的概念。
你之前已經停止在Sioctl!SioctlDeviceControl+0x103,所以使用dv 查看一個已知變量(查看dv 命令以獲得更多信息):
kd> dv Irp
Irp = 0xff70fbc0
該響應的意思是,Irp 變量包含0xFF70FBC0。更多地,dv 解釋C++語法中的參數。該響應基於變量內容,而不是地址。你可以確認它:
kd> ?? Irp
struct _IRP * 0xff70fbc0
?? 總是以C++ 為基礎(詳見??命令)。假如使用MASM類型的賦值,嘗試? (詳見? 命令):
kd> ? Irp
Evaluateexpression: -141181880 = f795bc48
這表示變量Irp 位於0XF795BC48。你可以通過使用dd (詳見dd 命令)顯示內存數據,確認該變量真的包含數據0xFF70FBC0。
kd> dd f795bc48 l1
f795bc48 ff70fbc0
以及內存指向這里:
kd> dd 0xff70fbc0
ff70fbc0 00940006 00000000 00000070 ff660c30
ff70fbd0 ff70fbd0 ff70fbd0 00000000 00000000
ff70fbe0 01010001 04000000 0006fdc0 00000000
ff70fbf0 00000000 00000000 00000000 04008f20
ff70fc00 00000000 00000000 00000000 00000000
ff70fc10 ff73f4d8 00000000 00000000 00000000
ff70fc20 ff70fc30 ffb05b90 00000000 00000000
ff70fc30 0005000e 00000064 0000003c 9c402408
查看象IRP這樣的變量,正如dt顯示(詳見dt 命令),Type和Size成員有一個似是而非的數據:
kd> dt Irp
Local var @ 0xf795bc48 Type _IRP*
0xff70fbc0
+0x000 Type : 6
+0x002 Size : 0x94
+0x004 MdlAddress : (null)
+0x008 Flags : 0x70
+0x00c AssociatedIrp :__unnamed
+0x010 ThreadListEntry :_LIST_ENTRY [ 0xff70fbd0 - 0xff70fbd0 ]
+0x018 IoStatus :_IO_STATUS_BLOCK
+0x020 RequestorMode : 1 ''
+0x021 PendingReturned : 0 ''
+0x022 StackCount : 1 ''
+0x023 CurrentLocation : 1 ''
+0x024 Cancel : 0 ''
+0x025 CancelIrql : 0 ''
+0x026 ApcEnvironment : 0 ''
+0x027 AllocationFlags : 0x4 ''
+0x028 UserIosb :0x0006fdc0
+0x02c UserEvent : (null)
+0x030 Overlay :__unnamed
+0x038 CancelRoutine : (null)
+0x03c UserBuffer :0x04008f20
+0x040Tail : __unnamed
有時,你會希望使用C++ 賦值代替MASM表達式。“@@” 前綴會完成它。擴展命令總是使用象MASM表達式一樣的參數,當你使用擴展命令!irp (詳見 IRPs),
你能看到@@的效果。
kd> !irp @@(Irp)
Irp is active with 1 stacks 1 is current(= 0xff70fc30)
NoMdl System buffer = ff660c30 Thread ff73f4d8: Irp stack trace.
cmd flg cl Device File Completion-Context
>[ e, 0] 5 0 82361348 ffb05b90 00000000-00000000
\Driver\SIoctl
Args:00000064 0000003c 9c402408 00000000
重復這個操作,不在上述的 Irp 變量中帶@@ 前綴,!irp 將會使用變量的地址,而不是變量的值。為了使這更加具體,如果變量位於0xF795BC48,它包含的數據是0xFF70FBC0,使用!irp Irp 代替@@(Irp)將會請求WinDbg 格式化位於0xF795BC48的IRP stack。
你需要進一步了解的是:@@前綴相當通用,正如它的正式意思,使用不同於當前表達式中正在使用的賦值方法。如果大部分表達式是MASM,@@代表C++,
如果它是C++,@@代表MASM。
最后一點建議:如果表達式不如你期望那樣工作,考慮你是否在請求調試器理解MASM或者C++語法。
有一些方法可以顯示和改變它們。
kd> dv
DeviceObject = 0x82361348
Irp = 0xff70fbc0
outBufLength = 0x64
buffer = 0x00000000 ""
irpSp = 0xff70fc30
data = 0xf886b0c0 "This String is from Device Driver !!!"
ntStatus = 0
mdl = 0x00000000
inBufLength = 0x3c
datalen = 0x26
outBuf = 0x00000030 ""
inBuf = 0xff660c30 "This String is from User Application; usingMETHOD_BUFFERED"
這是一個參數變量列表以及一些在斷點位置已知的變量。“已知”是一個重要的限定詞。例如如果一個變量優化成一個寄存器,它將不會被顯示,盡管可以反匯編它(View=>Disassembly 打開反匯編窗口)並且檢查寄存器。
如果只關心一個變量,你可以:
kd> dv outBufLength
outBufLength = 0x64
kd> dt Irp
Local var @ 0xf795bc48 Type _IRP*
0xff70fbc0
+0x000 Type : 6
+0x002 Size : 0x94
+0x004 MdlAddress : (null)
+0x008 Flags : 0x70
+0x00c AssociatedIrp :__unnamed
+0x010 ThreadListEntry :_LIST_ENTRY [ 0xff70fbd0 - 0xff70fbd0 ]
+0x018 IoStatus :_IO_STATUS_BLOCK
+0x020 RequestorMode : 1 ''
+0x021 PendingReturned : 0 ''
+0x022 StackCount : 1 ''
+0x023 CurrentLocation : 1 ''
+0x024 Cancel : 0 ''
+0x025 CancelIrql : 0 ''
+0x026 ApcEnvironment : 0 ''
+0x027 AllocationFlags : 0x4 ''
+0x028 UserIosb :0x0006fdc0
+0x02c UserEvent : (null)
+0x030 Overlay :__unnamed
+0x038 CancelRoutine : (null)
+0x03c UserBuffer :0x04008f20
+0x040 Tail : __unnamed
上面的數據說明了變量Irp在0xF795BC48,它的值是0xFF70FBC0;因為 dt 知道IRP變量的指針(“Type _IRP*”),0xFF70FBC0區域被格式化為IRP。
展開一級結構:
kd> dt -r1 Irp
Local var @ 0xf795bc48 Type _IRP*
0xff70fbc0
+0x000 Type : 6
+0x002 Size : 0x94
+0x004 MdlAddress : (null)
+0x008 Flags : 0x70
+0x00c AssociatedIrp :__unnamed
+0x000 MasterIrp :0xff660c30
+0x000 IrpCount :-10089424
+0x000 SystemBuffer :0xff660c30
+0x010 ThreadListEntry :_LIST_ENTRY [ 0xff70fbd0 - 0xff70fbd0 ]
+0x000 Flink :0xff70fbd0 [ 0xff70fbd0 - 0xff70fbd0 ]
+0x004 Blink :0xff70fbd0 [ 0xff70fbd0 - 0xff70fbd0 ]
+0x018 IoStatus :_IO_STATUS_BLOCK
+0x000 Status : 0
+0x000 Pointer : (null)
+0x004 Information : 0
+0x020 RequestorMode : 1 ''
+0x021 PendingReturned : 0 ''
+0x022 StackCount : 1 ''
+0x023 CurrentLocation : 1 ''
+0x024 Cancel : 0 ''
+0x025 CancelIrql : 0 ''
+0x026 ApcEnvironment : 0 ''
+0x027 AllocationFlags : 0x4 ''
+0x028 UserIosb :0x0006fdc0
+0x000 Status : 67142040
+0x000 Pointer :0x04008198
+0x004 Information : 0x2a
+0x02c UserEvent : (null)
+0x030 Overlay :__unnamed
+0x000 AsynchronousParameters : __unnamed
+0x000 AllocationSize :_LARGE_INTEGER 0x0
+0x038 CancelRoutine : (null)
+0x03c UserBuffer :0x04008f20
+0x040 Tail :__unnamed
+0x000 Overlay :__unnamed
+0x000 Apc : _KAPC
+0x000 CompletionKey : (null)
你可以顯示一些結構,甚至在它們不在范圍之內的時候(被詢問的內存不能以其他一些目的再生)
kd> dt nt!_IRP 0xff70fbc0
+0x000 Type : 6
+0x002 Size : 0x94
+0x004 MdlAddress : (null)
+0x008 Flags : 0x70
+0x00c AssociatedIrp :__unnamed
+0x010 ThreadListEntry :_LIST_ENTRY [ 0xff70fbd0 - 0xff70fbd0 ]
+0x018 IoStatus :_IO_STATUS_BLOCK
+0x020 RequestorMode : 1 ''
+0x021 PendingReturned : 0 ''
+0x022 StackCount : 1 ''
+0x023 CurrentLocation : 1 ''
+0x024 Cancel : 0 ''
+0x025 CancelIrql : 0 ''
+0x026 ApcEnvironment : 0 ''
+0x027 AllocationFlags : 0x4 ''
+0x028 UserIosb :0x0006fdc0
+0x02c UserEvent : (null)
+0x030 Overlay :__unnamed
+0x038 CancelRoutine : (null)
+0x03c UserBuffer :0x04008f20
+0x040 Tail :__unnamed
上面的命令,按照你知道的來說,就是IRP在0xFF70FBC0,而事實上,這是在ntoskrnl映射出的IRP結構。
kd> dt nt!_IRP Size 0xff70fbc0
unsigned short 0x94
更直接的方法是使用?? (“EvaluateC++ Expression”) 命令:
kd> ?? Irp->Size
unsigned short 0x94
那是??,了解它的參數指向適當結構中的一個成員。
kd> dd 0xff70fbc0 l0x10
ff70fbc0 00940006 00000000 00000070 ff660c30
ff70fbd0 ff70fbd0 ff70fbd0 00000000 00000000
ff70fbe0 01010001 04000000 0006fdc0 00000000
ff70fbf0 00000000 00000000 00000000 04008f20
kd> dw 0xff70fbc0 l0x20
ff70fbc0 0006 0094 0000 0000 0070 0000 0c30 ff66
ff70fbd0 fbd0 ff70 fbd0 ff70 0000 0000 0000 0000
ff70fbe0 0001 0101 0000 0400 fdc0 0006 0000 0000
ff70fbf0 0000 0000 0000 0000 0000 0000 8f20 0400
kd> db 0xff70fbc0 l0x40
ff70fbc0 06 00 94 00 00 00 00 00-70 00 00 00 30 0c 66 ff ........p...0.f.
ff70fbd0 d0 fb 70 ff d0 fb 70 ff-00 00 00 00 00 00 00 00 ..p...p.........
ff70fbe0 01 00 01 01 00 00 00 04-c0 fd 06 00 00 00 0000 ................
ff70fbf0 00 00 00 00 00 00 00 00-00 00 00 00 20 8f 00 04 ............ ...
(注意: 3個命令各自的第二個參數是一個長度,由l (字母“l”)后面的數值給出,例如0x10。)
第一個顯示16個雙字(每個4字節,或者共64個字節)。第二個顯示同樣的字。第三個顯示同樣的字節。
kd> outBufLength = 00
^ Syntax error in 'outBufLength = 00'
不工作?但是??完成了這個工作:
kd> ?? outBufLength = 0
unsigned long 0
現在回到IRP,你在上述使用的dt :
kd> dt Irp
Local var @ 0xf795bc48 Type _IRP*
0xff70fbc0
+0x000 Type : 6
+0x002 Size : 0x94
+0x004 MdlAddress : (null)
+0x008 Flags : 0x70
+0x00c AssociatedIrp :__unnamed
+0x010 ThreadListEntry :_LIST_ENTRY [ 0xff70fbd0 - 0xff70fbd0 ]
+0x018 IoStatus : _IO_STATUS_BLOCK
+0x020 RequestorMode : 1 ''
+0x021 PendingReturned : 0 ''
+0x022 StackCount : 1 ''
+0x023 CurrentLocation : 1 ''
+0x024 Cancel : 0 ''
+0x025 CancelIrql : 0 ''
+0x026 ApcEnvironment : 0 ''
+0x027 AllocationFlags : 0x4 ''
+0x028 UserIosb :0x0006fdc0
+0x02c UserEvent : (null)
+0x030 Overlay :__unnamed
+0x038 CancelRoutine : (null)
+0x03c UserBuffer :0x04008f20
+0x040 Tail :__unnamed
改變第一個字(2個字節),通過ew (“Enter Values”):
kd> ew 0xff70fbc0 3
kd> dt Irp
Local var @ 0xf795bc48 Type _IRP*
0xff70fbc0
+0x000 Type : 3
+0x002 Size : 0x94
+0x004 MdlAddress : (null)
+0x008 Flags : 0x70
+0x00c AssociatedIrp :__unnamed
+0x010 ThreadListEntry :_LIST_ENTRY [ 0xff70fbd0 - 0xff70fbd0 ]
+0x018 IoStatus :_IO_STATUS_BLOCK
+0x020 RequestorMode : 1 ''
+0x021 PendingReturned : 0 ''
+0x022 StackCount : 1 ''
+0x023 CurrentLocation : 1 ''
+0x024 Cancel : 0 ''
+0x025 CancelIrql : 0 ''
+0x026 ApcEnvironment : 0 ''
+0x027 AllocationFlags : 0x4 ''
+0x028 UserIosb :0x0006fdc0
+0x02c UserEvent : (null)
+0x030 Overlay :__unnamed
+0x038 CancelRoutine : (null)
+0x03c UserBuffer :0x04008f20
+0x040 Tail :__unnamed
當然,下面可能比ew更加自然:
kd> ?? irp->type = 3
Type does not have given member error at'type = 3'
kd> ?? irp->Type = 3
short 3
kd> dt irp
ioctlapp!Irp
Local var @ 0xf795bc48 Type _IRP*
0xff70fbc0
+0x000 Type : 3
+0x002 Size : 0x94
+0x004 MdlAddress : (null)
+0x008 Flags : 0x70
+0x00c AssociatedIrp :__unnamed
+0x010 ThreadListEntry :_LIST_ENTRY [ 0xff70fbd0 - 0xff70fbd0 ]
+0x018 IoStatus :_IO_STATUS_BLOCK
+0x020 RequestorMode : 1 ''
+0x021 PendingReturned : 0 ''
+0x022 StackCount : 1 ''
+0x023 CurrentLocation : 1 ''
+0x024 Cancel : 0 ''
+0x025 CancelIrql : 0 ''
+0x026 ApcEnvironment : 0 ''
+0x027 AllocationFlags : 0x4 ''
+0x028 UserIosb :0x0006fdc0
+0x02c UserEvent : (null)
+0x030 Overlay :__unnamed
+0x038 CancelRoutine : (null)
+0x03c UserBuffer :0x04008f20
+0x040 Tail :__unnamed
以上需要注意的兩件事。首先,結構中成員的大小寫是有意義的,正如WinDbg的提示那樣,在Irp 中沒有這樣的成員。第二,dt irp 是二義的,
但是WinDbg顯示了該實例,它的想法好象被修正了,其中一個在ioctlapp.exe而另外一個則在sioctl.sys。因為大小寫是有意義的,你應該在任何時候都使用它。
關於ew的更多信息,有其他 “Enter Values”命令:eb 用於字節,ed 用於雙字,eq 用於四倍字長(8字節)等等。參考WINDOWS調試工具幫助文件中的“Enter Values”。
本地窗口能更容易的顯示內嵌到結構中的結構指針
你可以在本地窗口中改寫它們的值。
kd> r
eax=81478f68ebx=00000000 ecx=814243a8 edx=0000003c esi=81778ea0 edi=81478f68
eip=f8803553esp=f7813bb4 ebp=f7813c3c iopl=0 nv up ei ng nz ac pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000292
或者只是:
kd> r eax
eax=81478f68
有時你會希望改變寄存器。例如,EAX經常被用於從例程退出時傳遞返回參數。因此,在例程退出之前:
r eax = 0xc0000001
現在顯示狀態數據為STATUS_UNSUCCESSFUL.
這里是其他的一些例子:
r eip = poi(@esp)
r esp = @esp + 0xc
他們分別表示,設置Eip (命令指針)為堆棧偏移為0x0指向的值,和Esp(堆棧指針)+0xC,有效的釋放堆棧。WINDOWS調試工具幫助文件中的 “RegisterSyntax”,
解釋了poi 命令和為什么寄存器一些地方需要加上“@”前綴。
你可能會問上述寄存器設置命令怎么用。考慮一下,當一個“壞”驅動的DriverEntry 將會引起故障檢查(“藍屏”)— 或許由於違規訪問。
你可以通過在ntoskrn加載時設置一個延遲斷點處理這些問題。下面命令必須在同一行中:
busioctl!DriverEntry "r eip = poi(@esp); r eax = 0xc0000001; r esp = @esp +0xc; .echo sioctl!DriverEntry entered; g"
它的意思是:在sioctl.sys的DriverEntry,1) 這樣設置命令指針 (Eip) 2) 這樣設置返回代碼 (Eax) 3) 這樣設置堆棧指針 (Esp) 4) 宣布已經進入DriverEntry 5) 繼續運行。(當然,這技術僅僅移除DriverEntry 引起崩潰的可能性,例如違規訪問。如果操作系統期待驅動程序供應函數,該函數將不可用,和可能是其他問題導致停機。)
在這里,你會想知道是否能用寄存器設置一個變量。例如,返回到IoCtl的dispatch routine:
kd> r
eax=00000000ebx=00000000 ecx=81a88f18 edx=81a88ef4 esi=ff9e18a8 edi=ff981e7e
eip=f87a40feesp=f88fac78 ebp=f88fac90 iopl=0 nv up ei pl zr na po nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000246
kd> ?? ntStatus = @ecx
long -2119659752
kd> dd &ntStatus l1
f88fac78 81a88f18
在這個情況中,應該使用@ecx格式,以保證WinDbg 知道你在引用一個寄存器。
寄存器的數量比默認顯示的要多。要查看所有寄存器,使用rM命令(“M”必須是大寫;實際上是r命令帶M參數,這里在命令和參數之間不允許空格):
kd> rM 0xff
eax=00000001ebx=0050e2a3 ecx=80571780 edx=000003f8 esi=000000c0 edi=d87a75a8
eip=804df1c0esp=8056f564 ebp=8056f574 iopl=0 nv up ei pl nz na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000202
fpcw=0000: rn 24------ fpsw=0000: top=0 cc=0000-------- fptw=0000
fopcode=6745 fpip=2301:a0020000 fpdp=dcfe:efcdab89
st0= 5.143591243081972142170e-4932 st1= 0.001025530551233493990e-4933
st2= 0.000000002357022271740e-4932 st3= 2.471625214254630491460e-4906
st4= 3.370207406893238285120e-4932 st5=-7.461339669368745455450e+4855
st6= 6.698191557136036873700e-4932 st7=-2.455410815115332972380e-4906
mm0=c3d2e1f010325476 mm1=0000ffdff1200000
mm2=000000018168d902 mm3=f33cffdff1200000
mm4=804efc868056f170 mm5=7430804efb880000
mm6=ff02740200000000 mm7=f1a48056f1020000
xmm0=09.11671e-041 3.10647e+035 -1.154e-034
xmm1=-7.98492e-039-2.83455e+038 -2.91106e+038 5.85182e-042
xmm2=1.77965e-043-1.17906e-010 -4.44585e-038 -7.98511e-039
xmm3=-7.98511e-0390 0 -7.98504e-039
xmm4=-7.98503e-0391.20545e-040 -1.47202e-037 -1.47202e-037
xmm5=-2.05476e+018-452.247 -1.42468e-037 -8.60834e+033
xmm6=2.8026e-044-1.47202e-037 -452.247 0
xmm7=8.40779e-045-7.98503e-039 0 -7.98511e-039
cr0=8001003b cr2=d93db000 cr3=00039000
dr0=00000000 dr1=00000000 dr2=00000000
dr3=00000000 dr6=ffff0ff0 dr7=00000400cr4=000006d9
如上圖,你可以改寫16進制的數值。
在前面的部分(詳見IoCreateDevice)你曾經想程序從一點運行到下一點,而不需要告訴它怎么做。這里有一些方法可以控制運行。下面所有的項目,除第一項,
都假設程序處於掛起狀態。
幾乎運行到某一點,都會有一個區域作為堆棧使用;該堆棧用於存放本地狀態,參數和返回地址。在內核空間中有一個內核棧,在用戶空間中有一個用戶棧。當中斷發生時,可能有幾個例程在當前的棧中。例如,如果由於sioctl.sys中PrintIrpInfo的斷點引起指令停止執行,使用k (“Stack Backtrace”):
kd> k
ChildEBPRetAddr
f7428ba8f889b54a SIoctl!PrintIrpInfo+0x6[d:\winddk\3790.1824\src\general\ioctl\sys\sioctl.c @ 708]
f7428c3c 804e0e0dSIoctl!SioctlDeviceControl+0xfa[d:\winddk\3790.1824\src\general\ioctl\sys\sioctl.c @ 337]
WARNING: Stackunwind information not available. Following frames may be wrong.
f7428c6080580e2a nt!IofCallDriver+0x33
f7428d00805876c2 nt!CcFastCopyRead+0x3c3
f7428d34804e7a8c nt!NtDeviceIoControlFile+0x28
f7428d6400000000 nt!ZwYieldExecution+0xaa9
最高一行(最新的)棧幀就是停止的地方。你也可以看到此前的一些調用。但是如果你沒有symbols,他們可能會顯示得不正常。
在驅動中的當前文件和行號信息,都會在每個棧幀中呈現,你將會享受到在sioctl.sys中使用symbols的樂趣。
你可以為IoCtl的IRP處理程序打開源代碼窗口。但是假如你對更早的例程不感興趣?你打開調用窗口 (ViewàCall stack),所以:
你可以雙擊入口,然后便會被帶到源代碼文件中,如果該文件已被定位。
如果你只對在堆棧中屬於例程的變量感興趣,你可以雙擊該例程所在的項目,或者你可以用kn (與k同屬) 然后.frame。例如,
取得關於調用了PrintIrpInfo的dispatch routine的信息:
kd> kn
# ChildEBP RetAddr
00 f7428ba8f889b54a SIoctl!PrintIrpInfo+0x6[d:\winddk\3790.1824\src\general\ioctl\sys\sioctl.c @ 708]
01 f7428c3c804e0e0d SIoctl!SioctlDeviceControl+0xfa [d:\winddk\3790.1824\src\general\ioctl\sys\sioctl.c@ 337]
WARNING: Stackunwind information not available. Following frames may be wrong.
02 f7428c6080580e2a nt!IofCallDriver+0x33
03 f7428d00805876c2 nt!CcFastCopyRead+0x3c3
04 f7428d34804e7a8c nt!NtDeviceIoControlFile+0x28
05 f7428d64 00000000nt!ZwYieldExecution+0xaa9
kd> .frame 1
01 f7428c3c804e0e0d SIoctl!SioctlDeviceControl+0xfa[d:\winddk\3790.1824\src\general\ioctl\sys\sioctl.c @ 337]
在設置楨號碼之后,便能顯示在楨中的已知變量和屬於該楨的寄存器:
kd> dv
DeviceObject = 0x80f895e8
Irp = 0x820572a8
outBufLength = 0x64
buffer = 0x00000000 ""
irpSp = 0x82057318
data = 0xf889b0c0 "This Stringis from Device Driver !!!"
ntStatus = 0
mdl = 0x00000000
inBufLength = 0x3c
datalen = 0x26
outBuf = 0x82096b20 "This Stringis from User Application; using METHOD_BUFFERED"
inBuf = 0x82096b20 "This Stringis from User Application; using METHOD_BUFFERED"
eax=00000000 ebx=00000000 ecx=80506be8 edx=820572a8 esi=81fabda0edi=820572a8
eip=f889bcf6 esp=f7428ba4 ebp=f7428ba8 iopl=0 nv up ei ng nz ac pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000292
SIoctl!PrintIrpInfo+0x6:
f889bcf6 8b4508 mov eax,[ebp+0x8] ss:0010:f7428bb0=820572a8
kd> x sioctl!*ioctl*
f8883080 SIoctl!SioctlUnloadDriver(struct _DRIVER_OBJECT *)
f8883010 SIoctl!SioctlCreateClose (struct_DEVICE_OBJECT *, struct _IRP *)
f8883450 SIoctl!SioctlDeviceControl(struct _DEVICE_OBJECT *, struct _IRP *)
它的意思是,告訴我所有在sioctl模塊中,包含“ioctl.”的symbols。
這看起來看瑣細。然而,想想,在一個實際案例中,這個信息在調試器中出現:
PopPolicyWorkerAction: action request 2failed c000009a
可以推測PopPolicyWorkerAction 在ntoskrnl中,你可能看到這些:
kd> xnt!PopPolicy*
805146c0nt!PopPolicyWorkerThread = <no type information>
8064e389nt!PopPolicySystemIdle = <no type information>
805b328dnt!PopPolicyWorkerNotify = <no type information>
8056e620nt!PopPolicyLock = <no type information>
8064d5f8nt!PopPolicyWorkerActionPromote = <no type information>
805c7d10 nt!PopPolicyWorkerMain = <no type information>
8064d51bnt!PopPolicyWorkerAction = <no type information>
80561c70nt!PopPolicy = <no type information>
8056e878nt!PopPolicyIrpQueue = <no type information>
80561a98nt!PopPolicyLockThread = <no type information>
8064e74ant!PopPolicyTimeChange = <no type information>
8056e8b0nt!PopPolicyWorker = <no type information>
由這些信息可以得出,你應該在被顯示為紅色的例程中設置斷點。
如果一個EXE文件在建立時作了一些優化,它可能很難在源碼窗口中跟蹤運行,一些本地變量可能無法使用,或者顯示為錯誤的數值。對於x86指令,你可能要嘗試在源代碼窗口和反匯編窗口(將這些窗口並排會方便你工作)中跟蹤它的運行。你不需要為了跟蹤控制流而對x86非常了解;主要看比較命令(例如test或者cmp)和分支命令(例如jnz),以便跟蹤控制流。
那適用於基本操作。盡管上面的焦點不是討論如何調查一些特殊區域,但是有大量調試器命令 — 從技術上來說,它們是擴展命令並且由DLL提供 — 仍然值得被提及,因為它們在很多方面都被反復使用。
查看當前進程(在停止的位置):
kd> !process
PROCESS 816fc3c0 SessionId: 1 Cid: 08f8 Peb: 7ffdf000 ParentCid: 0d8c
DirBase: 10503000 ObjectTable:e1afeaa8 HandleCount: 19.
Image: ioctlapp.exe
VadRoot 825145e0 Vads 22 Clone 0 Private 38. Modified 0. Locked 0.
DeviceMap e10d0198
Token e1c8e030
ElapsedTime 00:00:00.518
UserTime 00:00:00.000
KernelTime 00:00:00.109
QuotaPoolUsage[PagedPool] 9096
QuotaPoolUsage[NonPagedPool] 992
Working Set Sizes (now,min,max) (263, 50, 345) (1052KB, 200KB, 1380KB)
PeakWorkingSetSize 263
VirtualSize 6 Mb
PeakVirtualSize 6 Mb
PageFaultCount 259
MemoryPriority BACKGROUND
BasePriority 8
CommitCharge 48
THREAD 825d2020 Cid 08f8.0708 Teb: 7ffde000Win32Thread: 00000000 RUNNING on processor 0
進程塊地址(EPROCESS)和線程塊地址 (ETHREAD) 被標記為紅色。你可以在該處使用條件斷點。
使用摘要的形式查看所有進程:
kd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
PROCESS 826af478 SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 02c20000 ObjectTable:e1001e60 HandleCount: 363.
Image: System
PROCESS 82407d88 SessionId: none Cid: 0158 Peb: 7ffdf000 ParentCid: 0004
DirBase: 1fbe8000 ObjectTable:e13ff740 HandleCount: 24.
Image: smss.exe
PROCESS 82461d88 SessionId: 0 Cid: 0188 Peb: 7ffdf000 ParentCid: 0158
DirBase: 1f14d000 ObjectTable:e15e8958 HandleCount: 408.
Image: csrss.exe
...
查看一個詳細進程的線程摘要,給出進程塊的地址和通過第二個參數請求(查看WINDOWS調試工具幫助文件以取得更詳細的參數說明):
kd> !process 826af478 3
PROCESS 826af478 SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 02c20000 ObjectTable:e1001e60 HandleCount: 362.
Image: System
VadRoot 81a43840 Vads 4 Clone 0 Private 3. Modified 18884. Locked 0.
DeviceMap e1002868
Token e1002ae0
ElapsedTime 07:19:11.250
UserTime 00:00:00.000
KernelTime 00:00:11.328
QuotaPoolUsage[PagedPool] 0
QuotaPoolUsage[NonPagedPool] 0
Working Set Sizes (now,min,max) (54, 0, 345) (216KB, 0KB, 1380KB)
PeakWorkingSetSize 497
VirtualSize 1 Mb
PeakVirtualSize 2 Mb
PageFaultCount 4179
MemoryPriority BACKGROUND
BasePriority 8
CommitCharge 7
THREAD 826af1f8 Cid0004.0008 Teb: 00000000 Win32Thread:00000000 WAIT: (WrFreePage) KernelMode Non-Alertable
80580040 SynchronizationEvent
80581140 NotificationTimer
THREAD 826aea98 Cid0004.0010 Teb: 00000000 Win32Thread:00000000 WAIT: (WrQueue) KernelMode Non-Alertable
80582d80 QueueObject
THREAD 826ae818 Cid0004.0014 Teb: 00000000 Win32Thread:00000000 WAIT: (WrQueue) KernelMode Non-Alertable
80582d80 QueueObject
...
查看所有關於某線程的信息,使用!thread 命令和0xFF作為細節參數:
kd> !thread 826af1f8 0xff
THREAD 826af1f8 Cid 0004.0008 Teb: 00000000 Win32Thread: 00000000 WAIT: (WrFreePage) KernelModeNon-Alertable
80580040 SynchronizationEvent
80581140 NotificationTimer
Not impersonating
DeviceMap e1002868
Owning Process 826af478 Image: System
Wait Start TickCount 1688197 Ticks: 153 (0:00:00:02.390)
Context Switch Count 9133
UserTime 00:00:00.0000
KernelTime 00:00:03.0406
Start Address nt!Phase1Initialization(0x806fb790)
Stack Init f88b3000 Current f88b2780 Basef88b3000 Limit f88b0000 Call 0
Priority 0 BasePriority 0PriorityDecrement 0
ChildEBP RetAddr
f88b2798 804edb2b nt!KiSwapContext+0x26(FPO: [EBP 0xf88b27c0] [0,0,4])
f88b27c0 804f0e7a nt!KiSwapThread+0x280(FPO: [Non-Fpo]) (CONV: fastcall)
f88b27f4 80502fc2nt!KeWaitForMultipleObjects+0x324 (FPO: [Non-Fpo]) (CONV: stdcall)
如果你編寫了一個驅動程序,你將回經常查看設備堆棧。你應該在開始時查找設備屬於哪一個確定的驅動程序,並且檢查該設備堆棧。假設你對ScsiPort miniport driver aic78xx.sys感興趣。以 !drvobj開始:
kd> !drvobj aic78xx
Driver object (82627250) is for:
\Driver\aic78xx
Driver Extension List: (id , addr)
(f83864808267da38)
Device Objectlist:
82666030 8267b030 8263c030 8267ca40
這里有4個設備對象。通常查看第一個,使用!devobj 取得一些關於該設備的信息,而!devstack 則會顯示該設備對象堆棧屬於哪個設備對象:
kd> !devobj 82666030
Device object (82666030) is for:
aic78xx1Port2Path0Target1Lun0 \Driver\aic78xxDriverObject 82627250
Current Irp 00000000 RefCount 0 Type00000007 Flags 00001050
Dacl e13bb39cDevExt 826660e8 DevObjExt 82666d10 Dope 8267a9d8 DevNode 8263cdc8
ExtensionFlags (0000000000)
AttachedDevice (Upper) 826bb030\Driver\Disk
Device queue is not busy.
kd> !devstack 82666030
!DevObj !DrvObj !DevExt ObjectName
826bbe00 \Driver\PartMgr 826bbeb8
826bb030 \Driver\Disk 826bb0e8 DR2
> 82666030 \Driver\aic78xx 826660e8 aic78xx1Port2Path0Target1Lun0
!DevNode 8263cdc8 :
DeviceInst is"SCSI\Disk&Ven_QUANTUM&Prod_VIKING_II_4.5WLS&Rev_5520\5&375eb691&1&010"
ServiceName is "disk"
最普遍的與驅動程序的通信便是發送I/O請求包,或者IRP 。查看IRP的I/O堆棧,例如在Sioctl!SioctlDeviceControl+0x103:
kd> !irp @@(Irp)
Irp is active with 1 stacks 1 is current(= 0xff70fc30)
NoMdl System buffer = ff660c30 Thread ff73f4d8: Irp stack trace.
cmd flg cl Device File Completion-Context
>[ e, 0] 5 0 82361348 ffb05b90 00000000-00000000
\Driver\SIoctl
Args:00000064 0000003c 9c402408 00000000
取得IRP的所有內容,加上它的堆棧:
kd> !irp @@(Irp) 1
Irp is active with 1 stacks 1 is current(= 0xff70fc30)
NoMdl System buffer = ff660c30 Thread ff73f4d8: Irp stack trace.
Flags = 00000070
ThreadListEntry.Flink = ff70fbd0
ThreadListEntry.Blink = ff70fbd0
IoStatus.Status = 00000000
IoStatus.Information = 00000000
RequestorMode = 00000001
Cancel = 00
CancelIrql = 0
ApcEnvironment = 00
UserIosb = 0006fdc0
UserEvent = 00000000
Overlay.AsynchronousParameters.UserApcRoutine= 00000000
Overlay.AsynchronousParameters.UserApcContext= 00000000
Overlay.AllocationSize = 00000000 -00000000
CancelRoutine = 00000000
UserBuffer = 04008f20
&Tail.Overlay.DeviceQueueEntry =ff70fc00
Tail.Overlay.Thread = ff73f4d8
Tail.Overlay.AuxiliaryBuffer = 00000000
Tail.Overlay.ListEntry.Flink = 00000000
Tail.Overlay.ListEntry.Blink = 00000000
Tail.Overlay.CurrentStackLocation = ff70fc30
Tail.Overlay.OriginalFileObject =ffb05b90
Tail.Apc = 00000000
Tail.CompletionKey = 00000000
cmd flg cl Device File Completion-Context
>[ e, 0] 5 0 82361348 ffb05b90 00000000-00000000
\Driver\SIoctl
Args:00000064 0000003c 9c402408 00000000
只取得IRP中的第一級成員:
kd> dt nt!_IRP @@(Irp)
+0x000 Type : 6
+0x002 Size : 0x94
+0x004 MdlAddress : (null)
+0x008 Flags : 0x70
+0x00c AssociatedIrp :__unnamed
+0x010 ThreadListEntry :_LIST_ENTRY [ 0xff70fbd0 - 0xff70fbd0 ]
+0x018 IoStatus :_IO_STATUS_BLOCK
+0x020 RequestorMode : 1 ''
+0x021 PendingReturned : 0 ''
+0x022 StackCount : 1 ''
+0x023 CurrentLocation : 1 ''
+0x024 Cancel : 0 ''
+0x025 CancelIrql : 0 ''
+0x026 ApcEnvironment : 0 ''
+0x027 AllocationFlags : 0x4 ''
+0x028 UserIosb :0x0006fdc0
+0x02c UserEvent : (null)
+0x030 Overlay :__unnamed
+0x038 CancelRoutine : (null)
+0x03c UserBuffer :0x04008f20
+0x040 Tail :__unnamed
偶然會用到的命令!irql (WindowsServer 2003或以后的版本可用),因為它顯示有關處理器當前的IRQL。在 Sioctl!SioctlDeviceControl+0x0中斷:
kd> !irql
Debugger saved IRQL for processor 0x0 --0 (LOW_LEVEL)
舉個更高級別的IRQL例子,假設你為Sioctl!SioctlDeviceControl加上下面代碼,在IOCTL_SIOCTL_METHOD_BUFFERED項最后的語句中斷之前:
Irp->IoStatus.Information= (outBufLength<datalen?outBufLength:datalen);
{/* Begin added code */
KIRQL saveIrql;
ULONG i = 0;
KeRaiseIrql(DISPATCH_LEVEL,&saveIrql);
i++;
KeLowerIrql(saveIrql);
} /*End added code */
break;
現在,如果你在KeRaiseIrql 之后的語句設置斷點,並且發生中斷:
kd> !irql
Debugger saved IRQL for processor 0x0 --2 (DISPATCH_LEVEL)
順便說明一下!pcr 命令一般不會顯示你感興趣的IRQL,也就是說在該IRQL的斷點引起中斷。
這里有一些DUMP文件獨有的事要說明。只有少數一些事值得說明。
如果一個內核模式的DUMP文件在錯誤發生時被建立。調試該文件與使用調試器附加調試錯誤時相似。下面的部分將會展示一個現場調試的例子,
它與分析DUMP文件相似。
這是關於如何開始分析一個錯誤。在這個例子中,內核調試器在崩潰時附加,它的過程與分析一個內核模式DUMP文件是相似的
在這個例子中,Sioctl.sys 被加載,並且在Sioctl!DriverEntry設置斷點。當調試器在該斷點停止時,甚至EIP為0。這永遠都不會是一個有效的數值,因為命令指針不能為0。
然后通過F5繼續運行。一個內核錯誤發生,你可以開始查錯了。然后你可以使用!analyze 這個擴展命令進行調查:
kd> !analyze-v
*******************************************************************************
* *
* Bugcheck Analysis *
* *
*******************************************************************************
SYSTEM_THREAD_EXCEPTION_NOT_HANDLED(7e)
This is a verycommon bugcheck. Usually the exceptionaddress pinpoints
thedriver/function that caused the problem. Always note this address
as well as thelink date of the driver/image that contains this address.
Arguments:
Arg1: c0000005,The exception code that was not handled
Arg2: 00000000,The address that the exception occurred at
Arg3: f88f2bd8,Exception Record Address
Arg4: f88f2828,Context Record Address
DebuggingDetails:
------------------
EXCEPTION_CODE:(NTSTATUS) 0xc0000005 - The instruction at "0x%08lx" referencedmemory at "0x%08lx". The memory could not be "%s".
FAULTING_IP:
+0
00000000?? ???
EXCEPTION_RECORD: f88f2bd8 -- (.exr fffffffff88f2bd8)
ExceptionAddress:00000000
ExceptionCode: c0000005 (Access violation)
ExceptionFlags: 00000000
NumberParameters:2
Parameter[0]: 00000000
Parameter[1]: 00000000
Attempt to readfrom address 00000000
CONTEXT: f88f2828 -- (.cxr fffffffff88f2828)
eax=ffff99eaebx=00000000 ecx=0000bb40 edx=8055f7a4 esi=e190049e edi=81e826e8
eip=00000000esp=f88f2ca0 ebp=f88f2cf0 iopl=0 nv up ei pl nz na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010202
00000000?? ???
Resettingdefault scope
DEFAULT_BUCKET_ID: DRIVER_FAULT
CURRENT_IRQL: 0
ERROR_CODE:(NTSTATUS) 0xc0000005 - The instruction at "0x%08lx" referencedmemory at "0x%08lx". The memory could not be "%s".
READ_ADDRESS: 00000000
BUGCHECK_STR: 0x7E
LAST_CONTROL_TRANSFER: from 805b9cbb to 00000000
STACK_TEXT:
WARNING: FrameIP not in any known module. Following frames may be wrong.
f88f2c9c 805b9cbb 81e826e8 8123a000 00000000 0x0
f88f2d58 805b9ee5 80000234 8123a000 81e826e8 nt!IopLoadDriver+0x5e1
f88f2d80 804ec5c8 80000234 00000000 822aeda0 nt!IopLoadUnloadDriver+0x43
f88f2dac 805f1828 f7718cf4 00000000 00000000 nt!ExpWorkerThread+0xe9
f88f2ddc 8050058e 804ec50d 00000001 00000000nt!PspSystemThreadStartup+0x2e
00000000 00000000 00000000 00000000 00000000 nt!KiThreadStartup+0x16
FAILED_INSTRUCTION_ADDRESS:
+0
00000000?? ???
FOLLOWUP_IP:
nt!IopLoadDriver+5e1
805b9cbb3bc3 cmp eax,ebx
SYMBOL_STACK_INDEX: 1
SYMBOL_NAME: nt!IopLoadDriver+5e1
MODULE_NAME: nt
IMAGE_NAME: ntoskrnl.exe
DEBUG_FLR_IMAGE_TIMESTAMP: 3e800a79
STACK_COMMAND: .cxr fffffffff88f2828 ; kb
FAILURE_BUCKET_ID: 0x7E_NULL_IP_nt!IopLoadDriver+5e1
BUCKET_ID: 0x7E_NULL_IP_nt!IopLoadDriver+5e1
kd> .cxrfffffffff88f2828
eax=ffff99ea ebx=00000000 ecx=0000bb40 edx=8055f7a4 esi=e190049e edi=81e826e8
eip=00000000 esp=f88f2ca0 ebp=f88f2cf0 iopl=0 nv up ei pl nz na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010202
00000000 ?? ???
kd> kb
*** Stack trace for last set context - .thread/.cxr resetsit
ChildEBPRetAddr Args to Child
WARNING: FrameIP not in any known module. Following frames may be wrong.
f88f2c9c 805b9cbb 81e826e8 8123a000 00000000 0x0
f88f2d58 805b9ee5 80000234 8123a000 81e826e8 nt!IopLoadDriver+0x5e1
f88f2d80 804ec5c8 80000234 00000000 822aeda0 nt!IopLoadUnloadDriver+0x43
f88f2dac 805f1828 f7718cf4 00000000 00000000 nt!ExpWorkerThread+0xe9
f88f2ddc 8050058e 804ec50d 00000001 00000000nt!PspSystemThreadStartup+0x2e
00000000 00000000 00000000 00000000 00000000 nt!KiThreadStartup+0x16
最上層的堆棧入口看起來是錯誤的。這是你可能在DUMP文件中遇到的。如果你不知道該錯誤是如何發生的,你應該如何工作?
1. 使用.frame 1以取得該神秘例程的調用者nt!IopLoadDriver。
2. 切換到反匯編窗口,nt!IopLoadDriver的調用命令被顯示:
8062da9e ff572c call dword ptr [edi+0x2c]
8062daa1 3bc3 cmp eax,ebx
3. 這個調用是EDI寄存器包含的雙字再加上0x2C。這個地址是你所需要的,顯示EDI寄存器:
kd> r edi
Last set context:
edi=81a2bb18
4. 一個小運算:
kd> ? 81a2bb18+0x2c
Evaluate expression: -2120041660 =81a2bb44
5. 該地址在儲存器中的0x81A2BB44處:
kd> dd 81a2bb44 l1
81a2bb44 f87941a3
6. 該地址在哪里?
kd> dt f87941a3
GsDriverEntry
SIoctl!GsDriverEntry+0(
_DRIVER_OBJECT*,
_UNICODE_STRING*)
這樣你就知道了堆棧最上層的真實例程了。
你可以將偽寄存器當作變量使用以完成各種目的。有很多偽寄存器都被預定義:$ra 是當前call stack入口的返回地址,$ip 是指令指針,
$scopeip 代表當前作用域的地址(使當前例程中的本地變量可用的本地上下文),$proc 指向當前EPROCESS,等等。這些在條件語句中很有用。
當然也有由使用者定義的偽寄存器,從$t0到$t19。這能用於達成很多目的,例如計算中斷的次數。一個偽寄存器被用於儲存區域被頻繁更新的真實情況:
ba w4 81b404d8-18"r$t0=@$t0+1;as /x ${/v:$$t0} @$t0;.block {.echo hit # $$t0};ad${/v:$$t0};dd 81b404d8-18 l1;k;!thread -1 0;!process -1 0"
上式的近似意思是,當0x81B404D8中的雙字被更新時,偽寄存器$t0將作為中斷計數器,指出已中斷的次數,並且顯示0x81B404D8中的數值、當前的call stack ,
當前的進程和當前的線程 (請參考下面的別名使用以獲得更詳細描述)
另外一個用途說明來自於一個維護實例。該實例需要跟蹤Atapi.sys的DPC 例程的活動狀況(Atapi.sys 是一個標准的操作系統驅動程序)。該例程經常會被使用,
分析工程師對一個特殊的地方感興趣,一個IRP將要完成,而變量irp 指向相同的IRP。該工程師希望在正確的時間停止Tape.sys,
所以他在開始的時候為Atapi.sysDPC 設置了一個只中斷1次的斷點:
bp /1Atapi!IdeProcessCompletedRequest+0x3bd "dv irp; r$t0=@@(irp)"
該斷點的作用是設置偽寄存器$t0的值,使它與irp相等,即那個感興趣的IRP地址。(同樣會顯示irp的值)
當中斷發生,工程師這么做:
bp Tape!TapeIoCompleteAssociated+0x1c6 "j (@@(Irp)=$t0) '.echo stoppedat TAPE!TapeIoCompleteAssociated+0x1c6; dv Irp' ; 'g'"
這個意思是:當Tape.sys被第二個斷點中斷時,如果本地變量Irp 與$t0匹配,給出有用的信息並且顯示Irp的值。另一方面,如果Irp不等於$t0,繼續運行。
當第二個斷點使運行停止時,那便是工程師希望控制被掛起的地方。
將一些字符替換成其他命令字符可能會比較便利。其中一個用處便是用一個簡短的字符來代替長長的命令。例如,
kd> as Demo r; !process -1 0; k;!thread -1 0
kd> al
Alias Value
------- -------
Demo r; !process -1 0; k; !thread -1 0
kd> demo
Couldn't resolve error at 'emo'
kd> Demo
eax=00000001ebx=001a6987 ecx=80571780 edx=ffd11118 esi=0000003e edi=f8bcc776
eip=804df1c0esp=8056f564 ebp=8056f574 iopl=0 nv up ei pl nz na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000202
nt!RtlpBreakWithStatusInstruction:
804df1c0 cc int 3
PROCESS 80579f60 SessionId: none Cid: 0000 Peb: 00000000 ParentCid: 0000
DirBase: 00039000 ObjectTable:e1000e78 HandleCount: 234.
Image: Idle
ChildEBP RetAddr
8056f560 804e8682nt!RtlpBreakWithStatusInstruction
8056f560 804e61cent!KeUpdateSystemTime+0x132
80579f60 00000000 nt!KiIdleLoop+0xe
THREAD 80579d00 Cid 0000.0000 Teb: 00000000 Win32Thread: 00000000 RUNNING on processor 0
注意被替換成的假名是區分大小寫的。
如果你回到上面看看偽寄存器,你便會理解這里的第一個例子。繼續,假設這是一個實際的命令:
bp SioctlDeviceControl"r$t0=@$t0+1;as /x ${/v:$$t0} @$t0;.block {.echo hit # $$t0};ad${/v:$$t0};k ;!thread -1 0;!process -1 0;g"
這便是在WinDbg命令窗口中給出的,其中假名以紅色顯示:
hit # 0x1
ChildEBP RetAddr
f747dc20 80a2675cSIoctl!SioctlDeviceControl
f747dc3c 80c70bed nt!IofCallDriver+0x62
f747dc54 80c71b0dnt!IopSynchronousServiceTail+0x159
f747dcf4 80c673aant!IopXxxControlFile+0x665
f747dd28 80afbbf2nt!NtDeviceIoControlFile+0x28
f747dd28 7ffe0304nt!_KiSystemService+0x13f
0006fdc8 04003bcbSharedUserData!SystemCallStub+0x4
0006fde8 04002314 ioctlapp!_ftbuf+0x1b
0006ff78 04002e02 ioctlapp!main+0x1e4
0006ffc0 77e4f38cioctlapp!mainCRTStartup+0x14d
WARNING: Frame IP not in any knownmodule. Following frames may be wrong.
0006fff0 00000000 0x77e4f38c
THREAD feca2b88 Cid 0714.0e2c Teb: 7ffde000 Win32Thread: 00000000 RUNNING on processor 0
PROCESS ff877b50 SessionId: 1 Cid: 0714 Peb: 7ffdf000 ParentCid: 0d04
DirBase: 048f0000 ObjectTable:e2342440 HandleCount: 19.
Image: ioctlapp.exe
...
hit # 0x2
...
hit # 0x3
...
上面的基本技巧是將命令塊關聯到斷點中,使用該方法嵌入假名將不會被立刻解釋,而是當中斷發生時才解釋。這由${} (“Alias Interpreter”)命令完成,
使用/v標記選項指定假名不會在說明時計算(在bp命令中) 和.block (“Block”) 標記使假名在中斷發生和運行關聯命令時才計算。最后as 的 /x選項確保使用64位數值,
ad確保最近的假名被清除。
你可以使用script文件運行大量WinDbg 命令。想象這是一個64位系統中的DUMP文件。這里的焦點是來自於xStor.sys設備驅動的 SCSI Request Blocks (SRBs) 調用:
fffffadfe5df1010[fffffadfe5ee6760] irpStack: ( 4, 0) fffffadfe78cc060 [ \Driver\dmio] 0xfffffadfe6919470
被紅色顯示的地址便是該特殊的IRP。
fffffadfe5e4c9d0[00000000] irpStack: ( f, 0) fffffadfe783d050 [ \Driver\xStor] 0xfffffadfe6919470
!irpfffffadfe5e4c9d0 1
!irp 擴展命令在給出的地址中顯示IRP,包括IRP的首部和它的堆棧。保存debugtst1.txt。
1:kd> $$<c:\temp\debugtst1.txt
1:kd> !irp fffffadfe5e4c9d0 1
Irpis active with 2 stacks 2 is current (= 0xfffffadfe5e4cae8)
Mdl = fffffadfe600f5e0 Thread 00000000: Irp stack trace.
Flags= 00000000
ThreadListEntry.Flink= fffffadfe5e4c9f0
ThreadListEntry.Blink= fffffadfe5e4c9f0
IoStatus.Status= c00000bb
IoStatus.Information= 00000000
...
Tail.Apc= 0326cc00
Tail.CompletionKey= 0326cc00
cmd flg cl Device File Completion-Context
[ 0,0] 0 0 00000000 00000000 00000000-00000000
Args: 00000000 0000000000000000 00000000
>[ f, 0] 0 e1 fffffadfe783d050 00000000 fffffadfe3ee46d0-fffffadfe6869010 SuccessError Cancel pending
\Driver\xStor CLASSPNP!TransferPktComplete
Args: fffffadfe686913000000000 00000000 00000000
在這些條件式中,紅色的數值便是IRP中的SRB地址。
dtnt!SCSI_REQUEST_BLOCK fffffadfe6869130
注意:因為Microsoftsymbol 只保存"公開的symbols",nt!SCSI_REQUEST_BLOCK可能無法使用。為了當前的目的,想象它已經被定義在驅動程序的完整 symbols。
1: kd> dtnt!SCSI_REQUEST_BLOCK fffffadfe6869130
+0x000 Length : 0x58
+0x002 Function : 0 ''
+0x003 SrbStatus : 0''
+0x004 ScsiStatus : 0 ''
+0x005 PathId : 0 ''
+0x006 TargetId : 0xff ''
+0x007 Lun : 0 ''
...
+0x038 SrbExtension : (null)
+0x040 InternalStatus : 0x21044d0
+0x040 QueueSortKey : 0x21044d0
+0x044 Reserved : 0
+0x048 Cdb :[16] "*"
這樣,通過幾分鍾的工作,你找到並顯示所有你感興趣的SRBs。你也可以寫一個調試器插件完成同樣的工作,但是對於一次性的調查,一個簡易的script會是更好的方法。
你可以更進一步通過邏輯控制封裝一些命令以組成一些命令程序。由.if 和 .for控制流水作業。更多內容請見WINDOWS調試工具幫助文件中的 “Run Script File” 和 “Using DebuggerCommand Programs”。
調試器插件功能更加強大,但是你需要花費更多時間去編寫。由C 或者C++ 編寫,編譯成DLL,可以使用調試器的所有功能和它的引擎。一些常用的命令,
例如!process ,實際上,它是由插件提供的。編寫插件的細節部分已經超出本文范圍,請參考WINDOWS調試工具幫助中的 “Debugger Extensions”。
WinDbg (和 KD) 能夠連接目標以扮演服務器的角色,而調式實例則扮演客戶的角色,通過TCP/IP或者其他協議。待測系統通過 COMx 或者1394連接到調試器,
調試器則提供調試服務。然后開發者可以遠距離調查程序或者運行函數。在自動化測試中,安裝該調試器很有價值,它允許你在自己的桌面上研究實驗室的難題。
你可以使用該命令行選項表明它的任務以取得該特性:
windbg -server tcp:port=5555
或者你可以在WinDbg運行之后使用下面命令:
.servertcp:port=5005
任何一個方法都可以讓WinDbg 扮演調試服務器的角色,在TCP/IP的5005端口監聽。
另一個不同的WinDbg實例,使用下面命令作為客戶端連接:
.tcp:server=myserver,port=5005
臨時啟動一個WinDbg客戶端:
windbg -remotetcp:server=myserver,port=5005
COMx,named-pipe 和SSL 是其他可用的協議。
關於遠程調試的一些事:
WinDbg盡它的最大努力計算出callstack,但是有時它失敗了。檢索出這樣一個狀況是調試人員所面對的最難任務,因為他或者她必須用自己的知識對WinDbg作補充。
注意,然后堅持向前。
假設這個例子來自於一個雙重錯誤的DUMP文件(來自第一個參數0x00000008的非預期的內核模式陷阱):
kd> k
ChildEBP RetAddr
be80cff8 00000000 hal!HalpClockInterruptPn+0x84
看起來只有一個操作系統的時間中斷例程在堆棧中。它會失敗,這有點可疑。因此,現在看看當前線程:
kd> !thread
THREAD 88f108a0 Cid 8.54c Teb: 00000000 Win32Thread:00000000 RUNNING
...
Start Address rpcxdr!SunRpcGetRxStats(0xf7352702)
Stack Init be810000 Current be80fd34 Base be810000 Limit be80d000 Call 0
堆棧從0xBE810000開始,到0xBE80D000結束(正常狀態下是3頁)。顯然,失敗的時鍾例程的棧基 (ChildEBP)是0xBE80CFF8,在正常狀態堆棧結束位置的上面。
時鍾例程會使用超過標准的堆棧?
現在的偵察工作是查看堆棧中可能指出其他例程的地址。一般使用dds (“Display Words and Symbols”)尋找已保存的地址 (也可以使用dqs 和 dps ;
注意到3個命令都是區分大小寫的)。為了現在的目的忽略時間中斷例程,將焦點移到堆棧上面。但是沒有例程被時鍾例程中斷。但是不要完全回避時鍾例程:
從這個事實開始,它的棧基指針(ChildEBP上面)是0xBE80CFF8。
查看0xBE80CFF8,看看是否有什么有趣的東西顯示出來 (下面以C風格給出注釋):
2: kd> dds be80cff8 be80cff8+0x100
be80cff8 ???????? /* Invalidstorage address. */
be80cffc ???????? /* Invalidstorage address. */
be80d000 00000000
...
be80d034 00000000
be80d038 00000020
...
be80d058 be80d084
be80d05c 00000000
be80d060 bfee03e0 zzznds+0x103e0
be80d064 00000008
be80d068 00000246
be80d06c 8a61c004
be80d070 bfbb7858
be80d074 88c1da28
be80d078 00000000
be80d07c 00000000
be80d080 00000000
be80d084 be80d0d8 /* Saved Ebp ofzzznds+0xBED7, as explained below. */
be80d088 bfedbed7 zzznds+0xbed7
...
假設“zzzndx+0x103E0” 是一個驅動程序例程的標記,它被時鍾例程中斷。你會注意到前面的(棧地址的高位) “zzznds+0xBED7”標記。
現在看看zzznds+0xBED7之前的一些反匯編代碼(一個調用指針):
zzznds+0xbed0:
bfedbed0 53 push ebx
bfedbed1 57 push edi
bfedbed2 e8e7420000 call zzznds+0x101be (bfee01be)
注意到這里是調用zzznds+0x101BE,它接近第一個被識別的標記。因而可以很好的反匯編該調用。
現在反匯編zzznds+0x101BE,看看它是怎么工作的:
bfee01be 55 push ebp /* Save the caller's EBP. */
bfee01bf 8bec mov ebp,esp /* Make the current ESP our EBP. */
bfee01c1 83ec0c sub esp,0xc /* Adjust ESP by subtracting 0xC. */
bfee01c4 53 push ebx
回到上面,看看dds 的輸出,你可以看到調用程序被保存在0xBE80D084的Ebp。指令將Ebp壓入(在0xBFEE01BE push ebp ) 並且在0xBE80D084 保存它。
這代表ESP會在壓棧之后變成0xBE80D084,然后ESP變成當前的EBP (0xBFEE01BF處的指令),后來Esp減去0xC得到0xBFEE01C1。
0XBFEE01C4處指令的結果是 Esp =0xBE80D078。
現在你已經確定了調用zzznds+0xBED7時Ebp, Esp 和 Eip 的值,也就是,0xBE80D084, 0xBE80D078 和 0xBFEE01C4,然后你將它們提供給k 命令,
這比嘗試發現數值要好:
2: kd> k = 0xBE80D084 0xBE80D0780xBFEE01C4
ChildEBP RetAddr
WARNING: Stack unwind information notavailable. Following frames may be wrong.
be80d084 bfedbed7 zzznds+0x101c4
be80d0d8 bff6030f zzznds+0xbed7
be80d0fc 8046d778SCSIPORT!SpStartIoSynchronized+0x139
be80d114 bff60e4fnt!KeSynchronizeExecution+0x28
be80d148 8006627bSCSIPORT!SpBuildScatterGather+0x249
be80d174 8041d30e hal!HalAllocateAdapterChannel+0x11b
be80d18c bff5f8c8nt!IoAllocateAdapterChannel+0x28
be80d1bc 8041f73fSCSIPORT!ScsiPortStartIo+0x2ea
be80d1e0 bff5f4ec nt!IoStartPacket+0x6f
be80d214 bff601d0SCSIPORT!ScsiPortFdoDispatch+0x26c
be80d22c bff622f7 SCSIPORT!SpDispatchRequest+0x70
be80d248 bff5e390SCSIPORT!ScsiPortPdoScsi+0xef
be80d258 8041deb1SCSIPORT!ScsiPortGlobalDispatch+0x1a
...
這是堆棧中唯一的最近的一部分。但是你應該有通用的想法。
立即查看上面k 給出的參數,這需要大量的偵察工作,包括搜索堆棧和通過代碼查看堆棧是如何被建立到那些位置的。這里的工作將使一種情況改變成另一種情況。
這里的教訓是,如果WinDbg的堆棧反向跟蹤看起來很短,查看運行失敗的線程被分配到的內核堆棧。如果沒有,把它發掘出來。
如果你在很長一段時間內單步內核代碼(例如使用F10 或 F11),你會注意到控制器突然跳轉到非預期的地方。
這很可能是該代碼運行在低於DISPATCH_LEVEL的IRQL並且你使用了步過 (F10)。如果你知道你正在跟蹤特定線程,現在檢查正在運行的線程,你確定該線程的變化。
這很正常。調試器將調試指令(例如x86里的int 3)放在下一個指令或者下一個語句(在調試過程中,這些調試指令一般不可見)完成單步。如果移動當前指令/語句到下一個時,線程的時間片期滿,操作系統可能分派不同的線程,該線程可能遇到調試指令,於是調試器便取得控制。調試器不會檢查當前線程是不是上一次單步時的那個,僅僅停止運行。在這個情況下,你可以觀察跳轉。這個方案可能更接近步過需要大量處理的代碼,正如步過一個API,該API又調用API,該API又調用API,等等。
沒有簡單的方法可以對付這個預期的重復出現的行為。當你在單步中看到一個非預期跳轉時應該怎么做?檢查當前線程,假如你覺得它很可疑。如果你發現該線程被關掉,
你只能回到最后一個好的位置,重新測試,在該處設置一次性斷點,讓程序運行,直到它到達斷點。然后你可以繼續下去:你仍然易手該情況影響,
但是你沿着感興趣的路徑更進一步。
轉載:點擊打開鏈接
本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。