断点是使用 GDB 定位程序问题的基础。程序运行到断点时会暂停,开发者可以查看变量、调用栈和线程状态,再决定单步执行还是继续运行。

本文使用 Ubuntu 22.04 环境中的 GDB 12.1 和 GCC 11.3 进行示例说明。GDB 的命令在其他 Linux 平台通常也适用。

一、准备调试信息

准备一个简单的 main.cpp:

#include <iostream>

int main() {
  for (int i = 0; i < 100; ++i) {
    std::cout << i << '\n';
  }
  return 0;
}

使用 -g 或 -g3 生成调试信息。调试阶段通常使用 -O0,避免优化改变变量和源代码行之间的对应关系:

g++ -std=c++17 -g3 -O0 -Wall -Wextra -pedantic main.cpp -o main

启动 GDB:

gdb ./main

也可以在启动时直接设置要调试的可执行文件:

(gdb) file ./main

二、设置断点

2.1 根据函数设置

(gdb) break main

break 可以缩写为 b。如果程序中存在重载函数,GDB 可能会列出多个候选位置,需要选择具体的断点位置。

2.2 根据文件和行号设置

(gdb) break main.cpp:5

在当前文件中也可以直接指定行号:

(gdb) break 5

断点需要位于编译器生成了调试行信息的代码位置。若行号没有对应的可执行代码,GDB 可能会将断点调整到附近的代码行,或者暂时将断点设为 pending。

2.3 临时断点

如果只想让断点生效一次,可以使用 tbreak,它也可以缩写为 tb:

(gdb) tbreak main.cpp:5
(gdb) run

程序第一次命中临时断点后,该断点会自动删除。它适合只需要观察一次初始化过程的场景。

三、运行和查看状态

(gdb) run       # 从头运行程序
(gdb) continue  # 继续运行到下一个断点
(gdb) next      # 单步执行,不进入函数内部
(gdb) step      # 单步执行并进入函数
(gdb) finish    # 执行到当前函数返回
(gdb) until 20  # 执行到当前文件第 20 行附近

常用查看命令:

(gdb) print i          # 打印表达式,简写为 p
(gdb) display i        # 每次暂停时自动打印 i
(gdb) info locals      # 查看当前栈帧的局部变量
(gdb) info args        # 查看当前函数参数
(gdb) list             # 查看源代码,简写为 l
(gdb) backtrace        # 查看调用栈,简写为 bt

不再需要自动显示某个表达式时,可以先通过 display 输出的编号查询,再删除:

(gdb) info display
(gdb) undisplay 1

四、条件断点

当循环次数很多时,不需要每次都暂停,可以为断点增加条件:

(gdb) break main.cpp:5 if i == 50
(gdb) run

程序会在 i 等于 50 时暂停。条件断点也可以在断点创建后设置:

(gdb) info breakpoints
(gdb) condition 2 i == 50

这里的 2 是断点编号,需要根据 info breakpoints 的实际输出替换。

五、管理断点

(gdb) info breakpoints  # 查看断点、观察点及编号
(gdb) disable 2         # 暂时禁用断点
(gdb) enable 2          # 重新启用断点
(gdb) delete 2          # 删除指定断点
(gdb) delete            # 删除全部断点

断点可以使用 commands 配置命中后的自动操作。例如只打印变量而不进入交互暂停:

(gdb) commands 2
> silent
> printf "i = %d\n", i
> continue
> end

六、一个常用调试流程

遇到一个需要定位的问题时,可以按下面的顺序操作:

  1. 使用 -g3 -O0 重新编译程序。
  2. 在入口函数或怀疑出错的函数设置断点。
  3. 使用 run 启动程序。
  4. 使用 next、step 和 print 缩小问题范围。
  5. 使用 backtrace 检查调用路径。
  6. 对循环变量或关键状态设置条件断点。
  7. 找到原因后删除或禁用临时断点,继续验证修复结果。

如果程序使用了较高优化级别,变量可能被优化掉、合并或移动,GDB 可能提示变量不可用。此时应先在 -O0 下完成定位,再使用目标优化级别验证性能和最终行为。

七、总结

  • 使用 -g 生成调试信息,调试阶段通常同时使用 -O0。
  • break 设置普通断点,tbreak 设置只生效一次的临时断点。
  • 使用 break file:line if condition 设置条件断点。
  • next 不进入函数,step 会进入函数,finish 执行到当前函数返回。
  • 使用 info breakpoints、disable、enable 和 delete 管理断点。