Visual Studio调试器指南---断点和跟踪点
断点告诉调试器应用程序应该在某个点中断或暂停执行。当中断发生时,程序和调试器被称为处于中断模式。跟踪点是Visual Studio中的一个新调试器功能。跟踪点是具有与其关联的自定义操作的断点。当命中跟踪点时,调试器将执行指定的跟踪点操作,而不是中断程序执行。跟踪点的一个常见用途是在程序到达某个点时打印消息。您可以将跟踪点用于许多与使用跟踪相同的目的,但无需修改代码。另一个区别是,跟踪点只在调试器下运行时工作。
断点概述
断点标志符号
源窗口和反汇编窗口通过在左边距中显示名为glyph的符号来显示断点位置。下表描述了这些标志符号。如果将鼠标放在断点图示符上,则会出现一个断点提示,其中包含更多信息。此信息对于错误和警告断点特别有用。
Glyph |
Description |
---|---|
正常断点。实心标志符号表示断点已启用。空心标志符号表示它已被禁用。 |
|
高级断点。激活/禁用。符号+表示断点至少附加了一个高级功能(如条件、命中计数或筛选器)。 |
|
映射断点。激活/禁用。断点在ASP/ASP.NET代码中设置并映射到相应HTML页面中的断点,或者在服务器端脚本文件中设置并映射到相应的客户端脚本文件。 |
|
追踪点。激活/禁用。达到此点将执行指定的操作,但不会中断程序执行。 |
|
高级跟踪点。激活/禁用。符号+表示跟踪点至少附加了一个高级功能(如条件、命中计数或筛选器)。 |
|
映射跟踪点。激活/禁用。跟踪点在ASP/ASP.NET代码中设置,并映射到相应HTML页面中的跟踪点。 |
|
断点或跟踪点错误。X表示由于错误条件,无法设置断点或跟踪点。 |
|
断点或跟踪点警告。感叹号表示由于临时条件,无法设置断点或跟踪点。通常,这意味着断点或跟踪点位置的代码尚未加载。如果附加到进程并且未加载该进程的符号,则也可以看到。加载代码或符号时,将启用断点并更改图示符 |
基本断点
最基本的断点是在源代码行或函数上设置的断点。注意:如果一个源文件中有超过64000行代码,则不会命中64000行之后的行上的断点。
一行上有多个断点
有时,一行代码包含多个可执行语句。在这种情况下,可以在行上设置多个断点。包含当前选定断点的代码语句周围将出现一个框。此框可用于区分同一行代码上的多个断点。可以在“断点”窗口中选择断点,也可以单击“源”窗口中包含断点的语句。
高级断点
如果您有VisualC++或VisualStudio,任何版本,您可以设置更高级的断点,创建具有高级特性的断点,并以更强大的方式使用断点。
内存地址和内存更改上的断点
可以在内存地址上设置断点,也就是地址断点。在C/C++中,可以设置一个在内存更改时命中的断点,也就是数据断点(仅限本机)。
筛选、命中计数和条件
过滤器、命中计数和条件是可以附加到任何类型断点(包括跟踪点)的高级功能。
- 筛选器导致在指定的计算机、进程和线程上设置断点。在调试运行在多个处理器上的应用程序时,筛选器非常有用。
- 命中计数跟踪断点被命中的次数。默认情况下,断点在每次被命中时都会中断执行。您可以更改此行为,以便只有当命中计数等于或超过指定值或命中计数是给定值的倍数时,断点才会断开。指定的命中计数仅为调试会话保留。
- 条件是一个表达式,计算该表达式以确定断点是否将断开。
断点限制
在调试混合模式、本机和托管代码时,请避免在系统组件上设置断点。在混合模式调试期间在系统组件上设置断点可能会导致公共语言运行库中断,调试器停止响应。除非您单步执行调用,否则调试器不会自动附加到XML Web服务。这意味着在XML Web服务中设置的任何断点都不会被命中,除非您进入调用。如果尝试使用“开始调试”或“继续”来运行断点,而不是使用“单步执行”,则不会命中断点。
设置简单断点
Visual Studio调试器提供了许多设置断点的方法。下面提供了两种设置简单断点的快速方法。
- 在快捷菜单上设置简单断点
在源窗口中,右键单击要设置断点的可执行代码行。在快捷菜单上,单击“断点”,然后单击“插入断点”。 - 在“调试”菜单上设置简单断点
在源窗口中,单击要设置断点的可执行代码行。在“调试”菜单上,单击“切换断点”。
设置地址断点
可以在“反汇编”窗口中的内存地址处设置断点。设置断点后,可以在“断点”窗口中编辑地址断点。先转到汇编窗口,然后进行下面的操作:
在“反汇编”窗口中,单击一行代码,然后单击“调试”菜单上的“切换断点”。
-或者-
右键单击一行代码,然后选择“插入断点”。
设置函数断点
可以在函数的开头或函数中的指定位置设置断点。对于脚本,只能在函数的开头设置断点。
- 插入函数断点
(可选)在源窗口中,单击函数的名称。这会将函数的名称插入到“新建断点”对话框中,以便您不必键入它。
在“调试”菜单上,指向“新建断点”,然后单击“函数断点”。
出现“新建断点”对话框。
如果“函数”文本框不显示要设置断点的函数的名称,请在“函数”框中键入函数名称,并确保“语言”下拉列表显示该函数的正确编程语言。如果函数未重载,则只需要函数名。对于重载函数,可以指定参数以正确设置断点。键入函数名,后跟括在括号中的参数类型名。例如,对于一个名为a的C#方法,它接受一个带字符串参数和int参数,请键入a(int,string)。
在本机C++中,可以使用上下文运算符。(仅限Visual Basic和C)如果希望Visual Studio Intellisense验证输入的函数名,请选中“使用Intellisense验证函数名”复选框。
如果选中该复选框,并且键入的内容与重载签名不匹配,则会出现“选择断点”对话框,您可以选择要放入断点的重载。如果未选中该复选框并键入函数名,则断点将放入所有重载中。如果未选中该复选框,并且键入的签名不匹配,则不会在代码中插入断点。
断点设置在函数的开头。如果要在函数中的其他位置设置断点,请更改“行”和“字符”框中的值。
单击“确定”。 - 从“断点”窗口插入函数断点
后面的操作跟上面一样。
从调用堆栈窗口设置函数调用的断点
此功能不适用于Transact-SQL、Internet Explorer中的脚本或ASP。本主题描述如何使用调用堆栈窗口在对函数的特定调用上设置断点。断点在函数调用中的下一个可执行指令处设置。如果要在函数本身上设置断点,而不是在对函数的特定调用上设置断点。
在“调用堆栈”窗口中,在中断模式下,右键单击函数调用,然后单击快捷菜单(断点子菜单)上的“插入断点”。断点符号出现在函数调用名称旁边的左边距中。查看断点属性时,此断点显示为地址断点,其内存位置与函数中的下一个可执行指令相对应。
设置数据断点(Native Only)
Visual Studio 调试器指南---查看调试器中的数据
Visual Studio 调试器提供了各种用于检查和修改程序状态的工具。 这些工具中的大多数仅在中断模式下有效。
DataTips
数据提示是用于在调试过程中查看程序中的变量和对象的有关信息的最方便工具之一。 在调试器处于中断模式时,可以在当前范围内查看变量的值,方法是将鼠标指针置于源窗口中的变量上。
可视化工具
可视化工具是 Visual Studio 调试器的新组件,通过它可以以有意义的方式查看对象或变量的内容。 例如,可以使用 HTML 可视化工具来查看 HTML 字符串,因为这样可以解释该字符串并在浏览器中显示出来。 您可以通过数据提示、**“监视”窗口、“自动”窗口、“局部变量”窗口或“快速监视”**对话框来访问可视化工具。
变量窗口
可以通过变量窗口来了解变量、寄存器内容和表达式。可以将调试器窗口中使用的数字格式设置为十进制或十六进制。
其他调试器窗口
下面的调试器窗口提供有关程序的重要信息。
查看 |
使用 |
---|---|
寄存器内容 |
如何:使用“寄存器”窗口 |
内存内容 |
如何:使用“内存”窗口 |
|
如何:使用“调用堆栈”窗口 |
由编译器为程序生成的程序集代码 |
如何:使用“反汇编”窗口 |
由您的程序创建线程,即执行的连续流 |
如何:使用“线程”窗口 |
程序使用的模块(DLL 和 EXE) |
如何:使用“模块”窗口 |
Visual Studio 调试器指南---调试器中的表达式
Visual Studio 调试器包括表达式计算器,当您在**“快速监视”对话框、“监视”窗口或“即时”窗口中输入表达式时,这些计算器可以对其进行计算。 这些表达式计算器还可以在“断点”**窗口和调试器中的许多其他位置使用。
常见的表达式计算器功能
试器中常见的各种表达式计算器功能,这些功能仅因语言不同而不同。
隐式变量
在 Visual Basic 和 C# 中,通过使用表达式计算器可以创建隐式变量。 这些隐式变量永远不会超出范围,可以作为任何其他变量一样处理。在 C# 中,通过在表达式计算器中声明隐式变量可创建隐式变量。 例如,您可以在**“即时”**窗口中输入下列 C# 代码:
int b = 100;
在**“即时”窗口中执行此代码时,新的隐式变量将显示在“局部变量”**窗口中,其变量名称前有一个 $ 符号,在本例中,为 $b。
在 Visual Basic 中,不能在表达式计算器中声明隐式变量。 但是,如果在 Visual Basic 表达式计算器中使用未声明的变量,将会自动创建隐式变量。 在 Visual Basic 中,隐式变量不会列在**“局部变量”**窗口中。
断点
如果使用“即时”窗口计算包含断点的 Visual Basic 或 C# 方法或函数,将命中该断点并在**“调用堆栈”**上显示一个新框架。 下面是一个 C# 示例:
classProgram
{static void Main(string[] args)
{//Breakpoint here: int a = 20;
}
}
Visual Studion调试器指南---编辑并继续
“编辑并继续”是一种省时的功能,使您能够在程序处于中断模式时更改源代码。 当您通过选择一条类似 Continue 或 Step 的执行命令继续执行程序时,“编辑并继续”有限制地自动应用代码更改。 这允许您在调试会话期间更改代码,而不是停止程序,重新编译整个程序,再重新启动调试会话。
显式应用代码更改
在 Visual C++ 中,“编辑并继续”可以以两种方法应用代码更改。 代码更改可在选择执行命令时隐式应用,也可使用“应用代码更改”命令显式应用。当显式应用代码更改时,程序保持在中断模式下,不会执行。
在“调试”菜单中选择“应用代码更改”。安装 Visual Studio 时,“编辑并继续”在默认情况下是打开的。
启用和禁用“编辑并继续”
可以在“选项”对话框中禁用或启用“编辑并继续”。 无法在调试过程中更改此设置。“编辑并继续”仅在调试版本中起作用。 对于本机 C++,“编辑并继续”需要使用 /INCREMENTAL 选项,Visual C++ 环境中调试,如果设置了 /ZI 选项,仍可以使用“编辑并继续”。
启用/禁用“编辑并继续”
在“工具”菜单上,单击“选项”。
在“选项”对话框中打开“调试”节点,然后选择“编辑并继续”类别。
若要启用,请选择“启用‘编辑并继续’”复选框。 若要禁用,则清除该复选框。
提示
如果启用了 IntelliTrace 并且收集 IntelliTrace 事件和调用信息,则禁用编辑和继续。
Visual Studio调试本机代码
本文讲述本机应用程序的一些常见调试问题和调试技术。 本节阐述的技术属于高级别技术。
调试优化的代码
当编译器优化代码时,它将重新定位并重组指令, 这会得到更高效的编译的代码。 由于这种调整,调试器并不总能确定与一组指令对应的源代码。
优化可能影响到:
本地变量(可被优化器移除或移动到调试器无法识别的位置)。
函数内部的位置(当优化器合并代码块时发生变化的位置)。
调用堆栈上框架的函数名称(如果优化器合并两个函数,则函数名称可能是错误的)。
但是,假定所有框架都有符号,则在调用堆栈上看到的框架几乎总是正确的。 在下列情况下调用堆栈上的框架将是错误的:有堆栈损坏,有用汇编语言编写的函数,或者有操作系统框架在调用堆栈上没有匹配的符号。全局和静态变量总是正确显示。 结构布局也是这样。 如果您有指向结构的指针而且指针的值是正确的,那么结构的每个成员变量都将显示正确值。出于这些限制原因,只要有可能,就应使用程序的“未优化”版本进行调试。 默认情况下,优化在 Visual C++ 程序的“Debug”配置中关闭,在“Release”配置中打开。
但是,bug 可能仅在程序的优化版本中出现。 在此情况下,必须调试优化的代码。
在“Debug”生成配置中打开优化
创建新项目时,请选择 Win32 Debug目标。 接下来要一直使用 Win32 Debug目标,直至程序已进行全面调试,可以生成 Win32 Release目标为止。 调试器并不优化 Win32 Debug目标。
在解决方案资源管理器中选择项目。
在“视图”菜单上,单击“属性页”。
在“属性页”对话框中,确保在“配置”下拉列表框中选择了 Debug。
在左边的文件夹视图中,选择 C/C++ 文件夹。
在“C++”文件夹下选择 Optimization。
在右边的属性列表中找到“Optimization”。 它旁边的设置可能显示为“Disabled (/Od)”。 选择其他选项(“Minimum Size (/O1)”、“Maximum Speed (/O2)”、“Full Optimization (/Ox)”或“Custom”)之一。
如果为“Optimization”选择了“Custom”选项,现在便可为属性列表中显示的其他任何属性设置选项。
调试优化的代码时,请使用“反汇编”窗口以了解实际创建和执行了哪些指令。 设置断点时,需要注意断点可能随指令一起移动。 例如,考虑以下代码:
for (x=0; x<10; x++)
假定在该行设置了一个断点。 可能希望该断点被命中 10 次,但如果代码进行了优化,则只会命中该断点一次。 因为第一个指令将 x 的值设置为 0。 编译器认定该指令只需执行一次,将其移出循环。 断点随之移动。 而比较和递增 x 的指令仍留在循环内。 当查看“反汇编”窗口时,单步执行单元自动设置为“指令”以允许更大控制,这在逐句通过优化的代码时很有用。
DebugBreak 和 __debugbreak
可以在代码中的任意点调用 DebugBreak Win32 函数或 __debugbreak 内部类型。 DebugBreak 和 __debugbreak 具有与在该位置设置断点相同的效果。因为 DebugBreak 是系统函数调用,所以必须安装系统调试符号以确保中断后显示正确的调用堆栈信息。 否则,调试器可能在显示一帧调用堆栈信息后就停止显示。 如果使用 __debugbreak,则不需要符号。
断言
断言语句指定在程序的某些特定点应为真的条件。 如果该条件不为真,则断言失败,中断程序的执行,并显示“断言失败”对话框。
Visual C++ 支持基于下列构造的断言语句:
MFC 程序的 MFC 断言。
使用 ATL 的程序的 ATLASSERT。
使用 C 运行库的程序的 CRT 断言。
其他 C/C++ 程序的 ANSI assert 函数。
断言可以用于:
捕捉逻辑错误。
检查某操作的结果。
测试错误条件,这些错误条件应已处理。
MFC 和 C 运行库断言
当调试器由于 MFC 或 C 运行库断言而暂停时,它定位到源文件中的断言发生点(如果源可用)。 断言消息显示在“输出”窗口以及“断言失败”对话框中。 如果希望保存断言消息以供将来参考,可以将断言消息从“输出”窗口复制到某个文本窗口。 “输出”窗口可能还包含其他错误信息。 请仔细检查这些消息,因为它们提供了有关确定断言失败原因的线索。
通过在代码中大量使用断言,可以在开发期间捕捉许多错误。 为所做的每个假定编写一个断言是很好的规则。 例如,如果假定某个参数不为 NULL,请使用一条断言语句检查该假定。
_DEBUG
仅当定义了 _DEBUG 时断言语句才编译。 未定义 _DEBUG 时,编译器将断言作为空语句处理。 因此,断言语句在最终发布程序中系统开销为零;可以在代码中大量使用断言语句,而不影响“Release”版本的性能,并且不必使用 #ifdef 指令。
使用断言的副作用
当向代码添加断言时,请确保这些断言没有副作用。 例如,考虑以下断言:
ASSERT(nM++ > 0); // Don't do this!
因为在程序的“Release”版本中不计算 ASSERT 表达式,所以 nM 在“Debug”版本和“Release”版本中会有不同值。 在 MFC 中,可以使用 VERIFY 宏代替 ASSERT。 在“Release”版本中,VERIFY 计算该表达式,但不检查结果。
在断言语句中使用函数调用时应特别小心,因为计算函数可能会有意外的副作用。
ASSERT ( myFnctn(0)==1 ) // unsafe if myFnctn has side effects VERIFY ( myFnctn(0)==1 ) // safe
VERIFY 在“Debug”版本和“Release”版本中都调用 myFnctn,因此可以使用它。 但在“Release”版本中仍会有因不必要的函数调用而产生的系统开销。
调试内联汇编代码
调试器提供了两个用于调试内联程序集代码的窗口,即“反汇编”窗口和“寄存器”窗口。
调试内联程序集代码
使用“反汇编”窗口查看程序集指令。
使用“寄存器”窗口查看寄存器内容。
MFC 调试技术
AfxDebugBreak
MFC 提供特殊的 AfxDebugBreak 函数,以供在源代码中对断点进行硬编码:
AfxDebugBreak( );
在 Intel 平台上,AfxDebugBreak 将生成以下代码,它在源代码而不是内核代码中中断:
_asm int 3
在其他平台上,AfxDebugBreak 仅调用 DebugBreak。
确保在创建发布版本时移除 AfxDebugBreak 语句,或使用 #ifdef _DEBUG 环绕这些语句。
TRACE 宏
若要在调试器的“输出”窗口中显示来自程序的消息,可以使用 ATLTRACE 宏或 MFC TRACE 宏。 与断言类似,跟踪宏只在程序的“Debug”版本中起作用,在“Release”版本中编译时将消失。
下面的示例显示几种 TRACE 宏的用法。 与 printf 类似,TRACE 宏可处理许多参数。
int x = 1; int y = 16; float z = 32.0; TRACE( "This is a TRACE statement\n" ); TRACE( "The value of x is %d\n", x ); TRACE( "x = %d and y = %d\n", x, y ); TRACE( "x = %d and y = %x and z = %f\n", x, y, z );
TRACE 宏可正确处理 char* 参数和 wchar_t* 参数。 下面的示例说明如何将 TRACE 宏与不同字符串参数类型配合使用。
TRACE( "This is a test of the TRACE macro that uses an ANSI string: %s %d\n", "The number is:", 2); TRACE( L"This is a test of the TRACE macro that uses a UNICODE string: %s %d\n", L"The number is:", 2); TRACE( _T("This is a test of the TRACE macro that uses a TCHAR string: %s %d\n"), _T("The number is:"), 2);
CRT 调试技术
使用CRT 调试库
C 运行库提供广泛的调试支持。 若要使用 CRT 调试库之一,必须链接 /DEBUG,并用 /MDd、/MTd 或 /LDd 编译。CRT 调试的主要定义和宏可在 CRTDBG.h 头文件中找到。
CRT 调试库中的函数编译时带有调试信息(/Z7、/Zd、/Zi、/ZI(调试信息格式)),不进行优化。 某些函数包含断言以验证传递给它们的参数,并且提供源代码。 使用此类源代码,可以单步执行 CRT 函数,以确认这些函数按预期方式工作并检查错误的参数或内存状态。 (某些 CRT 技术是专有技术,不提供用于异常处理、浮点和少数其他例程的源代码。)
安装 Visual C++ 时,可以选择在硬盘上安装 C 运行库源代码。 如果不安装源代码,将需要 CD-ROM 才能单步执行 CRT 函数。
用于报告的宏
可以使用在 CRTDBG.H 中定义的 _RPTn 和 _RPTFn 宏替换 printf 语句进行调试。 未定义 _DEBUG 时,这些宏在发布版本中自动消失,因此不必将它们括在 #ifdef 内。
表 2 宏
函数
_RPT0, _RPT1, _RPT2, _RPT3, _RPT4
向四个参数输出一个消息字符串和零。
对于从 _RPT1 到 _RPT4,消息字符串作为参数的 printf 样式的格式化字符串。
_RPTF0、_RPTF1、_RPTF2、_RPTF4
与 _RPTn 相同,但这些宏还输出其所在的文件名和行号。