65.9K
CodeProject 正在变化。 阅读更多。
Home

10 个原生开发 Visual Studio 调试技巧(又十个)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.98/5 (107投票s)

2012 年 10 月 2 日

CPOL

10分钟阅读

viewsIcon

263360

本文提出了一个关于使用 Visual Studio 进行原生开发的调试技巧列表。

我最近偶然看到了 Ivan Shcherbakov 的这篇文章,名为《10+ 个强大的 Visual Studio 调试技巧》。尽管这篇文章介绍了一些相当基础的 Visual Studio 调试技巧,但还有其他同样有用的技巧。因此,我整理了一个关于原生开发的其他十个调试技巧列表,这些技巧至少适用于 Visual Studio 2008。(如果您处理的是托管代码,调试器会有更多功能,CodeProject 上也有几篇介绍这些功能的文章。)以下是我补充的技巧列表:

  1. 异常时中断
  2. 监视窗口中的伪变量
  3. 符号超出作用域后监视堆对象
  4. 监视数组中的值范围
  5. 避免单步进入不希望的函数
  6. 从代码启动调试器
  7. 输出到输出窗口
  8. 内存泄漏隔离
  9. 调试 Release 版本
  10. 远程调试

更多调试技巧,请查看本系列的第二篇文章:《10 个更多 Visual Studio 原生开发调试技巧》。

技巧 1:异常时中断

可以指示调试器在异常发生时,在调用处理程序之前中断。这允许您在异常发生后立即调试应用程序。通过导航调用堆栈,应该可以找出异常的根本原因。

Visual Studio 允许您指定要中断的异常类别或特定异常。可以在“调试”>“异常”菜单中找到一个对话框。您可以指定原生(或托管)异常,除了调试器已知的默认异常之外,还可以添加自定义异常。

以下是一个在抛出 std::exception 时调试器中断的示例。

其他阅读材料

技巧 2:监视窗口中的伪变量

监视窗口或“快速监视”对话框支持一些特殊的(调试器识别的)变量,称为伪变量。已文档化的包括:

  • $tid – 当前线程的线程 ID
  • $pid – 进程 ID
  • $cmdline – 启动程序的命令行字符串
  • $user – 运行程序的帐户信息
  • $registername – 显示寄存器 registername 的内容

然而,一个非常实用的伪变量是用于最后错误的代码:

  • $err – 显示最后错误的数字代码
  • $err, hr – 显示最后错误的描述信息

附加阅读

技巧 3:符号超出作用域后监视堆对象

有时,您希望在对象(在堆上)的符号超出作用域后仍然监视其值。当发生这种情况时,监视窗口中的变量将被禁用,无法再检查(也无法更新),即使对象仍然存在。如果您知道对象的地址,就可以继续完全监视它。然后,您可以将地址强制转换为该对象类型的指针,并将其放入监视窗口。

在下面的示例中,在退出 do_foo() 后,_foo 不再可在监视窗口中访问。但是,获取其地址并将其强制转换为 foo*,我们仍然可以监视该对象。

技巧 4:监视数组中的值范围

如果您处理大型数组(假设至少有几百个元素,但可能更少),在监视窗口中展开数组并查找特定范围的元素会很麻烦,因为您需要滚动很多次。如果数组在堆上分配,您甚至无法在监视窗口中展开其元素。对此有一个解决方案。您可以使用语法 (array + <offset>), <count> 来监视从 <offset> 位置开始的 <count> 个元素的特定范围(当然,这里的 array 是您的实际对象)。如果您想监视整个数组,只需说 array, <count> 即可。

如果您的数组在堆上,您可以在监视窗口中展开它,但要监视特定范围,您需要使用略有不同的语法:((T*)array + <offset>), <count>(请注意,此语法也适用于堆上的数组)。在这种情况下,T 是数组元素的类型。

如果您使用 MFC 并使用其中的“数组”容器,例如 CArrayCDWordArrayCStringArray 等,您可以应用相同的过滤,只不过您必须监视数组的 m_pData 成员,该成员是实际保存数据的缓冲区。

技巧 5:避免单步进入不希望的函数

在调试代码时,您可能经常单步进入那些您希望跳过的函数,无论是构造函数、赋值运算符还是其他函数。曾经最困扰我的是 CString 构造函数。这是一个示例,在单步进入 take_a_string() 函数时,会先单步进入 CString 的构造函数。

void take_a_string(CString const &text)
{
}

void test_string()
{
   take_a_string(_T("sample"));
}

幸运的是,您可以指示调试器跳过某些方法、类或整个命名空间。实现此功能的方式已发生变化。在 VS 6 时代,这通过 autoexp.dat 文件指定。自 Visual Studio 2002 起,已更改为注册表设置。要启用跳过函数,您需要在注册表中添加一些值(您可以在 这里找到所有详细信息)。

  • 实际位置取决于您的 Visual Studio 版本和操作系统平台(x86 或 x64,因为注册表对 64 位 Windows 有两个视图)。
  • 值名称是一个数字,表示规则的优先级;数字越大,规则的优先级越高。
  • 值数据是一个 REG_SZ 值,表示一个正则表达式,用于指定要过滤什么以及执行什么操作。

为了跳过任何 CString 方法,我添加了以下规则:

启用此设置后,即使您在上面的示例中按“单步进入”进入 take_a_string(),调试器也会跳过 CString 的构造函数。

其他阅读材料

技巧 6:从代码启动调试器

有时,您可能需要附加调试器到程序,但无法通过“附加”窗口做到这一点(也许是因为中断发生得太快而无法附加),也无法在开始时就用调试器启动程序。您可以通过调用 __debugbreak() 内建函数来导致程序中断,并让调试器有机会附加。

void break_for_debugging()
{
   __debugbreak();
}

实际上还有其他方法可以做到这一点,例如触发中断 3,但这只适用于 x86 平台(C++ 不再支持 x64 的汇编)。还有一个 DebugBreak() 函数,但它不可移植,因此内建函数是推荐的方法。

__asm int 3; 

当您的程序执行内建函数时,它会停止,您就有机会将调试器附加到进程。

其他阅读材料

技巧 7:输出到输出窗口

通过调用 DebugOutputString 可以将特定文本显示在调试器的输出窗口中。如果没有附加调试器,该函数将不执行任何操作。

技巧 8:内存泄漏隔离

内存泄漏是原生开发中的一个重要问题,在大型项目中查找它们可能非常具有挑战性。Visual Studio 提供有关检测到的内存泄漏的报告,还有其他应用程序(免费或商业)可以帮助您。然而,在某些情况下,可以使用调试器在发生最终会导致泄漏的分配时进行中断。要做到这一点,您必须找到一个可重现的分配编号(但这可能并不容易)。如果您能够做到这一点,调试器就可以在执行分配时立即中断。

让我们考虑这段分配了 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 CD CD 
Object dump complete.

在特定(可重现)分配时中断的步骤是:

  • 确保您有适当的内存泄漏报告模式(请参阅 使用 CRT 库查找内存泄漏)。
  • 多次运行程序,直到在程序结束时的内存泄漏报告中找到可重现的分配编号(例如我上面的示例中的 {341})。
  • 在程序开始时设置一个断点,以便您尽可能早地中断。
  • 用调试器启动应用程序。
  • 当命中初始断点时,在监视窗口的“名称”列中输入:{,,msvcr90d.dll}_crtBreakAlloc,然后在“值”列中输入您要调查的分配编号。
  • 继续调试(F5)。
  • 执行将在指定分配处停止。您可以使用调用堆栈导航回触发分配的代码。

按照这些步骤处理我的示例中的分配编号 341,我能够确定泄漏的来源。

技巧 9:调试 Release 版本

Debug 和 Release 版本有不同的用途。Debug 配置用于开发,而 Release 配置顾名思义,应该用于程序的最终版本。由于应用程序被认为是满足发布所需质量的,因此这种配置包含会破坏 Debug 版本调试体验的优化和设置。然而,有时您希望能够像调试 Debug 版本一样调试 Release 版本。要做到这一点,您需要对配置进行一些更改。但是,在这种情况下,有人可能会争辩说您不再调试 Release 版本,而是调试 Debug 和 Release 版本的混合体。

有几件事您应该做;强制性的包括:

  • C/C++ > 常规 > 调试信息格式应为“程序数据库 (/Zi)”
  • C/C++ > 优化 > 优化应为“禁用 (/Od)”
  • 链接器 > 调试 > 生成调试信息应为“是 (/DEBUG)”

附加阅读

技巧 10:远程调试

另一个重要的调试体验是远程调试。这是一个较大的主题,已经被讨论过很多次,所以我只想做一点总结。

  • 您需要在远程计算机上安装远程调试监视器。
  • 远程调试监视器必须“以管理员身份”运行,并且用户必须是管理员组的成员。
  • 当您运行监视器时,它会启动一个新服务器,您必须在 Visual Studio 的“附加到进程”窗口的“限定符”组合框中使用该服务器的名称。

  • 远程计算机和本地计算机上的防火墙必须允许 Visual Studio 和远程调试监视器之间的通信。
  • 要能够调试,PDB 文件至关重要;为了让 Visual Studio 调试器能够自动加载它们:
    • 原生 PDB 文件必须可在本地计算机上(在与远程计算机上的相应模块相同的路径上)。
    • 托管 PDB 文件必须可在远程计算机上。

远程调试监视器下载

其他阅读材料

结论

本文介绍的调试技巧和启发本文的 原始文章 应该为大多数调试经验和问题提供必要的技巧。要获取有关这些技巧的更多信息,我建议阅读其他阅读材料。

© . All rights reserved.