1. 設置軟件斷點,運行到目標位置啓動調試器css
方法①:使用匯編指令(注:x64 c++不支持嵌入彙編)html
_asm int 3
方法②:編譯器提供的方法c++
__debugbreak();
方法③:使用windows API正則表達式
DebugBreak();
WerFault.exe進程(Windows Error Reporting)彈出ConsoleTest.exe已中止工做:windows
要想出現「調試程序」選項,須要將Windows Error Reporting註冊表信息設置成以下圖所示(注:特別是紅框的內容)數組
若是在註冊表AeDebug的Debugger項配置了VSJitDebugger路徑,且VSJitDebugger安裝正常數據結構
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\AeDebug HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Windows NT\CurrentVersion\AeDebug // 注:64位系統上的32位程序使用該註冊表項
點擊「調試程序」選項就會彈出Visual Studio實時調試器對話框,選擇對應的調試器後,點擊「是」就能夠啓動調試器並中斷到軟件斷點位置了函數
須要注意的是,軟件斷點也是一種異常,一旦被處理,就不會傳到WerFault.exe進程上,那麼這種方法也就失效了!工具
下面兩種狀況軟件斷點異常會被處理:visual-studio
① 被SEH異常捕獲並處理
② 被自定義的全局異常函數處理
注:能夠將上面兩種狀況中的EXCEPTION_EXECUTE_HANDLER修改成EXCEPTION_CONTINUE_SEARCH來指明異常未獲得處理
2. 修改變量(注:在懸停出來小面板、Locals窗口、Autos窗口、Watch窗口、或Quick Watch窗口中進行修改;也能夠在Immediate窗口中執行:bFlag=false)
3. 格式化變量
注:d,i:有符號的十進制數
u:無符號的十進制數
o:無符號的八進制數
x:十六進制數(字母小寫)
X:十六進制數(字母大寫)
更多詳見:Format specifiers for C++ in the Visual Studio debugger
4. 修改內存(注:在內存窗口中,將光標定位到要修改的地方,直接按0-9輸入十六進制;要輸入a-f則需經過右鍵菜單中的「Edit Value」進行輸入)
5. 格式化顯示內存
6. 設置下一個運行位置(注:直接拖動黃色箭頭到想要的運行位置)
示例中:傳入的bFlag爲true,代碼開始運行到斷點處(43行),而後從新把黃色箭頭拖回39行,此時bFlag的值爲false,按F10會進入else分支
注:(1)跳過中間全部指令。意味着:printf("True\n")及CTest的析構函數均不會被執行
(2)當拖動箭頭到一個新的函數中時,vs會將原來的函數從棧中彈出,將新函數壓入棧頂;
因爲新函數與上層函數沒有調用關係,輸出類型的參數及返回值頗有可能寫壞上層函數的棧數據
(3)該調試技巧爲一種過後行爲,應謹慎使用,最好是隻在函數內局部使用
7. 編輯而後繼續運行
(1)不能在64位代碼上使用
(2)使用「Program Database for Edit & Continue (/ZI)」生成pdb文件
(3)僅適用於函數內部改變(若要修改函數原型或增長新函數,只能選擇重啓程序)
8. 變量的一些特殊查看方法
以$和@開頭的僞變量:(注:$和@兩個符號是同樣的,隨便用哪一個均可以)
$err -- 獲取GetLastError()的返回值
$err,hr -- 獲取GetLastError()的返回值並解釋返回值的含義
@eax -- 查看eax的值(64位爲@rax)
@esp+4 -- 函數的第一個參數地址
$handles -- 查看打開的句柄數
$tid -- 當前線程id
$vframe -- 當前棧幀的ebp
$clk -- 以時鐘週期爲單位顯示時間
$ReturnValue -- 查看函數的返回值
Message,wm --以windows消息的宏形式顯示 如:Message爲15時,顯示爲WM_PAINT(注:Message爲unsigned int類型)
hResult,hr --hResult爲0x80070005時,顯示爲E_ACCESSDENIED(注:hResult爲void*類型)
pArray,10 --從pArray地址起顯示後續10個int類型的數據(注:pArray爲int*類型)
(pArray+5),3 --從pArray[5]地址起顯示後續3個int類型的數據(注:pArray爲int*類型)
更多詳見:Pseudovariables in the Visual Studio debugger
9. 獲取簡單類型的函數返回值
注1:不能爲inline函數
注2:執行函數的下一條語句時,查看eax或僞變量ReturnValue的值
10. 使用指針類型轉換查看某個地址的變量
注:有時候,儘管對象仍然存在,在調試符號越界後,watch窗口中的變量是被禁用的,不能再查看(也不能更新)。
若知道對象的地址,則能夠將地址轉換爲該對象類型的指針,放在watch窗中來繼續觀察它。
11. Command窗口
經過命令來完成vs中的功能(不只僅在調試狀態時使用),另外其調試相關命令與windbg保持一致。
? nLocal //查看變量nLocal的值 ?? nLocal //將nLocal添加到Quick Watch窗口中 ? nLocal=100 //修改nLocal的值爲100 ? MySum(20,30) //調用全局函數MySum,並返回結果 k //打印當前線程堆棧 ~ //查看線程狀況 ~*k //打印出全部線程的堆棧信息 watch //打開watch窗口 memory2 //打開memory2窗口 g //繼續執行,F5功能 q //結束調試
12. 內存斷點
(1)在84行斷點停住後,查看&s.Age的地址爲0x0042FCEC
(2)點擊"Debug"-"New Breakpoint"-"New Data Breakpoint...",在彈出的對話框Address填入:0x0042FCEC,長度爲4便可
(3)當運行到88行時,因爲Scores數組越界引起了s.Age的內存修改,觸發了內存斷點
13. 條件斷點
斷點說明:
(1)設置斷點條件:i>6;且被命中次數>=2時才斷住程序,因此第一次斷住時i=8
(2)命中時,在Output窗口中打印當前函數名及線程ID(也能夠打印相關變量的值,詳見"When Breakpoint Is Hit"面板上的說明);在Command窗口中打印出堆棧信息
(3)若不想斷住程序,能夠把"When Breakpoint Is Hit"對話框中的"Continue execution"勾選上
注1:對於字符串的條件斷點,不能寫以下條件pStr=="Hello"(pStr爲char*類型),應該寫成:pStr[0]=='H' && pStr[1]=='e' && pStr[2]=='l' && pStr[3]=='l' && pStr[4]=='o' && pStr[5]=='\0'
vs2010及以上版本中,條件斷點中可以使用字符串:strcmp(pStr, "Hello")==0
支持的字符串函數有:strlen, wcslen, strnlen, wcsnlen, strcmp, wcscmp, _stricmp, _wcsicmp, strncmp, wcsncmp, _strnicmp, _wcsnicmp, strchr, wcschr, strstr, wcsstr.
注2:也能夠建立本身的宏,具體方法:"Tools"-"Macros"-"Macro Explorer",而後在下圖:MyMacros-Module1上右鍵快捷菜單中選擇"New macro",
如ChangeExpression宏函數會在Output窗口的Debugger過濾器下打印出"Hello World",而後修改變量code的值爲1000
編寫本身的宏時,能夠參考大量vs已有的宏(見:Samples節點下)
Public Module Module1 Function GetOutputWindowPane(ByVal Name As String, Optional ByVal show As Boolean = True) Dim window As Window Dim outputWindow As OutputWindow Dim outputWindowPane As OutputWindowPane window = DTE.Windows.Item(EnvDTE.Constants.vsWindowKindOutput) If show Then window.Visible = show outputWindow = window.Object Try outputWindowPane = outputWindow.OutputWindowPanes.Item(Name) Catch ex As Exception outputWindowPane = outputWindow.OutputWindowPanes.Add(Name) End Try outputWindowPane.Activate() Return outputWindowPane End Function Sub ChangeExpression() Dim bppane As EnvDTE.OutputWindowPane
bppane = GetOutputWindowPane("Debugger") bppane.OutputString("Hello World") DTE.Debugger.ExecuteStatement("code = 1000;") End Sub End Module
注3:若是彈出如下框,代表vs沒有開啓macro權限(Tools -- Options... -- Environment -- Add-in/Macro Security中勾選「Allow macros to run」)
注4:2014.2月windows系統更新後,各個版本的vs的宏失效。
vs2005sp1補丁 vs2008sp1補丁 vs2010sp1補丁
14.在windows API上打斷點
(1)例如:對SetWindowText打斷點。首先當前程序字符集爲未設置或多字節,則SetWindowTextA;爲Unicode則爲SetWindowTextW。下面以SetWindowTextA爲例。
(2)調試運行程序斷住後,打開Modules窗口能夠看到全部已經加載的模塊,找到windows API所在的模塊,右擊鼠標執行"Load Symbols From" - "Microsoft Symbol Servers"下載並加載對應模塊的pdb
(3)新建一個Break At Function斷點,填入:{,,user32.dll}_SetWindowTextA@8。能夠看到,VS裏面的符號跟windbg相比多了一些字符,其中‘_’表示stdcall類型,後面‘@8’表示全部參數的字節數的和。
有些函數Symbol Name與導出函數名可能不一致,例如GetDC(HWND),其Symbol Name爲NtUserGetDC,最後斷點應填入:{,,user32.dll}_NtUserGetDC@4
注:查找windows API符號名可使用windbg的x命令或者使用pdb解析工具(symView)
也能夠直接使用地址對windows API打斷點(這種方式不須要符號的支持):如對GetDC打斷點,能夠用Dependency查看其在user32.dll中導出函數地址(Entry Point列):0x000172CC
而後在Modules窗口中得到user32.dll模塊起始地址0x75840000,最後對兩個值相加後的絕對地址處直接設置斷點:{,,user32.dll}0x758572CC
15. 異常(First-chance)時斷住程序
在調試程序時,Output窗口有時會出現「First-chance exception in xxx.exe...」這樣的信息。
通常來講,這是因爲程序中發生了異常,被調試器捕獲而產生的輸出。
在調試器中運行程序時,若是程序產生異常,調試器會首先得到通知(即First-chance exception)。
若程序沒有捕獲該異常,則在結束進程以前,操做系統會再次通知調試器(即Second-chance exception,Last-chance exception)。
(1)在"Debug"-"Exceptions...",彈出以下對話框:點擊Add按鈕,新增一個int類型的C++ Exceptions異常,並勾選Thrown
(2)當int、int*、int&的異常被catch到時,會斷住程序進入調試狀態(注:以上void類型對應:char*、void*的異常)
16. 單步調試自動跳過沒必要進入的函數 (注:僅適用於Native c++)
Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\9.0\NativeDE\StepOver] "1"="\\scope:CString.*\\:\\:.*=NoStepInto"
注1:若是是32位windows,刪除上面路徑中的Wow6432Node
注2:不進入任何CString的方法(前面的1表示優先級,該值越大優先級越高)
++++++++++++++++++++++++++++++
NoStepInto 不可進入匹配函數
StepInto 可進入匹配函數
特殊字符串:
\cid 表明一個C/C++標識符
\funct 表明一個C/C++函數名
\scope 表明一個函數的做用範圍(命名空間+類名 如:ATL::CFoo::CBar::)
\anything 表明一個字符串
\oper 表明一個C/C++操做符
正則表達式:
\ 轉義字符 如:要使用 「\」 自己, 則應該使用「\\」
\: 表明字符:
. 匹配任意字符
* 其左邊的字符被匹配任意次(0次或屢次)。如:be*匹配「b」,「be」或「bee」
.* 匹配0個或多個字符
更多例子:
例1:不進入重載操做符函數:
10 \scope:operator\oper:=NoStepInto
例2:除了CComBSTRs的非操做符函數,不進入任何ATL::開頭的函數:
20 ATL\:\:CComBSTR::\funct:=StepInto
10 ATL\:\:.*=NoStepInto
例3:除了全局模版函數外,不進入任何模板函數:
20 \scope:\funct:=StepInto
10 .*[\<\>].NoStepInto
++++++++++++++++++++++++++++++
17. 使用OutputDebugString進行日誌調試
(1)調試狀態時,會將日誌輸出到Debug過濾器的Output窗口中
(2)非調試狀態時,可採用DbgView.exe來捕捉程序日誌
18. 使用autoexp.dat自定義調試時變量的顯示格式
文件所在位置:Microsoft Visual Studio 9.0\Common7\Packages\Debugger\autoexp.dat
在autoexp.dat中的[Visualizer]域能夠對各類類型變量的顯示格式進行配置,來優化變量在調試時顯示,提升效率。
注1:在vs中要讓autoexp.dat生效須要去掉"Tools"-"Options..."對話框中,
"Debugging"-"General"-"Show raw structure of objects in variables windows"的勾選
注2:vs2012版本後,autoexp.dat被廢棄,改用.natvis文件,該文件須要安裝到"%USERPROFILE%\Documents\Visual Studio 2012\Visualizers"目錄中
如UE4引擎源碼中的%EngineDir%\Engine\Extras\VisualStudioDebugging\UE4.natvis
(1) STL之string、vector、map
①原始顯示結果:
②配置了autoexp.dat的顯示結果:
-->對應的配置內容以下:
; std::string -- char std::basic_string<char,*>{ preview ( #if(($e._Myres) < ($e._BUF_SIZE)) ( [$e._Bx._Buf,s]) #else ( [$e._Bx._Ptr,s])) stringview ( #if(($e._Myres) < ($e._BUF_SIZE)) ( [$e._Bx._Buf,sb]) #else ( [$e._Bx._Ptr,sb])) children ( #if(($e._Myres) < ($e._BUF_SIZE)) ( #([actual members]: [$e,!] , #array( expr: $e._Bx._Buf[$i], size: $e._Mysize)) ) #else ( #([actual members]: [$e,!], #array( expr: $e._Bx._Ptr[$i], size: $e._Mysize)) ) ) } ;------------------------------------------------------------------------------ ; std::vector ;------------------------------------------------------------------------------ std::vector<*>{ children ( #array ( expr : ($e._Myfirst)[$i], size : $e._Mylast-$e._Myfirst ) ) preview ( #( "[", $e._Mylast - $e._Myfirst , "](", #array ( expr : ($e._Myfirst)[$i], size : $e._Mylast-$e._Myfirst ), ")" ) ) } ;------------------------------------------------------------------------------ ; std::map ;------------------------------------------------------------------------------ std::map<*>{ children ( #tree ( head : $e._Myhead->_Parent, skip : $e._Myhead, size : $e._Mysize, left : _Left, right : _Right ) : $e._Myval ) preview ( #( "[", $e._Mysize, "](", #tree ( head : $e._Myhead->_Parent, skip : $e._Myhead, size : $e._Mysize, left : _Left, right : _Right ) : $e._Myval, ")" ) ) }
(2) 自定義類MyArray
①原始顯示結果:
②配置了autoexp.dat的顯示結果:
-->對應的配置內容以下:
MyArray{ preview ( #( "[size is ", $c.m_nSize, "] m_pData is (", #array ( expr: ($c.m_pData)[$i], size: $c.m_nSize ), ")..." ) ) stringview ( #( "Hello MyArray!!!" ) ) children ( #( #array ( expr: ($c.m_pData)[$i], size: $c.m_nSize ) ) ) }
注1:雙引號中字符串不能含有冒號,如:"[size is "不能寫成"size: "
注2:多個類型使用 | 進行鏈接。如:MyArray|ArrayEx
注3:preview、stringview及children。對於不須要的部分能夠不用定義,且三個部分沒有前後順序之分。
注4:格式的定義的最外層用大括號{},其中的每一個部分使用小括號()。
注5:格式定義出錯時,運行VS會彈出提示窗口,對於格式配置錯誤的類型,在調試期間沒法正常顯示。
注6:最外層的左邊的大括號{必須緊挨着最後一個類型名,不然不管後面的格式正確與否,都沒法正常顯示。
注7:符號;爲行註釋符。
注8:$c表示當前所定義數據結構的對象,#array表示用數組形式顯示內容,$i表示數組中的每一個元素的索引,$e表示數組中的每一個元素的值
注9:array結構必須同時包含expr和size兩個部分,缺乏其中一個部分都將致使信息沒法正確顯示。
注10:可以使用#switch、#if進行條件分支判斷,要注意的是:#switch結構不能用於#array結構中,不然可能致使VS掛死。