Bug 和 Debug

Bug 和 Debug

2024 南京大学《操作系统:设计与实现》
Bug 和 Debug
2024 南京大学《操作系统:设计与实现》
Bug 和 Debug

Intel Pentium FDIV (1994)

center

2024 南京大学《操作系统:设计与实现》
Bug 和 Debug

Ariane 5 (1996)

“...attempted to convert large, unexpected 64-bit floating point numbers representing horizontal velocity into 16-bit integers. This resulted in an overflow error, causing the onboard computer to crash.”

center

2024 南京大学《操作系统:设计与实现》
Bug 和 Debug

调试 (Debug)

如果我们已经知道 bug 的存在

  • Segmentation Fault
  • Online Judge 拒绝
  • 虚拟机神秘重启
  • ……

怎么找到它?

2024 南京大学《操作系统:设计与实现》
Bug 和 Debug

开始调试之前:摆正心态

公理 1:机器永远是对的

  • CPU: “无情的、执行指令的机器”
  • Crash, Wrong Answer, 虚拟机神秘重启
    • 99.9999% 是自己的问题
    • 有亿点点概率是编译器错了 (但你可以知道)
    • 有亿点点点点概率是处理器错了 (你也可以知道)

公理 2:未测代码永远是错的

  • 反复测试过的代码都是错的
  • 你以为最不可能出 bug 的地方,往往 bug 就在那躺着
2024 南京大学《操作系统:设计与实现》
Bug 和 Debug

调试理论

“软件” 的两层含义

  • 人类需求在信息世界的投影
    • 理解错需求 → bug
  • 计算过程的精确 (数学) 描述
    • 实现错误 → bug

调试为什么困难?

  • Bug 的触发经历了漫长的过程
  • 可观测的现象未必能直接对应到 root cause 上
2024 南京大学《操作系统:设计与实现》
Bug 和 Debug

Fault, Error, 和 Failure

需求 → 设计 → 代码 (Fault/bug) → 执行 (Error) → 失败 (Failure)

  • 我们只能观测到 failure (可观测的结果错)
  • 我们可以检查状态的正确性 (但非常费时)
  • 无法预知 bug 在哪里 (每一行 “看起来” 都挺对的)
for (int i = 0; i < n; i++)
    for (int j = 0; j < n; i++) {
        ...
    }
  • 人总是 “默认” (不默认,浪费的时间就太多了)
2024 南京大学《操作系统:设计与实现》
Bug 和 Debug

调试理论

调试理论:如果我们能判定任意程序状态的正确性,那么给定一个 failure,我们可以通过二分查找定位到第一个 error 的状态,此时的代码就是 fault (bug)。

推论

  • 为什么我们喜欢 “单步调试”?
    • 从一个假定正确的状态出发
    • 每个语句的行为有限,容易判定是否是 error
  • 为什么调试理论看起来很没用?
    • “判定状态正确” 非常困难
    • (是否在调试 DP 题/图论算法时陷入时间黑洞?)
2024 南京大学《操作系统:设计与实现》
Bug 和 Debug

调试理论 (cont'd)

调试 = 观察状态机执行 (trace) 的某个侧面

  • 缩小错误状态 (error) 可能产生的位置
  • 提出假设,作出验证

观察状态机执行的两个基本工具

  • printf → 自定义 log 的 trace
    • 灵活可控、能快速定位问题大概位置、适用于大型软件
    • 无法精确定位、大量的 logs 管理起来比较麻烦
  • gdb → 指令/语句级 trace
    • 精确、指令级定位、任意查看程序内部状态
    • 耗费大量时间
2024 南京大学《操作系统:设计与实现》