通往WinDbg的捷徑(二)?

原文:http://www.debuginfo.com/articles/easywindbg2.html
譯者:arhat
時(shí)間:2006年4月14日
關(guān)鍵詞:CDB?WinDbg?

保存?dumps?
在我們調(diào)試不容易重現(xiàn)的問題時(shí),可能想把應(yīng)用程序狀態(tài)的快照(內(nèi)存內(nèi)容,打開名柄的列表,等等)保存起來(lái),以便日后分析。例如,當(dāng)我懷疑當(dāng)前的狀態(tài)可能包含我試圖解決的問題的關(guān)鍵點(diǎn),而想繼續(xù)運(yùn)行應(yīng)用程序來(lái)查看情形怎樣發(fā)展時(shí),它就很有用了。有時(shí)候,我會(huì)做一系列的快照,一個(gè)接一個(gè),以便稍后我能比較它們,查看在應(yīng)用程序運(yùn)行時(shí)有些數(shù)據(jù)結(jié)構(gòu)怎樣變化。當(dāng)我最終能重現(xiàn)這個(gè)問題時(shí),我總是創(chuàng)建一個(gè)快照來(lái)確保我沒有因?yàn)槟承╁e(cuò)誤(錯(cuò)誤關(guān)閉了調(diào)試會(huì)話)而丟失有價(jià)值的信息。或許,大家不難猜到當(dāng)我說“快照”時(shí),我真正的意思是“minidump”,因?yàn)閙inidump為隨時(shí)保存應(yīng)用程序的狀態(tài)提供了便利。
下面是創(chuàng)建minidump的命令行示例:
??cdb?-pv?-pn?myapp.exe?-c?".dump?/m?c:\myapp.dmp;q"
讓我們仔細(xì)看一下.dump命令。在上面的例子里,我們只用到這條命令的一個(gè)選項(xiàng)(/m),后面跟著minidump的文件名。用/m來(lái)指定minidump里應(yīng)當(dāng)包括哪種信息。最重要的(依我之見)/m選項(xiàng)的變量列在下表中:
---------------------------------------------------------------------------------------------------------------------------
選項(xiàng)??????描述??????????????????????????????????????????????????????????????????????????????????????????????例子
---------------------------------------------------------------------------------------------------------------------------
/m????????默認(rèn)就是這個(gè)選項(xiàng)。它創(chuàng)建標(biāo)準(zhǔn)的minidump,等同于MiniDumpNormal?minidump類型。由此生成的minidump
??????????一般很小,因此,如果你想通過慢速的網(wǎng)絡(luò)傳輸minidump,那么這個(gè)選項(xiàng)非常有用。但不幸地是,小體積的
??????????minidump也意味著在大多數(shù)情況下,它包含的信息不足以進(jìn)行完整的分析(你可以在這篇文章里找到更多有
??????????關(guān)minidump內(nèi)容的信息)。?????????????????????????????????????????????????????????????????????dump?/m?c:\myapp.dmp
---------------------------------------------------------------------------------------------------------------------------
/ma???????帶所有可選項(xiàng)的Minidump(完整的內(nèi)存內(nèi)容,名柄,已卸載的模塊,等等),由此生成的minidump將非常
??????????大。如果可以隨意使用磁盤空間,這個(gè)選項(xiàng)將非常適合本地調(diào)試。???????????????????????????????.dump?/ma?c:\myapp.dmp
---------------------------------------------------------------------------------------------------------------------------
/mFhutwd??這個(gè)選項(xiàng)將生成帶數(shù)據(jù)段,非共享讀/寫內(nèi)存頁(yè)和其它有用信息的minidump。如果你想盡可能的收集信息,
??????????但仍想使minidump保持小體積(并壓縮),就可以用這個(gè)選項(xiàng)。????????????????????????????????.dump?/mFhutwd?c:\myapp.dmp
---------------------------------------------------------------------------------------------------------------------------
下面的命令生成包含所有信息的minidump:
??cdb?-pv?-pn?myapp.exe?-c?".dump?/ma?c:\myapp.dmp;q"
如果我們想生成一個(gè)新minidump,并覆蓋已有的,該怎么辦呢?在默認(rèn)情況下,.dump命令不允許這樣做??它會(huì)抱怨文件已經(jīng)存在。為了改變默認(rèn)行為,覆蓋已存在的.dump文件,我們可以用/o選項(xiàng):
??cdb?-pv?-pn?myapp.exe?-c?".dump?/ma?/o?c:\myapp.dmp;q"
如果我們想生成一系列的minidump,一個(gè)接一個(gè),那么它能很方便的為minidump命名,并使文件名反映生成minidump時(shí)的時(shí)間嗎。嗯,如果我們指定了/u選項(xiàng),.dump命令就可以自動(dòng)為我們這樣做,這真是一個(gè)好消息,不是嗎?例如,下面的命令可以生成名為myapp_02CC_2006-01-28_04-11-18-171_0158.dmp的minidump(0158是進(jìn)程ID):
??cdb?-pv?-pn?myapp.exe?-c?".dump?/m?/u?c:\myapp.dmp;q"
.dump命令也支持其它有趣的選項(xiàng)(你可以在文檔里發(fā)現(xiàn)它們)。
如果你想生成運(yùn)行在Visual?Studio調(diào)試器下的進(jìn)程的minidump,我建議在生成dump前,先在Visual?Studio里臨時(shí)禁用所有的斷點(diǎn)。如果沒有禁用斷點(diǎn),生成的minidump將包含Visual?Studio調(diào)試器插入目標(biāo)進(jìn)程代碼里的斷點(diǎn)指令(int?3)。

分析故障轉(zhuǎn)儲(chǔ)
CDB也可以用于自動(dòng)分析故障轉(zhuǎn)儲(chǔ)。當(dāng)我們分析故障轉(zhuǎn)儲(chǔ)時(shí),通常會(huì)執(zhí)行同樣的操作,所以可以把這些操作自動(dòng)化。什么樣的操作呢?這要看故障轉(zhuǎn)儲(chǔ)的類型。我把所有的故障轉(zhuǎn)儲(chǔ)分成兩大類:
???帶異常信息的故障轉(zhuǎn)儲(chǔ)
???不帶異常信息的故障轉(zhuǎn)儲(chǔ)
當(dāng)應(yīng)用程序引發(fā)未經(jīng)處理的異常并調(diào)用just-in-time調(diào)試器(Dr.?Watson,NTSD??,?或其它的調(diào)試器),或者用為未經(jīng)處理的異常定制的過濾器?生成minidump時(shí),通常會(huì)生成帶異常信息的故障轉(zhuǎn)儲(chǔ)。通過寫入故障轉(zhuǎn)儲(chǔ)里的異常信息,我們可以確定異常的類型和發(fā)生時(shí)它在代碼里的位置。當(dāng)我們想為以后的分析生成進(jìn)程的快照時(shí)(例如,這方面的描述參見本文的前一部分“保存dumps”),通常手動(dòng)生成不帶異常信息的故障轉(zhuǎn)儲(chǔ)。
當(dāng)我們調(diào)試帶異常信息的故障轉(zhuǎn)儲(chǔ)時(shí),通常想知道下面這些信息:
???異常在代碼中出現(xiàn)的位置(地址,源文件和行號(hào))
???異常發(fā)生時(shí)的調(diào)用棧
???調(diào)用棧上一些或所有函數(shù)的參數(shù)值和局部變量
WinDbg和CDB為調(diào)試故障轉(zhuǎn)儲(chǔ)提供了非常有用的命令??!analyze。這條命令分析故障轉(zhuǎn)儲(chǔ)里的異常信息,確定異常發(fā)生的位置,調(diào)用棧,并顯示詳細(xì)的報(bào)告。下面是這條命令的示例:
??cdb?-z?c:\myapp.dmp?-logo?out.txt?-lines?-c?"!analyze?-v;q"
(-v選項(xiàng)要求!analyze輸出詳細(xì)的內(nèi)容)
CrashDemo.cpp?例子演示了怎樣用定制的過濾器捕獲未經(jīng)處理的異常并生成minidumps。如果你編譯并運(yùn)行它,然后用上述的CDB命令分析生成的minidump,你將可以得到和下面類似的輸出內(nèi)容:
0:001>?!analyze?-v
*******************************************************************************
*?????????????????????????????????????????????????????????????????????????????*
*????????????????????????Exception?Analysis???????????????????????????????????*
*?????????????????????????????????????????????????????????????????????????????*
*******************************************************************************

FAULTING_IP:?
CrashDemo!TestFunc+2e?[c:\tests\crashdemo\crashdemo.cpp?@?124]
004309de?c70000000000?????mov?????dword?ptr?[eax],0x0

EXCEPTION_RECORD:??ffffffff?--?(.exr?ffffffffffffffff)
.exr?ffffffffffffffff
ExceptionAddress:?004309de?(CrashDemo!TestFunc+0x0000002e)
ExceptionCode:?c0000005?(Access?violation)
ExceptionFlags:?00000000
NumberParameters:?2
Parameter[0]:?00000001
Parameter[1]:?00000000
Attempt?to?write?to?address?00000000

DEFAULT_BUCKET_ID:??APPLICATION_FAULT

PROCESS_NAME:??CrashDemo.exe

ERROR_CODE:?(NTSTATUS)?0xc0000005?-?The?instruction?at?"0x%08lx"?referenced?memory?
??at?"0x%08lx".?The?memory?could?not?be?"%s".

WRITE_ADDRESS:??00000000?

BUGCHECK_STR:??ACCESS_VIOLATION

LAST_CONTROL_TRANSFER:??from?0043096e?to?004309de

STACK_TEXT:
006afe88?0043096e?00000000?00354130?00350001?CrashDemo!TestFunc+0x2e?
??[c:\tests\crashdemo\crashdemo.cpp?@?124]
006aff6c?00430f31?00000000?52319518?00354130?CrashDemo!WorkerThread+0x5e?
??[c:\tests\crashdemo\crashdemo.cpp?@?115]
006affa8?00430ea2?00000000?006affec?7c80b50b?CrashDemo!_callthreadstartex+0x51?
??[f:\rtm\vctools\crt_bld\self_x86\crt\src\threadex.c?@?348]
006affb4?7c80b50b?00355188?00354130?00350001?CrashDemo!_threadstartex+0xa2?
??[f:\rtm\vctools\crt_bld\self_x86\crt\src\threadex.c?@?331]
006affec?00000000?00430e00?00355188?00000000?kernel32!BaseThreadStart+0x37


FOLLOWUP_IP:?
CrashDemo!TestFunc+2e?[c:\tests\crashdemo\crashdemo.cpp?@?124]
004309de?c70000000000?????mov?????dword?ptr?[eax],0x0

SYMBOL_STACK_INDEX:??0

FOLLOWUP_NAME:??MachineOwner

SYMBOL_NAME:??CrashDemo!TestFunc+2e

MODULE_NAME:??CrashDemo

IMAGE_NAME:??CrashDemo.exe

DEBUG_FLR_IMAGE_TIMESTAMP:??43dc6ee7

STACK_COMMAND:??.ecxr?;?kb

FAILURE_BUCKET_ID:??ACCESS_VIOLATION_CrashDemo!TestFunc+2e

BUCKET_ID:??ACCESS_VIOLATION_CrashDemo!TestFunc+2e

Followup:?MachineOwner
---------
注意用粗體表示的內(nèi)容。第一處報(bào)告了異常的地址和類型。第二外報(bào)告調(diào)用棧。第三處為我們提供了怎樣訪問保存在故障轉(zhuǎn)儲(chǔ)里的異常信息的額外信息。
現(xiàn)在,我們知道異常發(fā)生的位置,甚至可以查看調(diào)用棧。那么,是得到函數(shù)的參數(shù)值及局部變量的時(shí)候了。在開始之前,讓我們注意!analyze報(bào)告中的第三處信息。這里再重復(fù)一下第三處所包含的內(nèi)容:
STACK_COMMAND:??.ecxr?;?kb
對(duì)'kb'命令我們已經(jīng)不陌生了(它顯示調(diào)用棧)。但.ecxr是什么?這條命令要求調(diào)試器把當(dāng)前的內(nèi)容切換到保存在故障轉(zhuǎn)儲(chǔ)里的異常信息。我們執(zhí)行這條命令后,將能訪問異常拋出時(shí)調(diào)用棧和局部變量的值。
在我們要求調(diào)試使用異常的上下文后,我們可以用'dv'命令顯示函數(shù)的參數(shù)值以及局部變量。因?yàn)槲覀兺ǔO氩榭凑{(diào)用棧上每一個(gè)函數(shù)的信息,因此,我們可以用'!for_each_frame?dv?/t'命令(/t選項(xiàng)要求'dv'顯示有用的類型信息)。(當(dāng)然,我們必須記住,使用優(yōu)化編譯時(shí),在函數(shù)的整個(gè)生存期中,局部變量有可能會(huì)被取消,重注冊(cè)或被重用來(lái)保存其它的數(shù)據(jù),因此,可能會(huì)導(dǎo)致'dv'命令輸出錯(cuò)誤的值)。
下面是分析帶異常信息的故障轉(zhuǎn)儲(chǔ)的命令行示例:
??cdb?-z?c:\myapp.dmp?-logo?out.txt?-lines?-c?"!analyze?-v;.ecxr;!for_each_frame?dv?/t;q"
下面是'!for_each_frame?dv?/t'命令輸出的例子:
_?_?_?_?_?_?_?_?_?_?_?_?_?_?_?_
00?006afe88?0043096e?CrashDemo!TestFunc+0x2e?[c:\tests\crashdemo\crashdemo.cpp?@?124]
int?*?pParam?=?0x00000000
_?_?_?_?_?_?_?_?_?_?_?_?_?_?_?_
01?006aff6c?00430f31?CrashDemo!WorkerThread+0x5e?[c:\tests\crashdemo\crashdemo.cpp?@?115]
void?*?lpParam?=?0x00000000
int?*?TempPtr?=?0x00000000
_?_?_?_?_?_?_?_?_?_?_?_?_?_?_?_
02?006affa8?00430ea2?CrashDemo!_callthreadstartex+0x51?
??[f:\rtm\vctools\crt_bld\self_x86\crt\src\threadex.c?@?348]
struct?_tiddata?*?ptd?=?0x00355188
_?_?_?_?_?_?_?_?_?_?_?_?_?_?_?_
03?006affb4?7c80b50b?CrashDemo!_threadstartex+0xa2?
??[f:\rtm\vctools\crt_bld\self_x86\crt\src\threadex.c?@?331]
void?*?ptd?=?0x00355188
struct?_tiddata?*?_ptd?=?0x00000000
_?_?_?_?_?_?_?_?_?_?_?_?_?_?_?_
04?006affec?00000000?kernel32!BaseThreadStart+0x37
Unable?to?enumerate?locals,?HRESULT?0x80004005
Private?symbols?(symbols.pri)?are?required?for?locals.
Type?".hh?dbgerr005"?for?details.
_?_?_?_?_?_?_?_?_?_?_?_?_?_?_?_
00?006afe88?0043096e?CrashDemo!TestFunc+0x2e?[c:\tests\crashdemo\crashdemo.cpp?@?124]
如果minidump沒有包括目標(biāo)進(jìn)程內(nèi)存的完整內(nèi)容,那么只有當(dāng)調(diào)試器能正確發(fā)現(xiàn)被目標(biāo)進(jìn)程加載的、相同版本的可執(zhí)行模塊時(shí),才能分析dump。在某些情形下,你必須幫助調(diào)試器定位這些模塊??通過指定模塊搜索路徑。關(guān)于模塊搜索路徑的詳細(xì)信息和相關(guān)內(nèi)容可以在這篇文章?中找到。

現(xiàn)在,我們來(lái)處理不帶異常信息的故障轉(zhuǎn)儲(chǔ)。當(dāng)我們分析這樣的dump時(shí),通常想知道所有線程的調(diào)用棧。下面是怎樣得到這些信息:
??cdb?-z?c:\myapp.dmp?-logo?out.txt?-lines?-c?"~*kb;q"
如果我們不知道故障轉(zhuǎn)儲(chǔ)是否包含異常信息,該怎么做呢?對(duì)于minidumps來(lái)說,我們可以用MiniDumpView?打印dump的內(nèi)容,查看它里面是否包含異常信息。對(duì)于過時(shí)的'full?user?dumps',或許唯一的選擇是,照現(xiàn)在的樣子啟動(dòng)包含異常信息的dump,并查看!analyze是否報(bào)告了有意義的內(nèi)容。
有一個(gè)有趣的特例??因?yàn)槲唇?jīng)處理的異常生成故障轉(zhuǎn)儲(chǔ),但因?yàn)槟承┰驔]有包含異常信息是有可能的。在這種情形下,在下面過程的幫助下,仍可能找出異常發(fā)生的位置:
1.??打印所有線程的調(diào)用棧(用前面提過的CDB命令)。
2.??找出包含kernel32!UnhandledExceptionFilter函數(shù)調(diào)用棧的線程。
3.??使用UnhandledExceptionFilter?函數(shù)的第一個(gè)參數(shù)(包含一個(gè)指向EXCEPTION_POINTERS?結(jié)構(gòu)的指針)的實(shí)際值。
下面是EXCEPTION_POINTERS?結(jié)構(gòu)的聲明:
typedef?struct?_EXCEPTION_POINTERS?
{??
????PEXCEPTION_RECORD?ExceptionRecord;??
????PCONTEXT?ContextRecord;
}?EXCEPTION_POINTERS,?*PEXCEPTION_POINTERS;
如果我們知道這個(gè)結(jié)構(gòu)的地址,就能得到指向異常上下文的指針(保存在ContextRecord字段里),把它傳遞給.cxr命令,從而把調(diào)試器上下文切換到異常發(fā)生的位置。在.cxr命令執(zhí)行后,我們可以用'kb'命令得到異常發(fā)生時(shí)的調(diào)用棧。下面是一個(gè)例子:
1.??打印所有線程的調(diào)用棧。
??cdb?-z?c:\myapp.dmp?-logo?out.txt?-c?"~*kb;q"
0:000>?~*kb

.??0??Id:?6c4.73c?Suspend:?1?Teb:?7ffdf000?Unfrozen
ChildEBP?RetAddr??Args?to?Child??????????????
0012fdf8?7c90d85c?7c8023ed?00000000?0012fe2c?ntdll!KiFastSystemCallRet
0012fdfc?7c8023ed?00000000?0012fe2c?0012ff54?ntdll!NtDelayExecution+0xc
0012fe54?7c802451?0036ee80?00000000?0012ff54?kernel32!SleepEx+0x61
0012fe64?00430856?0036ee80?00330033?00300037?kernel32!Sleep+0xf
0012ff54?00431702?00000001?00352ed0?00352fb0?CrashDemo!wmain+0x96
0012ffb8?004314bd?0012fff0?7c816d4f?00330033?CrashDemo!__tmainCRTStartup+0x232
0012ffc0?7c816d4f?00330033?00300037?7ffd9000?CrashDemo!wmainCRTStartup+0xd
0012fff0?00000000?0042e5a5?00000000?00000000?kernel32!BaseProcessStart+0x23

???1??Id:?6c4.5cc?Suspend:?1?Teb:?7ffde000?Unfrozen
ChildEBP?RetAddr??Args?to?Child??????????????
006af6e4?7c90e273?7c863130?d0000144?00000004?ntdll!KiFastSystemCallRet
006af6e8?7c863130?d0000144?00000004?00000000?ntdll!NtRaiseHardError+0xc
006af96c?00438951?006af9e0?5d343834?00000000?kernel32!UnhandledExceptionFilter+0x59c
006af990?00430f2a?c0000005?006af9e0?0044ad30?CrashDemo!_XcptFilter+0x61
006af99c?0044ad30?00000000?00000000?00000000?CrashDemo!_callthreadstartex+0x7a
006af9b0?00438c67?00430f13?0049a230?00000000?CrashDemo!_EH4_CallFilterFunc+0x12
006af9e8?7c9037bf?006afad4?006aff98?006afaf0?CrashDemo!_except_handler4+0xb7
006afa0c?7c90378b?006afad4?006aff98?006afaf0?ntdll!ExecuteHandler2+0x26
006afabc?7c90eafa?00000000?006afaf0?006afad4?ntdll!ExecuteHandler+0x24
006afabc?004309be?00000000?006afaf0?006afad4?ntdll!KiUserExceptionDispatcher+0xe
006afe88?0043094e?00000000?00354130?00350001?CrashDemo!TestFunc+0x2e
006aff6c?00430f01?00000000?647bff58?00354130?CrashDemo!WorkerThread+0x5e
006affa8?00430e72?00000000?006affec?7c80b50b?CrashDemo!_callthreadstartex+0x51
006affb4?7c80b50b?00355188?00354130?00350001?CrashDemo!_threadstartex+0xa2
006affec?00000000?00430dd0?00355188?00000000?kernel32!BaseThreadStart+0x37
2.??改變調(diào)試器的上下文,得到異常的調(diào)用棧。
??cdb?-z?c:\myapp.dmp?-logo?out.txt?-lines?-c?".cxr?dwo(0x006af9e0+4);kb;q"
('dwo'操作符返回保存在指定地址里的double?word,并把它傳遞給.cxr命令)
本篇文章后面提供的批處理文件(實(shí)際上是DumpStackCtx.bat)將簡(jiǎn)化這個(gè)任務(wù)。
還有另外的方法可以解決這個(gè)問題??你可以在這里?找到更多的信息。

分析虛擬內(nèi)存
當(dāng)我們想審查被調(diào)試進(jìn)程的虛擬內(nèi)存布局時(shí),CDB可以協(xié)助Visual?Studio調(diào)試器。下面的命令顯示進(jìn)程完整的虛擬內(nèi)存映射:
??cdb?-pv?-pn?myapp.exe?-logo?out.txt?-c?"!vadump?-v;q"
(!vadump命令負(fù)責(zé)打印虛擬內(nèi)存映射,照常,用-v選項(xiàng)要求它顯示詳細(xì)的內(nèi)容)
下面是!vadump輸出的例子:
BaseAddress:???????00040000
AllocationBase:????00040000
AllocationProtect:?00000004??PAGE_READWRITE
RegionSize:????????0002e000
State:?????????????00002000??MEM_RESERVE
Type:??????????????00020000??MEM_PRIVATE

BaseAddress:???????0006e000
AllocationBase:????00040000
AllocationProtect:?00000004??PAGE_READWRITE
RegionSize:????????00001000
State:?????????????00001000??MEM_COMMIT
Protect:???????????00000104??PAGE_READWRITE?+?PAGE_GUARD
Type:??????????????00020000??MEM_PRIVATE

BaseAddress:???????0006f000
AllocationBase:????00040000
AllocationProtect:?00000004??PAGE_READWRITE
RegionSize:????????00011000
State:?????????????00001000??MEM_COMMIT
Protect:???????????00000004??PAGE_READWRITE
Type:??????????????00020000??MEM_PRIVATE
在Windows?XP和Windows?Server?2003上,CDB為審查虛擬內(nèi)存布局提供了一條更好的命令??!address。這條命令可以完成下面的任務(wù):
???顯示進(jìn)程的虛擬內(nèi)存映射(依我之見,比!vadump的輸出內(nèi)容更易閱讀)
???顯示有用的、虛擬內(nèi)存使用的統(tǒng)計(jì)數(shù)據(jù)
???確定指定的地址屬于哪種虛擬內(nèi)存區(qū)域(例如,它是屬于棧,堆,還是可執(zhí)行映象?)
下面是怎樣用!address報(bào)告虛擬內(nèi)存映射的示例:
??cdb?-pv?-pn?myapp.exe?-logo?out.txt?-c?"!address;q"
下面顯示被線程的棧占用的內(nèi)存區(qū)域:
????00040000?:?00040000?-?0002e000
????????????????????Type?????00020000?MEM_PRIVATE
????????????????????Protect??00000000?
????????????????????State????00002000?MEM_RESERVE
????????????????????Usage????RegionUsageStack
????????????????????Pid.Tid??658.644
???????????????0006e000?-?00001000
????????????????????Type?????00020000?MEM_PRIVATE
????????????????????Protect??00000104?PAGE_READWRITE?|?PAGE_GUARD
????????????????????State????00001000?MEM_COMMIT
????????????????????Usage????RegionUsageStack
????????????????????Pid.Tid??658.644
???????????????0006f000?-?00011000
????????????????????Type?????00020000?MEM_PRIVATE
????????????????????Protect??00000004?PAGE_READWRITE
????????????????????State????00001000?MEM_COMMIT
????????????????????Usage????RegionUsageStack
????????????????????Pid.Tid??658.644
注意,!address非常智能,可以報(bào)告屬于棧的線程的線程ID。
在!address報(bào)告虛擬內(nèi)存區(qū)域之后,它也能報(bào)告有趣的、虛擬內(nèi)存使用的統(tǒng)計(jì)數(shù)據(jù):
--------------------?Usage?SUMMARY?--------------------------
????TotSize???Pct(Tots)?Pct(Busy)???Usage
???00838000?:?0.40%???????27.96%??????:?RegionUsageIsVAD
???7e28c000?:?98.56%??????0.00%???????:?RegionUsageFree
???01348000?:?0.94%???????65.60%??????:?RegionUsageImage
???00040000?:?0.01%???????0.85%???????:?RegionUsageStack
???00001000?:?0.00%???????0.01%???????:?RegionUsageTeb
???001a0000?:?0.08%???????5.53%???????:?RegionUsageHeap
???00000000?:?0.00%???????0.00%???????:?RegionUsagePageHeap
???00001000?:?0.00%???????0.01%???????:?RegionUsagePeb
???00001000?:?0.00%???????0.01%???????:?RegionUsageProcessParametrs
???00001000?:?0.00%???????0.01%???????:?RegionUsageEnvironmentBlock
???????Tot:?7fff0000?Busy:?01d64000

--------------------?Type?SUMMARY?--------------------------
????TotSize???Pct(Tots)?Usage
???7e28c000?:?98.56%?????:?<free>
???01348000?:?0.94%??????:?MEM_IMAGE
???007b6000?:?0.38%??????:?MEM_MAPPED
???00266000?:?0.12%??????:?MEM_PRIVATE

--------------------?State?SUMMARY?--------------------------
????TotSize???Pct(Tots)?Usage
???01647000?:?1.09%??????:?MEM_COMMIT
???7e28c000?:?98.56%?????:?MEM_FREE
???0071d000?:?0.35%??????:?MEM_RESERVE

Largest?free?region:?Base?01014000?-?Size?59d5c000
當(dāng)我們正在調(diào)試內(nèi)存泄露,以及想確定內(nèi)存泄露的類型(堆,棧,原始的虛擬內(nèi)存,等等)時(shí),這些統(tǒng)計(jì)數(shù)據(jù)非常有用。通過最后一行,我們可以確定虛擬內(nèi)存中最大的空閑區(qū)域的大小,這在我們必須設(shè)計(jì)請(qǐng)求大量?jī)?nèi)存的應(yīng)用程序時(shí)非常有幫助。
如果你只想查看統(tǒng)計(jì)數(shù)據(jù),而不想看虛擬內(nèi)存映射,可以用-summary參數(shù):
??cdb?-pv?-pn?myapp.exe?-logo?out.txt?-c?"!address?-summary;q"
如果我們想確定給定地址的虛擬內(nèi)存屬于哪種類型,可以把這個(gè)地址作為參數(shù)傳遞給!address命令。下面是一個(gè)例子:
0:000>?!address?0x000a2480;q
????000a0000?:?000a0000?-?000d7000
????????????????????Type?????00020000?MEM_PRIVATE
????????????????????Protect??00000004?PAGE_READWRITE
????????????????????State????00001000?MEM_COMMIT
????????????????????Usage????RegionUsageHeap
????????????????????Handle???000a0000

搜索符號(hào)
有時(shí)候,我們可能需要確定符號(hào)(函數(shù)或變量)的地址。如果我們知道準(zhǔn)確的符號(hào)名,可以把它輸入Visual?Studio調(diào)試器的反匯編窗口,從中找出它對(duì)應(yīng)的地址。但是,假設(shè)我們忘了準(zhǔn)確的符號(hào)名呢?或者想找出名字上有相同規(guī)律的、一組符號(hào)的地址(例如,類的所有成員函數(shù))?CDB很容易解決這個(gè)問題??它提供'x'命令,可以列出匹配指定掩碼的所有符號(hào)名:
??x?Module!Symbol
下面的命令設(shè)法定位位于kernel32.dll?中的UnhandledExceptionFilter函數(shù)的地址:
??cdb?-pv?-pn?notepad.exe?-logo?out.txt?-c?"x?kernel32!UnhandledExceptionFilter;q"
下面是輸出內(nèi)容:
0:000>?x?kernel32!UnhandledExceptionFilter;q
7c862b8a?kernel32!UnhandledExceptionFilter?=?<no?type?information>
'x'命令可以接受多個(gè)通配符,提供一些有用的選項(xiàng),可用于排序輸出內(nèi)容以及輸出符號(hào)的額外信息??你可以在WinDbg文檔里找到更多的信息。例如,下面的命令可以把我們?cè)趹?yīng)用程序的主可執(zhí)行模塊中定義的CmainFrame類所有的成員函數(shù)和統(tǒng)計(jì)數(shù)據(jù)列出來(lái):
0:000>?x?myapp!*CMainFrame*
004542f8?MyApp!CMainFrame::classCMainFrame?=?struct?CRuntimeClass
00401100?MyApp!CMainFrame::`scalar?deleting?destructor'?(void)
004011a0?MyApp!CMainFrame::OnCreate?(struct?tagCREATESTRUCTW?*)
00401000?MyApp!CMainFrame::CreateObject?(void)
00401280?MyApp!CMainFrame::PreCreateWindow?(struct?tagCREATESTRUCTW?*)
00401070?MyApp!CMainFrame::GetRuntimeClass?(void)
00401120?MyApp!CMainFrame::~CMainFrame?(void)
00401090?MyApp!CMainFrame::CMainFrame?(void)
00401080?MyApp!CMainFrame::GetMessageMap?(void)
004578ec?MyApp!CMainFrame::`RTTI?Base?Class?Array'?=?<no?type?information>
004578dc?MyApp!CMainFrame::`RTTI?Class?Hierarchy?Descriptor'?=?<no?type?information>
004578c8?MyApp!CMainFrame::`RTTI?Complete?Object?Locator'?=?<no?type?information>
004579ec?MyApp!CMainFrame::`RTTI?Base?Class?Descriptor?at?(0,-1,0,64)'?=?<no?type?information>
00461e94?MyApp!CMainFrame?`RTTI?Type?Descriptor'?=?<no?type?information>
00454354?MyApp!CMainFrame::`vftable'?=?<no?type?information>
CDB也可以反著做??通過地址找符號(hào),使用'ln'命令:
??ln?Address
下面是它的用法:
??cdb?-pv?-pn?notepad.exe?-logo?out.txt?-c?"ln?0x77d491c8;q"
下面是它的輸出內(nèi)容:
0:000>?ln?0x77d491c8;q
(77d491c6)???USER32!GetMessageW+0x2???|??(77d49216)???USER32!CharUpperBuffW
注意,我們不必指定符號(hào)的起始地址(在這個(gè)例子里,是函數(shù)),而可以用符號(hào)占用的地址范圍內(nèi)的任何地址。'ln'將找出符號(hào),報(bào)告它的地址,另外還報(bào)告跟在指定內(nèi)容后面的地址和符號(hào)名。

顯示數(shù)據(jù)結(jié)構(gòu)
如果我們想研究數(shù)據(jù)結(jié)構(gòu)的內(nèi)容,通常會(huì)用Visual?Studio的Watch,QuickWatch或其它類似的窗口。這些窗口允許我們查看結(jié)構(gòu)成員變量的類型和值。但是假設(shè)我們也需要知道結(jié)構(gòu)的精確布局,包括它的成員的偏移量?Visual?Studio不提供易用的解決方法,但幸運(yùn)的是,CDB可以。在'dt'命令的幫助下,我們可以顯示數(shù)據(jù)結(jié)構(gòu)或類的精確布局。
如果我們只想了解數(shù)據(jù)類型的布局,可以用下面這條命令:
??dt?-b?TypeName
(-b選項(xiàng)啟用遞歸顯示,顯示結(jié)構(gòu)或類的成員類型的嵌入式數(shù)據(jù)結(jié)構(gòu))。
下面是命令的示例:
??cdb?-pv?-pn?myapp.exe?-logo?out.txt?-c?"dt?-b?CSymbolInfoPackage;q"
下面是輸出內(nèi)容(在運(yùn)行SymFromAddr?應(yīng)用程序時(shí)得到的):
0:000>?dt?/b?CSymbolInfoPackage;q
???+0x000?si???????????????:?_SYMBOL_INFO
??????+0x000?SizeOfStruct?????:?Uint4B
??????+0x004?TypeIndex????????:?Uint4B
??????+0x008?Reserved?????????:?Uint8B
??????+0x018?Index????????????:?Uint4B
??????+0x01c?Size?????????????:?Uint4B
??????+0x020?ModBase??????????:?Uint8B
??????+0x028?Flags????????????:?Uint4B
??????+0x030?Value????????????:?Uint8B
??????+0x038?Address??????????:?Uint8B
??????+0x040?Register?????????:?Uint4B
??????+0x044?Scope????????????:?Uint4B
??????+0x048?Tag??????????????:?Uint4B
??????+0x04c?NameLen??????????:?Uint4B
??????+0x050?MaxNameLen???????:?Uint4B
??????+0x054?Name?????????????:?Char
???+0x058?name?????????????:?Char
如果你想顯示特殊變量的布局,可以把它的地址傳遞給'dt'命令:
??dt?-b?TypeName?Address
下面是例子:
??cdb?-pv?-pn?myapp.exe?-logo?out.txt?-c?"dt?-b?CSymbolInfoPackage?0x0012f6d0;q"
0:000>?dt?/b?CSymbolInfoPackage?0x0012f6d0;q
???+0x000?si???????????????:?_SYMBOL_INFO
??????+0x000?SizeOfStruct?????:?0x58
??????+0x004?TypeIndex????????:?2
??????+0x008?Reserved?????????:?
???????[00]?0
???????[01]?0
??????+0x018?Index????????????:?1
??????+0x01c?Size?????????????:?0x428
??????+0x020?ModBase??????????:?0x400000
??????+0x028?Flags????????????:?0
??????+0x030?Value????????????:?0
??????+0x038?Address??????????:?0x411d30
??????+0x040?Register?????????:?0
??????+0x044?Scope????????????:?0
??????+0x048?Tag??????????????:?5
??????+0x04c?NameLen??????????:?0xe
??????+0x050?MaxNameLen???????:?0x7d1
??????+0x054?Name?????????????:??"S"
???????[00]?83?'S'
???+0x058?name?????????????:??"SymbolInfo"
????[00]?83?'S'
????[01]?121?'y'
????[02]?109?'m'
????[03]?98?'b'
????[04]?111?'o'
????[05]?108?'l'
????[06]?73?'I'
????[07]?110?'n'
????[08]?102?'f'
????[09]?111?'o'
????[10]?0?''
????[11]?0?''
????[12]?0?''
????[13]?0?''
????[14]?0?''
????[15]?0?''
????[16]?0?''
????[17]?0?''
????...?省略部分輸出內(nèi)容
????[1990]?0?''
????[1991]?0?''
????[1992]?0?''
????[1993]?0?''
????[1994]?0?''
????[1995]?0?''
????[1996]?0?''
????[1997]?-52?''
????[1998]?-52?''
????[1999]?-52?''
????[2000]?-52?''
注意,'dt'也顯示結(jié)構(gòu)成員變量的值。

批處理文件
我們已經(jīng)知道怎樣用CDB解決一些有趣的調(diào)試問題了。現(xiàn)在可以用它解決更多的問題了??用易用的批處理文件代替難記的CDB命令行。考慮我們?cè)诒疚拈_始部分使用的命令行示例:
??cdb?-pv?-pn?myapp.exe?-logo?out.txt?-c?"lm;q"
這條命令中的大部分是固定的,用不著改變。唯一可變的部分是目標(biāo)信息(-pn?myapp.exe),我們可能會(huì)用另外的可執(zhí)行文件名,或另外的附著方式(例如,通過進(jìn)程ID)替換它。
下面介紹了怎樣用批處理文件代替這條命令:
??;?lm.bat
??cdb?-pv?%1?%2?-logo?out.txt?-c?"lm;q"
如果我們運(yùn)行這個(gè)批處理文件,通過進(jìn)程來(lái)得到已加載模塊的列表,可以用下面的方法:
通過可執(zhí)行文件名附上進(jìn)程:
??lm?-pn?myapp.exe
通過進(jìn)程ID附上進(jìn)程:
??lm?-p?1234
通過服務(wù)名附上進(jìn)程:
??lm?-psn?MyService
打開故障轉(zhuǎn)儲(chǔ)文件:
??lm?-z?c:\myapp.dmp
這條命令與具體的目標(biāo)無(wú)關(guān),只做同樣的事情??打印已加載模塊的列表。
如果我們想為CDB命令指定另外的參數(shù),我們可以用同樣的方法。考慮下面用于顯示數(shù)據(jù)結(jié)構(gòu)布局的命令:
??cdb?-pv?-pn?myapp.exe?-logo?out.txt?-c?"dt?/b?MyStruct;q"
當(dāng)然,我們可能想使用任何數(shù)據(jù)類型運(yùn)行這條命令,而不僅僅是MyStruct。下面是怎樣做的示例:
??;?dt.bat
??cdb?-pv?%1?%2?-logo?out.txt?-c?"dt?/b?%3;q"
現(xiàn)在,我們可以象下面這樣運(yùn)行這條命令:
??dt?-pn?myapp.exe?CTestClass
或者象這樣:
??dt?-p?1234?SYMBOL_INFO
或者,也可以象這樣:
??dt?-z?c:\myapp.dmp?EXCEPTION_POINTERS
我們可以用同樣的方法處理其它的命令。你可以從這里?找到本文曾討論過的批處理文件。在以后,我準(zhǔn)備用另外有用的命令擴(kuò)充它。

將理論與實(shí)踐相結(jié)合--譯者語(yǔ)

(全文完)