Windbg 進程與線程 《第三篇》


  Windbg既可以顯示進程和線程列表,又可以顯示指定進程或線程的詳細信息。調試命令可以提供比taskmgr更詳盡的進程資料,在調試過程中不可或缺。

一、進程命令

  進程命令包括這些內容:顯示進程列表、進程環境塊、設置進程環境。

  1、進程列表

  多個命令可顯示進程列表,但一般只能在特定情況下使用,它們是:|、.this、!process、!dml_proc。

  豎線命令顯示當前被調試進程列表的狀態信息。

  • | [進程號]

  注意:被調試進程列表。大多數情況下,調試器中只有一個被調試進程,但可以通過.attach或者.create命令同時掛載或創建多個調試對象。當同時對多個進程調試時,進程號是從0開始的整數。切換第一篇中提過。

  • .tlist [選項] [模塊名]

  .tlist命令顯示當前系統中的進程列表,他是目前唯一可在用戶模式下顯示系統當前進程列表的命令。它有兩個可選項:-v顯示進程詳細信息,-c只顯示當前進程信息。

  內核模式下同樣可以使用.tlist,但更好的命令是!process。!process在內核模式下顯示進程列表,和指定進程的詳細信息,也能顯示進程中的線程和調用棧內容。典型格式如下

  • !process:顯示調試器當前運行進程信息
  • !process 0 0:顯示進程列表
  • !process PID:PID是進程ID,根據進程ID顯示此進程詳細信息。

  此外,還有一個DML版本的進程列表命令如下:

  • !ldm_proc [進程號|進程地址]

  此命令可以看成"|"和"process"命令的DML合並版本,可在用戶與內核模式下使用。顯示的進程信息偏重於線程和調用棧。用戶模式下次命令和"|"一樣,只能顯示被調試進程的信息。

  2、進程信息

  進程環境快是內核結構體,使用!peb命令參看其信息,但也可以用dt命令查看完整的結構體定義。格式如下:

  • !peb [地址]

  如果未設置PEB地址,則默認為當前進程。內核模式下可通過!process命令獲取PEB結構體地址;用戶模式下只能顯示當前進程的PEB信息,故而一般不帶參數。

  • dt nt!_peb 地址

  此命令顯示系統nt模塊中所定義的內核結構體PEB詳細內容。使用之前必須先熟悉結構體定義。

  3、進程切換

  進程環境的切換,將伴隨着與進程相關的寄存器、堆棧的切換。在不同進程環境中進程的調試結果有天壤之別。

  在內核模式中,進程切換不能使用 “| [進程號] s”了,不同於用戶模式下的被調試進程間切換,而是系統存在的多進程間切換。內核環境下,以進程地址作為參數,調用如下命令以進行進程環境切換。

  • .process [進程地址]

  如果不使用任何參數,.process命令將顯示當前進程地址。所謂進程地址,即ERPCESS結構體地址。

  或以頁目錄地址為參數,調用下面命令切換用戶地址空間:

  • .context [頁目錄地址]

  如果不使用任何參數,.context命令將顯示當前頁目錄地址。頁目錄地址就是!process命令中顯示的DirBase值。

  進程切換后,為了檢測是否正確切換,可再用!peb命令檢查當前進程的環境信息。

二、線程命令

  命令"~"能夠進行線程相關的操作。不帶任何參數的情況下,它列出當前調試進程的線程。

0:000> ~
.  0  Id: 152c.1530 Suspend: 0 Teb: 7ffdf000 Unfrozen
   1  Id: 152c.1524 Suspend: 0 Teb: 7ffdd000 Unfrozen

  使用此命令可進行的線程操作包括:線程切換、線程環境、線程時間等。

  1、線程冰凍

  參數f與u分別代表freeze和unfress,前者是指凍住指定線程,后者將被冰凍線程解凍。

  • ~2f

  表示把2號線程凍住,在解凍之前,不再分發CPU時間給它。

  若要讓指定線程重新運行,需使用參數u:

  • ~2u

  2、線程切換

  查看指定線程的信息,用下面的命令:

  • ~線程號

  線程號是由調試器軟件內部維護的線程ID值,是一個從0開始的整數,和線程ID不是一回事。

  線程信息中包括有線程環境塊地址,可通過!teb命令查看環境塊信息:

  • !teb [teb地址]

  如要在多線程間作切換,需使用~命令的s參數:

  • ~ 線程號 s

  由於線程號在外部是沒有太大意義的,所以另一個線程切換命令是以線程ID來標識一個線程的。這個命令比較奇怪,以雙波浪線打頭,格式如下:

  • ~~[線程ID] s

  注意這個命令中的[]並非可選符,而是命令的一部分。例如命令:~~[11a0] s,它將當前線程切換到線程ID為0x11a0的線程。線程ID是系統維護的系統唯一的ID值。

  線程切換實例:

0:000> ~
.  0  Id: 152c.1530 Suspend: 0 Teb: 7ffdf000 Frozen  
   1  Id: 152c.1524 Suspend: 0 Teb: 7ffdd000 Frozen  
0:000> ~~[1524]s
eax=00000000 ebx=7765fe8c ecx=00000002 edx=00000002 esi=0030d410 edi=00000000
eip=776770f4 esp=0076f82c ebp=0076f9c0 iopl=0         nv up ei ng nz ac po cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000293
ntdll!KiFastSystemCallRet:
776770f4 c3              ret
0:001> ~
#  0  Id: 152c.1530 Suspend: 0 Teb: 7ffdf000 Frozen  
.  1  Id: 152c.1524 Suspend: 0 Teb: 7ffdd000 Frozen  

  注意開始時0前面有1個英文句號。切換之后,英文句號變到1前面了。

  3、線程遍歷

  仍然是~命令。它出了能夠作為線程列表命令外,還可用來對線程進行遍歷,並執行指定命令。只需借助通配符"*"即可。

  • ~*k

  顯示所有線程棧信息(此命令意指:對所有線程執行k指令)。下圖中,當前進程共包含兩個線程,顯示了這兩個線程各自的棧信息:

0:001> ~*k

#  0  Id: 152c.1530 Suspend: 0 Teb: 7ffdf000 Frozen  
ChildEBP RetAddr  
0011facc 75adcde0 ntdll!KiFastSystemCallRet
0011fad0 75adce13 user32!NtUserGetMessage+0xc
0011faec 009c148a user32!GetMessageW+0x33
0011fb2c 009c16ec notepad!WinMain+0xe6
0011fbbc 7694ed6c notepad!_initterm_e+0x1a1
0011fbc8 776937eb kernel32!BaseThreadInitThunk+0xe
0011fc08 776937be ntdll!__RtlUserThreadStart+0x70
0011fc20 00000000 ntdll!_RtlUserThreadStart+0x1b

   1  Id: 152c.1524 Suspend: 0 Teb: 7ffdd000 Frozen  
ChildEBP RetAddr  
0076f828 77676a44 ntdll!KiFastSystemCallRet
0076f82c 7765fe39 ntdll!ZwWaitForMultipleObjects+0xc
0076f9c0 7694ed6c ntdll!TppWaiterpThread+0x33d
0076f9cc 776937eb kernel32!BaseThreadInitThunk+0xe
0076fa0c 776937be ntdll!__RtlUserThreadStart+0x70
0076fa24 00000000 ntdll!_RtlUserThreadStart+0x1b

  其他有用的遍歷指令包括:

  • ~*r

  顯示線程寄存器信息。

  • ~*e

  上面的e是execute(執行)的縮寫,后可跟一個或多個Windbg命令。它遍歷線程並對每個線程執行指定命令,如:

  • ~*e k;r

  此命令意為:在所用線程環境中(~*),分別執行(e)棧指令(k)和寄存器指令(r)。

~* e !clrstack    //列出所有線程的調用堆棧

  4、線程時間

  當軟件調試的時候,若發現某線程占用執行時間過多,就需要當心是否有問題。線程執行時間的多少,其實就是占用CPU執行工作的時間多少。某線程占用越多,其他線程占用的CPU時間就越少。

  線程的時間信息包括三個方面:自創建之初到現在的總消耗時間、用戶模式執行時間、內核模式執行時間。需要注意的是,消耗時間一定會遠遠大於用戶時間+內核時間,多出來的是大量空閑時間(為idle進程占用)。  使用下面的命令查看線程時間:

  • .ttime
  • !runaway 7

  在!runaway命令中加入標識值7,將顯示線程的全部三種時間值。

  這兩個命令的區別之處是,.ttime只能顯示當前線程的時間信息,!runaway能顯示當前進程的所有線程時間。

0:001> .ttime
Created: Thu May 15 08:50:17.973 2014 (UTC + 8:00)
Kernel:  0 days 0:00:00.000
User:    0 days 0:00:00.000
0:001> !runaway 7
 User Mode Time
  Thread       Time
   0:1530      0 days 0:00:00.031
   1:1524      0 days 0:00:00.000
 Kernel Mode Time
  Thread       Time
   0:1530      0 days 0:00:00.234
   1:1524      0 days 0:00:00.000
 Elapsed Time
  Thread       Time
   0:1530      0 days 0:04:40.263
   1:1524      0 days 0:04:40.026

 

三、異常與事件

  在調試器語境中,事件是一個基本概念,Windbg是事件驅動的。Windows操作系統的調試子系統,是“事件”的發生源。調試器的所有操作,都是因事件而動,因事件被處理而中繼。Windows定義了9類調試事件,異常是其中一類(ID為1)。所以異常和事件,這二者是前者包含於后者的關系。

  系統對各種異常和調試事件進行了分類,執行sx命令可以列出針對當前調試目標的異常或非異常事件的處理。

0:001> sx
  ct - Create thread - ignore
  et - Exit thread - ignore
 cpr - Create process - ignore
 epr - Exit process - ignore
  ld - Load module - output
  ud - Unload module - ignore
 ser - System error - ignore
 ibp - Initial breakpoint - ignore
 iml - Initial module load - ignore
 out - Debuggee output - output

  av - Access violation - break - not handled
asrt - Assertion failure - break - not handled
 aph - Application hang - break - not handled
 bpe - Break instruction exception - break
bpec - Break instruction exception continue - handled
  eh - C++ EH exception - second-chance break - not handled
 clr - CLR exception - second-chance break - not handled
clrn - CLR notification exception - second-chance break - handled
 cce - Control-Break exception - break
  cc - Control-Break exception continue - handled
 cce - Control-C exception - break
  cc - Control-C exception continue - handled
  dm - Data misaligned - break - not handled
dbce - Debugger command exception - ignore - handled
  gp - Guard page violation - break - not handled
  ii - Illegal instruction - second-chance break - not handled
  ip - In-page I/O error - break - not handled
  dz - Integer divide-by-zero - break - not handled
 iov - Integer overflow - break - not handled
  ch - Invalid handle - break
  hc - Invalid handle continue - not handled
 lsq - Invalid lock sequence - break - not handled
 isc - Invalid system call - break - not handled
  3c - Port disconnected - second-chance break - not handled
 svh - Service hang - break - not handled
 sse - Single step exception - break
ssec - Single step exception continue - handled
 sbo - Stack buffer overflow - break - not handled
 sov - Stack overflow - break - not handled
  vs - Verifier stop - break - not handled
vcpp - Visual C++ exception - ignore - handled
 wkd - Wake debugger - break - not handled
 wob - WOW64 breakpoint - break - handled
 wos - WOW64 single step exception - break - handled

   * - Other exception - second-chance break - not handled

  可以看到這幾個調試事件,當發生進程退出(Exit Process)和初始化斷點(Initial breakpoint)事件的時候,調試器應當被中斷(Break)。模塊加載(Load Modual)以及有調試輸出(Debuggen Output)時,需要輸出相關信息;其他的都被忽略掉,不做處理(Ignore)。我們分析以下前兩個事件。使用調試器調試記事本進程時,不管是用.attach掛載方式還是.create創建方式,在調試器正式侵入記事本進程前,都會有一個中斷(Initial breakpoint異常);調試開始后運行一段時間,在外面將記事本關閉,又會發生一個中斷(Exit process異常)。

  可以通過導航欄的Debug => Event Filters...打開事件設置對話框。這個對話框中列出了全部調試事件,用戶可分別對它們進行設置。

  

  這個對話框列出了對於當前調試會話可用的全部調試事件。針對每個調試事件,可設置其屬性。右列Execution和Continue兩組單選鍵,分別表示事件的中斷屬性與中繼屬性。右列Argument按鈕可設置調試事件執行參數(上圖中LoadModule事件有一個Kernel32.dll參數,即當Kernel32.dll模塊被加載時,調試器將被中斷),Commands按鈕可設置事件兩輪機會發生時的執行命令。

  • sxr:

  此命令將當前所有對調試事件的設置,恢復到調試器的默認設置。最后一個字母r表示Reset。

  • sx{e|d|n|i} -h

  這4個命令分別代表了圖8-38中Execution組(中斷屬性)中的四個按鈕,即Enable、Disable、Output、Ignore。Enable是開啟中斷,Disable是禁止事件中斷(但對於一場,只禁止第一輪機會,第二輪機會到來時仍會中斷到調試器),Output是禁止中斷但會輸出相關信息,Ignore表示完全忽略這個事件(對於異常,Output和Ignore兩選項使得兩輪機會都不會中斷到調試器)。

  • .lastevent

  顯示最近發生的一個調試事件,往往是導致中斷發生的那個。

0:001> .lastevent
Last event: 152c.1530: Break instruction exception - code 80000003 (first/second chance not available)
  debugger time: Thu May 15 13:40:01.914 2014 (UTC + 8:00)
  • .exr:

      此命令顯示一個異常記錄的詳細內容,傳入一個異常記錄地址:

  • .exr 記錄地址

  如果僅僅為了顯示最近的一條異常記錄,可以用-1代替異常記錄地址:

  • .exr -1

  由於異常是事件的一種,所以使用.exr -1命令得到的異常,可能和使用.lastevent命令獲取的事件,是同一個。但二者顯示的信息各有側重點。

0:001> .exr -1
ExceptionAddress: 00000000
   ExceptionCode: 80000003 (Break instruction exception)
  ExceptionFlags: 00000000
NumberParameters: 0
  • .bugcheck

  此命令不帶參數。在內核環境下,顯示當前bug check的詳細信息;可用於活動調試或者crash dump調試環境中。用戶環境中,此命令不可用。

  • !analyze

  此命令分析當前最近的異常事件(如果在進行dump分析,則是bug check),並顯示分析結果。這個異常事件,就是上面.lastevent命令對應的事件。

  1. -v:顯示異常的詳細信息,這個選項在調試錯誤的時候,最有用。
  2. -f:f是force的縮寫。強制將任何事件都當作異常來分析,即使僅僅是普通的斷點事件。將因此多輸出一些內容。
  3. -hang:這個選項很有用,對於遇到死鎖的情況,它會分析原因。在內核環境中,它分析內核鎖和DPC棧;在用戶環境中,它分析線程的調用棧。用戶環境中,調試器只會對當前線程進行分析,所以一定要將線程環境切換到最可能引起問題的那個線程中去,才有幫助。這個參數非常有用,當真的遇到死鎖時,它可以救命(另一個分析死鎖的有效命令是!locks)。
  4. -show bug-check-代碼 [參數]:在內核環境下,顯示指定的bug check的詳細信息。
  • !error

  此命令和VC里面內置的errlook工具類似。用來根據錯誤碼,查看對應的可讀錯誤信息。微軟系統中常用的全局錯誤碼有兩套,一套是Win32錯誤碼,通過函數GetLastError()獲得的值;另一套是NTSTATUS值。!error命令對這二者都能支持。區別的方法,若錯誤碼后面無參數1,則為Win32錯誤碼;否則就是NTSTATUS錯誤碼。

  比如,獲取錯誤碼為2的Win32錯誤信息,可用:!error 2

  獲取錯誤碼為2的NTSTATUS錯誤信息,可用:!error 2 1

  • !gle

  此命令是Get Last Error的縮寫。它調用Win32接口函數GetLastError()取得線程的錯誤值,並打印分析結果。如果帶有-all選項,則針對當前進程的所有線程執行GetLastError()操作;否則僅針對當前線程。

  • gh/gn

  這兩個命令是g命令的擴展。

  1. gh:是go with Exception handled的縮寫,意思是:把異常標識為已處理並繼續執行程序;gh的作用在於,當遇到某個可以忽略的非致命異常時,將它先跳過,繼續執行程序。
  2. gn:是go with Exception not handled的縮寫,對異常不進行任何處理,而繼續執行程序。這時候,程序自己的異常處理模塊將有機會處理異常。

四、局部變量

  有兩個命令可以打印當前的局部變量列表:x和dv。x第二篇已講過,dv是Display local Variable的縮寫。

  上圖無法體現dv比x命令在顯示局部變量上的高明之處。dv命令有幾個開關選項。

  • /v:顯示虛擬地址(virtual);
  • /i:顯示變量詳細信息(information),包括局部變量、全局變量、形參、函數變量等。
  • /t:顯示變量類型(type),如int、char等等。
  • /f:可指定進行分析的函數,需指定函數名。

  命令中選項/f wmain是指針對wmain函數(即_tmain)分析其局部變量。看第一個變量argc,"prv param"對應/i開關選項;"@ebp_0x08"對應/v開關選項;"int"對應/t開關選項。

五、顯示類型

  利用dt命令可以查看結構體的類型定義。命令dt是Display Type的縮寫。當我們要查看一些內核結構體的定義時,dt命令是最直接有效的手段。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM