使用正确的断点类型

本文介绍如何在 Visual Studio 中使用不同类型的断点来提高调试效率。 它涵盖了可以应用断点的各种方案,例如暂停代码执行、日志记录信息和跟踪变量状态中的更改。 本文介绍如何设置条件断点、跟踪点、数据断点、依赖断点和临时断点。 它还包括有关设置函数断点的详细说明。 本指南对于希望利用断点在 Visual Studio 中进行有效调试的开发人员至关重要。

在阅读本文之前,如果您不熟悉在 Visual Studio 中使用断点,请参阅 断点入门

若要获得最佳本文档体验,请从文章顶部的列表中选择首选开发语言或运行时。

方案

下表显示了断点的常见调试方案,以及方案的推荐断点类型。

方案 描述
如何暂停运行代码以检查可能包含 bug 的代码行? 设置断点。 有关详细信息,请参阅断点入门
我的变量是否具有意外值? 或者,是否要在应用达到特定状态时对其进行检查? 尝试使用条件断点,通过条件逻辑来控制断点的激活位置和时间。 右键单击断点以添加条件。 当变量等于意外值时,将条件设置为 true。 有关详细信息,请参阅 断点条件
如何在可配置条件下将信息记录到“输出”窗口,而无需修改或停止我的代码? 利用跟踪点,可以在可配置条件下将信息记录到“输出”窗口,而无需修改或停止代码。 有关详细信息,请参阅 在 Visual Studio 调试器中使用跟踪点。
如何知道变量的值何时发生更改? 对于 C++,请设置数据断点
对于使用 .NET Core 3 及更高版本的应用程序,还可以设置数据断点
此外,还可以使用条件断点跟踪对象 ID,但仅适用于 C# 和 F#。
如何在命中另一个断点时中断执行? 将依赖断点设置为仅当第一个命中另一个断点时,该断点才会中断执行。 有关详细信息,请参阅依赖断点
能否只命中一次断点? 设置一个临时断点,使你只能中断代码一次。 有关详细信息,请参阅临时断点
是否可以在特定迭代时暂停循环中的代码? 将依赖断点设置为仅当第一个命中另一个断点时,该断点才会中断执行。 有关详细信息,请参阅命中次数
当知道函数名称而不是函数位置时,是否可以在函数开头暂停代码? 你可以使用函数断点实现此操作。 有关详细信息,请参阅设置函数断点
是否可以在具有相同名称的多个函数的开头暂停代码? 如果有多个具有相同名称的函数(重载函数或来自不同项目的函数),则可以使用 函数断点

断点操作和跟踪点

“跟踪点”是将消息打印到“输出”窗口的断点。 跟踪点的作用像这种编程语言中的一个临时跟踪语句,且不会暂停代码的执行。 通过在“断点设置”窗口中设置特殊操作,可以创建跟踪点。 有关详细说明,请参阅在 Visual Studio 调试器中使用跟踪点

断点条件

可以通过设置条件来控制在何时何处执行断点。 条件可以是调试器能够识别的任何有效表达式。 (有关有效表达式的详细信息,请参阅调试器中的 表达式。)

设置断点条件:

  1. 右键单击该断点符号并选择“条件”(或按 Alt F9,C)。 或者将鼠标悬停在断点符号上,选择“设置”图标,然后在“断点设置”窗口中选择“条件” 。

    还可以右键单击代码行旁边的最左边距,然后从上下文菜单中选择“插入条件断点”以设置新的条件断点。

    还可以在“断点”窗口中设置条件,方法是右键单击断点并选择“设置”,然后选择“条件”

    断点设置

    断点设置

  2. 在下拉列表中,选择“条件表达式”、“命中计数”或“筛选器”,并相应地设置其值 。

  3. 选择“关闭”或按 CtrlEnter 关闭“断点设置”窗口 。 或者,从“断点”窗口中,选择“确定”以关闭对话框 。

设置了条件的断点在源代码和“断点”窗口中带有 + 符号。

创建条件表达式

选择“条件表达式”时,可以在两个条件之间进行选择:“为 true”或“更改时” 。 要在满足表达式时中断,请选择“为 true”;要在表达式的值更改时中断,请选择“更改时” 。

在下面的示例中,仅当 testInt 的值为“4”时才会命中断点:

断点条件为 true

断点条件为 true

在下面的示例中,仅当 testInt 的值更改时才会命中断点:

断点更改时

断点更改时

如果使用无效语法设置断点条件,则会显示警告消息。 如果在指定断点条件时使用的语法有效但语义无效,则在第一次命中断点将出现警告消息。 在这两种情况下,调试器都会在遇到无效断点时中断。 仅在条件有效且计算结果为 false时才会跳过断点。

注意

对于“更改时”字段,调试器不会将条件的第一次计算视为更改,所以第一次计算时不会命中断点。

在条件表达式中使用对象 ID(仅限 C# 和 F#)

有时,你想要观察特定对象的行为。 例如,你可能想要找出对象多次插入到集合中的原因。 在 C# 和 F# 中,可以创建引用类型的特定实例的对象 ID,并在断点条件下使用它们。 对象 ID 由公共语言运行时 (CLR) 调试服务生成并与该对象关联。

创建对象 ID:

  1. 在创建对象后的代码中设置断点。

  2. 开始调试,当执行在断点处暂停时,选择“调试”“窗口”>“局部变量”(或按 Ctrl Alt> V,L)打开“局部变量”窗口。

    在“局部变量”窗口中找到特定的对象实例,右键单击它,然后选择“生成对象 ID” 。

    应该会在“局部变量” $ 窗口中看到 $ 窗口中设置断点来中断调用函数返回到的指令或行处的执行。 这就是对象 ID。

  3. 在想要开展调查时添加一个新断点,例如将对象添加到集合中时。 右键单击该断点并选择“条件”。

  4. 在“条件表达式”字段中使用对象 ID。 例如,如果变量 item 是要添加到集合中的对象,则选择“为 true”,并键入“item == $n<>”,其中 >n 是对象 ID 号。

    会在将该对象添加到集合中时中断执行。

    若要删除对象 ID,请右键单击“局部变量”窗口中的变量,然后选择“删除对象 ID” 。

注意

对象 ID 创建弱引用,且不会阻止对象被垃圾回收。 它们仅对当前调试会话有效。

设置命中次数条件

如果怀疑代码中的循环在一定数量的迭代后开始产生错误行为,则可以设置断点以在该次数的命中后停止执行,而不必反复按 F5 以到达该迭代。

在“断点设置”窗口的“条件”下,选择“命中计数”,然后指定迭代次数 。 在下面的示例中,将断点设置为每隔一次迭代命中一次:

断点命中计数

断点命中计数

设置筛选条件

可以将断点限制为仅在指定设备上或在指定进程和线程中触发。

在“断点设置”窗口的“条件”下,选择“筛选器”,然后输入以下一个或多个表达式 :

  • MachineName = "name"
  • ProcessId = value
  • ProcessName = "name"
  • ThreadId = value
  • ThreadName = "name"

将字符串值放在双引号内。 可以使用 & (AND)、 || (OR)、 ! (NOT) 和括号合并子句。

设置函数断点

可以在调用函数时中断执行。 例如,当你知道函数名但不知道其位置时,这很有用。 如果多个函数(例如重载函数或不同项目中的函数)具有相同的名称,而你想要将其全部中断,这也很有用。

设置函数断点:

  1. 选择“调试”“新建断点”>“函数断点”,或按 Ctrl K,B。

    也可以在“断点”窗口中选择“新建”“函数断点” 。

  2. 在“新建函数断点”对话框中,在“函数名称”框中输入函数名称 。

    缩小函数规格:

    • 使用完全限定的函数名。

      示例: Namespace1.ClassX.MethodA()

    • 添加重载函数的参数类型。

      示例: MethodA(int, string)

    • 使用“!”符号指定模块。

      示例: App1.dll!MethodA

    • 使用本机 C++ 中的上下文运算符。

      {function, , [module]} [+<line offset from start of method>]

      示例: {MethodA, , App1.dll}+2

  3. 在“语言”下拉列表中,选择函数的语言。

  4. 选择“确定” 。

使用内存地址设置函数断点(仅限本机 C++)

你还可以使用对象的地址在类的特定实例调用的方法上设置函数断点。 例如,给定一个类型为 my_class 的可寻址对象,可以在实例调用的 my_method 方法上设置函数断点。

  1. 在实例化类的实例后的位置设置断点。

  2. 找到实例的地址(例如 0xcccccccc)。

  3. 选择“调试”“新建断点”>“函数断点”,或按 Ctrl K,B。

  4. 将下列内容添加到“函数名”框中,并选择“C++”语言 。

    ((my_class *) 0xcccccccc)->my_method
    

设置数据断点(.NET Core 3.x 或 .NET 5+)

当特定对象的属性更改时,数据断点中断执行。

设置数据断点:

  1. 在 .NET Core 或 .NET 5+ 项目中,启动调试,并等待到达断点。

  2. 在“自动”、“监视”或“局部变量”窗口中,右键单击一个属性并在上下文菜单中选择“当值更改时中断” 。

    托管数据断点

.NET Core 和 .NET 5+ 的数据断点不适用于:

  • 无法在工具提示、“局部变量”、“自动”或“监视”窗口中展开的属性
  • 静态变量
  • 具有 DebuggerTypeProxy 属性的类
  • 结构内的字段

有关可以设置的最大数目,请参阅数据断点硬件限制

设置数据断点(仅限本机 C++)

数据断点在存储在指定内存地址中的值更改时中断执行。 如果只读取但不更改该值,则执行不会中断。

设置数据断点:

  1. 在 C++ 项目中,启动调试,并等待到达断点。 在“调试”菜单上,选择“新建断点”“数据断点” 。

    还可以在“断点”窗口中选择“新建”“数据断点”,或者右键单击“自动”、“监视”或“局部变量”窗口中的某个项,然后在上下文菜单中选择“当值更改时中断” 。

  2. 在“地址”框中,键入内存地址或计算结果为内存地址的表达式。 例如,键入 &avar 以在变量 avar 的内容更改时执行中断操作。

  3. “字节计数” 下拉菜单中,选择你想要调试程序监视的字节数。 例如,如果选择 4,则调试程序将监视从 &avar 开始的四个字节,并在其中任何字节的值发生更改时执行中断操作。

数据断点在下列情况下无效:

  • 将未经调试的进程写入内存位置。
  • 在两个或多个进程间共享内存位置。
  • 内存位置在内核内更新。 例如,如果内存传递给 32 位 Windows ReadFile 函数,则内存将从内核模式进行更新,因此调试器不会在更新时中断。
  • 其中,监视表达式在 32 位硬件上大于 4 字节,在 64 位硬件上大于 8 字节。 这是 x86 体系结构的一个限制。

注意

  • 数据断点取决于特定的内存地址。 变量的地址随调试会话而更改,因此将在每个调试会话结束时自动禁用数据断点。

  • 如果在局部变量上设置数据断点,则断点在函数结束时仍处于启用状态,但内存地址不再适用,因此断点的行为不可预测。 如果在局部变量上设置了数据断点,则应在函数结束前删除或禁用该断点。

数据断点硬件限制

设置数据断点时,Windows 内核和基础硬件具有以下限制。 限制是指可以设置的最大数据断点数。

处理器体系结构 数据断点限制
x64 和 x86 4
ARM64 2
ARM 1

设置依赖断点

仅当第一次命中另一个断点时,依赖断点才会中断执行。 因此,在复杂场景中(例如调试多线程应用程序),可以在第一次命中另一个断点之后配置其他断点。 这可以使常见路径中的代码调试(例如游戏循环或实用工具 API)变得更加容易,因为这些函数中的断点可以配置为仅当从应用程序的特定部分调用函数时才启用。

设置依赖断点:

  1. 将鼠标悬停在断点符号上,选择“设置”图标,然后在“断点设置”窗口中选择“仅在命中以下断点时才启用”。

  2. 在下拉列表中,选择你希望当前断点依赖于的先决条件断点。

选择“关闭”或按 Ctrl+Enter 关闭“断点设置”窗口。 或者,从“断点”窗口中,选择“确定”以关闭对话框。 依赖断点

还可以使用右键单击上下文菜单设置依赖断点。

  1. 右键单击代码行旁边的最左边距,然后从上下文菜单中选择“插入依赖断点”。

    依赖断点

  • 如果你的应用程序中只有一个断点,则依赖断点不起作用。
  • 如果删除了先决条件断点,则会将依赖断点转换为普通行断点。

设置临时断点

此断点只允许你中断代码一次。 调试时,Visual Studio 调试器针对此断点仅暂停运行中的应用程序一次,然后在命中该断点之后立即将其删除。

设置临时断点:

  1. 将鼠标悬停在断点符号上,选择“设置”图标,然后在“断点设置”窗口中选择“仅在命中时删除断点”。

  2. 选择“关闭”或按 Ctrl+Enter 关闭“断点设置”窗口。 或者,从“断点”窗口中,选择“确定”以关闭对话框。

    临时断点

还可以使用右键单击上下文菜单设置临时断点。

  1. 右键单击代码行旁边的最左边距,然后从上下文菜单中选择“插入临时断点”。

    临时断点

或者,只需使用快捷方式 F9 + Shift + Alt、T,并根据需要在行上设置临时断点。