Visual Studio 调试系列3 断点

断点是开发人员的工具箱中最重要的调试技术之一。 若要暂停调试程序执行所需的位置设置断点。 例如,你可能想要查看代码变量的状态或查看调用堆栈的某些断点。

01 在源代码中设置断点

可以在任意可执行代码行上设置断点。 例如,在以下 C# 代码,可以设置断点在变量声明for循环中或内的任何代码for循环。 命名空间或类声明或方法签名,无法设置断点。

若要在源代码中设置断点,请单击代码行旁边的最左侧边距中。 您还可以选择行,然后按F9,选择调试 > 切换断点,或右键单击并选择断点 > 插入断点。 断点显示为左边距中的一个红点。

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

当调试器在断点处停止时,您可以查看应用程序,包括变量值和调用堆栈的当前状态。 有关调用堆栈的详细信息,请参阅如何:使用调用堆栈窗口

  • 断点是一个触发器。 您可以单击它,请按F9,或使用调试 > 切换断点删除或重新插入。
  • 若要禁用断点而不删除它,将鼠标悬停或右键单击它,然后选择禁用断点。 已禁用的断点显示为左边距中的空点或断点窗口。 若要重新启用断点,请将鼠标悬停或右键单击它,然后选择启用断点。
  • 设置条件和操作、 添加和编辑标签,或将断点导出,右键单击该和选择合适的命令,或将鼠标悬停其上,然后选择设置图标。

02 调用堆栈窗口中设置断点

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

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

  1. 若要打开调用堆栈窗口中,您必须在调试期间暂停。 选择调试 > Windows > 调用堆栈,或按Ctrl + Alt+C。
  2. 在中调用堆栈窗口中,右键单击调用函数,然后选择断点 > 插入断点,或按F9. 调用堆栈的左边距中的函数调用名称旁边会显示一个断点符号。

调用堆栈断点显示在断点窗口具有对应于在函数中的下一步可执行指令的内存位置的地址。

调试器在指令处中断。

03 在反汇编窗口中设置断点

  1. 若要打开反汇编窗口中,您必须在调试期间暂停。 选择调试 > Windows > 反汇编,或按Alt + 8。
  2. 在中反汇编窗口中,单击想要中断的指令的左边距中。 此外可以选择它,然后按F9,或右键单击并选择断点 > 插入断点。

04 设置函数断点

当调用函数,可以中断执行。

若要设置函数断点:

  1. 选择调试 > 新断点 > 函数断点,或按Alt +F9 > Ctrl+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. 选择 确定。查看编辑器,所有 Draw() 方法处都以自动插入了断点。

05 设置数据断点 (.NET Core 3.0 或更高版本)

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

若要设置数据断点

  1. 在.NET Core 项目中,开始调试,并等待,直到到达一个断点。
  2. 在自动,监视,或局部变量窗口中,右键单击一个属性,然后选择值更改时中断的上下文菜单中。

在.NET Core 中的数据断点不适用于:

  • 不是可扩展的工具提示中,局部变量,自动或监视窗口属性
  • 静态变量
  • 使用 DebuggerTypeProxy 特性类
  • 在结构内的字段

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

可以使用断点窗口来查看和管理你的解决方案中的所有断点。 此集中的位置是在大型解决方案中,或对于复杂断点非常关键的调试方案尤其有用。

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

若要打开断点窗口中,选择调试 > Windows > 断点,或按Alt+F9或Ctrl+Alt+B。

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

断点标签

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

1、若要将标签添加到断点中,右键单击该断点的源代码中或断点窗口中,并选择编辑标签。 添加新标签或选择一个现有证书,然后选择确定。

2、对在断点列表进行排序断点通过选择窗口标签,条件,或其他列标题。 可以选择要通过选择显示的列显示列工具栏中。

07 断点条件

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

若要设置断点条件:

  1. 右键单击断点符号,然后选择条件。 或悬停在断点符号,选择设置图标,并选择条件中断点设置窗口。 您还可以在设置条件断点窗口中的右键单击断点并选择设置,然后选择条件。
  1. 在下拉列表中,选择条件表达式,命中计数,或筛选器,并相应地设置值。
  2. 选择关闭或按Ctrl+Enter关闭断点设置窗口。 或者,从断点窗口中,选择确定关闭对话框。

条件表达式

当选择条件表达式,可以选择两个条件:为 true或发生更改时。 选择如此时,满足表达式时中断或发生更改时表达式的值已更改时中断。

1、示例1,设置条件表达式为 true, index == 1

按下F5,启动调试,由于第一次 index 等于0,所以37行断点没有命中,直接走到43行的正常断点处。

第一次循环结束后,index的值增加了1,等于1。进入到第二次循环时,按下F5,由于 index = 1,满足设置的表达式,所以命中了37行的断点。

第二次循环结束后,index的值增加了1,等于2。进入到第三次循环时,按下F5,由于 index = 2,不满足设置的表达式,所以没有命中37行的断点,直接走到43行的正常断点处。

2、示例2:设置条件表达式为 更改时, index

按下F5,启动调试,由于第一次 index 等于0,第一次进入循环,结束前 index的值未改变仍然为0,没有改变,所以37行断点没有命中,直接走到43行的正常断点处。

第一次循环结束后,index的值增加了1,等于1。进入到第二次循环时,按下F5,由于 index = 1,值更改了,满足设置的条件,所以命中了37行的断点。

第二次循环结束后,index的值增加了1,等于2。进入到第三次循环时,按下F5,由于 index = 2,值更改了,满足设置的条件,所以命中了37行的断点。

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

不同编程语言的“更改时”字段的行为不同 : 对于本机代码,调试器不会考虑更改,因此不会命中第一次计算断点条件的第一次计算。 对于托管代码,调试器命中断点后第一次计算发生更改时处于选中状态。

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

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

创建对象 ID

1、设置断点在代码中的某个位置后创建对象。

2、开始调试,并在断点处暂停执行,选择调试 > Windows > 局部变量Alt+ 4以打开局部变量窗口。

查找特定对象实例在局部变量窗口中,右键单击它,然后选择创建对象 ID

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

展开名称,看到 $1 与 tri 对象完全相同

以同样的方式给 rec、cir、shapes 对象分别创建对象ID,分别对应 $2 $3 $4

3、在该对象添加到集合处, 右键单击该断点并选择“条件” 。

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

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

tri 对象添加到集合处,设置条件为 tri == $1

rec 对象添加到集合处,设置条件为 rec == $2

cir 对象添加到集合处,设置条件为 rec == $3

按下F5继续运行,

代码走到61行时,断点变成黄色箭头,鼠标悬浮在黄色箭头上,提示如下,条件表达式计算结果为 true。所以命中61行的断点。

按下F5继续运行,

代码走到62行时,断点变成黄色箭头,鼠标悬浮在黄色箭头上,提示如下,条件表达式计算结果为 true。所以命中62行的断点。

按下F5继续运行,

代码走到63行时,断点变成黄色箭头,鼠标悬浮在黄色箭头上,提示如下,条件表达式计算结果为 true。所以命中63行的断点。

如果将61行的条件设置为 tri == $2。根据上述的生成的对象ID,该表达式返回false。

再次F5,运行到61行时,提示报错。因为 tri 是 Triangle 类的对象,$2 是 Rectangle 类的对象创建的ID,所以无法进行 == 运算符的比较。调试器报错。

但是按下F5,仍然可以继续运行。

如果把61行的条件表达式修改为 tri.Equals($2),再次调试时,由于该表达式返回false,所以没有命中61行的断点。直接跳到62行。

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

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

命中次数

如果你怀疑你的代码中的循环开始产生错误行为在一定数量的迭代后,可以设置一个断点以停止执行的命中数,而无需重复按该数后F5来访问该迭代。

下列条件中断点设置窗口中,选择命中计数,然后指定迭代数。 在以下示例中,断点设置为其他每次迭代命中:

F5调试,第一次 i = 0,不是 testInt 的2倍整数,所以没有命中74行的断点,直接跳到76行。

当 testInt的值为 2、4、16、22、46时,都能命中74行的断点。

筛选器

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

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

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

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

提醒:断点条件 模式下进行调试,不能按F10,只能按F5进行调试才能看到实际效果。

08 断点操作和跟踪点

“跟踪点”是将消息打印到“输出”窗口的断点 。 跟踪点的作用像这种编程语言中的一个临时跟踪语句。

若要设置跟踪点:

  1. 右键单击断点并选择操作。 或者,在断点设置窗口中,悬停在所需断点,选择设置图标,,然后选择操作。
  2. 输入中的消息将消息记录到输出窗口字段。 消息可以包含通用文本字符串,值的变量或表达式括在大括号和格式说明符 ( C# C++ ) 的值。 此外可以在消息中使用以下特殊关键字:
    • $ADDRESS -当前指令
    • $CALLER -调用函数名
    • $CALLSTACK -调用堆栈
    • $FUNCTION -当前函数名
    • $PID -进程 id
    • $PNAME -进程名称
    • $TID -线程 id
    • $TNAME -线程名称
    • $TICK -选中计数 (从 Windows GetTickCount)
  3. 若要打印到的消息输出但不会中断,选择窗口继续执行复选框。 若要打印在跟踪点的消息和中断执行,请清除该复选框。

跟踪点显示为红色方块中的源代码的左边距和断点windows。

按下F5,运行结束后,查看【输出】窗口

09 断点警告

断点在调试时,有两个可能的可视状态: 一个实心的红色圆和 (白色填充) 空心圆。 如果调试器能够成功在目标进程中设置断点,它将保持一个实心的红色圆。 如果断点是空心圆,禁用断点,或尝试设置断点时出现警告。 若要确定的不同,断点上悬停并查看是否存在一条警告。

以下两个部分介绍重要警告以及如何解决这些问题。

“尚未为此文档加载任何符号”

转到模块窗口 (调试 > Windows > 模块) 并检查是否为你的模块加载。

  • 如果加载你的模块,则检查符号状态列,以查看是否已加载符号。
    • 如果还未加载符号,检查符号状态来诊断问题。 从上下文菜单中的模块上模块窗口中,单击符号加载信息... 若要查看其中调试器尝试并加载符号。 有关加载符号的详细信息,请参阅指定符号 (.pdb) 和源文件
    • 如果已加载符号,PDB 不包含有关源文件的信息。 以下是几个可能的原因:
      • 如果最近添加的源文件,确认正在加载的模块的最新版本。
      • 可以创建使用去除的 Pdb /PDBSTRIPPED链接器选项。 去除的 Pdb 不包含源文件信息。 确认你正在使用完整 PDB 和不去除的 PDB。
      • PDB 文件部分已损坏。 删除文件,并执行干净的生成的模块来尝试解决此问题。
  • 如果你的模块未加载,请检查以下内容来查找原因:
    • 确认您正在调试的正确过程。
    • 请检查你正在调试的代码正确的类型。 您可以了解哪种代码将调试器配置为在调试进程窗口 (调试 > Windows > 进程)。 如果想要调试 C# 代码,例如,确认是否为适当类型的.NET Framework 配置您的调试器 (例如,托管 (v4*) 与托管 (v2*/v3*) 与托管 (CoreCLR))。

"… 当前源代码是从...中内置的版本不同"

如果源文件已更改,并且源与正在调试的代码不再匹配,调试器不会设置断点在代码中默认情况下。 通常情况下,此问题发生时更改源文件,但不重新生成的源代码。 若要解决此问题,重新生成项目。 如果生成系统认为该项目已经是最新但没有,可以强制项目系统在重新生成通过再次保存源文件或通过清除项目的生成输出生成前。

在极少数情况下,你可能想要调试而无需匹配的源代码。 调试没有匹配的源代码可以令人混淆的潜在顾客调试体验,因此请确保这是你想要继续操作。

若要禁用这些安全检查,请执行以下操作:

  • 若要修改单个断点,请将鼠标悬停在编辑器中的断点图标,然后单击设置 (齿轮) 图标。 查看窗口添加到在编辑器中。 在查看窗口顶部,没有指示的断点的位置的超链接。 单击超链接,以允许修改的断点位置,然后检查允许源代码与原始不同。
  • 若要修改此设置对所有断点,请转到调试 > 选项和设置。 在 “调试”/“常规” 页上,清除 “要求源文件与原始版本完全匹配” 选项。 请务必重新启用此选项,在完成时调试。

10 断点已成功设置 (无警告),但未命中

本部分提供信息以对问题进行故障排除时调试器未显示任何警告 – 断点是一个实心的红色圆时主动进行调试,但未命中断点。

下面是要检查的几个事项:

  1. 如果在多个进程或多台计算机运行你的代码,请确保你正在调试的正确的进程或计算机。
  2. 确认你的代码正在运行。 若要测试你的代码运行,将调用添加到System.Diagnostics.Debugger.Break(C#/VB) 或__debugbreak(C++) 到在您尝试设置了断点,然后重新生成你的项目的代码行。
  3. 如果你正在调试优化的代码,请确保在其中设置断点的函数不被内联到另一个函数。Debugger.Break如何工作的上一个检查中所述的测试,测试以及此问题。

11 删除了断点,但在再次启动调试时继续命中该断点

如果在调试时删除了断点,可能在下一步启动调试的时再次命中该断点。 要停止命中此断点,请确保从 “断点” 窗口删除该断点的所有实例。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券