前言 :
在我的職業生涯中,我看見大部分的人都使用 Visual Studio 來做 debug 的工作,但是實際上還有許多其他免費的 debugger 可以使用。你會想要有這樣 debugger 是有許多理由的,例如有時候在電腦沒有開發環境的時候,但是莫名其妙crash的問題卻是層出不窮。如果可以直接從 stack dump 中觀察,可以直接找出可能是某個軟體出了問題。
目前沒有一個快速的教學可以幫助你了解 windbg,因此這裡採用 example 教學的方式,讓你多了解 windbg 帶來 degger 工作中的便利與使用方式.
- WinDbg
WinDbg 是一個把 NTSD 和 KD 包裝起來的一個UI軟體,他提供 command line 的方式來輸入指令,指令大致上分成三種類型 :
- PDB Files
PDB files 是由 Linker 產生的 program database,Private PDB files 包含了 private symbols、public symbols、source lines、types、locals、globals 的資訊。而 Puliblic PDB files 不包含types、locals、globals 的資訊.
Debugging Scenario :
- Debugging Exception
debugger 有兩次處理 exeption 的機會。當 exception 發生時,debugger 會在 application 處理此exception 之前獲得通知 ("first-chance exception")。假如 application 沒有去主動處理這個exception,debugger 仍然有第二次機會來處理這個 exception ("second-chance exception")。最後假如 debugger 也沒有嘗試解決此exception,那麼 application 就會被終止.
以上兩行指令都可以顯示 exception 發生時的 exception record 和 stack trace of the function。你也可以使用 .exr, .cxr 和 .ecxr 指令來顯示 exception 和 context records.
WinDbg Feature :
- DumpFiles
可以透過 dump 的功能把 process 在某個時間點上的狀態 sanpshop 下來。一個 mini-dump 的檔案大小是滿小的,除非你使用 full-memory minidump (.dump /mf)。通常我們也會將 handle 的資訊 dump 下來 (.dump/mfh)。mini-dump 包含所有 thread 的資訊,例如 stacks 和 list of loaded modulesm,而 full dump 則包含了較多的資訊,像是 the process heap.
- Crash Dump Analysis
如果 crash 發生在 Window OS上的時候,我們可以將當時的狀況給 dump 下來。首先打開 Task Manager ,在 Processes Tab 中找到發生 crash 的那個 process,滑鼠作 right - click 會跳出 pop up menu,接著選擇 Create Dump File,稍等一點時間,會在跳出一個 message box 告訴你 dump file 在哪裡,這個 dump file 裡頭就有當時 crash 的相關資訊,windbg 就可以利用這份資料來做分析. 想要分析 dump file,請依造下來步驟來執行 :
假如你的 symbol 沒辦法 match,你可能會有一個很艱難的時候去了解程式的流程。如果 symbol 可以和source file 相符合的話,你可能就可以很快找到問題的所在點.
WinDbg Settings :
- Symbol Files and Directories
通常我們在除錯的過程中都需要symbol來幫助我們更有效的除錯。Symbol files 可以是 COFF 或是 PDB 的格式。Private symbol files 包含 functions、local and global variables 和 line information,line information用來幫助 assmbly code 對應到 source code。通常我們將 public symbol files 提供給客戶,裡頭只包含 public members 相關的資訊.
你可以透過 windbg 上的 UI 來設定 symbol path (File->Symbol File Path),或者透過 windbg 中的 command 方式來下指令. 指令的原型如下 :
實際的指令可能像這個樣子 :
當我們從symbol server上下載 symbol 後會存放到 C:\symbols 裡頭。而 Microsoft 提供的只有 public symbol files。假如你只有 public symbol file,你只可以從 call stack 中看見 function names and their arguments。假如 binary (DLL or exe) 和 PDB 都是從你自己的 applicaiton 中產生的話,你可以額外看見 private functions, local variables and type information.
有時候可能在使用 windbg 的時候,發現顯示出來的 call stack 或是其他資訊有點怪怪時,可以重新 reload symbol,因為有時候 windbg 會偷懶,直接找近似的 symbol 來做 match. 執行命令如下 :
- Source Code Directories
你可以透過 windbg 上的 UI 來設定 symbol path (File->Source File Path) 或是用 .srcpath 從 windbg 的 command 來下指令.
- Breakingpoints, Tracing
Example :
- Example 1
這個例子是說明有一個 thread 拿了 critical section 後並沒有釋放掉,造成另一條 thread 想要拿這個 critical section 的使用拿不到,並且無限的等待 :
- #include
- CRITICAL_SECTION cs;
- DWORD WINAPI newThreadProc(LPVOID lpParameter)
- {
- InitializeCriticalSection(&cs);
- EnterCriticalSection(&cs);
- return 0;
- }
- void Example1()
- {
- DWORD tid = 0;
- HANDLE hThread = CreateThread(NULL, 0, newThreadProc, NULL, 0, &tid); // let the thread execute immediately
- WaitForSingleObject(hThread, INFINITE);
- EnterCriticalSection(&cs);
- }
- void main()
- {
- Example1();
- }
接著在 windbg 中打入~*kb,會有下面的資料出在畫面上 :
我們可以發現 thread 0 停在 RtlEnterCriticalSection,合理的判斷是他拿不到 critical section,因此常試去解讀第一個 argument,裡頭帶有 critical section 的相關訊息。在 command 中打入 dt :
我們發現此 critical section 被 0x00001004 thread 拿住了。然後再去查看目前所有的 thread id :
最後發現目前的所有 thread 並不包括 0x00001004,所以最後可以知道是一個生命週期已經結束的 thread 沒有正確釋放掉 critical section!
- Example 2
第二個例子滿簡單的,就是常見 Divide by zero 的 exception :
- void Example2()
- {
- int y = 71;
- y = y / (71 - y);
- }
- void main()
- {
- Example2();
- }
我們可以發現訊息中就顯示了 Integer divide-by-zero 的問題,接著我們打入 u eip,來看看目前程式執行的 assembly code :
大家如果對組語有一點熟悉的話,應該可以發現 idiv 就是整數除法的意思,接著來看看分母是多少,所以就要看 ecx 這個值,打入 r ecx :
最後發現分母為0,在整數除法中是不允許的!
- Example 3
這個例子是幫助大家熟悉 windbg 中使用 break point 的操作。基本上就是將 break point 設定在 k = rand(); 這行程式碼上,然後列印出 k 的值 :
- #include
- void Example3()
- {
- int k = 0;
- for (int i = 0; i < 5; ++i)
- {
- k = rand();
- }
- }
- void main()
- {
- Example3();
- }
接著很快就發現程式又停了下來,這就是我們剛剛的 break point 的功勞。如果有設定 source path 或者有時候 windbg 很聰明,會自動將執行檔位置的 source 自動讀取進來,現在應該可以發現 source 已經顯示在 windbg 上了。接著按 F10 三次,試著將程式執行的位置移動到 k = rand()。這時候我們打入 ~*kb 來觀察一下目前的 code stack,可以發現 k = rand() 這行程式的記憶體位置應該是 Example3+0x3d :
接著我們要先將原來的 break point 刪除,打入 bl 將所有的 break point 顯示出來。接著打bc0 將編號 0的 break point 刪除,為了確定真得已經刪除掉了,再次打入 bl 來做驗證,發現全部都不見了 :
- Example 4
這個例子示範當我們將一個 constant value 指定給一個尚未初始化的指標時所產生的問題.
- #include
- void Example4()
- {
- int* i = NULL;
- *i = 80;
- }
- void main()
- {
- Example4();
- }
從 windbg 中就已經發現有 access violation的問題了。那我們來看看目前的組語吧。打入 u :
看到第一行是一個 mov 的動作,大概就是常見的 eax裡面所含的記憶體位置是不合法的。打入 r eax,看一下他的內容 :
- Example 5
第五個例子是展示我們不小心將同一個記憶體位置區段做了兩次的 delete動作 :
- #include
- void Example5()
- {
- char* str = new char[20];
- delete [] str;
- delete [] str; // error!
- strcpy(str, "hi");
- }
- void main()
- {
- Example5();
- }
應該可以發現 process 0 旁邊有個小句點,代表目前使用的是 process 0,但是如果我們的程式 WinDebugTest 卻是在 process 1。所以打入 ~1s 來做切換 thread 的動作。然後再重複打一次 ~* 看一下目前的情況.
這個時候只要打 kb 觀察我們所在 thread 的 code stack 狀況 :
可以發現目前正處於彈出一個 message box 的狀態,然後仔細在看一下,有一行 MSVCR90D!operator delete+0xa0 ,看起來好像就是這行出了問題!
- Example 6
第六個例子是關於 stack over flow 的問題 :
- void Example6_2();
- void Example6_1()
- {
- Example6_2();
- }
- void Example6_2()
- {
- Example6_1();
- }
- void main()
- {
- Example6_1();
- }
windbg 上面就顯示了"Stack overflow" !
補充說明 :
* MSDN : EnterCriticalSection Function
學習了!
回覆刪除