通往WinDbg的捷徑(二)

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

分析故障轉儲
CDB也可以用於自動分析故障轉儲。當我們分析故障轉儲時,通常會執行同樣的操作,所以可以把這些操作自動化。什麼樣的操作呢?這要看故障轉儲的類型。我把所有的故障轉儲分成兩大類:
•  帶異常信息的故障轉儲
•  不帶異常信息的故障轉儲
當應用程序引發未經處理的異常並調用just-in-time調試器(Dr. Watson,
NTSD  , 或其它的調試器),或者用爲未經處理的異常定製的過濾器 生成minidump時,通常會生成帶異常信息的故障轉儲。通過寫入故障轉儲裏的異常信息,我們可以確定異常的類型和發生時它在代碼裏的位置。當我們想爲以後的分析生成進程的快照時(例如,這方面的描述參見本文的前一部分“保存dumps”),通常手動生成不帶異常信息的故障轉儲。
當我們調試帶異常信息的故障轉儲時,通常想知道下面這些信息:
•  異常在代碼中出現的位置(地址,源文件和行號)
•  異常發生時的調用棧
•  調用棧上一些或所有函數的參數值和局部變量
WinDbg和CDB爲調試故障轉儲提供了非常有用的命令――!analyze。這條命令分析故障轉儲裏的異常信息,確定異常發生的位置,調用棧,並顯示詳細的報告。下面是這條命令的示例:
  cdb -z c:\myapp.dmp -logo out.txt -lines -c "!analyze -v;q"
(-v選項要求!analyze輸出詳細的內容)
CrashDemo.cpp 例子演示了怎樣用定製的過濾器捕獲未經處理的異常並生成minidumps。如果你編譯並運行它,然後用上述的CDB命令分析生成的minidump,你將可以得到和下面類似的輸出內容:
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
---------
注意用粗體表示的內容。第一處報告了異常的地址和類型。第二外報告調用棧。第三處爲我們提供了怎樣訪問保存在故障轉儲裏的異常信息的額外信息。
現在,我們知道異常發生的位置,甚至可以查看調用棧。那麼,是得到函數的參數值及局部變量的時候了。在開始之前,讓我們注意!analyze報告中的第三處信息。這裏再重複一下第三處所包含的內容:
STACK_COMMAND:  .ecxr ; kb
對'kb'命令我們已經不陌生了(它顯示調用棧)。但.ecxr是什麼?這條命令要求調試器把當前的內容切換到保存在故障轉儲裏的異常信息。我們執行這條命令後,將能訪問異常拋出時調用棧和局部變量的值。
在我們要求調試使用異常的上下文後,我們可以用'dv'命令顯示函數的參數值以及局部變量。因爲我們通常想查看調用棧上每一個函數的信息,因此,我們可以用'!for_each_frame dv /t'命令(/t選項要求'dv'顯示有用的類型信息)。(當然,我們必須記住,使用優化編譯時,在函數的整個生存期中,局部變量有可能會被取消,重註冊或被重用來保存其它的數據,因此,可能會導致'dv'命令輸出錯誤的值)。
下面是分析帶異常信息的故障轉儲的命令行示例:
  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沒有包括目標進程內存的完整內容,那麼只有當調試器能正確發現被目標進程加載的、相同版本的可執行模塊時,才能分析dump。在某些情形下,你必須幫助調試器定位這些模塊――通過指定模塊搜索路徑。關於模塊搜索路徑的詳細信息和相關內容可以在
這篇文章 中找到。

現在,我們來處理不帶異常信息的故障轉儲。當我們分析這樣的dump時,通常想知道所有線程的調用棧。下面是怎樣得到這些信息:
  cdb -z c:\myapp.dmp -logo out.txt -lines -c "~*kb;q"
如果我們不知道故障轉儲是否包含異常信息,該怎麼做呢?對於minidumps來說,我們可以用
MiniDumpView 打印dump的內容,查看它裏面是否包含異常信息。對於過時的'full user dumps',或許唯一的選擇是,照現在的樣子啓動包含異常信息的dump,並查看!analyze是否報告了有意義的內容。
有一個有趣的特例――因爲未經處理的異常生成故障轉儲,但因爲某些原因沒有包含異常信息是有可能的。在這種情形下,在下面過程的幫助下,仍可能找出異常發生的位置:
1.  打印所有線程的調用棧(用前面提過的CDB命令)。
2.  找出包含kernel32!UnhandledExceptionFilter函數調用棧的線程。
3.  使用
UnhandledExceptionFilter 函數的第一個參數(包含一個指向EXCEPTION_POINTERS 結構的指針)的實際值。
下面是EXCEPTION_POINTERS 結構的聲明:
typedef struct _EXCEPTION_POINTERS 
{  
    PEXCEPTION_RECORD ExceptionRecord;  
    PCONTEXT ContextRecord;
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;
如果我們知道這個結構的地址,就能得到指向異常上下文的指針(保存在ContextRecord字段裏),把它傳遞給.cxr命令,從而把調試器上下文切換到異常發生的位置。在.cxr命令執行後,我們可以用'kb'命令得到異常發生時的調用棧。下面是一個例子:
1.  打印所有線程的調用棧。
  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.  改變調試器的上下文,得到異常的調用棧。
  cdb -z c:\myapp.dmp -logo out.txt -lines -c ".cxr dwo(0x006af9e0+4);kb;q"
('dwo'操作符返回保存在指定地址裏的double word,並把它傳遞給.cxr命令)
本篇文章後面提供的批處理文件(實際上是DumpStackCtx.bat)將簡化這個任務。
還有另外的方法可以解決這個問題――你可以在
這裏 找到更多的信息。

分析虛擬內存
當我們想審查被調試進程的虛擬內存佈局時,CDB可以協助Visual Studio調試器。下面的命令顯示進程完整的虛擬內存映射:
  cdb -pv -pn myapp.exe -logo out.txt -c "!vadump -v;q"
(!vadump命令負責打印虛擬內存映射,照常,用-v選項要求它顯示詳細的內容)
下面是!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爲審查虛擬內存佈局提供了一條更好的命令――!address。這條命令可以完成下面的任務:
•  顯示進程的虛擬內存映射(依我之見,比!vadump的輸出內容更易閱讀)
•  顯示有用的、虛擬內存使用的統計數據
•  確定指定的地址屬於哪種虛擬內存區域(例如,它是屬於棧,堆,還是可執行映象?)
下面是怎樣用!address報告虛擬內存映射的示例:
  cdb -pv -pn myapp.exe -logo out.txt -c "!address;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非常智能,可以報告屬於棧的線程的線程ID。
在!address報告虛擬內存區域之後,它也能報告有趣的、虛擬內存使用的統計數據:
-------------------- 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
當我們正在調試內存泄露,以及想確定內存泄露的類型(堆,棧,原始的虛擬內存,等等)時,這些統計數據非常有用。通過最後一行,我們可以確定虛擬內存中最大的空閒區域的大小,這在我們必須設計請求大量內存的應用程序時非常有幫助。
如果你只想查看統計數據,而不想看虛擬內存映射,可以用-summary參數:
  cdb -pv -pn myapp.exe -logo out.txt -c "!address -summary;q"
如果我們想確定給定地址的虛擬內存屬於哪種類型,可以把這個地址作爲參數傳遞給!address命令。下面是一個例子:
0:000> !address 0x000a2480;q
    000a0000 : 000a0000 - 000d7000
                    Type     00020000 MEM_PRIVATE
                    Protect  00000004 PAGE_READWRITE
                    State    00001000 MEM_COMMIT
                    Usage    RegionUsageHeap
                    Handle   000a0000

搜索符號
有時候,我們可能需要確定符號(函數或變量)的地址。如果我們知道準確的符號名,可以把它輸入Visual Studio調試器的反彙編窗口,從中找出它對應的地址。但是,假設我們忘了準確的符號名呢?或者想找出名字上有相同規律的、一組符號的地址(例如,類的所有成員函數)?CDB很容易解決這個問題――它提供'x'命令,可以列出匹配指定掩碼的所有符號名:
  x Module!Symbol
下面的命令設法定位位於kernel32.dll 中的UnhandledExceptionFilter函數的地址:
  cdb -pv -pn notepad.exe -logo out.txt -c "x kernel32!UnhandledExceptionFilter;q"
下面是輸出內容:
0:000> x kernel32!UnhandledExceptionFilter;q
7c862b8a kernel32!UnhandledExceptionFilter = <no type information>
'x'命令可以接受多個通配符,提供一些有用的選項,可用於排序輸出內容以及輸出符號的額外信息――你可以在WinDbg文檔裏找到更多的信息。例如,下面的命令可以把我們在應用程序的主可執行模塊中定義的CmainFrame類所有的成員函數和統計數據列出來:
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也可以反着做――通過地址找符號,使用'ln'命令:
  ln Address
下面是它的用法:
  cdb -pv -pn notepad.exe -logo out.txt -c "ln 0x77d491c8;q"
下面是它的輸出內容:
0:000> ln 0x77d491c8;q
(77d491c6)   USER32!GetMessageW+0x2   |  (77d49216)   USER32!CharUpperBuffW
注意,我們不必指定符號的起始地址(在這個例子裏,是函數),而可以用符號佔用的地址範圍內的任何地址。'ln'將找出符號,報告它的地址,另外還報告跟在指定內容後面的地址和符號名。

顯示數據結構
如果我們想研究數據結構的內容,通常會用Visual Studio的Watch,QuickWatch或其它類似的窗口。這些窗口允許我們查看結構成員變量的類型和值。但是假設我們也需要知道結構的精確佈局,包括它的成員的偏移量?Visual Studio不提供易用的解決方法,但幸運的是,CDB可以。在'dt'命令的幫助下,我們可以顯示數據結構或類的精確佈局。
如果我們只想瞭解數據類型的佈局,可以用下面這條命令:
  dt -b TypeName
(-b選項啓用遞歸顯示,顯示結構或類的成員類型的嵌入式數據結構)。
下面是命令的示例:
  cdb -pv -pn myapp.exe -logo out.txt -c "dt -b CSymbolInfoPackage;q"
下面是輸出內容(在運行
SymFromAddr 應用程序時得到的):
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 '
    ... 省略部分輸出內容
    [1990] 0 '
    [1991] 0 '
    [1992] 0 '
    [1993] 0 '
    [1994] 0 '
    [1995] 0 '
    [1996] 0 '
    [1997] -52 '
    [1998] -52 '
    [1999] -52 '
    [2000] -52 '
注意,'dt'也顯示結構成員變量的值。

批處理文件
我們已經知道怎樣用CDB解決一些有趣的調試問題了。現在可以用它解決更多的問題了――用易用的批處理文件代替難記的CDB命令行。考慮我們在本文開始部分使用的命令行示例:
  cdb -pv -pn myapp.exe -logo out.txt -c "lm;q"
這條命令中的大部分是固定的,用不着改變。唯一可變的部分是目標信息(-pn myapp.exe),我們可能會用另外的可執行文件名,或另外的附着方式(例如,通過進程ID)替換它。
下面介紹了怎樣用批處理文件代替這條命令:
  ; lm.bat
  cdb -pv %1 %2 -logo out.txt -c "lm;q"
如果我們運行這個批處理文件,通過進程來得到已加載模塊的列表,可以用下面的方法:
通過可執行文件名附上進程:
  lm -pn myapp.exe
通過進程ID附上進程:
  lm -p 1234
通過服務名附上進程:
  lm -psn MyService
打開故障轉儲文件:
  lm -z c:\myapp.dmp
這條命令與具體的目標無關,只做同樣的事情――打印已加載模塊的列表。
如果我們想爲CDB命令指定另外的參數,我們可以用同樣的方法。考慮下面用於顯示數據結構佈局的命令:
  cdb -pv -pn myapp.exe -logo out.txt -c "dt /b MyStruct;q"
當然,我們可能想使用任何數據類型運行這條命令,而不僅僅是MyStruct。下面是怎樣做的示例:
  ; dt.bat
  cdb -pv %1 %2 -logo out.txt -c "dt /b %3;q"
現在,我們可以象下面這樣運行這條命令:
  dt -pn myapp.exe CTestClass
或者象這樣:
  dt -p 1234 SYMBOL_INFO
或者,也可以象這樣:
  dt -z c:\myapp.dmp EXCEPTION_POINTERS
我們可以用同樣的方法處理其它的命令。你可以從
這裏 找到本文曾討論過的批處理文件。在以後,我準備用另外有用的命令擴充它。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章