使用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解決更多的問題。

安裝程序

取得最新版!

取得最新版的調試器,並且有規律的更新它。這里並沒有誇大最新版的價值,因為調試器會經常改進和修復錯誤。你將能在下面網址下載:

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.” 主題有關於它們的描述。

你的第一次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 命令)TypeSize成員有一個似是而非的數據

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 格式化位於0xF795BC48IRP 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

上面的數據說明了變量Irp0xF795BC48,它的值是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

那是??,了解它的參數指向適當結構中的一個成員。

  • 顯示內存,而不使用上述的格式,一些可用的命令,如dddw 和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:

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

  • 如果你不想使用命令作改變,你可以打開內存窗口(ViewàMemory),變量窗口 (ViewàLocals)或者寄存器窗口 (ViewàRegisters),並且改寫你想要數值。
  • 例如,

如上圖,你可以改寫16進制的數值。

運行控制

在前面的部分(詳見IoCreateDevice)你曾經想程序從一點運行到下一點,而不需要告訴它怎么做。這里有一些方法可以控制運行。下面所有的項目,除第一項,

都假設程序處於掛起狀態。

  • 中斷(CTRL-BREAK ) — 該快捷鍵總是中斷系統,只要系統正在運行並與WinDbg 處於通信狀態 (在KD 快捷鍵是CTRL-C)。
  • 步過(F10)     — 每按一次運行一條語句(如果C 或者C++     和     WinDbg處於“source     mode”, 可通過DebugàSource     Mode切換),或者一條指,
  • 並且規定如果遇到一個函數調用,將會運行過該函數,而不會進入它。
  • 步進(F11) — 就象步過那樣,除了運行到一個函數調用時,會進入該調用例程。
  • 步出     (SHIFT-F11) — 這會使程序運行直到完成當前例程(在call stack中的當前地址)。如果你對該例程已經了解得足夠多,這個快捷鍵很有用。
  • 運行到光標(F7 or CRTL-F10) — 當你想運行到該處中斷,你可以將光標放到源代碼窗口或者反匯編窗口中相應的位置,按下F7;程序將會運行到該位置。
  • 有一點要注意,然而:如果運行流程與該處不匹配(例如,一個IF語句不運行),WinDbg 將不會中斷,因為並沒有運行到指定地方。
  • 運行 (F5) — 運行直到遇到斷點或者錯誤事件被檢測到。你可以將“運行”想象為正常執行狀態。
  • 將指令設置在當前行(CTRL-SHIFT-I) — 在源代碼窗口,你可以把光標放在一行中,使用該快捷鍵,只要你允許(例如 F5或者F10),程序便從該處開始運行。
  • 在你想重復一些指令序列時,這很有用。但是要注意一些事情。例如,寄存器和變量的數據不會象你正常運行到該處時看到那樣。
  • 直接設置Eip — 你可以為Eip寄存器設置一個數值,然后按下F5(或者F10或者其他的什么),運行開始於該地址。顯然易見,該功能就象將指令設置在當前行
  • ,除非你指定了一個匯編指令的地址。

call stack

幾乎運行到某一點,都會有一個區域作為堆棧使用;該堆棧用於存放本地狀態,參數和返回地址。在內核空間中有一個內核棧,在用戶空間中有一個用戶棧。當中斷發生時,可能有幾個例程在當前的棧中。例如,如果由於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"

kd> r

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

 

 

 

 

在模塊中尋找名字

x (“Examine Symbols”)命令能定位模塊中的symbols。例如,如果你想在Ioctl 例程中設置斷點,以便處理DeviceIoControl IRPs 。

但是你不太記得該例程的名字了,你可以這么做:

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"

 

 

 

IRPs

最普遍的與驅動程序的通信便是發送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

偶然會用到的命令!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是最好的,但是有些更小體積的內核DUMP已經可以滿足大多數情況。也有小內存DUMP,
  • 它只有64KB(比起其他兩種類型生成得更快)。由於小內存DUMP沒有關於執行體的所有信息,你可能需要使用.exepath 命令指定執行體鏡象。
  • 你可以通過配置WINDOWS以便當崩潰出現時建立一個DUMP文件。
  • 調查DUMP文件時,不需要為WinDbg指定目標系統。在WinDbg 中使用FileàOpen Crash Dump 打開DUMP文件。如果symbol路徑和source 路徑都已經設置好,
  • 它們會幫助你。
  • 現在,在WinDbg的命令窗口 使用 !analyze –v取得摘要。該命令可能會提出執行上下文 (.cxr);通過設置該上下文,你可以訪問錯誤發生時的call stack
  •  (最接近錯誤的那個)。你需要進入進程和線程 (!process     和 !thread),查看內核的模塊列表 (lmnt),在該列表中挑選需要查看的驅動對象 (!drvobj)    
  • 和可能要查看設備節點 (!devnode),設備對象(!devobj)和設備堆棧 (!devstack)。但是在查看DUMP文件中,沒有比使用!analyze –v更簡單的方法了。

如果一個內核模式的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文件和其他減少工作量的方法

你可以使用script文件運行大量WinDbg 命令。想象這是一個64位系統中的DUMP文件。這里的焦點是來自於xStor.sys設備驅動的 SCSI Request Blocks (SRBs) 調用:

  1. 使用!irpfind (詳見WINDOWS調試工具幫助文件) 查找在non-paged     pool中的IRPs。你會得到下面幾行字:

fffffadfe5df1010[fffffadfe5ee6760] irpStack: ( 4, 0) fffffadfe78cc060 [ \Driver\dmio] 0xfffffadfe6919470

被紅色顯示的地址便是該特殊的IRP。

  1. 復制這幾行到一個文件中。
  2. 在該文件中,選擇所有包括 xStor的項目並且將這些項目放到另外一個文件中,debugtst1.txt。該輸出行是:

fffffadfe5e4c9d0[00000000] irpStack: ( f, 0) fffffadfe783d050 [ \Driver\xStor] 0xfffffadfe6919470

  1. 編輯debugtst1.txt,修改每一行:

!irpfffffadfe5e4c9d0 1

!irp 擴展命令在給出的地址中顯示IRP,包括IRP的首部和它的堆棧。保存debugtst1.txt。

  1. 現在,在WinDbg中,使用命令$$<c:\temp\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地址。

  1. 為了得到 SRBs 的格式化輸出,復制上面所有包括 ‘Args:     ffff’ 的輸出並保存在debugtst2.txt。然后象這樣改變每一行:

dtnt!SCSI_REQUEST_BLOCK fffffadfe6869130

注意:因為Microsoftsymbol 只保存"公開的symbols"nt!SCSI_REQUEST_BLOCK可能無法使用。為了當前的目的,想象它已經被定義在驅動程序的完整 symbols

  1. 保存debugtst2.txt。然后在WinDbg輸入$$<c:\temp\debugtst2.txt。你會看到以下輸出:

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 是其他可用的協議。

關於遠程調試的一些事:

  • 如果本地系統網絡和目標系統網絡之間有防火牆,那么遠程調試將更加復雜。詳見WINDOWS調試工具幫助文件。
  • 訪問symbols和 source依賴於你在遠程服務器中登陸的權限,而不是客戶機使用者的權限。
  • 客戶通過.lsrcpath (“local source path”) 命令定位source文件,而不是.srcpath.

“Short” call stacks

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,等等。

沒有簡單的方法可以對付這個預期的重復出現的行為。當你在單步中看到一個非預期跳轉時應該怎么做?檢查當前線程,假如你覺得它很可疑。如果你發現該線程被關掉,

你只能回到最后一個好的位置,重新測試,在該處設置一次性斷點,讓程序運行,直到它到達斷點。然后你可以繼續下去:你仍然易手該情況影響,

但是你沿着感興趣的路徑更進一步。

轉載:點擊打開鏈接


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
  © 2014-2022 ITdaan.com 联系我们: