在 Visual Studio 调试器中使用断点

在开发人员的工具箱中,断点是最重要的调试技术之一。 你可以在希望暂停调试器执行的任何位置设置断点。 例如,你可能想要查看代码变量的状态,或查看某个断点处的调用堆栈。 如果你正在尝试解决使用断点时出现的警告或问题,请参阅 Visual Studio 调试器中的断点排除故障

注意

如果知道要解决的任务或问题,但想要了解应使用哪种断点,请参阅常见问题解答 - 查找调试功能

在源代码中设置断点

可以在任意可执行代码行上设置断点。 例如,在下面的 C# 代码中,可以在包含变量声明 (int testInt = 1) 的代码行、for 循环或 for 循环内的任何代码上设置断点。 如果没有赋值和 getter/setter,则不能在方法签名、命名空间或类的声明,或者变量声明上设置断点。

在源代码中设置断点:

  • 单击代码行最左边的边距。 你还可以选择行并按“F9”,选择“调试”>“切换断点”,或者右键单击并选择“断点”>“插入断点” 。 断点显示为左边距中的一个红点。

对于大多数语言(包括 C#),会自动突出显示断点和当前执行的行。 对于 C++ 代码,可以通过选择“工具”(或“调试”)>“选项”>“调试”>“突出显示断点和当前语句的整个源行(仅限 C++)”。

设置断点

设置断点

调试时,在执行断点所在行上的代码之前,执行会在断点处暂停。 断点符号显示黄色箭头。

在以下示例中的断点处,testInt 的值仍然为 3。 也就是说,从变量初始化(设置为值 3)以来,该值没有更改,因为尚未执行黄色语句。

断点执行已停止

在以下示例中的断点处,testInt 的值仍然为 1。 也就是说,从变量初始化(设置为值 1)以来,该值没有更改,因为尚未执行黄色的语句。

断点执行已停止

当调试器在断点处停止时,可以查看应用程序的当前状态,包括变量值调用堆栈

下面是关于使用断点的一些常规说明。

  • 断点是一个开关。 你可以单击它,按 F9,或使用“调试”>“切换断点”来删除或重新插入该断点 。

  • 若要禁用断点而不删除它,请将鼠标悬停在其上或右键单击它,然后选择“禁用断点”。 禁用的断点在左边距或“断点”窗口中显示为空心圆点。 若要重新启用断点,请将鼠标悬停在断点上或右键单击它,然后选择“启用断点”。

  • 设置条件和操作,添加和编辑标签,或通过右键单击断点并选择适当的命令导出断点,或将鼠标悬停在断点上并选择“设置”图标。

断点操作和跟踪点

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

断点条件

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

设置断点条件:

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

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

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

    断点设置

    断点设置

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

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

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

创建条件表达式

选择“条件表达式”时,可以在两个条件之间进行选择:“为 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,并根据需要在行上设置临时断点。

在“断点”窗口中管理断点

可以使用“断点”窗口查看和管理解决方案中的所有断点。 在大型解决方案中,或者在断点非常重要的复杂调试方案中,这一集中位置尤为有用。

在“断点”窗口中,可以搜索、排列、筛选、启用/禁用或删除断点。 还可以设置条件和操作,或添加新的函数或数据断点。

若要打开“断点”窗口,请选择“调试” >“窗口”>“断点”,或按 Ctrl +Alt+B。

“断点”窗口

“断点”窗口

若要选择要在“断点”窗口中显示的列,请选择“显示列” 。 选择列标题以按该列对断点列表进行排序。

断点标签

可以使用标签对“断点”窗口中的断点列表进行排序和筛选。

  1. 若要向断点添加标签,请在源代码或“断点”窗口中右键单击断点,然后选择“编辑标签” 。 添加新标签或选择现有标签,然后选择“确定”。
  2. 通过选择“标签”、“条件”或其他列标题,在“断点”窗口中对断点列表进行排序 。 可以通过选择工具栏中的“显示列”来选择要显示的列。

断点组

对于复杂的调试方案,可能需要创建断点组来组织断点。 这样,你可以基于正在尝试调试的当前方案快速启用和禁用断点的逻辑分组。

可以通过选择新建 > 断点组并为组提供名称,在断点窗口中创建断点。 若要向组添加断点,请右键单击断点,然后选择添加到断点组><组名称>。 或者,将断点拖放到所需的组中。

断点组的屏幕截图。

若要设置默认断点组,请右键单击某个组,然后选择设置为默认断点组。 设置默认断点组时,新创建的断点会自动添加到组中。

导入和导出断点

若要保存或共享断点的状态和位置,可以导出或导入断点。

从 Visual Studio 2022 版本 17.12 预览版 3 开始,断点组也包含在导出断点和导入断点中。

  • 若要将单个断点导出到 XML 文件,请在源代码或“断点”窗口中右键单击该断点,然后选择“导出”或“导出已选内容” 。 选择导出位置,然后选择“保存”。 默认位置是解决方案文件夹。
  • 若要导出多个断点,请在“断点”窗口中选择断点旁边的框,或在“搜索”字段中输入搜索条件 。 选择“导出与当前搜索条件匹配的所有断点”图标,然后保存文件。
  • 若要导出所有断点,请取消选中所有框并将“搜索”字段留空。 选择“导出与当前搜索条件匹配的所有断点”图标,然后保存文件。
  • 若要导入断点,请在“断点”窗口中选择“从文件导入断点”图标,导航到 XML 文件位置,然后选择“打开” 。

从调试器窗口设置断点

还可以从“调用堆栈”和“反汇编”调试器窗口设置断点 。

在“调用堆栈”窗口中设置断点

若要在调用函数返回的指令或行处中断,可以在“调用堆栈”窗口中设置断点。

在调用堆栈窗口中设置断点:

  1. 若要打开“调用堆栈”窗口,必须在调试期间暂停。 选择“调试”>“窗口”>“调用堆栈”,或按 Ctrl+Alt+C 。

  2. 在“调用堆栈”窗口中,右键单击调用函数并选择“断点”>“插入断点”,或按 F9 。

    断点符号显示在调用堆栈左边距中的函数调用名称旁。

调用堆栈断点在“断点”窗口中显示为一个地址,其内存位置对应于函数中的下一个可执行指令。

调试器在指令处中断。

有关调用堆栈的详细信息,请参阅如何:使用“调用堆栈”窗口

要在代码执行期间以可视化方式跟踪断点,请参阅调试时映射调用堆栈上的方法

在“反汇编”窗口中设置断点

  1. 若要打开“反汇编”窗口,必须在调试期间暂停。 选择“调试”>“窗口”>“反汇编”,或按 Ctrl+Alt+D 。

  2. 在“反汇编”窗口中,单击要中断的指令的左边距。 你还可以选择它并按 F9,或者右键单击并选择“断点”>“插入断点” 。