调试工具入门:GDB、pdb、IDE 调试器

调试是编程的基本功,说白了你写的代码不可能一次全对。我刚入行时也怵调试,总觉得打几个 log 凑合够了。后来被一个内存泄漏折腾了两天,才老老实实把 GDB 捡起来。下面咱们挨个聊。

1. GDB — C/C++ 的调试利器

GDB 是命令行工具,没有图形界面,但功能极强。我个人习惯记几个常用的:

gdb ./myprogram
(gdb) break main          # 在 main 入口设断点
(gdb) run                 # 开始运行
(gdb) next                # 步过(不进入函数)
(gdb) step                # 步入(进入函数内部)
(gdb) print variable      # 打印变量值
(gdb) backtrace           # 看调用栈
(gdb) continue            # 继续运行到下一个断点
(gdb) quit

嗯,这里要注意——编译时一定要加 -g 选项。我曾经在 Makefile 里漏写了 -g,结果 GDB 进去符号全是乱码,当时我还以为工具坏了……其实是自己挖的坑。

曾经踩过的坑: 生产环境的二进制通常 strip 过,不要直接拿它调试。先用 -g -O0 重新编译一份。

再看一个实际场景:data-race 排查。我习惯设 watchpoint(硬件断点)看某个变量何时被修改:

(gdb) watch myvar
(gdb) continue

一旦值变化,GDB 自动暂停,配合 backtrace 就能定位写它的函数。这个技巧我在一次多线程 bug 中立了大功。

2. pdb — Python 调试器

Python 自带 pdb,简单直接。两种启动方式:

# 方式一:运行时插入断点
import pdb; pdb.set_trace()

# 方式二:命令行启动
python -m pdb my_script.py

常用命令:

  • l — 列出上下文代码
  • n — 执行下一行
  • s — 步入函数
  • c — 继续到下一个断点
  • p — 打印表达式
  • q — 退出

举个例子:

def divide(a, b):
    import pdb; pdb.set_trace()
    return a / b

result = divide(10, 0)

调试时输入 p a, b 就能看到两个参数的值,然后用 n 步进发现除零错误。我通常在测试环境这么用,但记得上线前要删掉 set_trace(),不然服务会挂住。

小技巧: 嫌每次打 import pdb; pdb.set_trace() 麻烦?可以预先在代码里只写 breakpoint()。Python 3.7+ 内置了这个函数,行为等价于 pdb.set_trace(),还能通过环境变量 PYTHONBREAKPOINT 切换调试器。

3. IDE 调试器(VS Code、PyCharm)

图形化调试让入门变得更轻松。断点、变量监视、调用栈全在一个窗口里。我记着第一次用 PyCharm 的 Evaluate Expression 时,感觉就像打开了新世界。

VS Code 调试:

  • 在行号左侧点击设置断点(红色圆点)
  • 按 F5 启动调试,选择对应的 launch 配置
  • 调试侧栏可以看:变量、监视表达式、调用栈、断点列表
  • 支持条件断点:右键断点,输入 x > 100 只在 x 大于 100 时暂停
  • 日志断点(logpoint):不暂停,直接打印信息到控制台
// .vscode/launch.json 示例
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python Debug",
            "type": "debugpy",
            "request": "launch",
            "program": "${file}",
            "console": "integratedTerminal"
        }
    ]
}

PyCharm 调试:

  • 断点、单步操作和 VS Code 类似
  • 特色:Evaluate Expression(Alt+F8),可以在运行中间任意执行 Python 表达式,改变量值
  • 条件断点、异常断点(停在任何异常处)都很方便
  • 调试多进程/多线程时,可以在调试工具窗口的「线程」标签页切换上下文

有一次我在 PyCharm 里调试一个并发爬虫,几百个线程同时改共享队列。用条件断点设 queue.qsize() > 500,一下抓住数据竞争的点。要是用 print 日志,估计得打印半栋楼那么高的日志。

工具 适用语言 启动方式 常用操作/命令 优缺点
GDB C/C++、Rust、Go gdb program break, run, next, print, backtrace, watch 轻量,无 GUI;适合嵌入式、服务器调试
pdb Python python -m pdb script.py 或 breakpoint() l, n, s, p, c, q 内建,零配置;但缺少变量树浏览
VS Code 多语言 按 F5 + launch.json 断点/条件断点/日志断点、监视、调用栈 跨平台,生态好;远程调试配置较复杂
PyCharm Python 为主 点击绿色爬虫按钮 Evaluate Expression、条件断点、框架树 功能最全,开箱即用;商业版收费

4. 调试流程与最佳实践

不管用什么工具,调试的套路是相通的。下面这张图概括了我在实际项目中总结的流程:

调试通用流程 复现错误 缩小范围 / 加断点 单步执行 检查变量 / 调用栈 定位到问题? 修改代码 重新测试 验证通过 → 结束 否 是 虚线表示“未定位到问题”时回退到单步执行或调整断点

一般我按这个思路走:

  1. 复现错误 — 确定环境、输入和步骤。不能稳定复现的问题最难调。
  2. 缩小范围 — 用二分法或 log 点,锁定出错的函数或代码块。
  3. 设置断点 + 单步执行 — 观察每一步变量变化,和预期对比。
  4. 检查调用栈 — 尤其在看异常时,调用栈能告诉你错误是怎么一路传上来的。
  5. 定位问题后修代码,修完马上重新测试。不要修完一个就跑,经常一修引出新 bug。
核心观点: 调试的本质是观察程序状态。工具只是帮助你观察的手段,关键是心里要有“它应该是什么值”的预期。
小技巧: 有时候加个条件断点比一路 step 快得多。比如循环 10000 次,只在 i == 9999 时停住,一步到位。

嗯,记住这些,调试就不再是撞大运,而是一套可以重复执行的流程。动手试一次比看十遍文章管用,赶紧打开你的 IDE 或者终端练一把吧。