VC調試技巧
Visual C++ 的 C 運行時刻函數庫標識模板
0xCD 已經分配的數據(alloCated Data)
0xDD 已經釋放的數據(Deleted Data)
0xFD 被保護的數據(Fence Data)
Visual C++ 的 C 運行時刻函數庫內存塊類型標識符
_NORMAL_BLOCK 由程序直接分配的內存
_CLIENT_BLOCK 由程序直接分配的內存,可以通過內存調試函數對其擁有特殊控制權
_CRT_BLOCK 由運行時刻函數庫內部分配的內存
_FREE_BLOCK 已經被釋放,但是跟蹤仍然被保留下來的內存,這在用戶選擇了調試堆的選項 _CRTDBG_DELAY_FREE_MEM_DF 以后會出現
_IGNORE_BLOCK 當使用 _CrtDbgFlag 關閉內存調試操作以后分配的內存
Visual C++ 的 C 運行時刻函數庫提供的幫助調試內存錯誤的函數
_CrtCheckMemory 檢查每一個內存塊的內部數據結構和守護(guard)字節,以測試其完整性。
_CrtIsValidHeapPointer 檢驗指定指針是否存在于本地堆中
_CrtIsValidPointer 檢驗給定內存范圍對讀寫操作是否合法
_CrtIsMemoryBlock 檢驗給定內存范圍是否位于本地堆當中,是否擁有例如 _NORMAL_BLOCK 這樣的有效內存塊類型標識符(該函數還可以被用以獲得分配數目以及進行內存分配的源文件名和行號)
用于調試內存泄露的 Visual C++ 的 C 運行時刻函數庫中的函數
_CrtSetBreakAlloc 在給定的分配數目上分配斷點,每一塊被分配的內存都被指派一個連續的分配號。(查找特定的內存泄露十分有用)
_CrtDumpMemoryLeaks 判斷內存泄露是否發生。如果發生則將本地堆中所有當前分配的內存按照用戶可以閱讀的方式進行內存映象轉儲。(在程序結束的時候檢測內存泄露十分有用)
_CrtMemCheckPoint 在 _CrtMemState 結構中產生一個本地堆的當前狀態的快照
_CrtMemDifference 比較兩個堆中的斷點,將不同之處保存在 _CrtMemState 結構中。如果不同則返真。(檢測特殊區域代碼的內存泄露十分有用)
_CrtMemDumpAllObjectsSince將從給定堆斷點或者從程序開始分配的內存的所有信息按照用戶可以閱讀的方式進行內存映象轉儲
_CrtMemDumpStatistics 將信息按照用戶可以閱讀的方式進行內存映象轉儲到一個 _CrtMemState 結構中。(對于得到被使用的動態內存的全面觀察信息來說十分有用)
用于一般內存調試的 Visual C++ 的 C 運行時刻函數庫中的函數
_CrtSetDbgFlag 控制內存調試函數的行為
_CrtSetAllocHook 加裁在內存分配過程中的鉤子函數。(對于檢測內存使用狀況或者模擬內存不足情況十分有用)
_CrtSetReportHook 加裁進行定制報告處理的函數。
_CrtSetDumpClient 加裁對用戶進行內存映象轉儲的函數。
_CrtDoForAllClientObject 對于所有作為用戶塊進行分配的數據,調用指定的函數
ATL 內存調試
在 #include <AtlCom.h> 之前,定義控制預處理的常量 _ATL_DEBUG_INTERFACES,這樣就可以對接口資源泄露進行跟蹤(跟蹤 AddRef 和 Release)
INTERFACE LEAK: RefCount = 7, MaxRefCount = 10, {Allocation = 42}
CMyComClass - Leak
然后在服務器初始化的時候對 CComModule 對象的 m_nIndexBreakAt 成員變量進行設置
#define _ATL_DEBUG_INTERFACES
BOOL WINAPI DllMain(...)
{
if (dwReason == DLL_PROCESS_ATTACH) {
...
_Module.m_nIndexBreakAt = 42; // Set breakpoint to find interface leak
}
...
}
使用調試堆
必須使用程序的調試版本,連接的是 C 運行時刻函數庫的調試版本,必須定義 _DEBUG,這樣調試堆版本的 new 和 delete 才會被調用。
調試堆選項
_CRTDBG_ALLOC_MEME_DF,啟動堆分配檢查
_CRTDBG_DELAY_FREE_MEM_DF,阻止內存被真正釋放
_CRTDBG_CHECK_ALWAYS_DF,每次內存分配和釋放都調用 _CrtCheckMemory
_CRTDBG_CHECK_CRT_DF,一般不使用
_CRTDBG_LEAK_CHECK_DF,在程序結束時調用 _CrtDumpMemoryLeaks
推薦總是使用 _CRTDBG_ALLOC_MEM_DF 和 _CRTDBG_LEAK_CHECK_DF,僅僅在調試內存錯誤時才用 _CRTDBG_CHECK_ALWAYS_DF 和 _CRTDBG_DELAY_FREE_MEM_DF,
顯示內存泄露
#define _CRTDBG_MAP_ALLOC 在所有頭文件之前,
在 cpp 文件中,加上
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
查看 Windows 內存地址
Windows 進程一般放在 0x00400000 的地址,0x00400000 是所有版本的 Windows 能使用的最低地址,進程實例句柄的值總是和它的基地址相同,
所有未被初始化的自動變量都會設上 0xCCCCCCCC,
Windows 2000 的虛擬地址空間的使用
0x00030000 ~ 0x0012FFFF 線程棧
0x00130000 ~ 0x003FFFFF 堆(有時堆位于此處)
0x00400000 ~ 0x005FFFFF 可執行代碼
0x00600000 ~ 0x0FFFFFFF 堆(有時堆位于此處)
0x10000000 ~ 0x5FFFFFFF App Dlls, Msvcrt.dll, Mfc42.dll
0x77000000 ~ 0xFFFFFFFF Advapi32.dll,...
通過設置數據斷點,在對 0xCDCDCDCD,0xCCCCCCCC,0xDDDDDDDD 等地址修改時,調試器會提醒你,寫非合法數據
調試比較難的內存破壞問題時,可以試試 _CRTDBG_CHECK_ALWAYS_DF 和 _CRTDBG_DELAY_FREE_MEM_DF,
當類需要析構函數或者復制構造函數或者賦值操作符時,它同時需要這三個,否則可能導致內存破壞和泄露,
用分配號定位內存泄露
_CrtSetBreakAlloc(27)
Watch 窗口 {,,msvcrtd.dll}_CrtSetBreakAlloc(27)
使用內存檢查點
void LeakyFunction()
{
_CrtMemState oldState, newState, stateDiff;
_CrtMemCheckPoint(&oldState);
{
...
}
_CrtMemCheckPoint(&newState);
if (_CrtMemeDifference(&stateDiff, &oldState, &newState)) {
_CrtMemDumpStatistics(&stateDiff);
_CrtMemDumpAllObjectsSince(&oldState);
}
}
使用 _CRTDBG_DELAY_FREE_MEM_DF 調試堆選項防止 _CrtMemDumpAllObjectsSince 導出錯誤的結果
在刪除圖形設備接口對象前,一定確定它們沒有被任何有效的設備上下文選中
在 Windows 2000 里發現資源泄露是最簡單方法是運行性能監視工具,監視程序的私有空間和句柄數隨時間的變化,如果私有空間或者句柄數據持續增長,就出現了內存泄露
函數的返回值是通過 EAX 傳遞的,
--------------------------------------------------------------------------------------------
函數運行時間
@CLK,d
@CLK,0
函數返回值
32位 - @EAX
64位 - @EAX(低32位),@EDX(高32位)
大于64位,會在EAX中放入指向返回值指針,如返回一個 CRect, (CRect *) @EAX / 在內存窗口的Address欄中鍵入EAX查看
API 調用失敗,鍵入@ERR可查看 GetLastError()的值, 翻譯錯誤代碼"@ERR,hr"
Windows自身會創建退出代碼為 -1 的線程,如顯示一通用對話框時,不用擔心其返回為 -1,
關閉 GDI 的批處理功能,GdiSetBatchLimit(1)便于調試繪圖代碼;
畫圖代碼閃爍的調試,
1.不適當的UpdateWindow調用,
2.調用InvalidateRect而不指定更新矩形,
3.調用InvalidateRect而將擦除背景參數不適當地設置為真,
4.不適當地使用CS_HREDRAW和CS_VREDRAW窗口風格,僅當客房區大小改變需要重畫整個窗口時,才需要設置這兩種風格。
如果窗口中的某些元素需要居中放置,這是必要的,
調試WM_MOUSEMOVE消息,大部分情況下,你希望在鼠標移動到窗口的特定位置或在特殊的環境下才發生中斷,這時可以這樣寫
void CMyWnd::OnMouseMove(UINT nFlags, CPoint point)
{
#ifdef _DEBUG
if (GetAsyncKeyState(VF_CONTROL) < 0) {
int bogus = 0; // 可以在這里設置斷點,僅當你按下Ctrl鍵時,才進入這里.
}
#endif
}
WM_LBUTTONDOWN 和 WM_LBUTTONUP 消息也是一個問題,因為在 WM_LBUTTONDOWN 消息處理函數中設置一個斷點
很可能會導致 WM_LBUTTONUP 被調試器吸收.繞開這個問題的辦法是在調試器中一直保持鼠標按下狀態,只使用鍵盤控制調試器,
程序重新獲得了輸入焦點,你就可以釋放鼠標按鈕了。
使用 Spy++的 Log Messages 調試與消息有關的問題.
使用回調幫助調試代碼,如調試工具提示時,使用 LPSTR_TEXTCALLBACK,
--------------------------------------------------------------------------------------------
某些變量應使用 volatile 避開編譯器優化,使編譯器產生的代碼總是直接訪問內存。
當前線程 ID, 在 Watch 窗口中輸入 dw(@TIB+0x24)。
設置特定線程的斷點,在關注的線程的 Watch 窗口中輸入 @TIB 以確定線程的 TIB 地址,然后設置條件斷點,@TIB == TIBAddress,TIBAddress是偽寄存器 @TIB 的值。
利用 TIB 的 pvArbitary 域設置了線程名稱以后,可以在 Watch 窗口中輸入 (PCHAR)(dw(@TIB+0x14))以顯示當前線程的名稱。
如果要單獨調試某一線程,可以先在線程列表選擇目標線程,再 SetFocus 這一線程,而后暫停所有其他線程,