首页 > 代码库 > VC调试技巧
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 这一线程,而后暂停所有其他线程,