C++(Qt)软件调试---断点高级用法(20) 原创
C++(Qt)软件调试—断点高级用法(20)
更多精彩内容 |
---|
👉内容导航 👈 |
👉C++软件调试、异常定位 👈 |
1、概述
断点是调试程序时常用的工具之一,它允许开发人员暂停程序的执行,以便检查程序的状态。
除了基本的断点设置外,还有许多高级用法可以让调试过程更加高效和精确。<
以下是使用断点的一些高级技巧,包括条件断点、忽略断点、一次性断点、日志断点、异常断点、函数入口断点以及断点的管理和优化等。
本文中会详细说明在VS Code、Visual Studio、Qt Creator这三种IDE中断点的高级用法。
注意:可能调试工具不同,支持的断点有一些区别。
演示环境:
-
系统:
- Windows11
-
编辑器:
- Visual Studio 2017
- Qt Creator10.0.2
- VS Code
-
编译器:
- MSVC2017-64
- MinGW64
2、断点高级用法
1.1 条件断点
条件断点允许你设置一个表达式,只有当表达式的值为true时,程序才会在该断点处暂停。这在处理循环或大量日志输出时特别有用。
这可以极大地帮助开发者过滤掉不相关的执行路径,专注于特定的逻辑分支或错误情况。
表达式可以使用一个条件或者多个条件,只有当所有条件都满足时,断点才会触发。多个条件之间可以用逻辑运算符连接(如
&&
、||
)。
注意: 条件断点可能会影响调试性能,尤其是当条件表达式较为复杂时。
可用条件语句: 条件可以是任意合法的 C++ 表达式,包括但不限于:
- 变量检查:如
x > 10
。 - 逻辑运算:如
x > 10 && y < 20
。 - 算术运算:如
x + y > 30
。 - 比较运算:如
x == 10
。 - 成员访问:如
obj.member > 10
。 - 数组索引:如
arr[0] > 10
。 - 指针和引用:如
*ptr > 10
。
1.2 日志断点/记录点/消息追踪点
日志断点(Log Breakpoints)是一种特殊的断点类型,它允许你在程序执行到特定位置时不会停止程序的执行,而是记录一些有关当前状态的信息到日志/控制台中。这种方法可以让你在不影响程序执行流的情况下收集调试信息,从而帮助你更快地诊断问题。
日志断点的特点
- 非侵入性:日志断点不会完全停止程序的执行,因此不会影响程序的性能。
- 信息记录:可以记录程序状态的快照,包括变量值、内存状态等。
- 灵活性:可以设置多种条件的日志断点,如条件日志断点、计数日志断点等。
- 持续性:即使程序没有完全停止,也可以持续记录日志信息。
1.3 函数断点
调试器可以支持通过指定函数名称来创建断点,而不是直接在源代码中放置断点。这在源不可用但函数名称已知的情况下非常有用。
函数断点是在函数入口处设置的断点。当程序执行到该函数时,调试器会暂停执行,并允许你检查函数调用前后的状态。函数断点不会在函数体内的每一行都停止执行,而是只在函数入口处暂停。
GDB命令行支持使用正则表达式设置函数断点,可匹配包含部分名称的函数,但IDE一般都不支持。
当你知道函数名但不知道其位置时,这很有用。
如果多个函数(例如重载函数或不同项目中的函数)具有相同的名称,而你想要将其全部中断,这也很有用。
1.4 命中次数断点
命中次数断点(Hit Count Breakpoint)是一种调试工具,它允许开发者设置断点在被命中特定次数后触发。这种类型的断点特别适用于循环或其他重复执行的代码块,可以帮助开发者在不需要每次都停止的情况下,逐步调试代码。
命中次数断点可以与条件断点结合使用,例如,你可以设置一个条件断点,当变量满足特定条件且断点被命中特定次数时才触发。
可用语句
10
只在第10次命中时中断;>10
前十次不中断,10次之后每次都中断;>=10
从第10次开始命中时中断;<3
仅在前 2 次命中时中断;%2
每间隔一次中断;
使用场景
- 当你怀疑一个错误可能在循环的某次迭代中出现,但不确定具体是哪一次时,使用命中次数断点可以节省时间,避免重复手动触发断点。
- 如果你在调试一个递归函数,并且你只想在递归达到一定深度时才进行调试,你可以设置一个命中次数断点来实现这一点。
1.5 异常断点
异常断点是一种特殊的断点类型,用于在程序执行过程中遇到异常情况时自动暂停程序执行。
这种类型的断点对于调试错误和异常处理逻辑非常有用,因为它可以让开发者在异常发生的确切位置停下来,从而更容易地诊断问题所在。
这里主要将异常抛出断点和异常捕获断点。
在IDE中默认情况下抛出异常会导致程序直接退出,想要在Debug模式下定位到异常抛出位置或者异常捕获位置就可以设置异常断点。
- 异常抛出断点: 会在抛出异常的位置中断;
- 异常捕获断点: 异常抛出时不会中断,会在捕获异常的位置中断。
1.6 等待断点/触发断点
触发断点(Triggered breakpoints)是在命中另一个断点后自动启用的断点。种断点比普通的断点更加灵活,因为它们可以根据程序状态的变化来决定是否激活。
在诊断仅在特定前提条件之后发生的代码中的失败情况时,它们可能非常有用。
1.7 临时断点/一次性断点
临时断点(Temporary Breakpoints)是调试过程中的一种特殊断点类型,它在被命中一次后即自动移除。这意味着一旦程序执行达到这个断点并暂停,断点就会自动消失,不再影响后续的程序执行。临时断点对于调试者来说非常有用,尤其是当他们只需要查看特定位置的一次执行情况时。
临时断点的特点
- 一次性使用:临时断点在被触发一次后就会自动删除,不会再次影响程序的执行。
- 减少干扰:由于临时断点不会在多次执行中反复生效,所以它减少了对程序自然执行流的影响。
- 方便快捷:设置临时断点可以快速帮助开发者定位问题,无需手动删除断点。
临时断点的用途: 临时断点主要用于以下场景:
- 单次检查:当只需要检查某个代码段一次执行的情况时,使用临时断点可以避免在每次调试时都打断程序执行。
- 排除偶然性问题:有时程序中的问题是偶然出现的,使用临时断点可以在问题发生的位置立即停止程序,便于捕捉这一瞬间的问题。
- 减少断点管理:在调试过程中,临时断点可以减少需要管理的断点数量,简化调试流程
1.8 启用/禁用断点
在调试过程中,断点是程序员用来暂停程序执行的重要工具之一。
然而,在调试复杂的应用程序时,可能会设置大量的断点,这可能会导致调试过程变得混乱。此时,能够动态地启用或禁用断点就显得非常重要了。
禁用断点的作用
禁用断点意味着暂时不让该断点生效。这样做有几个好处:
- 减少干扰:当程序执行时,禁用不必要的断点可以避免频繁的暂停,从而不影响正常的调试流程。
- 提高效率:在查找特定问题时,禁用不相关的断点可以加快调试速度。
- 简化调试:当调试特定路径或功能时,禁用其他断点可以使调试更加集中和有针对性。
启用断点的作用
启用断点则是恢复断点的功能,使其能够像正常断点一样在程序执行到达指定位置时暂停程序。
3、Visual Studio
1.1 设置断点
-
方法1:鼠标选中行,按F9键可创建/删除断点;
-
方法2:鼠标在代码行左侧点击可创建/删除断点;
-
如下图所示,鼠标在断点位置,右键选择【条件】可看出断点设置菜单由【条件】、【操作】两部分组成;
1.2 条件表达式
-
下拉框选择【条件表达式】【为true】,输入栏输入条件表达式,点击【关闭】后生效;
-
启动调试后只有a的值为5时断点才会中断;
-
点击表达式后,已保存前的
X
可删除这一条表达式; -
下拉框选择【条件表达式】【为true】,输入栏输入需要检测的变量,如下图为局部变量和成员变量;
-
仅当
a
、test.m_a
的值更改时才会命中断点; -
如果使用无效语法设置断点条件,则会显示警告消息。 如果在指定断点条件时使用的语法有效但语义无效,则在第一次命中断点将出现警告消息。
1.3 命中次数
-
如图所示,下拉框选择【命中次数】,可选择设置命中次数断点的策略;
-
如设置【命中次数】【=】,输入
5
,则只有这个断点命中第5次才会中断;
1.4 筛选器
有时候在调试多线程的时候只是需要在某个线程才进入断点,此时可以选择筛选器输入对应的线程号,在筛选器可以选择机器名、进程的 ID 和进程名、线程号和线程名,可以判断当前是等于或不等于
如我需要判断当前的线程是 2 的时候进入断点,我可以这样写
ThreadId = 19768
这里的等于号写 =
或 ==
都可以,多个条件可以使用 &
与和 ||
或连接
可以选的判断有机器名、进程的 ID 和进程名、线程号和线程名
- MachineName = “name”
- ProcessId = value
- ProcessName = “name”
- ThreadId = value
- ThreadName = “name”
1.5 多条件断点
-
条件断点设置时支持条件表达式、命中次数、筛选器混合使用;
-
如果同时需要添加筛选器和条件表达式可以点击添加条件,多个条件是与关系,需要同时成立才能进入断点。
-
设置完成一个条件后,【添加条件】按键会高亮显示,可添加其它条件;
1.6 设置操作/日志断点
-
【将消息记录到输出窗口】,这里其实就是日志断点/消息追踪点,可将输入的消息打印到【输出】窗口;
-
在消息框中键入要输出的字符串(无需引号)。 如果要显示变量的值,请确保将其括在大括号中。例如,如果要在输出控制台中显示
counter
变量的值,请在消息文本框中键入{counter}
。 -
【调试】->【窗口】->【输出】可打开输出窗口;
-
有时候不能进入断点但是希望知道当前程序的运行,例如我在触摸的时候我就不能直接进断点,我需要在方法里面变量输出到控制台,此时就可以用到追踪点功能;
-
勾选【继续执行】后,断点不会中断,不勾选则会中断。
-
此外,还可以使用特殊关键字来显示更多特定信息。 完全按照以下所示输入关键字(在每个关键字前面使用“$”,并且关键字本身全部大写)。
关键字 | 显示内容 |
---|---|
$ADDRESS | 当前指令 |
$CALLER | 调用函数名 |
$CALLSTACK | 调用堆栈 |
$FUNCTION | 当前函数名 |
$PID | 进程 ID |
$PNAME | 进程名 |
$TID | 线程 ID |
$TNAME | 线程名 |
$TICK | 滴答计数(来自 Windows GetTickCount) |
1.7 设置函数断点
-
设置方式:
-
方法1:【调试】【新建断点】【函数断点】;
-
方法2:打开【断点】窗口,点击【新建】【函数断点】;
-
方法3:使用快捷键打开函数断点设置窗口。
-
-
打开函数断点设置窗口后如下图所示;
-
语法:
fun
/fun()
:全局作用域下名为fun()的函数;fun(int)
:全局作用域下fun(int)重载函数;Test::fun
:Test作用域下的fun函数,可能是Test成员函数,也可能是Test命名空间函数;App1.dll!MethodA
:使用“!”符号指定模块;{MethodA, , App1.dll}+2
:使用本机 C++ 中的上下文运算符。{function, , [module]} [+<line offset from start of method>]
;
1.8 设置异常断点
-
设置方法:
- 方法1:【调试】【窗口】【异常设置】;
- 方法2:快捷键
Ctrl + Alt + E
;
-
如下图所示,勾选对应的异常类型后,当出现相同异常时会在异常出现位置中断。
-
如下图所示,fun()函数抛出异常,如果异常没被捕获则会中断,如果异常被捕获了则默认不会中断,当勾选上对应的异常类型后,抛出异常时会中断。
1.9 保存断点到文件
-
如下图所示,可将所有断点保存到xml文件中,在需要使用时从文件中加载断点;
1.10 启用/禁用断点
- 方法1:在断点位置鼠标右键,选择【启用/禁用断点】;
- 方法2:【调试】【窗口】【断点】打开断点管理窗口,每个断点前由一个复选框,勾选复选框就启用断点,取消勾选就禁用断点,或者通过工具栏可禁用/启用所有断点;
4、VS Code
1.1 设置断点
-
方法1:鼠标选中行,按F9键可创建/删除断点;
-
方法2:鼠标在代码行左侧点击可创建/删除断点;
-
方法3:鼠标选中行,【运行】【切换断点】或【新建断点】。
1.2 设置函数断点
设置方法:
- 方法1:【运行】【新建断点】【函数断点】;
- 方法2:在【调试和运行】窗口【断点】栏,点击
+
添加函数断点;
VS Code可配置不同的编译器,而不同编译器对应的调试工具支持的函数断点语法也存在一些差异。
MSVC编译器
- 语法:
fun
/fun()
:全局作用域下名为fun()的函数;fun(int)
:全局作用域下fun(int)重载函数;A::fun
:A作用域下的fun函数,可能是A的成员函数,也可能是A命名空间函数;App1.dll!MethodA
:使用“!”符号指定模块;{MethodA, , App1.dll}+2
:使用本机 C++ 中的上下文运算符。{function, , [module]} [+<line offset from start of method>]
;
MinGW/GCC编译器
- 语法:
fun
:所有名称为fun的函数,包括全局函数、成员函数、命名空间函数、重载函数等;fun()
:名为fun()的全局函数;A::fun
:A作用域下的所有名为fun的函数,可能是A的成员函数,也可能是A命名空间函数;file:fun
:file文件中所有名为fun的函数;fun(int)
:所有名为fun(int)的函数;A:fun(int)
:A作用域下名为fun(int)的函数。- gdb还支持通过
rbreak 正则表达式
设置函数断点;
1.3 条件断点
设置方法:
- 方法1:鼠标选择行,【运行】【新建断点】【条件断点】,下拉框选择表达式,输入表达式后按回车键生效;
- 方法2:在代码行左侧点击创建断点,鼠标右键【编辑断点】,下拉框选择表达式,输入表达式后按回车键生效;
1.4 命中次数
设置方法:
- 方法1:鼠标选择行,【运行】【新建断点】【条件断点】,下拉框选择命中次数,输入表达式后按回车键生效;
- 方法2:在代码行左侧点击创建断点,鼠标右键【编辑断点】,下拉框选择命中次数,输入表达式后按回车键生效;
1.5 日志消息断点
设置方法:
- 方法1:鼠标选择行,【运行】【新建断点】【记录点】,下拉框选择日志消息,输入需要打印的内容后按回车键生效;
- 方法2:在代码行左侧点击创建断点,鼠标右键【编辑断点】,下拉框选择日志消息,输入需要打印的内容后按回车键生效;
语法:
- 在消息框中键入要输出的字符串(无需引号)。 如果要显示变量的值,请确保将其括在大括号中。例如,如果要在输出控制台中显示
counter
变量的值,请在消息文本框中键入{counter}
。 - 此外,还可以使用特殊关键字来显示更多特定信息。 完全按照以下所示输入关键字(在每个关键字前面使用“$”,并且关键字本身全部大写)。
- 下列关键字cdb全部支持,gdb支持部分。
关键字 | 显示内容 |
---|---|
$ADDRESS | 当前指令 |
$CALLER | 调用函数名 |
$CALLSTACK | 调用堆栈 |
$FUNCTION | 当前函数名 |
$PID | 进程 ID |
$PNAME | 进程名 |
$TID | 线程 ID |
$TNAME | 线程名 |
$TICK | 滴答计数(来自 Windows GetTickCount) |
1.6 等待断点
- 如下图所示,设置一个条件断点,条件为
a > 5
; - 设置一个等待断点,选择等待之前设置的条件断点;
- 当条件断点触发后,等待断点启动,命中等待断点后中断;
- 如果条件断点未触发,则等待断点不生效,命中后不会中断。
1.7 异常断点
- 如果抛出的异常没有被捕获,则默认会中断;
- 如果抛出的异常被捕获,则默认不会中断,想要捕获后中断则需要设置异常断点;
设置方法:
- 在【调试和运行】窗口、【断点】栏,勾选【All Exceptions】;
- 默认为所有异常,如果想只监听指定异常,则点击右侧的画笔图标,填入需要监听的异常;
- VS Code使用MSVC编译器时支持设置异常断点,使用MinGW编译器时不支持异常断点;(我使用时MinGW无法中断)
- 可设置捕获所有异常或者捕获异常类型及其父类异常类型,例如抛出
std::out_of_range
,可设置异常断点类型为std::out_of_range
、std::exception
。
1.8 禁用/启用断点
- 方法1:在断点位置鼠标右键,选择【启用/禁用断点】;
- 方法2:在【运行和调试】窗口【断点】栏,每个断点前由一个复选框,勾选复选框就启用断点,取消勾选就禁用断点,或者通过工具栏可禁用/启用所有断点;
- 方法3:【运行】【启用/禁用所有断点】。
5、Qt Creator
注意: 调试程序尽量使用高版本的Qt Creator,Qt Creator调试功能做得并不是很好,复杂的调试可能会出现卡死、性能第、异常退出等情况;
1.1 断点管理
- 点击窗口右下角【视图(V)】按键打开视图管理菜单;
- 或者在菜单栏选择【视图(V)】【视图(V)】打开视图管理菜单;
- 勾选【 Breakpoint Preset】打开断点管理窗口;
1.2 调试视图管理
- 按F5快捷键开始调试;
- 或者点击窗口左下角图标开始调试;
- 启动调试后点击窗口右下角【视图(V)】按键打开视图管理菜单,可选择打开各项调试需要的视图窗口;
- 或者在菜单栏选择【视图(V)】【视图(V)】打开视图管理菜单;
1.3 添加断点窗口
- 在断点位置鼠标右键打开菜单,选择【编辑断点】;
- 或者在断点管理窗口【 Breakpoint Preset】双击打开【添加断点】窗口;
- 如下图所示,断点类型很多,但常用的不多,大部分断点类型日常用不到。
1.4 启用/禁用断点
-
在断点位置,鼠标右键选择【启用/禁用断点】;
-
在断点管理窗口选中断点,右键选择【启用/禁用断点】;
1.5 函数断点
- 如下图所示,打开【添加断点】窗口,断点类型选择【函数名】;
- 在【函数】栏输入函数名称
fun
,为所有名为fun的函数打断点。
1.6 异常断点
- 使用
throw
抛出异常,并且异常未被捕获处理时默认情况下会导致程序终止; - 使用
throw
抛出异常后,异常被捕获则不会触发中断; - 打开【添加断点】窗口,选择【break when C++ exception is thrown】,抛出异常并捕获异常后会在异常抛出位置
throw
触发中断; - 打开【添加断点】窗口,选择【break when c++ exception is caught】,抛出异常并捕获异常后会在捕获异常
catch
位置触发中断;
1.7 一次性断点
- 如下图所示,点击代码左侧创建一个普通断点,鼠标右键选择【编辑断点】,勾选【仅触发一次】;
- 然后运行程序,命中断点后触发中断,同时断点自动删除,后续不会再被触发;
- 除了普通断点外,其它类型断点都可被设置为一次性断点。
1.8 条件断点
- 如下图所示,创建一个普通断点;
- 鼠标右键选择【编辑断点】,在【新增断点】窗口中【条件】项中输入条件表达式
a == 10
; - 运行程序后当变量a的值为10时命中断点,触发中断。
1.9 忽略次数
- 如下图所示,创建一个普通断点;
- 鼠标右键选择【编辑断点】,在【新增断点】窗口中【忽略次数】项中输入整数数值;
- 表示该断点命中n次后才会触发中断。
- 注意: 这个功能在QT5.14.2、Qt Creator10.0.2版本时MSVC执行正常,MinGW会忽略n次,再次运行后由忽略n次,然后才正常中断断点。
1.10 断点执行命令
- 如下图所示,创建一个普通断点,打开【编辑断点】窗口;
- 在【命令】栏输入gdb命令,每一行为一条命令;
- 在【视图】中勾选【Debugger Log】窗口;
- 运行程序,命中断点,触发中断后,在【Debugger Log】窗口中可看到gdb命令执行结果。
1.11 跟踪点
- 如下图所示,创建一个普通断点,打开【编辑断点】;
- 勾选【仅跟踪点】,在【消息(M)】栏中填入需要输出的消息;
- 确认后断点图标就会变成黑色;
- 程序运行后命中断点,不会触发中断,会在【Debugger Log】和【应用程序输出】栏打印消息;
- 注意: 这里跟踪点功能在MinGW时可使用部分关键字进行替换(例如
$PID
),在MSVC时不能使用关键字。
6、参考
在 Visual Studio Code 中调试_Vscode中文网 (github.net.cn)
std::exception - cppreference.com
Breakpoints (Debugging with GDB) (sourceware.org)
在调试器中使用断点 - Visual Studio (Windows) | Microsoft Learn
使用跟踪点记录信息 - Visual Studio 2017 |Microsoft 学习