英文原文:10 MoreVisual Studio Debugging Tips for Native Development
我最近遇到了一篇Ivan Shcherbakov写的名为10+个关于Visual Studio的强大调试技巧的文章。然而这篇文章只是提供了一些相对基本的关于Visual Studio的调试技巧。这里至少还有一些同样有用的其他技巧。因此,我整理了一些工作在最新的Visual Studio 2008上的原生开发的10个调试技巧。(如果你工作在托管代码下,这调试器有更多的特性,在CodeProject中有一些介绍它们的文章。)这里是我的一系列附加技巧:
- 1. 异常中断
- 2. 在Watch窗口中的伪变量
- 3. 在符号超过范围之后查看堆对象
- 4. 查看1个数组内的一序列值
- 5. 避免进入不需要的函数
- 6. 从代码启动调试器
- 7. 在Output窗口打印
- 8. 内存泄漏隔离
- 9. 调试发行版
- 10. 远程调试
技巧1:异常中断
在1个处理被调用之前,异常发生了,它可以命令调试器中断。那使得你能够在异常发生之后立即去调试你的应用程序。操作这调用栈可以让你去发现这异常的根本原因。
Visual Studio允许你指定你想中断的特殊异常的类型。1个对话框提供从菜单Debug>Exceptions中。你能够指定原生的(或者托管的)异常和除调试器已知的默认异常。你还能够添加你自定义的异常。
下面是一个当std::exceptin被抛出时调试器中断的例子。
更多阅读:
- 当异常抛出时如何中断
- 如何添加新的异常
技巧2:Watch窗口中的伪变量
Watch窗口或者QuickWatch对话框都提供了一些指定的(调试器能够识别的)被称作伪变量的变量。这些被制作成文档的包含:
- $tid – 当前线程的的线程ID
- $pid – 进程ID
- $cmdline – 附加进程序的命令行字符串
- $user – 运行在程序中的帐户信息
- $registername – 显示指定寄存器的寄存器内容
无论如何,关于最近错误的伪变量是非常有用的。
- $err – 显示最近错误的错误码
- $err, hr – 显示最近错误的消息
更多阅读:
技巧3:在符号超过范围之后查看堆对象
有时,你喜欢查看1个对象(在堆上)甚至是在符号超过范围的对象的值。当这发生了,这在Watch窗口中的变量是灰掉的并且是不能被检查(也不能更新),尽管这个对象依然是生存和正常的。如果你知道这个对象的地址,在全性能下继续观察它是可能的。你能够转换这个地址成这个对象类型的指针然后将它放入Wathc窗口中。
在下面的例子中,在运行出do_foo()之后,_foo是不再能够被访问的。然而,用它的地址并且转换它成foo*,这样我们依然能够观察这个对象。
技巧4:查看1个数组内的一序列值
如果你工作在1个展开在Watch中的大数组(至少几百个元素,但是可能更少)然后去查找一些特定范围的元素将是难以处理的。因为你必须大量滚动。但是如果这个数组是分配在堆上,你甚至都不能在Watch窗口中展开它的元素。这里有1个关于那个问题的解决方案。你能够用语法(array+ <offset>), <count>去查看特定范围<count> 开始于<offset>位置(你的真实对象的源数组)。如果你想查看全部的数组,你能够简单的使用rray,<count>。
如果你的数组是在堆上,那么你能够在Watch窗口里将它展开,但是要查看1个特定范围,你必须用一个稍微不同的语法: ((T*)array+ <offset>), <count>(注意这个语法对在堆上的多维数组也有效)。在这种情况下,T是这种数组的元素的类型。
如果你工作在MFC下并用来自于其中的"array"容器,像CArray,CDWordArray,CStringArray,等等。你当然能够应用这相同的筛选,除此之外你必须查看array的成员变量m_pData,它是拥有数据的真实缓存。
技巧 5: 避免进入不需要的函数
当你在调试代码的时候,你可能经常进入到那些你想跳过的函数,无论它是构造函数,赋值操作或是其他的。它们中的1个经常困扰着我,最甚者就是CString构造函数。这里有1个例子,当准备进入take_a_string() 函数时,首先进入了CString的构造函数。
void take_a_string(CString const &text)
{
}
void test_string()
{
take_a_string(_T("sample"));
}
幸运的是,告诉调试器去越过一些函数、类或者整个命名空间。过去曾用的方法已经改变了。回到VS 6的日子,它通常是通过autoexp.dat文件来指定的。由于Visual Studio2002改变为通过寄存器设置。为了跳过函数,你必须添加一些值在寄存器中 (你能够找到更多详情在 here):
- 这实际的位置依赖于你的Visual Studio版本和操作系统平台(x86或者x64,因为寄存器必须显示在64位的Windows下)
- 这值的名字是1个数字,表示这规则的优先级;更高的数字更高的超越其他的优先级。
- 这个值数据是1个表示什么样的过滤和什么样的行为在执行的正则表达式的REG_SZ值。
为了避免进入任何 CString 函数,我添加了下面的值:
使得它可用,尽管当你强迫进入在上面例子中的take_a_string(),这调试器将跳过CString构造函数。
更多阅读:
技巧 6: 从代码启动调试器
你可能很少需要附加调试器到程序中去,但你不能执行它在Attach窗口(可能因为中断发生太快而没有被捕获),你也不能开始程序在调试器最初的地方。你能够在程序中产生一个中断并给调试器一个机会通过调用内在的__debugbreak()去附加。
void break_for_debugging()
{
__debugbreak();
}
这里有一个事实上的其他方法去完成这。例如触发中断3,但是这仅仅工作在X86平台上(ASM不再在C++支持X64)。这里另外还有1个DebugBreak() 函数,但这是不是简便的,所以推荐的方法是内部的。
__asm int 3;
当你的程序产生一个内部的停止,你有机会附加一个调试器进这个进程。
更多阅读:
- DebugBreak and ASSERTs that work always anywhere
- Debugging with Visual Studio 2005/2008, Part 4: Setting up Code for the Debugger
技巧 7: 在Output窗口打印
通过调用DebugOutputString在调试器的output窗口中显示特定的文本是可行的。如果没有调试器附加,这函数不起作用。
更多阅读:
Tip 8: 内存泄露隔离
在原生开发中内存泄漏是非常重大的问题,找到它们是一个严重的挑战特别是在大型项目中。Visual Studio 提供关于检测到的内存泄漏的报告。这里也有其他的应用程序(免费的或商业的)去帮助你关于它。尽管在一些情况下,它是可能的当分配最终泄漏用调试器去中断。 然而为了达到这一点,你必须找到1个可复现的位置号(尽量那可能是不容易的)。如果你能够做到这一点,接下来当它运行时调试器将会中断。
让我们设想1份分配8个字节的代码,但是绝不释放这分配的内存。Visual Studio会显示这个泄漏对象的报告。多运行几次,我们会发现它一直是相同的位置数(341)。
void leak_some_memory()
{
char* buffer = new char[8];
}
Dumping objects ->
d:\marius\vc++\debuggingdemos\debuggingdemos.cpp(103) : {341} normal block at 0x00F71F38, 8 bytes long.
Data:< > CD CD CD CD CD CD CDCD
Object dump complete.
在1个特定(可复现)位置中断的步骤是:
- 确保你有充分的关于内存泄漏的报告模式 (看Finding Memory Leaks Using the CRT Library).
- 多次运行程序直到你找到可复现的在程序运行结束的内存泄漏报告中位置数({341}在上面我的例子中)
- 放一个断点在程序开始的某个地方,以便你能够尽早的中断。
- 当这初始的中断发生,在Watch窗口Name列写:{,,msvcr90d.dll}_crtBreakAlloc,在Value列显示这你想侦察的分配的数。
- 继续调试(F5).
- 这执行停止在这指定的位置。你能够用用Call Stack返回到你的分配被触发的代码。
- 跟随我的例子的步骤,用这个分配为341的数是能够识别泄漏内存的源头的。
译者注:
- {341}表示第341次内存分配未释放。
- 上面的方法在VS2008下验证通过。在VS2005上需要将msvcr90d.dll改为msvcr80d.dll,验证通过。如果是静态连接到MFC的话,则只需要在Name栏中填写_crtBreakAlloc,不需要包含前面的库。
- 另外还可以通过函数_CrtSetBreakAlloc(341),它表示中断于第341次内存分配的地方。此方法在VS2005上验证通过。
技巧 9: 调试发行版
调试和发行版本意味着不同的目的。调试配置用来做开发,顾名思义发行配置是用来作为程序的最终版本的。据估计是由于它满足发行必须的质量要求,如此1个配置包含优化及去掉了在调试版本中的调试信息。然而,有时你可以想像调试调试版一样去调试发行版。为了做到这一点,你必须在配置中做一些改变。但是在这种情况下,它证明你不再是在调试发行版,而是相当于一个调试和发行的混合版。
这儿有一些你必须做的事,这必须的是:
- C/C++ > General > Debug Information Format should be "Program Database (/Zi)"
- C/C++ > Optimization > Optimization should be "Disabled (/Od)"
- Linker > Debugging > Generate Debug Info should be "Yes (/DEBUG)"
更多阅读:
技巧 10: 远程调试
另一个重要的调试经验是远程调试,这是一个巨大的话题,多次被提到,所以我仅仅简而概之。
- 你需要在远程机器上安装远程调试监控器。
- 这远程调试器必须以管理员运行并且这用户必须是管理员组之一。
- 当你运行你的监控器,它启动一个新的服务。这个服务的名字必须用在Visual Studio的Attach to Process窗口中的Qualifier。
- 这防火墙必须允许在Vistual Studio与远程调试器之间连通。
- 为了能够调试,这PDB文件是关键;在Visual Studio调试器的命令下是能够自动导入它们的。
- 在本地机器上的本地PDB文件必须是可用的(在远程机器上的相同路径有1个相应的模块被安置)。
- 在远程机器上的被管理的PDB文件必须是可用的。
远程调试监控下载:
更多阅读:
- How to: Run the Remote Debugging Monitor
- Loading debug symbols when remote debugging: native vs managed
- PDB Files: What Every Developer Must Know