分类 调试 下的文章

弗雷德里克·布鲁克斯( F r e d e r i c k P·B r o o ks ) 博士在他那篇著名的《没 有 银弹— 软件工程 中的根本和次要问 题》 一文 中 , 将软件项目比作可怕的人狼 (w e r e w o l v e s ) , 并大胆地预言十年内不会 找到特别有效 的银 弹 。 该论文发表的时 间是 19 8 6 年 , 如今整整 2 0 年过去了 , 尽 管不时有人惊呼找到 了神奇的银弹 , 但是 冷静的人们很快发现那只是美好的愿望 。
如果说 软件工业 中与人狼 的战 斗还在持续 , 那么在这些战役 中一 定会有程 序 员的身影 , 笔者 也是 其中的一个。 我的 编程生涯是从使用汇编语言编写 D O S 下 的 T SR 程序开始的。 今天 D O S 操作 系统 已经成为历史 , 在那个年代最值得炫 耀 的 T S R 技术也早 已经过时了 。 十几年 中 ,O W L、V F W、V D X、ISA P I、A e ti v e M o v i e 等技术也被 时间淘汰… …然而 , 在这漫 长的时间当中 , 我最看重的是软件调试技术。它是十几年中我学到的最有用、一直受用、而且 日久弥新的一项技术。
从软件 工程 的角度来讲 , 软件调试 是软件 工程 的一个重要部分 , 软件调试 过程 出现在软 件工程的各个阶段 。 从最初的可行性分析、原型验证、到开发和测试阶段、再到发布后的维护与支持,都有调试过程的参与。通常认为,一个完整的软件调试过程由以下几个步骤组成:

  • 重现故障
    通常是用于调试的系统上重复导致故障的步骤,使要解决的问题出现在被调试的系统中。
  • 定位根源
    及综合利用各种调试工具,使用各种调试手段寻找导致软件故障的根源 (o r o t c a u s e )。通常测试人员 报告和描述的是软件界面或工作 行为中所表现出的异常 , 或者是 与软件需求 和 功能 规约不 符 的地方 , 泛 指软 件 缺 欠(d e f e c t ) 或者故障 (f a i l u e r )。 而这些表面 的缺欠总是 由于一或 多个内在 因素所 导 致的 。 这些内因要 么是代码的行为错误 , 要么是不行为错误 (该作而未作 )。
  •  探索和 实现解决方案
    即根据寻 找到的故障根源 、 和资源情况 、 紧迫程度 等要求设计和实现解决方案。
  • 验证方案 ,
    在 目标环境中测试方 案的有效性 , 又称 为回 归 (e r g e r s s ) 测试。 如果 问题 已 经解决 , 那么 就可以关闭问题。 如果没有解决则回到第 3 步调 整和修 改解决方案 。

这些步骤 中 , 定位根 源常常是最困 难也是 最关键的步骤 , 它是软件调试过 程 的核心 和灵魂 。 如果没有找到故障根源 , 那 么解决方案便很是隔靴搔痒 , 或者 头痛医脚 , 白白浪费了时 间。 对软件调试的另一种更通俗的解释是指使 用调试工具求解各种软件 问题的 过程, 例如跟踪软件的执行过程, 探索软件本身或者与其配套 的其它软件或者硬件 系统的工作原理等 , 这些过程 的 目的有可 能是为了去除软 件缺欠, 也 可能不是 。

在了解了软件调试技术的基本概念 以后 , 下面我们来看一 下支撑软件调试 技术的几种基本机制 。

  • 断点 : 即当被调试程序执行到某一 空 间或时间点时将其中断 到调试器 中。 根据中断条件分为如下几种 :
    a、代码断点: 当程序执行到指定 内存 地址的代码时中断到调试器 。
    b、数据断点 : 当程序访问指定内存地 址的数据时中断到调试器 。
    c、I/O 断点: 当程序访 问指定 1 / 0 地址 的端口 时中断到调试器 。
    根据断点的设置方法 , 断点又分为软 件断 点和硬件断点。 软 件断点通常是通过 向指定的代码位置插入专用的断点指令来实现的 . 比如 IA 32 C p U 的 IN 丁 3指令 (机器码 为o x C C ) 就是断点指令。 硬件断 点通常是通过 设置 C PU 的调试寄存器来 设置的。 IA 3 2 C PU 定义了 8 个调试寄存 器 , D R O一 D R 7 , 可以最多同时设置 4 个硬 件断点〔对于一 个调试会话)。 通过调试寄 存器可以设置以上三种断点中的任一种, 但是通过断点指令只可以设置代码断点。
  • 单步跟踪 : 即让应用程序按照某单 位一步步执行。 根据单位 , 又 分几种:
    a、每次执行一 条汇编指令 , 称为汇 编语言一级的单步跟踪。 设置 IA 32 C Pu 标志寄存器的 T F (rT 叩 lF a g , 即陷阱标 志位 ) 位 , 便可以让 C P U 每执行完一条 指令便产生一 个调试异常 (N I T I ) , 中断 到调试器 。
    b、每次执行源代码 (比汇编语 言更 高级的程序语言, 如 C / C料 ) 的一条语句 , 又称为源代码级的单步跟踪 。 通常高级 语言的单步跟踪是通过反复设置 C P U 的 陷阱标志位来实现的, 如果 当前源代码行还没有执行完 , 那么调试器重新设置 陷阱 标志并让程序继续执行, 直到该语句结 束(E I P 指向另一语句 ) 才中断给用 户 。
    c、每次执行一个程序分支 , 又称 为 分支到分支单步跟踪。 设置 IA 32 c Pu 的o bg e t lM s R寄存器的 B T F (B ra n e h T ra pF l g a )标志后 , 便可以启用分支到分支单 步跟踪 。
    d、每次执行一 个任务 (线程) , 即当 一个任务 (线程 ) 被调度执行时中断到调 试器 。 IA 3 2 架构所 定义 的任 务状态段( T Ss ) 中的 T标志为实现这一功能提供 了 硬件一级的 支持 , 但 是很多调试器还 有 提供这项功 能。
  • 栈 回溯 (s ta e k ba e k ta r e e ) : 即通过记 录在栈中的 函数返 回地址显示 (追溯 ) 函 数调用过程 。 在将返 回 地址翻译 成函数 名时需要有调试符号 ( de b u g sy n t b o l ) 的 支持 。 大 多数编译 器都支持 在编译时生 成调试符号 。 微软的调 试符号服 务 器
    ( h t t P: // m s d lm ie r o s o f te o m /d o w n lo a d /sy n t b o ls )提供 了大 多数Wi n d o w s 系统文件 的调试符号 , 是调试和学习w id n o w s操作 系统的宝贵资源。
  • 调试信息输出(de b铭 o u tP u印i r n t ) : 即将程序运行 的位置 、 变量状态等信 息 输出到调试器 、 窗 口 、 文件或者其它可以 观察到的地方 。 这种方法的优点是 简单方便、不依赖于调试器 , 但也有明显的缺点 , 如效率低 , 安全性差 , 通常不可以动 态开启 , 且难以管理等 。 在W i n d o w s 操作 系统中 , 驱动程序可以 使用 D b g r P in t /D b g P i r in E x 来输出调试信息 , 应用程序可 以调用o u tP u tD e b u g s i t r n g API
  •  日志 ( 1 0 9 ) : 将程序运 行的状态信 息写人到特定的文件或者数据库中。 W in d o w s 操作 系统提供 了记录、观察和管理 (删除和备份 ) 日志的功能 。 Win do w s v is a t 新引入 T 名 为 C o m m o n L o g F i le s y s t e m (c L s F . S Y s ) 的内核模块 , 用于进一步加 强 日志功能 。 .
  • 事件迫踪 (e v e n t t r a e e ) : 通 常用来 监视频 繁的复杂的软件过程 , 满足普通 日志机制难以胜任 的需求 。 比如监视大 信息t 的文件操作、 网络通信等。 E T W ( E v e n t T r a c e f o r Wi n d o w s ) 是Wid n o w s 操 作系统内建的事件追踪机制 , Wi n d o w s 内 核本身和很多W in do w s下的软件工具(如 B o v o t i s , T C即 P V ie w )都使用了该机制 。

在以 上机制中 , 断 点和单步跟踪通常必须在有调试 器参与的情况下才能使 用 。 调试器 ( s o f t w a e r de b u g g e r ) 是综合提 供 各种调试功能的软 件工具 。 除了处理断点、单步跟踪、模块映射等调试事件外 , 调试器通常还提供如下功能 :

  • 观察和编辑被调试程 序的内存和数据 , 如全局变量、局部变量、以及程序的栈和堆等重要数据结构 。
  • 观察和 反 汇编被调试程序的代码。 
  • 显示线程栈 中的函数调 用信息 。
  • 管理调试符号 。
  • 控制进程和线 程 , 例如将 被调试 程序中断到调试器中 , 和恢 复其执行 等。

根据调试器 所调 试目标程序的工作模式 , 可 以把调试器分 为用户态调试器 和内核态调试器 , 前者用于调试用户态下的各种程序 (应 用程序、系统服务 、 或者用户态的 D L L 模块 ) , 后者用于调试工 作在内核模式 的程序 , 如驱动程 序和操 作系统的内核 部分 。 Wi n D b g 是微软开 发 的一个免 费调试器 , 它既可以用 作用户 态调试器 , 也可以用作 内核态调试器 , 是调试 Win d o w s 操作系统下的各种软件的 一 个强 有力工 具 。 我 几乎 每天都使 用 w in D bg , 它是我 的计算机 中使用频率最 高的软件之一。

最后 , 简要地描述一下软件调试技 术的几个特征。

系统性— 很多看似简单的调试机制 都是依靠系统 内的 多个部件协 同工作 而完 成的 。 以软件断点为例 , C P U 提供了 指令支持和硬 件级的异常机制 , 操 作系统将异常以调试事件的形式分发 给调试 器 , 调试器响应调试事件并与用户交互。 如果在做源 代码级的调试 , 那 么调试 器 又需要编译器所产生的调试符号来帮忙 。

全局性— 对于一 个软件项 目 , 应该 在项 目的设计 和架构阶段就制 定出全 局的调试支持机制 , 并贯彻实施。 比如 , 所有模块都应 该使用统一的 方法 来输出调试信息、记录 日志 、 报告错误 , 并公开 统一的 接 口 用做单元测 试和 故障诊 断 。 这样不仅可以避免重复工作 , 而且增加T 软件的 可调适性 (d e bu g g a bi一 i ty ) , 有利 于保证产品的质量和进度。

困难性— 《C 语言编程》 一书的作者 B r ia n K e r n ig ha n 曾经说过 , “ 调试天生就 比编写代码难上一倍 , 如果你 写出 了最聪明的代码 , 那 么你的智商就不足以 调试这个代码。 ” 因为 , 要调试一个程序 ,就必 须深刻理解它的 工 作原理 , 不仅要知道 h o w 和表 层的东西 , 还要知道w h y 和 深层次的 内幕。 另外 , 调试需要锲而不 舍的探索精神和坚韧的耐 力 , 这 也让很 多 人望而却步 。


综上所述 , 软件调试技术是与软 件 开发 密不可分的一门技 术 , 其初衷是 为了定位和 去除软件故障 , 但 因 为调试技术所具有 的对软件的 强大控制 力和观察 力 , 其应 用早已 延伸到 了很 多其它领域 , 比如逆向 工程 、 计算机安 全等等。 学习和 灵活运用软件 调试技术 , 不仅可以提 高程序 员的 工作效率 , 而且有 利于提升 对代码的感 知力和控制 力 , 加 深对软件和系统的理解。 此 外 , 调试技术 是解决各 种软件 难题 的一 种有效 武器 。 它直击要害 、 锐不可档 , 相对其 它间接方 法具有明显的优势 。 软件有大美 , 调试 见真功。 在寻找银 弹的 努力还 在继续的时候 , 衷心地 希望所有程序员朋友都学 会使 用调试这把利 剑吧 , 使用它为你披荆斩棘 , 帮你探索前进。 只要你的这把剑依然 锋利 , 那你的软 件青春就永远不老 。

如果您使用了WinDBG源代码调试功能,您可能会发现,每当您在调试中遇到一个与尚未打开的WinDBG文件中的源代码相关的点时,都会打开另一个源代码窗口,这很烦人。这导致了大量的源窗口和一组不断重新排列的窗口。

如果您进入View->Options,并将“打开此多个后重用”选项设置为1,而不是默认值(0):

 

然后你会发现它总是重用同一个源窗口来显示当前相关的源代码。
这意味着您可以将源窗口拖到WinDBG主窗口之外的某个位置(例如,拖到另一个监视器上),每次打开一个新的源文件时,它都会在那里打开,而不会扰乱WinDBG主区域中窗口的排列。

 

在硬 件可靠性 理 论 中 , 典 型 的失效 率 又( t ) 函 数是 一条 形 似“ 浴 盆” 的曲线 。 浴 盆 曲线 由三 段 构成 : 早 期故 障期 、 偶 然故 障期 、 耗损 故 障期 。 这种 曲线描 述 了事物 生命期 的 整个过 程 , 而 且具 有普遍 性 。 对 软件来 说 , 在调 试排错 阶 段 , 故 障易 被发现 , 也 易于 纠正 , 而且 在纠 正 中引 人新 故 障的概率 较小 , 因此 , 在 此 阶段 , 软件 失效率 是递 减 的 ; 当软件 中存在 的残 留错误 数减 少 到一 定数 目后 , 由于 故 障发 现率 较 低 , 故 障机 理 较复 杂 , 再 发现 的错 误 一 般就 难 以 得 到 纠 正 , 甚至 在纠正 过 程 中又 引人新 的错误 , 从 而使 得 软件 在运 行 阶 段失 效 率基 本保 持 不变 ; 在 软件 生存 的后期 , 由于 软件 应用 范 围 的扩 大 , 或 用 户对 软件 功 能 提 出更 新 的要求 , 从 而 导致 软件故 障增多 , 而 对其 纠 正越来 越 困 难 , 很 易引入新故 障 , 这样 使 得软件 失效 率呈 递增趋 势 , 直到软件寿命 期结 束 。 因 此 , 人 们把 浴盆 曲线 理论运 用到 软件 可 靠性领 域来 , 结合 软件 固 有 特性 , 提 出了许 多评 估软 件 可 靠性 的模 型 , 并 在实 践 中 得到 了广 泛 应用 , 取 得 了 良好 效 果 。
J一M 模型 就是 其 中 的一 种 , 它对 应 于浴 盆 曲 线 的第一 段 , 适 用 于软 件 调 试排 错 阶 段 , 这类 模 型 统称 为软件 出生 模型 ( Born-In模型)。

1、J一 M 模型简介

J 一M 模型 是 由 2 . Je lin s ki 和 P . B . M o r a n d a 于 19 7 2 年提 出的一种 确定 性模 型 , 简 称 J一 M 模型 , 用来描述 软件 错误 的检 测过 程 。 根据软件 特性 , 在 B o r n 一 n I 模 型 中规 定 了 四 条基本 假设 :

  • 假 设 1 : 错误 纠正 时 间忽略 不计 ;
  • 假 设 2 : 纠正 所发 现 的错误 不会 引人新 的错误 ;
  • 假 设 3 : 两次 失效 时 间间隔相 互独 立 ;
  • 假设 4 : 两次 失效 之间 的失效 率是 与时 间 t 无 关的 常数 。

J一M 模 型 除 上述 四 条基 本假 设外 , 还增 加两 条假设 :

  • 假 设 5 : 软件 失效 率与 当前 的软 件 残 留错 误数 成正 比 ;
  • 假 设 6 : 所有 残 留错 误 的错 误尺 寸 视为相 同 。 所 谓错误 尺寸 , 是指 因某 一软 件错误而 导致该 软 件失 效 的概 率 。

由上 述假设 可 得到 J一M 模 型 的失 效率 曲线 如 图  所示 :

 

失 效率 和 可靠 度 函 数 可 表示 为 :

 

 

式 中 , N 表 示 开始 时 软 件 存 在 的 错 误 总 数 ; ∮表 示错 误 尺 寸 , 由假设 6 知∮是 个 常数 ; λ表示第( i一 1 ) 次 失 效 至 第 i 次 失 效 为止 , 这 段时 间 的失效 率 ; Ri ( t )表 示第( i 一 l ) 次失 效 至第 i 次失 效 为止 , 这段 时 间的软 件可靠性 函 数 。
式 中的 N 、 ∮必两参 数可 由极 大 似然法 估计 出来 。 如果 以 X i ( i=1 , 2 , … , n ) 表示被 观察 到 的一 系 列失 效 间 隔时 间 , 则 可 求 得第 n 次失 效后 的软件 可 靠性 函 数 估 计 , 以 及到 第 n + 1 次 失效 发生 的平均 间隔 时间 的估 计 。

2、 J一 M 模型分 析

J一M 模 型 的准确性 主要 取 决于 假设 的有效 性 。 在 软件 的调试排 错 阶段 , B o r n 一玩 模 型 的 四 个基 本假 设 是 比 较 合理 的 , 而 对于 J一M 模 型 所 附 加 的 两条 假设 , 在 软 件 工 程实 践 中具 有 一 定的缺 陷 。 首先 , 软件 的失效 率不 仅仅 取决 于 当前残 留错 误数 。 例如 , 一个 仅含 有 两个不 常碰到 的 错 误 ( 即错 误 尺 寸非 常小 , 如 0 5 死锁 等 ) 的 软件 5 1 , 另 一个 仅 含有 一个 经 常 出现 的错 误 ( 即 错误尺 寸较大 , 如 下标动 态超 界等 ) 的软件 5 2 。 显而 易见 S , 比 S : 可靠性 高 。 这样 , 假 设 5 就 不 成 立 。 其次 , 根 据 定义 , 错 误尺 寸是 某一 错误 导致 软件 失效 的概率 , 即对 软件 失效 率 的贡 献率 。 很 明显 , 错误 尺 寸越大 , 就越 容易 造成 软件 运行 失效 , 就越 容易被 发现 ; 反 之 , 错 误尺 寸越小 , 就越难 被发现 。 从 另一 角度说 , 越 先 发现 的错误 , 其错 误尺 寸越大 ; 而越 是 后发 现的错 误 , 其 错 误尺 寸越小 。 也 就是说 , 不 同的 软件 错误 , 其错 误尺 寸是 不相等 的 , 而是 随着被 发现 的顺 序 呈 递减 序列 。 这样 , 假设 6 也 不成 立 。 综 上 所述 , J一 M 模型 所 附加 的 两 条 假 设 , 对于 某 些 软 件 工程不 适 用 , 不 能很 好地 反映 软 件 的客观状 况 。 此外 , 适 用于 软 件 调试 阶段 的其它 出生模 型 , 如 B a y e S 模 型 、 Sh o o m a n 模 型 、 非 出错计数 模 型 等 , 也都存 在 类似 的 问题 , 为此 , 应 予改 进 。

3、新的评估模型

软件 的失效 率不 仅与其 残 留错误 数 有关 , 而 且与每 个错 误 的错 误尺 寸有 关 。 如 果软件 残设 : 开始 时软件 的残 留错误 数 为 N , 其 相 应 的错 误尺 寸 为 ∮j( j= 1, 2 , … , N ) , 则 失 效 率与可靠性 函 数 由下 式表 示 :

 

 

由于 错误 尺 寸随 着发现 的先后 次 序呈 递减 序列 , 因此 : ∮j>∮j+1; 。 如果 直接 用 ( 2 ) 式 进行分 析计算 , 比较 困难 。 为减化计算 , 根据 ( 3 ) 式 引人假 设 7;

假设 7 : 第 i ( + l ) 个 错 误 与 第 i 个 错 误 的 错 误 尺 寸 之 比 为 常 数 。 即 : ∮j+1=K*∮j( 0 < K < 1 )。
令 笋 ∮j=∮1 ,则,∮j=kj-1* ∮这样 ( 2 ) 式变 为 :

 

 

 

 

 

 

4、软件故障数据 的收集与预处理

软件 测试 数据 是 软件 可靠性 评估 的基础 , 在软 件调 试排错 阶段 , 开 发人 员应详细记 录每 次 软件故 障 间隔 时间 , 保证 数据 的客 观性 和 准确性 。 对 收集 的数据 在用 于评估 前 , 应进 行初 步处 理 。 对重 复 出现 的故 障 , 应将 前 几 次故 障数据 剔除 , 保 留最后一 次真 正 消除软 件故 障的 那个数 据 , 即如 果第 i 次 出现 的故 障剔 除 不成 功 , 应将 x ` 删 除 , 同时 将 x 、 ,数 据 赋 予 x 、 , 即 : x 、 一` + , 一 t、 , 以 此 类 推 。 另外 , 如果 有 多个 程 序模 块 , 则 各模 块 的故 障数 据应 分别 记 录 , 不 要 混 淆 , 同一程 序模 块 的不 同版本 的软 件故 障数 据也 不要混 淆 。 收集到 一定 数量 的数 据后 , 就 可 以 进行 数据 分析 , 代 入评估模型 进 行评估 。
5 结束语
过 去 由于缺 少切 合 软件 开发 实际 的 软件 可靠 性评估 模 型 , 在 系统 工程 产 品 的可 靠性 分 析 和评估 中 , 只 对 硬件进 行 , 而将软 件 可靠 度视 为 1 , 即不 考虑软件 可靠 性 问题 。 事 实上 随着 硬件 可靠性 的不断 提高 , 软件 可靠性 显 得 日益突 出 。 而 且一 个软件 模块 , 开发 人员 调试到 什 么 程 度就 可结 束调 试排错 , 也 就是 如何 确定 软件 调试周 期也 是个 棘手 的问题 。 本文 正是基 于 这 种 情况 , 结 合 系统 工程 实 际 , 对 软件 调试 排错 阶段 的可 靠性 评估 模 型 进 行探 索 , 使 得软 件 开发 人员在 软件 调试 阶段 收集 n 个 数据 后 , 经 过 预处理 , 运 用本 文提供 的评估 模型 计算得 到 该 软件 残 留错 误总数 及下 一个 软件故 障出现 的平 均时 间 。 排除第 n 个软 件错 误后 , 也可 以估 计 出该软件 的平 均无 故障 时 间及 排 除剩 余软件 错 误还需 工作 的时 间 。 这 对于 开展 软件可 靠 性 增 长试验 , 合理 确定 调试 周期 , 调整 软件 开发 计划等有 较大 作用 。




有时在调试时,您希望知道某个特定进程是否使用 /LARGEADDRESSAWARE标志链接,从而能够使用高于2Gb边界的用户模式地址。我的解决方案是:
!address -summary将显示进程的有效用户模式地址空间:

0:022> !address –summary
..
       Tot: 7fff0000 (2097088 KB) Busy: 278fd000 (648180 KB) <<< 2Gb for non-large-address-aware EXE or large address aware EXE on x86 system without /3Gb in boot.ini


或者


Tot: bd7f0000 (3104704 KB) Busy: 23dee000 (587704 KB) <<< 3Gb for large-address-aware EXE on x86 system with /3Gb in boot.ini


或则


Tot: ffff0000 (4194240 KB) Busy: 268b2000 (631496 KB) <<< 4Gb for large-address-aware EXE running with WoW64 on x64 system


但是,由于第一种情况不明确,要实际查看EXE是否与/LargeAddressware链接,请执行以下操作:


    0:000> !dlls -c inetinfo    <<< inetinfo is the module name of the EXE in this case]
    Dump dll containing 0x01000000:

    0x00081eb0: C:WINDOWSsystem32inetsrvinetinfo.exe
          Base   0x01000000  EntryPoint  0x0100326e  Size        0x00006000
          Flags  0x00004000  LoadCount   0x0000ffff  TlsIndex    0x00000000
                 LDRP_ENTRY_PROCESSED
    0:000> .shell -i - -ci "!dlls -f 0x00081eb0" FIND "characteristics"
         12F characteristics
特征字段是:0x12f&0x20==0x20。这是IMAGE_FILE_LARGE_ADDRESS_AWARE的值。–有关此定义和相关定义,请参阅平台SDK中的winnt.h。
所以这个EXE可以识别大地址。
注意,上面使用的.shell命令(用于shell到另一个EXE,在本例中是“FIND”)是我一直用来过滤调试器命令输出的内容。非常方便。

Diomidis Spinellis是《代码阅读与代码质量》一书的作者。在GOTO阿姆斯特丹2016大会上,他就如何有效地调试软件和预防错误做了演讲。InfoQ采访了Spinellis,内容涉及发现和修复软件中的错误、软件调试的原则、如何提高调试效率、如何编写出不怎么需要调试的代码以及管理人员如何为错误预防和处理提供支持。

InfoQ:是什么让软件错误的发现和修复如此困难?

Diomidis Spinellis:如果你认为编写代码困难,那就尝试下代码调试吧。你编写了一个新的函数或方法,并加上一些语句形成某个只是貌似正确的东西。当你调试一个程序的时候,你要面对数千或数百万行貌似正确的代码,并设法找出其中的错误。这必定要困难许多。然后,你还要应对各种系统和层次之间的复杂交互、每秒执行数十亿次的CPU指令、难以再现的Bug以及来自生产环境系统的压力。

而且,课堂上很少教调试;从一切可能出错的东西辛苦得来的经验很难压缩到一次演讲中。此外,由于系统失败的方式各不相同,你必须不断地改进和调整你所用的工具和方法。你可以从观察开始,继之以数据分析,然后做一些试验,最后推断出Bug的原因。没有什么标准的初学者技能。


InfoQ:软件调试有什么一般原则吗?


Spinellis:很遗憾,由于软件会出现各种难以想象的错误情况,我不认为有什么可以在软件调试过程中遵循的一般原则。退而求其次,我归纳出了三大类方法:

高级策略,比如由故障特征推断出原因,或者确保某些代码满足了其前提条件;

方法和实践,比如确保Bug可以有效地再现,或者着重突出故障的影响;

通用工具,比如Unix命令行工具、跟踪(考虑下strace、dtrace和systemtap)工具和版本控制系统。


InfoQ:程序员做什么能够提高调试效率?


Spinellis:首先要为调试成功做好准备。让自己相信问题将会得到解决,留出足够的时间用于调试,不要分心,要坚持不懈,必要的时候,留待第二天解决。重要的是,要不断地在环境、工具和知识上投入。购买高效工作所需的软件和硬件。例如,如果软件生成大量的调试日志文件,你就应该有足够的磁盘空间、CPU处理能力和带宽,以便可以高效地处理它们。在调试的过程中,你很容易遇到千奇百怪的问题,因此,花些精力管理和优化自己的环境和工具配置。这包括按键绑定、别名、辅助脚本、快捷方式和工具配置;所有这些都可以显著地提高调试生产力。


InfoQ:有什么技术或方法可以编写出不怎么需要调试的代码吗?


Spinellis:当然!编写可维护的代码——可读、稳定、易于分析和修改的代码——带来的Bug也比较少。此外,像单元测试、代码审查这样的方法以及使用断言都有助于最小化进入生产环境的错误。

在设计时使用高级抽象(例如使用一个框架的算法或容器数据结构,而不是选择自己开发一种方案)可以减少代码和错误。另外,让程序易于调试也很重要。这包括为详细地记录日志提供便利,当出现内部错误时报告丰富的上下文信息,并将问题及崩溃的详细信息发送到一个中央存储库。


InfoQ:管理人员如何为组织里的错误预防和处理提供支持?


Spinellis:设定基本的过程有助于确保软件错误不会失控。部署并采用一个问题跟踪系统,用它把要处理的问题分类并排定优先级。将软件变更恰当地记录在进一个运作良好的版本管理系统里,并将它与问题跟踪系统联系起来;我经常仅仅通过仔细研究一个文件的历史和变更就修复了Bug。在软件建设方面,推广单元测试的应用,把软件的构建性能分析、静态分析和动态分析包含进来,并维护一个快速、精益、均衡的构建-测试周期。这有助于帮助开发人员尽早捕获Bug,并迅速修复。最后,在运维方面,逐步部署软件,允许新旧版本对比,努力确保所使用的工具和所部署的环境的多样性,并有组织地升级工具和库。

转自:http://lf.lnu.edu.cn/detail.jsp?id=55243