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 相同,但这些宏还输出其所在的文件名和行号。
Visual Studio调试托管代码
“输出”窗口中的诊断消息
可以使用 Debug 类或 Trace 类(属于 System.Diagnostics 类库)将运行时消息写到“输出”窗口。 如果只在程序的调试版本中输出,则使用 Debug 类。 如果要同时在调试版本和发布版本中输出,则使用 Trace 类。
输出方法
Trace 和 Debug 类提供下列输出方法:
在不中断执行情况下输出信息的各种 Write 方法。 这些方法取代了在 Visual Basic 早期版本中使用的 Debug.Print 方法。
Debug.Assert 和 Trace.Assert 方法,如果指定的条件失败,这些方法将中断执行并输出信息。 默认情况下,Assert 方法显示对话框中的信息。 有关更多信息,请参见托管代码中的断言。
Debug.Fail 和 Trace.Fail 方法,它们总是中断执行并输出信息。 默认情况下,Fail 方法在对话框中显示信息。
除了应用程序的程序输出外,**“输出”**窗口还可以显示下列信息:
调试器已经加载或卸载的模块。
引发的异常。
退出的进程。
退出的线程。
托管代码中的断言
断言(或 Assert 语句)测试您作为 Assert 语句的参数指定的条件。 如果此条件计算为 true,不发生操作。 如果此条件计算为 false,则断言失败。 如果正在调试版本中运行,则程序进入中断模式。
在 Visual Basic 和 Visual C# 中,可以使用方法调用,可以从 System.Diagnostics 命名空间下的 Debug 或 Trace 调用 Assert 方法。 在您的程序的发布版本中不包含 Debug 类方法,因此它们不增大或减小发布代码的速度。
C++ 不支持 Debug 类方法。 使用带有条件编译的 Trace 类(如 #ifdef DEBUG...)可获得同样的效果。 #endif.
Debug.Assert 方法
可随意使用 Debug.Assert 方法测试条件,如果代码正确,该条件应保持为真。 例如,假设已编写一个整数除法函数。 根据数学规则,除数永远不能为零。 可以使用断言测试该条件:
[C#]
Visual Studio调试技巧---Output窗口输出定位
在使用VC++时,经常使用OutputDebugString往往Output窗口写一些调试信息,如果输出信息遵循某种格式,那么在Output窗口中点击相应输出文本就会跳到相应的代码行中。具体格式MSDN中有说,简单的说法,在输出文本的最前面是"文件名(行号):"的格式就可以了。一个例子,用在我的内存泄露检测代码中:
wsprintf(output_temp,"%s(%d): >>>>>>>>>>>>mem leaks! size=%d",
memory_allocated[i].file,
memory_allocated[i].line,
memory_allocated[i].size);
OutputDebugString(output_temp);Visual Studio调试指南---仅调试具有仅我的代码的用户代码
仅我的代码是一种 Visual Studio 调试功能,可自动执行对系统、框架和其他非用户代码的调用。 在 "调用堆栈" 窗口中,仅我的代码将这些调用折叠到 [外部代码] 帧中。在 .NET、 C++和 JavaScript 项目中,仅我的代码的工作方式有所不同。
启用或禁用“仅我的代码”
对于大多数编程语言,默认情况下启用仅我的代码。
- 若要在 Visual Studio 中启用或禁用仅我的代码,请在 "工具" > 选项"(或"调试 > 选项") >调试 > 常规",选择或取消选择 "启用仅我的代码"。
注意:启用仅我的代码是一项全局设置,适用于所有语言的所有 Visual Studio 项目。
“仅我的代码”调试
在调试会话期间,"模块" 窗口显示调试器将哪个代码模块视为我的代码(用户代码)以及其符号加载状态。
![模块 窗口中的用户代码](../debugger/media/dbg_justmycode_module.png ""模块" 窗口中的用户代码")
在 "调用堆栈" 或 "任务" 窗口中,仅我的代码将非用户代码折叠成标记为
[External Code]
的灰色批注代码框架。若要查看折叠的 [外部代码] 帧中的代码,请在 "调用堆栈" 或 "任务" 窗口中单击右键,然后从上下文菜单中选择 "显示外部代码"。 展开的外部代码行替换 [外部代码] 框架。
在 "调用堆栈" 窗口中双击展开的外部代码行会在源代码中突出显示以绿色显示的调用代码行。 对于 Dll 或未找到或加载的其他模块,可能会打开 "符号或源找不到" 页。
.NET 仅我的代码
在 .NET 项目中,仅我的代码使用符号( .pdb)文件和程序优化来对用户和非用户代码进行分类。 .NET 调试器将优化的二进制文件和非加载 .pdb文件视为非用户代码。
这三个编译器属性还会影响 .NET 调试器认为是用户代码:
- DebuggerNonUserCodeAttribute 通知调试器,它应用到的代码不是用户代码。
- DebuggerHiddenAttribute 对调试器隐藏代码,即使“仅我的代码”关闭;
- DebuggerStepThroughAttribute 通知调试器遍历应用到的代码,而不是单步执行代码。
.NET 调试器将所有其他代码视为用户代码。
在 .NET 调试期间:
- 调试 > 单步执行(或按F11)在非用户代码上逐过程执行代码,并将代码移到用户代码的下一行。
- 调试 > 非用户代码上的 "跳出" (或Shift +F11)运行到用户代码的下一行。
如果没有更多的用户代码,调试将继续,直到它结束、到达另一个断点或引发错误。
如果调试器在非用户代码中中断(例如,在非用户代码中使用 "调试" > "全部中断" 和 "暂停"),则不会显示 "无源" 窗口。 然后,你可以使用 "调试 > 步骤" 命令来执行用户代码的下一行。
如果非用户代码中出现未经处理的异常,调试器将在生成异常的用户代码行处中断。
如果对异常启用了第一次机会异常,则调用用户代码行在源代码中以绿色突出显示。 "调用堆栈" 窗口显示标记为 [外部代码] 的带批注的帧。
C++“仅我的代码”
从 Visual Studio 2017 15.8 版开始,还支持代码单仅我的代码。 此功能还要求使用/JMC (仅我的代码调试)编译器开关。 默认情况下,在项目中C++启用此开关。 对于 "调用堆栈" 窗口和仅我的代码中的调用堆栈支持,不需要/JMC 开关。
若要归类为用户代码,必须由调试器加载包含用户代码的二进制文件的 PDB (使用 "模块" 窗口进行检查)。
对于调用堆栈行为(如 "调用堆栈" 窗口中的), C++中的仅我的代码仅将这些函数视为非用户代码:
- 在其符号文件中去除了源信息的函数。
- 符号文件指示没有对应于堆栈帧的源文件的函数。
- %VsInstallDirectory%\Common7\Packages\Debugger\Visualizers文件夹中 * natjmc文件中指定的函数。
对于代码单步执行行为, C++仅我的代码仅将这些函数视为非用户代码:
- 调试器中尚未加载相应的 PDB 文件的函数。
- %VsInstallDirectory%\Common7\Packages\Debugger\Visualizers文件夹中 * natjmc文件中指定的函数。
为了仅我的代码中的代码步进支持C++ ,必须在 Visual Studio 15.8 Preview 3 或更高版本中使用 MSVC 编译器来编译代码,并且必须启用/JMC 编译器开关(默认情况下启用)。 有对于使用较旧的编译器编译的代码, . natstepfilter files 是自定义代码单步执行的唯一方法,该方法与仅我的代码无关。
调试C++期间:
- 调试 > 单步执行(或按F11)在非用户代码上逐过程执行代码,并将代码移到用户代码的下一行。
- 调试 > 非用户代码上的 "跳出" (或Shift +F11)运行到用户代码的下一行。
如果没有更多的用户代码,调试将继续,直到它结束、到达另一个断点或引发错误。
如果调试器在非用户代码中中断(例如,在非用户代码中使用 "调试" > "全部中断" 和 "暂停"),则在非用户代码中继续执行。
如果调试器遇到异常,则它会在异常上停止,无论它是在用户代码还是非用户代码中。 "异常设置" 对话框中的用户未处理的选项将被忽略。
自C++定义调用堆栈和代码单步执行行为
对于C++项目,可以指定模块、源文件和函数,"调用堆栈" 窗口将其视为非用户代码,方法是在 * natjmc文件中指定它们。 如果使用的是最新的编译器,此自定义也适用于代码单步执行(请参阅 C++仅我的代码)。
- 若要为 Visual Studio 计算机的所有用户指定非用户代码,请将natjmc文件添加到 %VsInstallDirectory%\Common7\Packages\Debugger\Visualizers文件夹。
- 若要为单个用户指定非用户代码,请将natjmc文件添加到 %USERPROFILE%\My 文档 \ < Visual Studio 版本 > \visualizers文件夹中。
Natjmc文件是具有以下语法的 XML 文件:
XML