大家应该已经领教到bug的厉害了
今天来给大家揭秘
需求 -> 设计 -> 代码 -> 运行结果
需求 -> 设计 -> 代码 -> 运行结果
需求 -> 设计 -> 代码 -> Fault -> Error -> Failure
调试 = 看着Failure, 找到Fault
需求 -> 设计 -> 代码 -> Fault -> Error -> Failure
代码 =(小错误)=> Fault =(也许?)=> Error =(也许马上?)=> Failure
需求 -> 设计 -> 代码 -> Fault -> Error -> Failure
Start Fault Failure
+---------------------+------------------+
|------ Error -----|
程序员能做的
但检查这件事并不容易
好一点的定位方法 - 二分调试理论
for (L = Start, R = Failure; L < R; ) {
S = L和R的中间点;
if (check_state(S) == GOOD) L = S; else R = S;
}
第一个Error状态\(S = min_{s}(check\_state(s) == BAD)\)即为Fault
但这种方法扩展性很低: 几百行的小程序还行, 复杂的项目就搞不定了
check_state(S)
也不容易
科学的调试方法 = 从时空角度高效地解决上述挑战
机器永远是对的
程序出错了, 不要怀疑真机的硬件/操作系统/编译器, 先怀疑自己的代码
未测试代码永远是错的
bug往往出现在那些你觉得 “应该没问题”的地方
调试公理告诉大家: 要解决问题, 先摆正心态
需求 -> 设计 -> 代码 -> Fault -> Error -> Failure
你想当然地认为
调这种bug的唯一方法 - 仔细RTFM, 确认自己正确理解需求
回顾防御性编程: assert - 将预期的正确行为直接写到程序中
segmentation fault
->
yemu.c:27: main: ...
一些好的编程习惯
assert(idx < ARRAY_SIZE);
assert(p != NULL);
switch-case
不存在默认情况时default: assert(0);
// nemu/src/isa/riscv64/local-include/reg.h
static inline int check_reg_idx(int idx) {
IFDEF(CONFIG_RT_CHECK, assert(idx >= 0 && idx < 32));
return idx;
}
这个assert()
看上去很蠢, 但你能100%保证它不会失败吗?
// nemu/src/memory/paddr.c
static void out_of_bound(paddr_t addr) {
panic("address = " FMT_PADDR " is out of bound of pmem [" FMT_PADDR ", "
FMT_PADDR "] at pc = " FMT_WORD, addr, PMEM_LEFT, PMEM_RIGHT, cpu.pc);
}
物理内存越界是PA2中相对难调的一类错误
但如果去掉这些检查, 把Error放过去, 将会发生什么?
让编译器自动插入assert, 拦截常见的非预期行为
打开后程序运行效率有所下降
man gcc
查看具体用法测试需要测试用例
写单元测试是份累活
通过分析代码(静态程序分析), 提示编译通过但有潜在错误风险的代码
-Wall
, -Werror
-Wall
核心思想: 尽量让编译过程拦截错误(甚至是设计语义), 避免其进入仿真
回顾: 正确的代码 != 好代码
好代码的两条重要准则
使用正确的编程模式写出好代码
assert
检查非预期行为用它来实现指令, 只需要无脑抄手册 😂
有同学用verilog实现INSTPAT
技术债(Technical Debt)
每当你写出难以维护的代码, 将来调试的时候都是要还的.
程序是个状态机: \(S = \{<PC, v_1, v_2, ...>\}\)
软件工程领域的一个观察: Error通常只会影响程序中很少一部分的状态
启发: 没有必要观察整个状态空间, 通常只需观察若干关键变量即可
为了提升调试效率, 我们需要从不同层次理解程序的状态和行为
所以我们需要不同层次的trace!
si
- 细粒度的状态转移info r
/x
- 检查\(R\), \(M\)
sdb与gdb结合使用
随着处理器复杂度上升, 添加更多trace将更容易理解模块级行为
波形和trace结合使用
gem5中的O3 Pipeline Viewer通过脚本对trace文件可视化
apt
可一键安装
blktrace
, dnstracer
, extrace
,
fatrace
, ltrace
, mutrace
,
nfstrace
, strace
, tcptrace
,
traceroute
, xtrace
-v
等输出
make
, gcc
, ssh
程序的运行结果正确, 但就是跑得慢 - performance bug
第四期有同学通过火焰图分析出verilator仿真变慢的原因, 优化verilog设计后仿真效率提升10倍!
面向处理器体系结构优化的profiling
A Top-Down Method for Performance Analysis and Counters Architecture. Ahmad Yasin. In IEEE International Symposium on Performance Analysis of Systems and Software, ISPASS 2014.
道理我都懂, 但我就是不知道如何下手
如果一个bug要调好几天, 或者长时间没有新的认识, 说明
你需要意识到, 当你觉得调试非常困难, 并不是bug本身非常困难
这不是做大项目该有的心态 - 课程作业/小项目确实无所谓
printf()
多输出xxx可以帮助我调试我们设置预学习阶段, 就是希望大家端正心态和学习习惯
对于大部分初学者, “吃亏学习法”还是很有用的
但为了让你意识到自己吃了亏, 你需要提高对自己的要求
如果你能持续找到对自己不满意的地方, 这个过程中掉过的坑, 吃过的亏, 就值得了