微软技术

大规模调试
十年实现与经验

Windows 错误报告 (WER) 系统是一个分布式系统,自动化处理来自十亿台机器的错误报告,十年来已收集数十亿份错误报告。

Windows 错误报告
调试革命性工具

作者: Kirk Glerum, Kinshuman Kinshumann, Steve Greenberg 等

机构: Microsoft Corporation

10+
运行年限
10亿+
设备覆盖
数十亿
错误报告

系统概述

Windows 错误报告 (WER) 是一个分布式系统,用于自动化处理来自十亿台计算机的错误报告。它收集错误数据并将错误分类到"桶"中,以帮助开发人员优先处理最重要的问题。WER 使用渐进式数据收集策略,最小化大多数报告的开销,但在需要时允许开发人员收集详细信息。

WER 通过"自动错误诊断"、"渐进式数据收集"和"基于统计的调试"三个关键原则赢得了广泛采用。这些原则使大规模错误处理成为可能,并帮助程序员更有效地提高系统质量。

"数据,而非分贝。"

— WER 团队早期格言

WER 系统四大独特优势

  • 规模最大的错误报告系统

    约十亿台计算机运行 WER 客户端代码,涵盖自 Windows XP 以来的所有 Windows 系统

  • 自动化额外数据收集

    当初始错误报告提供的数据不足以调试问题时,WER 可以自动收集额外的客户端数据

  • 自动引导用户解决方案

    WER 自动引导用户获取已修正错误的解决方案,有效解决用户常常运行过时程序的问题

  • 通用性

    WER 被用于操作系统和应用程序,支持各类错误报告:崩溃、非致命断言失败、挂起、安装失败、异常执行和设备失败

关键特性与原则

错误桶分类

WER 将可能源自同一 bug 的错误报告聚合到称为"桶"的集合中。理想的桶分类算法应严格维持正交性:每个桶一个 bug,每个 bug 一个桶。

通过两阶段桶分类实现:首先在客户端进行标记,然后在 WER 服务中进行分类,随着收集到更多数据,错误报告可能会被重新分类。

渐进式数据收集

WER 采用渐进式数据收集策略,减少错误报告的成本,使系统能够扩展到高容量,同时提供足够的调试详细信息。

大多数错误报告仅包含简单的桶标识符。如果需要更多数据,WER 会收集迷你转储,进一步需要时,可收集完整内存转储和其他相关数据。

最小化人工交互

WER 通过自动化错误诊断,将用户从错误报告的所有步骤中移除,只保留授权步骤。

用户交互在大多数情况下减少为简单的是/否授权。用户可以永久选择加入或退出未来的授权请求,管理员可以应用组织策略。

保护用户隐私

WER 非常注重避免收集个人身份信息,减少监管负担并鼓励用户参与。

WER 客户端代码会清零序列号和其他已知的唯一标识符,错误报告仅在用户同意的情况下发送,所有同意请求默认为否定,要求用户明确选择加入。

为用户提供解决方案

许多错误有已知的修正方法。WER 服务维护从桶到解决方案的映射关系。

解决方案是网页 URL,描述用户应采取的步骤以防止错误再次发生。解决方案 URL 可以将用户链接到特定问题的补丁页面、最新版本的更新站点或描述解决方法的文档。

基于统计的调试

WER 将所有错误报告记录到一个中央数据库中,程序员可以挖掘 WER 数据库,比单一的、非结构化的错误报告流更有效地改进调试过程。

WER 数据可用于优先排序调试工作、发现隐藏的根本原因、测试根本原因假设、测量解决方案部署情况以及监控回归。

系统架构

WER 是一个分布式系统。客户端软件检测错误情况,生成错误报告,标记桶,并将错误报告给 WER 服务。WER 服务记录错误发生情况,然后根据特定错误的已知信息,可能会要求客户端提供额外数据,或引导客户端获取解决方案。

WER 系统架构图

graph TB A[Windows 客户端] -->|1. 错误发生| B[WER 客户端] B -->|2. 收集错误数据| B B -->|3. 桶标记| B B -->|4. 错误报告| C[前端 IIS 服务器] C -->|5. 保存桶参数| D[主 SQL 服务器] C -->|6. 保存 CAB 文件| E[SAN 存储] D -->|7. 数据复制| F[分发 SQL 服务器] F -->|8. 数据复制| G[查询 SQL 服务器] C -->|9. 处理错误| H[在线作业服务器] C -->|10. 处理错误| I[离线作业服务器] H -->|11. 访问符号| J[符号服务器] I -->|12. 访问符号| J G -->|13. 访问| K[门户网站] style A fill:#f9f9f9,stroke:#333,stroke-width:1px style B fill:#f9f9f9,stroke:#333,stroke-width:1px style C fill:#dbeafe,stroke:#2563eb,stroke-width:2px style D fill:#dbeafe,stroke:#2563eb,stroke-width:2px style E fill:#dbeafe,stroke:#2563eb,stroke-width:2px style F fill:#dbeafe,stroke:#2563eb,stroke-width:2px style G fill:#dbeafe,stroke:#2563eb,stroke-width:2px style H fill:#dbeafe,stroke:#2563eb,stroke-width:2px style I fill:#dbeafe,stroke:#2563eb,stroke-width:2px style J fill:#dbeafe,stroke:#2563eb,stroke-width:2px style K fill:#dbeafe,stroke:#2563eb,stroke-width:2px

错误报告生成

错误报告是响应操作系统可见事件(如崩溃、挂起和安装失败)创建的,或由应用程序直接调用一组用于创建和提交错误报告的 API 创建的。

一旦触发报告,werfault.exe 服务负责获取用户授权并将错误报告提交给 WER 服务器。如果计算机未连接到互联网,报告会立即提交或排队等待提交。

通信协议

WER 客户端通过四阶段协议与 WER 服务通信,该协议最小化 WER 服务器的负载和提交完整错误报告的客户端数量:

  1. 客户端发出未加密的 HTTP/GET 请求,报告桶标签并确定 WER 服务是否需要额外数据
  2. 客户端发出加密的 HTTPS/GET 请求,确定 WER 服务所需的数据
  3. 客户端通过加密的 HTTPS/PUT 请求将请求的数据(压缩的 CAB 文件)推送到服务
  4. 客户端发出加密的 HTTPS/GET 请求,表示完成并请求任何已知的错误解决方案

WER 服务

WER 服务由大约 60 台服务器组成,连接到一个 65TB 的存储区域网络(用于存储错误报告数据库)和一个 120TB 的存储区域网络(用于存储长达 6 个月的原始 CAB 文件)。

20+
前端 IIS 服务器
23
作业服务器
1亿+
每日处理能力

获取额外数据

虽然许多错误可以通过简单的内存转储进行调试,但有些错误无法通过这种方式调试。使用 WER 门户,程序员可以创建"数据需求"请求。

数据请求记录在主 SQL 服务器中,静态页面被删除。在随后的错误报告中,WER 服务将要求客户端收集所需的数据并提交。程序员可以请求的额外数据包括完整的进程内存转储、与进程相关的内核内存的实时转储、等待链中其他进程的转储、命名文件、命名注册表键和 WMI 查询输出。

桶分类算法

WER 最重要的元素之一是其将错误报告分配到桶的机制。这是通过一组启发式方法实现的。概念上,WER 桶分类启发式可以沿两个轴划分。

客户端桶分类(标记)

当生成错误报告时,第一个桶分类启发式在客户端运行。这些标记启发式的主要目标是基于本地信息产生一个独特的桶标签,该标签可能与同一 bug 导致的其他报告对齐。

启发式 影响 描述
program_name 扩展 在桶标签中包含程序名称
module_name 扩展 在标签中包含故障模块名称
module_offset 扩展 包含崩溃指令在故障模块中的偏移量
exception_code 扩展 未处理异常的原因
pc_on_stack 压缩 代码在堆栈上运行,移除模块偏移量
assert_tags 压缩 用唯一的代码内断言 ID 替换模块信息

服务器端桶分类(分类)

服务器端桶分类的启发式方法尝试分类错误报告,以最大化程序员的效率。它们被编入 !analyze("bang analyze"),这是 Windows 调试器的扩展。

启发式 影响 描述
find_correct_stack 扩展 遍历数据结构查找陷阱帧等结构到堆栈
skip_core_modules 扩展 降低内核代码或核心 OS 用户模式代码的优先级
function_name 压缩 用函数名替换模块偏移量
one_bit_corrupt 压缩 转储中的代码与存档副本相比有单比特错误
malware_identified 压缩 包含已知恶意软件
heap_corruption 压缩 堆函数失败,堆已损坏

扩展和压缩启发式应该是互补的,而不是相互冲突的。扩展启发式不应该为同一 bug 引入新桶。压缩启发式不应该将两个 bug 放入一个桶。正确配合工作时,扩展和压缩启发式应该使 WER 向着每个 bug 一个桶的理想目标迈进。

十年发展历程

1999年:WER 诞生

Windows 团队和 Office 团队合作创建了 Windows 错误报告系统。Windows 团队开发了一个可以自动诊断系统崩溃核心转储的工具,而 Office 团队开发了在未处理异常时自动收集堆栈跟踪的工具。

这两个团队意识到可以将 Windows 团队的自动诊断工具与 Office 团队的自动收集工具结合起来,创建一个新服务——Windows 错误报告 (WER)。

2001年:首个百万错误报告

WER 在部署 8 个月内收集了第一百万份错误报告,标志着系统开始得到广泛使用。团队采用"数据,而非分贝"的格言,强调使用来自 WER 的数据来优先处理调试工作,修复影响最多用户的 bug,而不仅仅是来自最大声客户的 bug。

2004年:Windows XP SP2

Windows XP Service Pack 2 引入了几项改进:

  • 修改了迷你转储生成代码,明确添加池损坏页面
  • 在迷你转储中添加了系统管理 BIOS 表的大部分内容,包括 OEM 型号名称和 BIOS 版本
  • 添加了信息以帮助识别报告是否来自超频 CPU

微软团队决定禁用帧指针省略 (FPO),尽管最初颇具争议,但广泛的内部测试表明 FPO 没有提供统计上可证明的好处,但显著阻碍了非预期调用堆栈的事后调试。

2006年:Windows Vista

Vista 对内核崩溃报告进行了两项增强:

  • Vista 在崩溃时创建完整的内核转储,在重启后从内核转储中提取并报告迷你转储
  • 增加了次要数据的大小限制,从 XP 的 32KB 增加到 Vista 的 128KB,在 SP2 中进一步增加到 1MB

Windows Vista 程序员使用 WER 在 Vista 测试版本中发现并修复了 5,000 多个 bug。这些 bug 是在程序员使用静态分析和模型检查工具找到并修复了 10 万多个 bug 之后,但在 Vista 正式发布之前发现的。

2007年:处理 Renos 恶意软件

2007 年 2 月,Windows Vista 用户受到 Renos 恶意软件的攻击。安装在客户端后,Renos 导致 Windows GUI shell(explorer.exe)在尝试绘制桌面时崩溃。

用户遇到的 Renos 感染是一个连续循环:shell 启动、崩溃和重启。虽然 Renos 感染的系统对用户来说毫无用处,但系统启动得足够远,可以向 WER 报告错误并从 Windows Update 接收更新。

2 月 27 日,Microsoft 通过 Windows Update 发布了针对 Renos 感染的 Windows Defender 签名。三天内,足够多的系统接收到新签名,使报告数量降至每天 10 万以下。到 3 月底,原始 Renos 变种的报告变得微不足道。

2009年:扩展至第三方开发者

到 2009 年,超过 700 家第三方公司开始使用 WER,注册了近 7,000 个程序和近 30 万个模块。175 家提供商注册了解决方案,以指导用户找到 bug 修复。

一些软件提供商开始更主动地使用 WER。例如,2007 年 5 月,一家内核模式提供商首次开始使用 WER。30 天内,该提供商解决了其代码的前 20 个报告问题。5 个月内,随着 WER 引导用户获取修复程序,归因于该提供商的所有内核崩溃百分比从 7.6% 降至 3.8%。

影响与效果

基于统计的调试

WER 最重要的特性可能是基于统计的调试。WER 将所有错误报告的数据记录到一个单一数据库中。程序员可以挖掘 WER 数据库,比单纯的、非结构化的错误报告流更有效地改进调试。

基于统计的调试策略可分为五类:优先排序调试工作、发现隐藏的原因、测试根本原因假设、测量解决方案部署情况和监控回归。

错误报告分布

我们在许多应用程序和操作系统发布版本中的经验表明,错误报告遵循帕累托分布,少数 bug 占据了大多数错误报告。

程序 第一四分位 第二四分位 第三四分位 第四四分位
Excel 0.227% 1.690% 9.80% 88.4%
Outlook 0.058% 0.519% 6.31% 93.1%
PowerPoint 0.106% 0.493% 7.99% 91.4%
Word 0.057% 0.268% 7.95% 91.7%

表格展示了按错误报告四分位数的桶百分比。一小部分桶收到了大多数错误报告。

桶分类效率

理想的桶分类算法应将所有由一个 bug 引起的错误报告映射到一个唯一的桶中,不包含其他 bug。我们知道 WER 的桶分类启发式中存在两种形式的弱点:

  • 压缩启发式中的弱点,导致将来自一个 bug 的报告映射到太多桶中
  • 扩展启发式中的弱点,导致将多个 bug 映射到同一个桶中

找到隐藏原因

WER 数据库可用于查找在内存转储中不是立即明显的根本原因。例如,当桶分类将责任指向信誉良好的代码时,我们会搜索错误报告以寻找替代解释。

一种有效的策略是寻找错误与看似无辜的第三方之间的相关性。在许多情况下,我们发现第三方设备驱动程序或其他插件在错误报告中出现的频率高于一般群体。

测量解决方案部署

WER 数据库的一个最近用途是确定软件更新的部署范围。部署可以通过缺失来测量,即测量软件更新修复的错误报告的减少情况。

部署也可以通过其他问题的错误报告中新程序或模块版本的增加存在来测量。通过传递解决方案,WER 帮助提高了软件质量并改善了用户体验。

关键术语表

错误 (Error)

程序行为与程序员预期不同的单一事件。

Bug

程序代码中导致一个或多个错误的根本原因。

桶 (Bucket)

可能由同一 bug 引起的错误报告的集合。

桶分类 (Bucketing)

将错误报告分类到桶中的过程。

迷你转储 (Minidump)

一种缩写的堆栈和内存转储,包含故障系统的配置信息。

数据需求 (Data Wanted)

程序员通过 WER 门户创建的请求,用于获取特定错误的额外信息。

解决方案 (Solution)

描述用户应采取的步骤以防止错误再次发生的网页 URL。

!analyze

Windows 调试器的扩展,编纂了用于桶分类的启发式方法。

延伸阅读

Why Programs Fail: A Guide to Systematic Debugging

Andreas Zeller

这本书详细介绍了系统化调试的原则和技术,包括如何自动分析程序失败,如何通过实验找到故障原因,以及如何应用科学方法构建调试工具。与 WER 系统的设计理念高度一致。

查看更多

Automating Software Failure Reporting

Brendan Murphy, ACM Queue

这篇文章总结了自动化错误报告系统的历史和动机,以 WER 为例。它解释了为什么这些系统对于现代软件开发如此重要,以及它们如何改变了软件质量管理的方式。

查看更多

Better Bug Reporting With Better Privacy

Miguel Castro, Manuel Costa, Jean-Philippe Martin

这篇论文探讨了如何在自动错误报告系统中改进隐私保护。作者提出使用符号执行系统地用触发错误的条件变量替换内存转储,以减小错误报告的大小并提高匿名性。

查看更多

Systems Performance: Enterprise and the Cloud

Brendan Gregg

这本书介绍了系统性能分析和调优的方法论,包括如何收集和分析系统行为数据。书中的许多概念与 WER 用于大规模调试的统计方法相关,特别是关于如何从大量数据中提取有意义的见解。

查看更多

Bug Isolation via Remote Program Sampling

Ben Liblit, Alex Aiken, Alice X. Zheng, Michael I. Jordan

这篇论文讨论了通过远程收集程序所有执行的数据来解决错误诊断问题的方法。作者展示了如何使用逻辑回归结合样本找到错误的根本原因,以及如何通过统计采样以较小的运行时开销收集样本。

查看更多