天天看點

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)

  if (GetAsyncKeyState(VF_CONTROL) < 0) {

     int bogus = 0;  // 可以在這裡設定斷點,僅當你按下Ctrl鍵時,才進入這裡.

  }

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 這一線程,而後暫停所有其他線程,