回顾:调试理论

程序 = 物理世界过程在信息世界中的投影


Bug = 违反程序员对 “物理世界” 的假设和约束

  • Bug 违反了程序的 specification
    • 该发生的必须发生
    • 不该发生的不能发生
  • Fault → Error → Failure

编程语言与 Bugs

编译器/编程语言

  • 只管 “翻译” 代码,不管和实际需求 (规约) 是否匹配
    • “山寨支付宝” 中的余额 balance
      • 正常人看到 0 → 18446744073709551516 都认为 “这件事不对” (“balance” 自带 no-underflow 的含义)

怎么才能编写出 “正确” (符合 specification) 的程序?

  • 证明:Annotation verifier (Dafny), Refinement types
  • 推测:Specification mining (Daikon)
  • 构造:Program sketching
  • 编程语言的历史和未来
    • 机器语言 → 汇编语言 → 高级语言 → 自然编程语言

回到现实

今天 (被迫) 的解决方法

  • 虽然不太愿意承认,但始终假设自己的代码是错的
  • (因为机器永远是对的)

然后呢?

  • 首先,做好测试
  • 检查哪里错了
  • 再检查哪里错了
  • 再再检查哪里错了
    • “防御性编程”
    • 把任何你认为可能 “不对” 的情况都检查一遍

防御性编程:实践

把程序需要满足的条件用 assert 表达出来。


及早检查、及早报告、及早修复

  • Peterson 算法中的临界区计数器
    • assert(nest == 1);
  • 二叉树的旋转
    • assert(p->parent->left == p || p->parent->right == p);
  • AA-Deadlock 的检查
    • if (holding(&lk)) panic();
    • xv6 spinlock 实现示例

防御性编程和规约给我们的启发

你知道很多变量的含义

#define CHECK_INT(x, cond) \
  ({ panic_on(!((x) cond), "int check fail: " #x " " #cond); })
#define CHECK_HEAP(ptr) \
  ({ panic_on(!IN_RANGE((ptr), heap)); })

变量有 “typed annotation”

  • CHECK_INT(waitlist->count, >= 0);
  • CHECK_INT(pid, < MAX_PROCS);
  • CHECK_HEAP(ctx->rip); CHECK_HEAP(ctx->cr3);
  • 变量含义改变 → 发生奇怪问题 (overflow, memory error, ...)
    • 不要小看这些检查,它们在底层编程 (M2, L1, ...) 时非常常见
    • 在虚拟机神秘重启/卡住/...前发出警报