1. 設定軟體斷點,運作到目标位置啟動調試器
方法①:使用彙編指令(注:x64 c++不支援嵌入彙編)
_asm int 3
方法②:編譯器提供的方法
__debugbreak();
方法③:使用windows API
DebugBreak();
WerFault.exe程序(Windows Error Reporting)彈出ConsoleTest.exe已停止工作:

要想出現“調試程式”選項,需要将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程序上,那麼這種方法也就失效了!
下面兩種情況軟體斷點異常會被處理:
① 被SEH異常捕獲并處理
② 被自定義的全局異常函數處理
注:可以将上面兩種情況中的EXCEPTION_EXECUTE_HANDLER修改為EXCEPTION_CONTINUE_SEARCH來指明異常未得到處理
2. 修改變量(注:在懸停出來小面闆、Locals視窗、Autos視窗、Watch視窗、或Quick Watch視窗中進行修改;也可以在Immediate視窗中執行:bFlag=false)
3. 格式化變量
注:d,i:有符号的十進制數
u:無符号的十進制數
o:無符号的八進制數
x:十六進制數(字母小寫)
X:十六進制數(字母大寫)
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*類型)
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節點下)
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. 異常時斷住程式
(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]域可以對各種類型變量的顯示格式進行配置,來優化變量在調試時顯示,提高效率。
注:在vs中要讓autoexp.dat生效需要去掉"Tools"-"Options..."對話框中,
"Debugging"-"General"-"Show raw structure of objects in variables windows"的勾選
(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挂死。