使用WinDbg核心除錯
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解決更多的問題。
安裝程式
取得最新版!
取得最新版的偵錯程式,並且有規律的更新它。這裡並沒有誇大最新版的價值,因為偵錯程式會經常改進和修復錯誤。你將能在下面網址下載:
主機與目標之間的連線
偵錯程式有使用null-modemcable 或者1394cable連線兩臺電腦的安裝方案 。本教程不分析單作業系統的本地除錯(即在偵錯程式執行的電腦上進行分析)。
3臺電腦(目標電腦,除錯伺服器,除錯客戶端)的除錯將會被簡要的討論。
在主機除錯軟體(WinDbg或者KD)和目標作業系統之間,是一個協同處理的除錯過程。每一部分都必須做些什麼。更明確地,WinDbg 不是作為一個“管理作業系統”,
象客戶和一個真正作業系統那樣執行目標。WinDbg是一個除錯軟體,象目標作業系統的合作伙伴那樣知道它在除錯過程中的角色。在這種關係中,WinDbg從目標接收資訊,
並且向目標傳送資訊。這是一種有效的通訊機制。
serialprotocol是偵錯程式與目標系統之間可靠的通訊機制。你能通過null-modem cable使用COM埠連線主機和目標機器。另一個可供選擇的通訊機制是1394。
在除錯工具的幫助檔案中的“Configuring Software on the Target Computer.” 主題有關於它們的描述。
你的第一次session
假設你的主機使用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重新開始。
查詢symbols和source
現在你很可能渴望開始除錯,但仍然有一些東西你必須去做,因為它們將會很好的改善你的除錯體驗。
首先確認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以取得更多關於偵錯程式查詢它們的資訊。
Workspaces
目前你還不能開始除錯,除非你已經準備好打很多字。很多設定都被儲存在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與C++
在驅動程式之中使用變數提供引數,如程序地址。你或許同意那是很容易的一件事。然而,你需要理解一些偵錯程式的表示式。
偵錯程式有兩種評價表示式的方法,參考“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,使用!irpIrp 代替@@(Irp)將會請求WinDbg 格式化位於0xF795BC48的IRP stack。
你需要進一步瞭解的是:@@字首相當通用,正如它的正式意思,使用不同於當前表示式中正在使用的賦值方法。如果大部分表示式是MASM,@@代表C++,
如果它是C++,@@代表MASM。
最後一點建議:如果表示式不如你期望那樣工作,考慮你是否在請求偵錯程式理解MASM或者C++語法。
顯示和設定記憶體,變數,暫存器等等
有一些方法可以顯示和改變它們。
- 在當前例程中顯示一個變數(當前的“scope”),使用dv (“Display Variables”)。例如,如果停止在Sioctl!SioctlDeviceControl+0x103:
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
- 另外一個有用的命令是dt (“Display Type”)。例如,繼續使用在Sioctl!SioctlDeviceControl+0x103的斷點:
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
那是??,瞭解它的引數指向適當結構中的一個成員。
- 顯示記憶體,而不使用上述的格式,一些可用的命令,如dd,dw 和db (“Display Memory”) :
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個位元組)。第二個顯示同樣的字。第三個顯示同樣的位元組。
- 怎麼改變變數?繼續在Sioctl!SioctlDeviceControl+0x103,你會看到下面格式。
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: