Windbg的學習記錄(一)

來源:互聯網
上載者:User
Windbg是Microsoft退出的一款調試工具,它不像Visual Studio是針對特殊用例的調試器,它的調試手段覆蓋了整個作業系統。有些時候程式的運行崩潰令人困惑找出原因也相當費時費力(可能也和方法的不正確有關)。Windbg可以協助我們比Visual Studio更細緻的進行調試,包括作業系統的資訊、進程啟動並執行狀態、時間和環境變數、彙編指令、call stack等等,很多情況下可以查出許多隱性的錯誤。所以對於Windbg的學習和使用是對於開發人員更綜合的要求,需要掌握和具備一些更基礎的知識。

現在以調試一個簡單的程式為例,看一下Windbg可以提供給我們的有用資訊。運行Windbg選擇File -> Open Executable開啟可執行檔,然後設定Symbol file路徑(如果想要更多關於作業系統資訊的Symbol file需要到Microsoft網站下載)。在命令列視窗輸入x加上可執行檔名加上!再加上代碼中的函數名可以獲得函數的入口地址,這樣就可以方便的設定調試斷點。輸入x cpp200803272307!getConstBuffer獲得getConstBuffer的地址

輸入bp(break point)加上函數入口地址就可以設定函數端點。比如bp 004113c0,然後運行程式輸入g(即go)可以看到原始碼的函數被增加了相應的斷點。你會看到再次運行時程式會在相應的斷點前停下。現在停止在地址為004113c0的getConstBuffer函數下。bp還可以輸入如bp cpp200803272307!getConstBuffer的方式定義函數斷點。

使用k命令可以查看當前的callstack。繼續輸入g讓程式運行會看到最後程式停留在出現錯誤的位置,Exception是Access Violation。看到一條mov指令在寫入eax指向的記憶體,這就是出現錯誤的指令。使用dc可以查看eax上的記憶體資料。即輸入dc eax便可以快速查看,發現就是在修改字元+的時候出現了錯誤。

如果有作業系統的Symbol file那麼可以繼續進行調試,即查看eax的相應記憶體頁的屬性。輸入!address eax便可以查看了,發現這塊地區的記憶體是直讀的即是常量指標。所以使用mov命令會導致程式出錯。

輸入u加上地址可以查看反組譯碼指令,使用!address加上某個函數的地址也可以查看記憶體資訊。比如使用x cpp200803272307!getConstBuffer獲得地址,然後使用u 004113c0和!address 004113c0查看更詳細的資訊。

Windbg可以用於Kernel模式調試和使用者模式調試,還可以調試Dump檔案。由於大部分程式員不需要做Kernel模式調試, 我在這篇文章中不會介紹Kernel模式調試。Kernel模式調試對學習Windows核心極有協助。如果你對此感興趣,可以閱讀Inside Windows 2000和Windbg所帶的協助檔案。

這篇文章得主要目的是介紹WINDBG的主要功能以及相關的命令。關於這些命令的詳細文法,請參閱協助檔案。對文章中提到的許多命令,WINDBG有相應的菜單選項。

如何得到協助

在命令(Command)視窗中輸入.hh 命會調出協助檔案令。

.hh keyword

會顯示關於keyword的詳細命令。

啟動Debugger

Windbg可以用於如下三種調試:

  1. 遠端偵錯:你可以從機器A上調試在機器B上執行的程式。具體步驟如下:
  • 在機器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的名字。

  1. Dump檔案調試:如果在你的客戶的機器上出現問題,你可能不能使用遠端偵錯來解決問題。你可以要求你的使用者將Windbg附加到出現問題的進程上,然後在命令視窗中輸入:

.dump /ma File Name

建立一個Dump檔案。在得到Dump檔案後,使用如下的命令來開啟它:

windbg –z DUMP_FILE_NAME

  1. 本地進程調試:你可以在Windbg下直接運行一個程式:

Windbg “path to executable” arguments

也可以將Windbg附加到一個正在啟動並執行程式:

Windbg –p “process id”

Windbg –pn “process name”

注意有一種非侵入(Noninvasive)模式可以用來檢查一個進程的狀態並不進程的執行。當然在這種模式下無法控制被偵錯工具的執行。這種模式也可以用於查看一個已經在Debugger控制下啟動並執行進程。具體命令如下:

Windbg –pv –p “process id”

Windbg –pv –pn “process name”

調試多個進程和線程

如果你想控制一個進程以及它的子進程的執行,在Windbg的命令列上加上-o選項。Windbg中還有一個新的命令.childdbg 可以用來控制子進程的調試。如果你同時調試幾個進程,可以使用 | 命令來顯示並切換到不同的進程。

在同一個進程中可能有多個線程。~命令可以用來顯示和切換線程。

調試前的必備工作

在開始調試前首先要做的工作是設定好符號(Symbols)路徑。沒有符號,你看到的呼叫堆疊基本上毫無意義。Microsoft的作業系統符號檔案(PDB)是對外公開的。另外請注意在編譯你自己的程式選擇產生PDB檔案的選項。如果設定好符號路徑後,呼叫堆疊看起來還是不對。可以使用lm, !sym noisy, !reload 等命令來驗證符號路徑是否正確。

Windbg也支援源碼級的調試。在開始源碼調試前,你需要用.srcpath設定原始碼路徑。如果你是在產生所執行代碼的機器上進行調試,符號檔案中的源碼路徑會指向正確的位置,所以不需要設定原始碼路徑。如果所執行代碼是在另一台機器上產生的,你可以將所用的源碼拷貝(保持原有的目錄結構)的一個可以訪問的檔案夾(可以是網路路徑)並將原始碼路徑設為該檔案夾的路徑。注意如果是遠端偵錯,你需要使用.lsrcpath來設定源碼路徑。

靜態命令:

顯示呼叫堆疊:在串連到一個調試視窗後,首先要知道的就是程式當前的執行情況k* 命令顯示當前線程的堆棧。~*kb會顯示所有線程的呼叫堆疊。如果堆棧太長,Windbg只會顯示堆棧的一部分。.kframes可以用來設定預設顯示架構數。

顯示局部變數:接下來要做通常是用dv顯示局部變數的資訊。CTRL+ALT+V可以切換到更詳細的顯示模式。關於dv要注意的是在最佳化過的代碼中dv的輸出極有可能是不準確的。這時後你能做的就是閱讀彙編代碼來發現你感興趣的值是否儲存在寄存器中或堆棧上。有時後當前的架構(Frame)上可能找不到你想知道的資料。如果該資料是作為參數傳到當前的方法中的,可以讀一讀上一個或幾個架構的彙編代碼,有可能該資料還在堆棧的某個地址上。靜態變數是儲存在固定地址中的,所以找出靜態變數的值較為容易。.Frame(或者在呼叫堆疊視窗中雙擊)可以用來切換當前的架構。注意dv命令顯示的是當前架構的內容。你也可在watch視窗中觀察局部變數的值。

顯示類和鏈表dt可以顯示資料結構。比如dt PEB 會顯示作業系統進程結構。在後面跟上一個進程結構的地址會顯示該結構的詳細資料:dt PEB 7ffdf000

Dl命令可以顯示一些特定的鏈表結構。

顯示當前線程的錯誤值!gle會顯示當前線程的上一個錯誤值和狀態值。!error命令可以解碼HRESULT。

搜尋或修改記憶體:使用s 命令來搜尋位元組,字或雙字,QWORD或字串。使用e命令來修改記憶體。

計算運算式:?命令可以用來進行計算。關於運算式的格式請參照協助文檔。使用n命令來切換輸入數位進位。

顯示當前線程,進程和模組資訊!teb顯示當前線程的環境資訊。最常見的用途是查看當前線程堆棧的起始地址,然後在堆棧中搜尋值。!peb顯示當前進程的環境資訊,比如執行檔案的路徑等等。lm顯示進程中載入的模組資訊。

顯示寄存器的值r命令可以顯示和修改寄存器的值。如果要在運算式中使用寄存器的值,在寄存器名前加@符號(比如@eax)。

顯示最相近的符號ln Address。如果你有一個C++對象的指標,可以用來ln來查看該物件類型。

尋找符號x命令可以用來尋找全域變數的地址或過程的地址。x命令支援匹配符號。x kernel32!*顯示Kernel32.dll中的所有可見變數,資料結構和過程。

查看lock:!locks顯示各線程的鎖資源使用方式。對調試死結很有用。

查看handle:!handle顯示控制代碼資訊。如果一段代碼導致控制代碼泄漏,你只需要在代碼執行前後使用!handle命令並比較兩次輸出的區別。有一個命令!htrace對調試與控制代碼有關的Bug非常有用。在開始調試前輸入:

!htrace –enable

然後在調試過程中使用!htrace handle_value 來顯示所有與該控制代碼有關的呼叫堆疊。

顯示彙編代碼u

程式執行控制命令:

設定代碼斷點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應當執行的命令串。在該命令串中使用J命令可以用來設定條件斷點:

bp `mysource.cpp:143` "j (poi(MyVar)”0n20) ''; 'g' "

上面的斷點只在MyVar的值大於32時被啟用(g命令

條件斷點的用途極為廣泛。你可以指定一個斷點只在特殊的情況下被啟用,比如傳入的參數滿足一定的條件,調用者是某個特殊的過程,某個全域變數被設為特殊的值等等。

設定記憶體斷點:ba可以用來設定記憶體斷點。調試過程中一個常見的問題是跟蹤某些資料的變化。如下的斷點:

ba w4 0x40000000 "kb; g"

可以列印出所有修改0x40000000的呼叫堆疊。

控製程序執行p, pa,t, ta等命令可以用來控製程序的執行。

控制異常和事件處理:Debugger的預設設定是跳過首次異常(first chance expcetion),在二次異常(second chance exception)時中斷程式的執行。sx命令顯示Debugger的設定。sxesxd可以改變Debugger的設定。

sxe clr

可以控制Debugger在託管異常發生時中斷程式的執行。常用的Debugger事件有:

av 訪問異常

eh C++異常

clr 託管異常

ld 模組載入

-c 選項可以用來指定在事件發生時執行的調試命令。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.