Vistual Studio原生開發(fā)的20條調(diào)試技巧(下)
我的上篇文章《Vistual Studio原生開發(fā)的10個調(diào)試技巧》 引發(fā)了很多人的興趣,所以我決定跟大家分享更多的調(diào)試技巧。接下來你又能看到一些對于原生應(yīng)用程序的很有幫助的調(diào)試技巧(接著上一篇文章來編號)。這些技 巧需要應(yīng)用在Vistual Studio 2005 或者更新的版本中(當(dāng)然也有一些適用于舊版本)。如果你能閱讀本文中推薦的一些相關(guān)文章,就可以知道每一個技巧的更多信息。
- 11. 數(shù)據(jù)斷點
- 12. 線程重命名
- 13. 給指定線程設(shè)置斷點
- 14.(粗略)估算執(zhí)行時間
- 15. 數(shù)字格式化
- 16. (內(nèi)存)數(shù)據(jù)格式化
- 17.系統(tǒng)DLL中斷
- 18.加載符號表
- 19. 監(jiān)測MFC中的內(nèi)存泄漏
- 20. 調(diào)試ATL
技巧11: 數(shù)據(jù)斷點
當(dāng)數(shù)據(jù)所在的內(nèi)存位置發(fā)生變化時,可以通知調(diào)試器進(jìn)行中斷,但是每次只能創(chuàng)建4個字節(jié)這樣的硬件數(shù)據(jù)斷點。數(shù)據(jù)斷點只能在調(diào)試期間添加,可以通過菜 單(Debug>New Breakpoint>New Data Breakpoint) 或者斷點窗口來添加。
你可以使用內(nèi)存地址或者地址表達(dá)式。盡管棧上和堆上的值你都可以看到,但是我認(rèn)為當(dāng)堆上的數(shù)值發(fā)生變化時,這個功能才會更有用處。它對于識別內(nèi)存損壞有很大的幫助。
下面的例子中,指針的值發(fā)生了變化,不再是它所指向?qū)ο蟮闹?。為了找出在什么地方發(fā)生改變的,我在存儲指針值的位置設(shè)置了一個斷點, 即&ptr(注意必須在指針初始化之后)。數(shù)據(jù)發(fā)生變化就意味著有人修改了指針的值,調(diào)試器發(fā)生中斷,我就能找出是哪段代碼引起的改變。
更多閱讀:
1.怎樣查明指針是否損壞內(nèi)存
2.怎樣查明指針在什么地方發(fā)生改變
技巧12: 線程重命名
在調(diào)試多線程應(yīng)用程序時,線程窗口會顯示創(chuàng)建了哪些線程以及當(dāng)前正在運行的線程。線程越多,想找到你想要的線程就越困難(尤其是當(dāng)一段程序被多個線程同時執(zhí)行的時候,你不能確切地知道哪個才是當(dāng)前正在執(zhí)行的線程實例)。
調(diào)試器允許修改線程的名字,可以在線程窗口使用線程的快捷菜單,給線程重命名。
也可以在程序里給線程命名,盡管有點棘手,而且必須在線程啟動之后給它命名,否則調(diào)試器會以默認(rèn)命名規(guī)范將它重新初始化。定義一個線程,并用下面的函數(shù)重命名該線程。
- typedef struct tagTHREADNAME_INFO
- {
- DWORD dwType; // must be 0x1000
- LPCSTR szName; // pointer to name (in same addr space)
- DWORD dwThreadID; // thread ID (-1 caller thread)
- DWORD dwFlags; // reserved for future use, most be zero
- } THREADNAME_INFO;
- void SetThreadName(DWORD dwThreadID, LPCSTR szThreadName)
- {
- THREADNAME_INFO info;
- info.dwType = 0x1000;
- info.szName = szThreadName;
- info.dwThreadID = dwThreadID;
- info.dwFlags = 0;
- __try
- {
- RaiseException(0x406D1388, 0, sizeof(info)/sizeof(DWORD), (DWORD*)&info);
- }
- __except (EXCEPTION_CONTINUE_EXECUTION)
- {
- }
- }
更多閱讀:
設(shè)置線程名字(非托管)
#p#
技巧13: 給指定線程設(shè)置斷點
對于多線程應(yīng)用程序來說,另一個有用的技巧就是給指定的線程,進(jìn)程,甚至是計算機(jī)中的斷點設(shè)置過濾.可以通過斷點的Filter命令來實現(xiàn)此功能.
調(diào)試器允許你指定線程名,線程ID,進(jìn)程名,進(jìn)程ID和機(jī)器名的任意組合(使用AND,OR,NOT)來設(shè)置過濾。了解怎樣設(shè)置線程名字也使得這項過濾操作變得更加簡單。
更多閱讀:
- 怎樣指定斷點過濾器
- 設(shè)置斷點過濾
技巧14: (粗略)估算執(zhí)行時間
在上一篇文章中,我有寫關(guān)于Watch窗口中的偽變量,有一個沒提到的是@clk,它用于顯示計數(shù)器的值,可以粗略地計 算出兩個斷點之間的代碼的執(zhí)行時間,單位是微秒(μS)。但是,千萬不要用這個方法來分析程序的執(zhí)行效率,應(yīng)該使用Visual Studio 分析工具或者性能計時器來分析。
可以在Watch 窗口或者即時窗口添加@clk=0來完成對計時器的重置。因此要想估算執(zhí)行一段代碼需要多長時間,可以按照下面的步驟來操作:
- 在代碼塊的開始位置設(shè)置斷點
- 在代碼塊的結(jié)束位置設(shè)置斷點
- 在Watch窗口添加 @clk
- 程序進(jìn)入到第一個斷點時,在即時窗口輸入@clk=0
- 運行程序直到執(zhí)行進(jìn)入代碼塊末尾的斷點,查看Watch窗口 @clk的值。
注意網(wǎng)上有一些技巧說在Watch窗口添加兩個表達(dá)式:@clk和@clk=0,需要在每次執(zhí)行斷點的時候都要重置計時器。這種用法只適用于Visual Studio的老版本,在VS2005及以上版本不再適用。
更多閱讀:
調(diào)試技巧-@CLK
技巧15:數(shù)字格式化
當(dāng)你在Watch或者Quick Watch窗口查看變量時, 這些值是以默認(rèn)的預(yù)定義可視化形式顯示的。而對于數(shù)字,則是根據(jù)數(shù)據(jù)類型(integer, float, double),用十進(jìn)制形式顯示的。但是你可以使用調(diào)試器把數(shù)字用不同的類型或者進(jìn)制數(shù)顯示出來。
想要改變顯示類型可在變量前加以下前綴:
- by –unsigned char (又稱為unsigned byte)
- wo – unsigned shot(又稱為 unsigned word)
- dw – unsigned long(又稱為 unsigned double word)
要改變顯示的進(jìn)制數(shù)在變量前加下列前綴:
- d 或者 i– 有符號十進(jìn)制數(shù)
- u – 無符號十進(jìn)制數(shù)
- o - 無符號八進(jìn)制數(shù)
- x – 小寫十六進(jìn)制數(shù)
- X – 大寫十六進(jìn)制數(shù)
更多閱讀:
C++ 調(diào)試技巧
#p#
技巧16:(內(nèi)存數(shù)據(jù))格式化
除了數(shù)字,調(diào)試器還可以在Watch窗口顯示格式化的內(nèi)存數(shù)據(jù),最多64 bytes。 你可以使用在表達(dá)式(變量或內(nèi)存地址)后添加下列說明符作為后綴來格式化數(shù)據(jù):
- mb 或者 m – 十六進(jìn)制顯示的16字節(jié)數(shù)據(jù),后跟16個ASCII 字符
- mw – 8 words
- md – 4 double words
- mq - 2 quad-words
- ma – 64個ASCII字符
- mu – 2字節(jié)的UNICODE字符
更多閱讀:
- C++中的格式說明符
- Developer Studio的調(diào)試技巧
技巧17: 系統(tǒng)DLL的中斷
有時候在DLL中的函數(shù)被調(diào)用時進(jìn)行中斷是很有用的,像系統(tǒng)DLL(比如 Kernel32.dll 或者user32.dll).實現(xiàn)此中斷,需要使用本機(jī)調(diào)試器提供的上下文運算符.你可以設(shè)定斷點位置,變量名或者表達(dá)式:
1.{[函數(shù)],[源碼],[模塊]}位置
2. [函數(shù)],[源碼],[模塊]}變量名
3. [函數(shù)],[源碼],[模塊]}表達(dá)式
花括號里可以是函數(shù)名,源代碼和模塊的任意組合,但是逗號不能省略.
我們假設(shè)想要在CreateThread函數(shù)被調(diào)用時發(fā)生中斷,這個函數(shù)是從kernel32.dll中導(dǎo)出的,所以上下文運算符應(yīng)該為: {,,kernel32.dll}CreateThread. 然而,這樣并不可行,因為上下文運算符需要CreatThread的修飾符,可以使用DBH.exe來獲取一個特定函數(shù)的修飾符。
下面就是如何得到CreateThread函數(shù)的修飾符的:
- C:\Program Files (x86)\Debugging Tools for Windows (x86)>dbh.exe -s:srv*C:\Symbo
- ls*http://msdl.microsoft.com/Download/Symbols -d C:\Windows\SysWOW64\kernel32.dl
- l enum *CreateThread*
- Symbol Search Path: srv*C:\Symbols*http://msdl.microsoft.com/Download/Symbols
- index address name
- 1 10b4f65 : _BaseCreateThreadPoolThread@12
- 2 102e6b7 : _CreateThreadpoolWork@12
- 3 103234c : _CreateThreadpoolStub@4
- 4 1011ea8 : _CreateThreadStub@24
- 5 1019d40 : _NtWow64CsrBasepCreateThread@12
- 6 1019464 : ??_C@_0BC@PKLIFPAJ@SHCreateThreadRef?$AA@
- 7 107309c : ??_C@_0BD@CIEDBPNA@TF_CreateThreadMgr?$AA@
- 8 102ce87 : _CreateThreadpoolCleanupGroupStub@0
- 9 1038fe3 : _CreateThreadpoolIoStub@16
- a 102e6f0 : _CreateThreadpoolTimer@12
- b 102e759 : _CreateThreadpoolWaitStub@12
- c 102ce8e : _CreateThreadpoolCleanupGroup@0
- d 102e6e3 : _CreateThreadpoolTimerStub@12
- e 1038ff0 : _CreateThreadpoolIo@16
- f 102e766 : _CreateThreadpoolWait@12
- 10 102e6aa : _CreateThreadpoolWorkStub@12
- 11 1032359 : _CreateThreadpool@4
看上去實際名字應(yīng)該是_CreateThreadStub@24,這樣我們就可以創(chuàng)建斷點,{,,kernel32.dll}_CreateThreadStub@24。
運行程序,發(fā)生中斷時會有消息提示斷點處無相關(guān)源代碼,直接忽略它。
使用調(diào)用棧窗口查看調(diào)用該函數(shù)的代碼。
更多閱讀:
1. 在Visual Studio 2010中,沒有源代碼如何設(shè)置斷點
2. 上下文運算符(C/C++語言表達(dá)式)
3. 怎樣給函數(shù)設(shè)置斷點
#p#
技巧18:加載符號表
在調(diào)試程序的時候,調(diào)用棧窗口不會顯示完整的調(diào)用棧,跳過了系統(tǒng)DLL(比如kernel32.dll 和 user32.dll)的信息。
可以通過加載這些DLL的符號表來獲得完整的調(diào)用棧信息,直接在調(diào)用棧窗口使用快捷菜單就能完成。你可以從預(yù)先指定的符號路徑或者微軟的符號服務(wù)器(如果是系統(tǒng)DLL)來下載符號。符號下載完成后,直接導(dǎo)入到調(diào)試器,調(diào)用棧就會得到更新。
這些符合也可以從組件Modules窗口導(dǎo)入。
一旦下載完成,符號會保存在緩存中,可以在Tools>Options>Debugging>Symbols中配置。
技巧19:監(jiān)測MFC中的內(nèi)存泄漏
如果你想要在MFC應(yīng)用程序中檢測內(nèi)存泄漏,需要使用宏DEBUG_NEW來重新定new運算符,這是new運算符的修改版本,記錄了每個對象內(nèi)存分配的文件名和行號.在發(fā)行版中DEBUG_NEW會解析成new運算符.
向?qū)傻腗FC源文件在#includes后包含了下面的預(yù)處理指令:
- #ifdef _DEBUG
- #define new DEBUG_NEW
- #endif
這就是怎樣重新定義new運算符的。
然而,很多STL頭文件和重新定義的new運算符和版本不兼容.如果你重新定義了new運算符后,又包含 了<map>,<vector>,<list>,<string>等頭文件的話,就會出現(xiàn)下面的錯誤 (以<vector>為例):
- 1>c:\program files (x86)\microsoft visual studio 9.0\vc\include\xmemory(43) : error C2665: 'operator new' : none of the 5 overloads could convert all the argument types
- 1> c:\program files\microsoft visual studio 9.0\vc\include\new.h(85): could be 'void *operator new(size_t,const std::nothrow_t &) throw()'
- 1> c:\program files\microsoft visual studio 9.0\vc\include\new.h(93): or 'void *operator new(size_t,void *)'
- 1> while trying to match the argument list '(const char [70], int)'
- 1> c:\program files (x86)\microsoft visual studio 9.0\vc\include\xmemory(145) : see reference to function template instantiation '_Ty *std::_Allocate<char>(size_t,_Ty *)' being compiled
- 1> with
- 1> [
- 1> _Ty=char
- 1> ]
- 1> c:\program files (x86)\microsoft visual studio 9.0\vc\include\xmemory(144) : while compiling class template member function 'char *std::allocator<_Ty>::allocate(std::allocator<_Ty>::size_type)'
- 1> with
- 1> [
- 1> _Ty=char
- 1> ]
- 1> c:\program files (x86)\microsoft visual studio 9.0\vc\include\xstring(2216) : see reference to class template instantiation 'std::allocator<_Ty>' being compiled
- 1> with
- 1> [
- 1> _Ty=char
- 1> ]
解決辦法就是總是把包含這些STL頭文件放在重新定義new運算符之前.
更多閱讀:
DEBUG_NEW
技巧20: 調(diào)試ATL
在開發(fā)ATL COM組件時,你可以在調(diào)試器觀察COM對象的QueryInterface,AddRef和Release的調(diào)用情況.默認(rèn)情況下并不支持這些,但是你 只要在預(yù)處理定義或者預(yù)編譯頭文件時定義兩個宏,宏定義好之后,關(guān)于這些函數(shù)的調(diào)用信息就會顯示在output窗口.
這兩個宏如下:
- _ATL_DEBUG_QI: 顯示你定義的對象里每一個被查詢的接口的名字,必須在atlcom.h被包含之前定義.
- _ATL_DEBUG_INTERFACES: 在每次AddRef 或者Release被調(diào)用時,顯示接口的當(dāng)前引用計數(shù)以及對應(yīng)的類名和接口名,必須在atlbase.h被包含之前定義.
更多閱讀:
- 調(diào)試技巧
- ATL調(diào)試技巧
- _ATL_DEBUG_INTERFACES是如何工作的?
結(jié)束語
盡管這兩篇文章并不是包含了所有的調(diào)試技巧,但是足以幫你解決原生開發(fā)中調(diào)試時遇到的大多數(shù)問題。
原文鏈接:http://www.codeproject.com/Articles/518159/10-Even-More-Visual-Studio-Debugging-Tips-for-Nati
譯文鏈接:http://blog.jobbole.com/45447/