什么是WinDBG?
WinDbg是微軟開發(fā)的免費源碼級調試工具。Windbg可以用于Kernel模式調試和用戶模式調試,還可以調試Dump文件。由于大部分程序員不需要做Kernel模式調試, 我在這篇文章中不會介紹Kernel模式調試。Kernel模式調試對學習Windows核心極有幫助。如果你對此感興趣,可以閱讀Inside Windows 2000和Windbg所帶的幫助文件。
這篇文章得主要目的是介紹WINDBG的主要功能以及相關的命令。關于這些命令的詳細語法,請參閱幫助文件。對文章中提到的許多命令,WINDBG有相應的菜單選項。
如何得到幫助
在命令(Command)窗口中輸入.hh 命會調出幫助文件令。
.hh keyword
會顯示關于keyword的詳細命令。
啟動Debugger
Windbg可以用于如下三種調試:
在機器B上啟動一個調試窗口(Debug Session)。你可以直接在Windbg下運行一個程序或者將Windbg附加(Attach)到一個進程。
在機器B的Windbg命令窗口上啟動一個遠程調試接口(remote):
.server npipe:pipe=PIPE_NAME
PIPE_NAME是該接口的名字。
在機器A上運行:
windbg –remote npipe:server=SERVER_NAME,pipe=PIPE_NAME
SERVER_NAME是機器B的名字。
.dump /ma File Name
創(chuàng)建一個Dump文件。在得到Dump文件后,使用如下的命令來打開它:
windbg –z DUMP_FILE_NAME
Windbg “path to executable” arguments
也可以將Windbg附加到一個正在運行的程序:
Windbg –p “process id”
Windbg –pn “process name”
注意有一種非侵入(Noninvasive)模式可以用來檢查一個進程的狀態(tài)并不進程的執(zhí)行。當然在這種模式下無法控制被調試程序的執(zhí)行。這種模式也可以用于查看一個已經在Debugger控制下運行的進程。具體命令如下:
Windbg –pv –p “process id”
Windbg –pv –pn “process name”
調試多個進程和線程
如果你想控制一個進程以及它的子進程的執(zhí)行,在Windbg的命令行上加上-o選項。Windbg中還有一個新的命令.childdbg 可以用來控制子進程的調試。如果你同時調試幾個進程,可以使用 | 命令來顯示并切換到不同的進程。
在同一個進程中可能有多個線程。~命令可以用來顯示和切換線程。
調試前的必備工作
在開始調試前首先要做的工作是設置好符號(Symbols)路徑。沒有符號,你看到的調用堆?;旧虾翢o意義。Microsoft的操作系統(tǒng)符號文件(PDB)是對外公開的。另外請注意在編譯你自己的程序選擇生成PDB文件的選項。如果設置好符號路徑后,調用堆??雌饋磉€是不對??梢允褂?strong style="mso-bidi-font-weight: normal">lm, !sym noisy, !reload 等命令來驗證符號路徑是否正確。
Windbg也支持源碼級的調試。在開始源碼調試前,你需要用.srcpath設置源代碼路徑。如果你是在生成所執(zhí)行代碼的機器上進行調試,符號文件中的源碼路徑會指向正確的位置,所以不需要設置源代碼路徑。如果所執(zhí)行代碼是在另一臺機器上生成的,你可以將所用的源碼拷貝(保持原有的目錄結構)的一個可以訪問的文件夾(可以是網絡路徑)并將源代碼路徑設為該文件夾的路徑。注意如果是遠程調試,你需要使用.lsrcpath來設置源碼路徑。
靜態(tài)命令:
顯示調用堆棧:在連接到一個調試窗口后,首先要知道的就是程序當前的執(zhí)行情況k* 命令顯示當前線程的堆棧。~*kb會顯示所有線程的調用堆棧。如果堆棧太長,Windbg只會顯示堆棧的一部分。.kframes可以用來設置缺省顯示框架數。
顯示局部變量:接下來要做通常是用dv顯示局部變量的信息。CTRL+ALT+V可以切換到更詳細的顯示模式。關于dv要注意的是在優(yōu)化過的代碼中dv的輸出極有可能是不準確的。這時后你能做的就是閱讀匯編代碼來發(fā)現你感興趣的值是否存儲在寄存器中或堆棧上。有時后當前的框架(Frame)上可能找不到你想知道的數據。如果該數據是作為參數傳到當前的方法中的,可以讀一讀上一個或幾個框架的匯編代碼,有可能該數據還在堆棧的某個地址上。靜態(tài)變量是儲存在固定地址中的,所以找出靜態(tài)變量的值較為容易。.Frame(或者在調用堆棧窗口中雙擊)可以用來切換當前的框架。注意dv命令顯示的是當前框架的內容。你也可在watch窗口中觀察局部變量的值。
顯示類和鏈表: dt可以顯示數據結構。比如dt PEB 會顯示操作系統(tǒng)進程結構。在后面跟上一個進程結構的地址會顯示該結構的詳細信息:dt PEB 7ffdf000。
Dl命令可以顯示一些特定的鏈表結構。
顯示當前線程的錯誤值:!gle會顯示當前線程的上一個錯誤值和狀態(tài)值。!error命令可以解碼HRESULT。
搜索或修改內存:使用s 命令來搜索字節(jié),字或雙字,QWORD或字符串。使用e命令來修改內存。
計算表達式:?命令可以用來進行計算。關于表達式的格式請參照幫助文檔。使用n命令來切換輸入數字的進制。
顯示當前線程,進程和模塊信息:!teb顯示當前線程的環(huán)境信息。最常見的用途是查看當前線程堆棧的起始地址,然后在堆棧中搜索值。!peb顯示當前進程的環(huán)境信息,比如執(zhí)行文件的路徑等等。lm顯示進程中加載的模塊信息。
顯示寄存器的值:r命令可以顯示和修改寄存器的值。如果要在表達式中使用寄存器的值,在寄存器名前加@符號(比如@eax)。
顯示最相近的符號:ln Address。如果你有一個C++對象的指針,可以用來ln來查看該對象類型。
查找符號:x命令可以用來查找全局變量的地址或過程的地址。x命令支持匹配符號。x kernel32!*顯示Kernel32.dll中的所有可見變量,數據結構和過程。
查看lock:!locks顯示各線程的鎖資源使用情況。對調試死鎖很有用。
查看handle:!handle顯示句柄信息。如果一段代碼導致句柄泄漏,你只需要在代碼執(zhí)行前后使用!handle命令并比較兩次輸出的區(qū)別。有一個命令!htrace對調試與句柄有關的Bug非常有用。在開始調試前輸入:
!htrace –enable
然后在調試過程中使用!htrace handle_value 來顯示所有與該句柄有關的調用堆棧。
顯示匯編代碼:u。
程序執(zhí)行控制命令:
設置代碼斷點:bp/bu/bm 可以用來設置代碼斷點。你可以指定斷點被跳過的次數。假設一段代碼KERNEL32!SetLastError在運行很多次后會出錯,你可以設置如下斷點:
bp KERNEL32!SetLastError 0x100.
在出錯后使用bl 來顯示斷點信息(注意粗體顯示的值):
0 e 77e7a3b0 004f (0100) 0:*** KERNEL32!SetLastError
重新啟動調試(.restart命令)并設置如下的斷點:
bp Kernel32!SetLastError 0x100-0x4f
Debugger會停在出錯前最后一次調用該過程的地方。
你可以指定斷點被激活時Debugger應當執(zhí)行的命令串。在該命令串中使用J命令可以用來設置條件斷點:
bp `mysource.cpp:143` "j (poi(MyVar)”0n20) ''; 'g' "
上面的斷點只在MyVar的值大于32時被激活(g命令
條件斷點的用途極為廣泛。你可以指定一個斷點只在特殊的情況下被激活,比如傳入的參數滿足一定的條件,調用者是某個特殊的過程,某個全局變量被設為特殊的值等等。
設置內存斷點:ba可以用來設置內存斷點。調試過程中一個常見的問題是跟蹤某些數據的變化。如下的斷點:
ba w4 0x40000000 "kb; g"
可以打印出所有修改0x40000000的調用堆棧。
控制程序執(zhí)行:p, pa,t, ta等命令可以用來控制程序的執(zhí)行。
控制異常和事件處理:Debugger的缺省設置是跳過首次異常(first chance expcetion),在二次異常(second chance exception)時中斷程序的執(zhí)行。sx命令顯示Debugger的設置。sxe和sxd可以改變Debugger的設置。
sxe clr
可以控制Debugger在托管異常發(fā)生時中斷程序的執(zhí)行。常用的Debugger事件有:
av 訪問異常
eh C++異常
clr 托管異常
ld 模塊加載
-c 選項可以用來指定在事件發(fā)生時執(zhí)行的調試命令。