调试
主线:代码做的不是你以为的事,而是你叫它做的事——调试就是弥合这个差距。工具按信号成本排阶梯:能 print 就不用 gdb,能 strace 就不上 bpftrace。本 skill 只做 MIT 讲义的科学面(原理与具体工具);与工程策略(艺术面)混合的版本见 dsh 的 debugging-and-profiling 与 diagnosing-bugs。
Printf 与 Logging
- print 迭代法:在怀疑处加 print,反复跑直到信息足够定位。有效,但每次都改代码重跑。
- logging = 「更讲究的 print」:框架自带日志路由(定向到别处)、级别过滤(INFO/DEBUG/WARN/ERROR)、结构化日志(数据事后好提取)。写代码时就主动埋日志——需要的数据可能已经在。
- 修完 bug 后,把诊断用的 print 转成正式 log 再删——下次同类问题不用再改代码。
- 现成程序的日志:很多程序支持
-v/--verbose(重复加更详细);服务日志看/var/log/或journalctl -u <service>;第三方库查它是否支持 debug 日志(环境变量/配置)。
调试器(gdb)
- 什么时候上调试器:不知道该看什么信息、bug 只在难复现条件下出现、改代码重启太贵(长启动时间、复杂状态)。
- 调试器能:某一行停下、逐指令单步、崩溃后看变量、条件成立才停。
- 通用调试器 gdb/lldb 可调试任意原生二进制;语言专属(Python pdb、Java jdb)与运行时集成更紧。
- gdb 常用:
run;b {函数}或b {文件}:{行}断点;c继续;step/next/finish进/过/出;p {变量}打印值;bt调用栈;watch {表达式}值变化即停。 - 要有意义的栈:用
-g编译(DWARF 调试信息);性能工具再加-fno-omit-frame-pointer让栈更可靠。TUI 模式gdb -tui(或内部 Ctrl-x a)分屏看源码。
Record-Replay(rr)
- Heisenbug(一观察就消失:竞态、时序、环境依赖)用传统调试器无效——print 拖慢代码后竞态可能不再发生。
- rr 记录执行、确定性重放,还能倒着走:
reverse-continue(rc)反向继续、reverse-step(rs)反向单步、reverse-next(rn)反向跳过函数、reverse-finish反向出当前函数。 - 崩溃排查套路:跑到崩溃 → 看被破坏的变量 →
watch它 →reverse-continue精确找到被写坏的那一行。 - 适用:偶发失败测试、竞态与多线程、难复现崩溃、一切「想回到过去」的 bug。
- 注意:rr 仅 Linux、需要硬件性能计数器(多数 AWS EC2 虚拟机不行、不支持 GPU);rr 序列化线程调度,依赖时序的竞态可能不复现,要多录几次。macOS 看 Warpspeed。
系统调用追踪
- strace 观察程序与内核的每次交互(打开文件、分配内存、建进程……):程序卡在哪、想访问什么文件、时间花在等什么。
- 常用:
strace ./prog;-e trace=file只看文件调用;-f跟子进程;-p <PID>追运行中的进程;-T显示耗时。macOS/BSD 用 dtruss(包装 dtrace)。 - bpftrace/eBPF:在内核里跑沙箱程序,awk 式语法但表达力极强——按系统调用名聚合计数/延迟、看调用参数。strace 胜在零门槛;bpftrace 胜在低开销、可看内核函数、可聚合。要 root、默认监控整个内核:按
comm == "bash"过滤,或-c 'ls -la'从启动开始追一个命令(cpid 即其 PID)。
网络调试
- tcpdump:
sudo tcpdump -i any port 80抓包;-w capture.pcap存文件给 Wireshark 分析。 - HTTPS 有加密:mitmproxy 做中间人代理看加密流量;Web 应用最省事的是浏览器 DevTools 的 Network tab(解密后的请求/响应、头、耗时都在)。
内存调试
- 内存 bug(越界、use-after-free、泄漏)常不立刻崩,而是很久以后才发作。
- Sanitizers 是编译期插桩:AddressSanitizer 查越界/UAF/泄漏(
-fsanitize=address);ThreadSanitizer 查数据竞争(-fsanitize=thread);MemorySanitizer 查未初始化内存读(-fsanitize=memory);UBSan 查未定义行为如整数溢出(-fsanitize=undefined)。要重编译,但快到能进 CI 与日常开发。 - 不能重编译(无源码、第三方库)用 Valgrind:类虚拟机运行、更慢——
valgrind --leak-check=full ./prog。
AI 辅助调试
- 擅长:解释天书级报错(C++ 模板、Rust borrow checker);跨语言/抽象边界(FFI、构建系统、「我怀疑是依赖的 bug」);把模糊症状关联到根因(「功能正常但内存多用了 10 倍」);分析崩溃转储与栈。
- 局限:会一本正经胡说;可能给出掩蔽 bug 而非修复 bug 的建议;永远用真调试工具验证它的话——AI 是理解的补充,不是替代。
练习
学习材料在 exercises.md。
改编自 MIT The Missing Semester 课程 Lecture 4: Debugging and Profiling(讲义 + 口播稿,CC BY-NC-SA 4.0):https://creativecommons.org/licenses/by-nc-sa/4.0/ · 课程站点:https://missing.csail.mit.edu/ · 讲座视频:https://www.youtube.com/watch?v=8VYT9TcUmKs