2011年9月18日 星期日

[ DDK 文章收集 ] Windows Debuggers: Part 1: A WinDbg Tutorial (中文翻譯)

轉載自 這裡
前言 :
在我的職業生涯中,我看見大部分的人都使用 Visual Studio 來做 debug 的工作,但是實際上還有許多其他免費的 debugger 可以使用。你會想要有這樣 debugger 是有許多理由的,例如有時候在電腦沒有開發環境的時候,但是莫名其妙crash的問題卻是層出不窮。如果可以直接從 stack dump 中觀察,可以直接找出可能是某個軟體出了問題。
目前沒有一個快速的教學可以幫助你了解 windbg,因此這裡採用 example 教學的方式,讓你多了解 windbg 帶來 degger 工作中的便利與使用方式.
- WinDbg
WinDbg 是一個把 NTSD 和 KD 包裝起來的一個UI軟體,他提供 command line 的方式來輸入指令,指令大致上分成三種類型 : 

* regular commands (e.g.: k). The regular commands are to debug processes.
* dot commands (e.g.: .sympath). The dot commands are to control the debugger.
* extension commands (e.g.: !handle) – these are custom commands that you can add to WinDbg; they are implemented as exported functions in extension DLLs

- 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 就會被終止.
.lastevent
!analyze –v

以上兩行指令都可以顯示 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,請依造下來步驟來執行 :
Step 1: In WinDbg, File->’Open Crash Dump’, and point to the dump file
Step 2: WinDbg will show you the instruction your app was executing when it crashed.
Step 3: Set your symbol path and source path properly.

假如你的 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 方式來下指令. 指令的原型如下 :
.sympath+ SRV*downstream_store*http://msdl.microsoft.com/download/symbols

實際的指令可能像這個樣子 :
.sympath+ SRV*C:\symbols*http://msdl.microsoft.com/download/symbols

當我們從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. 執行命令如下 :
.reload

- Source Code Directories
你可以透過 windbg 上的 UI 來設定 symbol path (File->Source File Path) 或是用 .srcpath 從 windbg 的 command 來下指令.

- Breakingpoints, Tracing
* Set soft breakpoints using the bp commands or using the toolbar breakpoint icon.
* Set hard breakpoints using code like DbgBreakPoint() or KdBreakPoint().
* Use tracing routines DbgPrintKdPrintOutputDebugString to print out to the WinDbg output window, from debugger extension DLLs.

Example :
- Example 1
這個例子是說明有一個 thread 拿了 critical section 後並沒有釋放掉,造成另一條 thread 想要拿這個 critical section 的使用拿不到,並且無限的等待 :
  1. #include   
  2.   
  3. CRITICAL_SECTION cs;  
  4.   
  5. DWORD WINAPI newThreadProc(LPVOID lpParameter)  
  6. {  
  7.     InitializeCriticalSection(&cs);   
  8.     EnterCriticalSection(&cs);  
  9.     return 0;  
  10. }  
  11. void Example1()  
  12. {  
  13.     DWORD tid = 0;  
  14.     HANDLE hThread = CreateThread(NULL, 0, newThreadProc, NULL, 0, &tid); // let the thread execute immediately  
  15.   
  16.     WaitForSingleObject(hThread, INFINITE);  
  17.     EnterCriticalSection(&cs);  
  18. }  
  19. void main()  
  20. {  
  21.     Example1();  
  22. }  
在執行完程式後, 接著我們要做的就是讓 WinDbg attach 剛剛執行程式的 Process. 再打開 WinDbg 後請按 F6 快捷鍵或透過 Menu > File > Attach to a Process. 如下所示 :

接著在 windbg 中打入~*kb,會有下面的資料出在畫面上 :
~: Lists all threads
*: Specify process id
kb: Displays stack trace of current thread. Kb causes the display to include the first three parameters passed to each function.


我們可以發現 thread 0 停在 RtlEnterCriticalSection,合理的判斷是他拿不到 critical section,因此常試去解讀第一個 argument,裡頭帶有 critical section 的相關訊息。在 command 中打入 dt :
* Dt [dt module!typedef adr]: Dump type. Will dump the contents of the memory using typedef as a template.


我們發現此 critical section 被 0x00001004 thread 拿住了。然後再去查看目前所有的 thread id :

最後發現目前的所有 thread 並不包括 0x00001004,所以最後可以知道是一個生命週期已經結束的 thread 沒有正確釋放掉 critical section!

- Example 2
第二個例子滿簡單的,就是常見 Divide by zero 的 exception :
  1. void Example2()  
  2. {  
  3. int y = 71;  
  4. y = y / (71 - y);  
  5. }  
  6.   
  7. void main()  
  8. {  
  9. Example2();  
  10. }  
根據嘗試的結果,如果直接執行執行檔,在用 windbg 去 attatch to a process,無法直接看到此 exception 訊息,效果比較不好。如果透過 windbg 直接將此執行檔執行起來,可以很容易發現 divide by zero 的問題. 首先我們先透過 windbg 的 UI,File -> Open Executable,選擇 Example2 的執行檔。接著打入 g . 表示我們要開始跑這個程式,畫面如下 :
* g: The g command starts executing the given process or thread. Execution will halt at the end of the program, when BreakAddress is hit, or when another event causes the debugger to stop.


我們可以發現訊息中就顯示了 Integer divide-by-zero 的問題,接著我們打入 u eip,來看看目前程式執行的 assembly code :
u (Unassemble): The u command displays an assembly translation of the specified program code in memory.
eip: The eip register points to the memory address which the processor will next attempt to execute


大家如果對組語有一點熟悉的話,應該可以發現 idiv 就是整數除法的意思,接著來看看分母是多少,所以就要看 ecx 這個值,打入 r ecx :
r: The r command displays or modifies registers, floating-point registers, flags, pseudo-registers, and fixed-name aliases.


最後發現分母為0,在整數除法中是不允許的!

- Example 3
這個例子是幫助大家熟悉 windbg 中使用 break point 的操作。基本上就是將 break point 設定在 k = rand(); 這行程式碼上,然後列印出 k 的值 :
  1. #include   
  2.   
  3. void Example3()  
  4. {  
  5. int k = 0;  
  6. for (int i = 0; i < 5; ++i)  
  7. {  
  8. k = rand();  
  9. }  
  10. }  
  11.   
  12. void main()  
  13. {  
  14. Example3();  
  15. }  
首先,我們還是透過 File -> Open Executable 的方式來跑執行檔,然後打入 bp WinDbgExam!Example3,這樣我們就成功將 break point 設定在 Example3() 函式,接著打入g, 讓執行檔開始跑.
bp: sets a new breakpoint at the address of the breakpoint location that is specified in the command.
WinDbgExam!Example3: 這個欄位放的是module name,如果沒有是唯一性,debugger不會在symbol上產生混淆,是可以不用給定。Example3這個是 function name。"!" 這個符號是連接 module name 和 function name


接著很快就發現程式又停了下來,這就是我們剛剛的 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 來做驗證,發現全部都不見了 :
* bl: The bl command lists information about existing breakpoints
* bc: The bc command permanently removes previously set breakpoints from the system.



- Example 4
這個例子示範當我們將一個 constant value 指定給一個尚未初始化的指標時所產生的問題.
  1. #include   
  2.   
  3. void Example4()  
  4. {  
  5. int* i = NULL;  
  6. *i = 80;  
  7. }  
  8.   
  9. void main()  
  10. {  
  11. Example4();  
  12. }  
首先就直接讓執行檔跑起來吧,打入g :

從 windbg 中就已經發現有 access violation的問題了。那我們來看看目前的組語吧。打入 u :

看到第一行是一個 mov 的動作,大概就是常見的 eax裡面所含的記憶體位置是不合法的。打入 r eax,看一下他的內容 :


- Example 5
第五個例子是展示我們不小心將同一個記憶體位置區段做了兩次的 delete動作 :
  1. #include   
  2.   
  3. void Example5()  
  4. {  
  5. char* str = new char[20];  
  6. delete [] str;  
  7. delete [] str;     // error!  
  8. strcpy(str, "hi");  
  9. }  
  10.   
  11. void main()  
  12. {  
  13. Example5();  
  14. }  
首先先將執行檔跑起來,會跳出一個exception視窗警告你,這時候不要將視窗關掉,先用 windbg 做 attach 動作。然後直接打入 ~* 看一下目前所有的 process 資訊 :

應該可以發現 process 0 旁邊有個小句點,代表目前使用的是 process 0,但是如果我們的程式 WinDebugTest 卻是在 process 1。所以打入 ~1s 來做切換 thread 的動作。然後再重複打一次 ~* 看一下目前的情況.
* ~s (change current processor): The ~s command sets which processor is debugged on a multiprocessor system.

這個時候只要打 kb 觀察我們所在 thread 的 code stack 狀況 :

可以發現目前正處於彈出一個 message box 的狀態,然後仔細在看一下,有一行 MSVCR90D!operator delete+0xa0 ,看起來好像就是這行出了問題!

- Example 6
第六個例子是關於 stack over flow 的問題 :
  1. void Example6_2();  
  2.   
  3. void Example6_1()  
  4. {  
  5. Example6_2();  
  6. }  
  7.   
  8. void Example6_2()  
  9. {  
  10. Example6_1();  
  11. }  
  12.   
  13. void main()  
  14. {  
  15. Example6_1();  
  16. }  
首先就直接讓執行檔跑起來吧,打入g :

windbg 上面就顯示了"Stack overflow" !

補充說明 :
MSDN : EnterCriticalSection Function
Waits for ownership of the specified critical section object. The function returns when the calling thread is granted ownership.

1 則留言:

[Git 常見問題] error: The following untracked working tree files would be overwritten by merge

  Source From  Here 方案1: // x -----删除忽略文件已经对 git 来说不识别的文件 // d -----删除未被添加到 git 的路径中的文件 // f -----强制运行 #   git clean -d -fx 方案2: 今天在服务器上  gi...