调试理论
调试理论¶
概览¶
编写程序,难免会遇到bug。调试就是为了测试,修正bug。
bug的流程:
Fault (程序/输入/配置错) → Error → Failure (可观测)
调试前我们需要端正三个心态
- 机器永远是对的
- 未测代码永远是错的:自己写的代码总是容易错的,所以要做好测试
- 写出不好维护的代码,你都在给你未来的调试/需求变更挖坑
- 在代码中写明需求,并保证需求与代码实现一致
- 多写
assert,写出一些隐藏假设
总结为写好代码、做好测试、多写断言
调试程序¶
而在调试时,我们应当分别从理论和实现两个角度思考,检查。
我们喜欢 “单步调试” -> 可以更好的判断每个语句的结果是否正确(当然,使用二分会更好)
printf→ 自定义 log 的 trace- 灵活可控、能快速定位问题大概位置、适用于大型软件
- 无法精确定位、大量的 logs 管理起来比较麻烦
gdb→ 指令/语句级 trace- 精确、指令级定位、任意查看程序内部状态
- 耗费大量时间
- 可参考的指令:GDB Cheat Sheet
调试一切状态机¶
不仅是程序,我们在状态机上所遇到的一切问题,都可以类似的解决(程序 = 计算机系统 = 状态机)
- 大部分 Error 和 Failure 都比较接近
- 出错时,使用 perror 打印日志
- 当报告不够详细:使用
-v等选项检查日志
在此,我们还可以引入两个工具
- Profiler:
perf- “采样” 状态机 - Trace:
strace- 追踪系统调用
调试案例¶
案例一¶
E:'sys/cdefs.h': No such file or directory
理应能够找到该头文件,为何找不到?检查是否找到:
- 日志 --verbose
- strace,直接看访问过的文件!
案例二¶
多写断言
// 结构约束
assert(u == u->parent ||
u == u->parent->left ||
u == u->parent->right);
assert(!u->left || u->left->parent == u);
assert(!u->right || u->right->parent == u);
// 数值约束
assert(!u->left || u->left->val < u->val);
assert(!u->right || u->right->val > u->val);
案例三¶
-fsanitize=address
CC 编译器提供的一个强大的内存错误检测工具,属于 AddressSanitizer(ASan) 的一部分
ASan 会为程序内存分配一块额外的“影子内存”,用于标记每一字节内存的状态,进而定位内存错误的位置