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. 一次性使用:临时断点在被触发一次后就会自动删除,不会再次影响程序的执行。
  2. 减少干扰:由于临时断点不会在多次执行中反复生效,所以它减少了对程序自然执行流的影响。
  3. 方便快捷:设置临时断点可以快速帮助开发者定位问题,无需手动删除断点。

临时断点的用途: 临时断点主要用于以下场景:

  1. 单次检查:当只需要检查某个代码段一次执行的情况时,使用临时断点可以避免在每次调试时都打断程序执行。
  2. 排除偶然性问题:有时程序中的问题是偶然出现的,使用临时断点可以在问题发生的位置立即停止程序,便于捕捉这一瞬间的问题。
  3. 减少断点管理:在调试过程中,临时断点可以减少需要管理的断点数量,简化调试流程

1.8 启用/禁用断点

在调试过程中,断点是程序员用来暂停程序执行的重要工具之一。

然而,在调试复杂的应用程序时,可能会设置大量的断点,这可能会导致调试过程变得混乱。此时,能够动态地启用或禁用断点就显得非常重要了。

禁用断点的作用

禁用断点意味着暂时不让该断点生效。这样做有几个好处:

  1. 减少干扰:当程序执行时,禁用不必要的断点可以避免频繁的暂停,从而不影响正常的调试流程。
  2. 提高效率:在查找特定问题时,禁用不相关的断点可以加快调试速度。
  3. 简化调试:当调试特定路径或功能时,禁用其他断点可以使调试更加集中和有针对性。

启用断点的作用

启用断点则是恢复断点的功能,使其能够像正常断点一样在程序执行到达指定位置时暂停程序。

3、Visual Studio

1.1 设置断点

  • 方法1:鼠标选中行,按F9键可创建/删除断点;

  • 方法2:鼠标在代码行左侧点击可创建/删除断点;

    在这里插入图片描述

  • 如下图所示,鼠标在断点位置,右键选择【条件】可看出断点设置菜单由【条件】、【操作】两部分组成;

    在这里插入图片描述

1.2 条件表达式

  • 下拉框选择【条件表达式】【为true】,输入栏输入条件表达式,点击【关闭】后生效;

  • 启动调试后只有a的值为5时断点才会中断;

  • 点击表达式后,已保存前的X可删除这一条表达式;

    在这里插入图片描述

  • 下拉框选择【条件表达式】【为true】,输入栏输入需要检测的变量,如下图为局部变量和成员变量;

  • 仅当 atest.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_rangestd::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 学习

在匿名空间设置断点 | 100个gdb小技巧 (gitbooks.io)

添加断点 |Qt Creator 文档

posted @ 2024-09-19 15:51  mahuifa  阅读(0)  评论(0编辑  收藏  举报  来源