一切皆已崩溃:在 Mozilla 发布 rust-minidump – 第 1 部分

一切皆已崩溃:在 Mozilla 发布 rust-minidump

在过去的一年里,我一直领导着rust-minidump的开发,这是一个纯 Rust 语言编写的google-breakpad中用于处理 minidump 的部分的替代方案。

实际上,在某种意义上,我已经完成了这项工作,因为 Mozilla 已经在 6 个月前部署了它作为Firefox 的崩溃处理后端,它的运行速度是以前的一半,并且似乎更加可靠。(而且你知道,不是一个来自互联网的解析和评估任意输入的可怕 C++ 程序包。我们尽力隔离 Breakpad,但仍然……哎呀。)

这是一个非常棒的结果,但总是有更多工作要做,因为Minidumps 是一个无底深渊,你越深入,它就越深…等等,我有点超前了。先有光明,然后是深渊。是的。先有光明。

我可以说的是,我们对 minidump 解析 + 分析的核心功能(针对最大的平台(x86、x64、ARM、ARM64;Windows、MacOS、Linux、Android))有了非常可靠的实现。但是,如果你想读取在PlayStation 3 上生成的 minidump 或处理完整内存转储,你将无法获得良好的服务。

我们投入了大量精力来记录和测试这个东西,所以我对它很有信心!

不幸的是!信心!是!不值!一文!

这就是为什么这是一段关于我们如何尽力使这场噩梦尽可能健壮的故事,但我们仍然被来自太空的突然而难以置信的模糊测试@5225225彻底击垮。

这篇文章分为两部分

  1. 什么是 minidump,以及我们如何制作 rust-minidump
  2. 我们如何被简单的模糊测试完全打败

你现在正在阅读第 1 部分,我们将在这里建立我们的傲慢。

背景:什么是 Minidump,为什么编写 rust-minidump?

你的程序崩溃了。你想要知道你的程序为什么崩溃,但它发生在一个用户机器上,在地球的另一边。完整的核心转储(程序分配的所有内存)非常庞大——我们不能让用户向我们发送 4GB 的文件!好吧,让我们只收集最重要的内存区域,比如堆栈和程序崩溃的位置。哦,我想如果我们有时间,我们也可以在其中添加一些关于系统和进程的元数据。

恭喜你发明了Minidumps。现在你可以将一个原本会占用 4GB 的 100 线程核心转储转换成一个 2MB 的小文件,你可以通过互联网发送它,然后对其进行事后分析。

或者更确切地说,微软发明了它。那是很久以前的事了,他们的文档甚至都没有讨论平台支持。MiniDumpWriteDump 支持的版本仅仅是“Windows”。微软研究院可能已经开发了一台时间机器来保证这一点。

然后谷歌出现了(大约在 2006 年到 2007 年),说“如果我们可以在任何平台上创建 minidump 会不会很好”?幸运的是,微软实际上已经非常扩展地构建了这种格式,因此扩展格式以支持 Linux、MacOS、BSD、Solaris 等等并不太困难。这些扩展成为了google-breakpad(或简称为 Breakpad),其中包含大量用于生成、解析和分析其扩展的 minidump 格式(以及原生微软格式)的不同工具。

Mozilla 在这方面提供了很多帮助,因为显然,我们的崩溃报告基础设施(“Talkback”)在 2007 年左右非常糟糕,而这似乎是一个不错的改进。不用说,我们目前对 breakpad 的 minidump 投入了相当多的精力。

快进到今天,在命运的戏弄下,像 VSCode 这样的产品意味着微软现在支持在 Linux 和 MacOS 上运行的应用程序,因此它在生产环境中运行 breakpad,并且必须在其崩溃报告基础设施中的某个地方处理非微软 minidump,因此他们自己的格式的另一个扩展不知何故变成了他们的问题!

与此同时,谷歌已经转向了Crashpad。我说“有点”,因为其中仍然存在大量的 Breakpad,但他们更感兴趣的是在它之上构建工具,而不是改进 Breakpad 本身。对 Breakpad 进行了一些修改:坦率地说,很公平,我不想再继续使用它了。尽管如此,这对我们来说还是有点问题,因为这意味着这个项目的人员越来越少。

当我开始研究崩溃报告时,Mozilla 基本上已经放弃了向上游提交 Breakpad 的修复程序/改进,只是在使用他们自己的修补过的分支。但是,即使不需要向上游提交补丁,对 Breakpad 的每一次更改都会让我们感到恐惧:我们对崩溃报告基础设施提出的许多改进都停滞不前,原因是“是时候在 Breakpad 中实现它了”。

你可能会问,为什么在 Breakpad 上工作如此痛苦?

解析和分析 minidump 基本上是在进行一项针对平台特定格式嵌套在格式中嵌套在格式中的分形解析器的练习。针对多种操作系统。针对多种硬件架构。而且所有你正在解析和分析的输入都很糟糕且有错误,因此你必须编写一个非常宽松的解析器,并尽可能地向前爬行。

Windows XP 中包含的某些特定 MSVC 工具链在其调试信息格式中存在错误?太可惜了,无论如何都要对那个堆栈帧进行符号化!

程序崩溃是因为它严重破坏了自己的堆栈?太可惜了,无论如何都要生成回溯!

minidump 编写器本身完全崩溃,并向某个流中写入了一堆垃圾?太可惜了,无论如何都要生成你能生成的任何输出!

嘿,你知道谁在处理用 C++ 编写的非常复杂的宽松解析器方面经验丰富吗?Mozilla!这就像一个Web 浏览器核心功能

你知道 Mozilla 在用 C++ 编写非常复杂的宽松解析器方面的秘密解决方案是什么吗?

我们停止了这项工作。

我们开发了 Rust,并将我们最难看的解析器移植到它。

我们已经做了很多次,而且当我们做的时候,我们总是会说“哇,这可靠多了,维护起来也容易多了,而且现在速度更快了”。Rust 是一种非常适合编写解析器的语言。C++ 真的不适合。

所以我们用 Rust 重写了它(或者正如孩子们所说,“氧化了它”)。Breakpad 很大,所以我们并没有真正涵盖它的所有功能。我们专门编写和部署了

  • dump_syms,它将原生构建工件处理成符号文件。
  • rust-minidump,它是一组解析和分析 minidump 的板条箱。或者更确切地说,我们部署了minidump-stackwalk,它是所有 rust-minidump 的高级 CLI 接口。

值得注意的是,图片中缺少minidump 编写,或者 google-breakpad 所谓的客户端(因为它运行在客户端的机器上)。我们正在开发一个基于 Rust 的 minidump 编写器,但我们目前还不能推荐使用它(尽管它在Embark Studios的帮助下速度已经提高了很多)。

这可以说是最混乱、最难的工作,因为它需要完成一项糟糕的工作:使用一堆原生系统 API 收集有关崩溃的各种特定于操作系统和特定于硬件的信息,并且要在导致程序崩溃的机器上针对刚崩溃的程序执行操作。

我们还有很长的路要走,但每当我们完成其中一个项目时,它都会让人感到很棒

 

背景:堆栈回溯和调用约定

rust-minidump(minidump-stackwalk)最重要的工作之一是获取线程的状态(通用寄存器和堆栈内存),并为该线程创建一个回溯(展开/堆栈回溯)。这是一项令人惊讶地复杂且混乱的工作,更复杂的是,我们试图分析一个已经崩溃的进程的内存

这意味着我们的堆栈回溯器天生就在处理可疑数据,我们所有的堆栈回溯技术都基于可能会出错的启发式方法,我们很容易发现自己处于堆栈回溯向后或向侧面或无限循环的情况,而我们只能尽力处理它!

堆栈回溯器开始产生幻觉也是很常见的,这是我用来描述“堆栈回溯器发现了一些看似合理的东西,并开始沿着堆栈进行疯狂的冒险,编造了一堆无用的垃圾帧”的术语。幻觉最常发生在堆栈的底部附近,那里也是最不具冒犯性的。这是因为你回溯的每一帧都意味着出错的机会增加,但也越来越不令人感兴趣,因为你很少想确认一个线程是从所有线程都启动的同一个函数开始的。

如果每个人都同意正确地保留他们 CPU 的完全正常专用的帧指针寄存器,那么所有这些问题基本上都会消失。开玩笑,启用帧指针也行不通,因为微软发明了混乱的帧指针,这些帧指针无法用于展开!我假设这是因为他们在穿越时空发明 minidump 时,不小心踩到了错误的蝴蝶。(我确信这是一个 20 年前更有意义的决定,但它并没有很好地过时。)

如果你想了解更多关于解压缩的不同技术,我在我的这篇文章里写了一些,这是我写的关于苹果的 Compact Unwind Info的文章。我还尝试了记录breakpad的 STACK WIN 和 STACK CFI 解压缩信息格式,它们更类似于 DWARF 和 PE32 解压缩表(它们基本上是微型编程语言)。

如果你想更多了解ABI,我在这篇文章中写了关于它们的所有内容。文章的结尾也包含了关于调用约定如何工作的介绍。了解调用约定是实现解压缩器的关键。

 

你到底测试了多努力?

希望你现在对为什么分析小型转储是一个巨大的难题有所了解。当然,你也知道故事的结局:模糊测试器让我们被打败了!但为了真正品味我们的失败,你必须看看我们为了做好工作付出了多少努力!现在是时候增强我们的自负,拍拍自己的后背了。

那么,在模糊测试器开始工作之前,真正投入到使rust-minidump健壮的工作量是多少呢?

相当多!

我绝不会说我们所做的所有工作都是完美的,但我们确实做了一些不错的工作,无论是针对合成输入还是真实世界的输入。可能我们方法论中最大的“缺陷”是,我们只专注于让 Firefox 的用例正常工作。Firefox 在很多平台上运行,并看到了很多混乱的东西,但它仍然是一款相当连贯的产品,只使用了小型转储的某些功能。

这是我们最近与Sentry合作的益处之一,Sentry 基本上是一家“崩溃报告即服务”公司。它们更有可能对格式的各种奇怪角落进行压力测试,而 Firefox 不会这样做,并且它们确实发现了一些错误或缺失的地方,并对其进行了修复!(而且它们最近也将其部署到生产环境中!🎉)

但是嘿,不要相信我的话,看看我们做了哪些不同的测试。

用于单元测试的合成小型转储

rust-minidump 包含一个合成小型转储生成器,它允许你提出小型转储内容的高级描述,然后生成一个实际的小型转储二进制文件,我们可以将其馈送到完整的解析器中。

// 让我们使用特定的 Crashpad Info 创建一个合成小型转储...

let module = ModuleCrashpadInfo::new(42, Endian::Little)
    .add_list_annotation("annotation")
    .add_simple_annotation("simple", "module")
    .add_annotation_object("string", AnnotationValue::String("value".to_owned()))
    .add_annotation_object("invalid", AnnotationValue::Invalid)
    .add_annotation_object("custom", AnnotationValue::Custom(0x8001, vec![42]));

let crashpad_info = CrashpadInfo::new(Endian::Little)
    .add_module(module)
    .add_simple_annotation("simple", "info");

let dump = SynthMinidump::with_endian(Endian::Little).add_crashpad_info(crashpad_info);

// convert the synth minidump to binary and read it like a normal minidump
let dump = read_synth_dump(dump).unwrap();

// 现在检查小型转储是否报告了我们期望的值...

minidump-synth 故意避免与实际实现共享布局代码,这样布局的错误更改就不会“意外地”通过测试。

简要介绍一些历史:这个测试框架是由该项目的最初负责人Ted Mielczarek开始的。他在 1.0 版本发布时,将 rust-minidump 作为学习 Rust 的一个副项目,并且一直没有时间完成它。当时他在 Mozilla 工作,也是 Breakpad 的主要贡献者,这就是为什么 rust-minidump 具有很多类似的设计选择和术语的原因。

这种情况也不例外:我们的 minidump-synth 毫无疑问地复制了breakpad 代码中的 synth-minidump 工具,该工具最初由我们的另一个同事Jim Blandy编写。Jim 是世界上为数不多的、我承认确实写了非常好的测试和文档的人之一,所以我完全乐意在这里公然复制他的作品。

由于这一切都是一个学习实验,因此 Ted 对测试的严格程度明显低于平时。这意味着当我加入时,很多 minidump-synth 尚未实现,这也意味着很多小型转储功能完全没有经过测试。(他构建了一个绝对很棒的骨架,只是没有时间全部填充它!)

我们花费了大量时间来完善 minidump-synth 的实现,以便我们可以编写更多测试并捕获更多问题,但这是我们测试中最薄弱的部分。在我加入之前,已经实现了一些东西,所以我甚至不知道缺少哪些测试!

这是一个关于进行一些代码覆盖率检查的有力论据,但它可能会返回“哇,你应该编写更多测试”,然后我们会看看它,然后说“哇,我们确实应该”,然后我们可能会永远不会完成它,因为我们应该做很多事情。

另一方面,Sentry 在这方面非常有用,因为它们已经拥有成熟的测试套件,里面充满了它们长期积累的奇怪用例,因此它们可以轻松识别真正重要的事情,知道修复方法应该是什么,并且可以贡献现有的测试用例!

集成和快照测试

我们尽最大努力通过添加更多整体测试来弥补单元测试中的覆盖率问题。我们有一些已经签入的真实小型转储,我们对它们有一些集成测试,以确保我们正确处理真实输入。

我们甚至编写了许多针对 CLI 应用程序的集成测试,并对其输出进行快照,以确认我们从未意外地更改结果。

这样做部分原因是为了确保我们不会破坏 JSON 输出,我们还为此编写了一个非常详细的模式文档,并且正在努力使其保持稳定,以便人们可以在实际实现细节仍在不断变化的情况下真正依赖它。

是的,minidump-stackwalk 应该是稳定的,并且可以在生产环境中合理使用!

对于我们的快照测试,我们使用insta,我认为它很棒,更多人应该使用它。你只需要断言任何你想跟踪的输出的快照,它就会神奇地处理存储、加载和差异比较。

这是一个快照测试示例,其中我们调用 CLI 接口并对 stdout 进行快照

#[test]
fn test_evil_json() {
    // For a while this didn't parse right
    let bin = env!("CARGO_BIN_EXE_minidump-stackwalk");
    let output = Command::new(bin)
        .arg("--json")
        .arg("--pretty")
        .arg("--raw-json")
        .arg("../testdata/evil.json")
        .arg("../testdata/test.dmp")
        .arg("../testdata/symbols/")
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .output()
        .unwrap();

    let stdout = String::from_utf8(output.stdout).unwrap();
    let stderr = String::from_utf8(output.stderr).unwrap();

    assert!(output.status.success());
    insta::assert_snapshot!("json-pretty-evil-symbols", stdout);
    assert_eq!(stderr, "");
}

堆栈遍历器单元测试

堆栈遍历器无疑是新实现中最复杂和最微妙的部分,因为每个平台都可能存在细微的怪癖,你需要实现几种不同的解压缩策略,并仔细调整所有内容以在实践中正常工作。

最可怕的部分是调用帧信息 (CFI) 解压缩器,因为它们基本上是我们需要在运行时解析和执行的小型虚拟机。值得庆幸的是,breakpad 很早就通过定义简化和统一的 CFI 格式 STACK CFI(好吧,几乎统一,x86 Windows 仍然是一个特例,称为 STACK WIN)解决了这个问题。因此,即使 DWARF CFI 具有许多复杂的功能,我们也只需要实现一个逆波兰记号计算器,除了它可以读取寄存器并从它计算的地址加载内存(对于 STACK WIN,它可以访问它可以声明和修改的命名变量)。

不幸的是,Breakpad 对这种格式的描述相当不明确,所以我基本上必须选择我认为有意义的一些语义,然后继续使用它们。这让我对实现感到非常焦虑。(是的,在本部分中我将更多地使用第一人称,因为这部分是我个人花了大部分时间并从头开始做了很多事情的地方。这里的所有责任都归我!)

STACK WIN/STACK CFI 解析器+求值器 占 1700 行。其中 500 行是对该格式的详细文档和讨论,而 700 行是一个庞大的测试用例堆,其中我尝试了所有我能想到的角落情况。

我甚至签入了两个我知道会失败的测试,只是为了诚实地承认确实有一些情况需要修复!其中一个涉及除以负数的角落情况,几乎可以肯定的是,这种情况并不重要。另一个是旧的 x86 Microsoft 工具链实际上会生成的错误输入,解析器需要处理。后者在模糊测试开始之前就已修复。

而 5225225 仍然在 STACK WIN 预处理步骤中发现了一个整数溢出!(这并不奇怪,它是一个hacky的混乱,试图掩盖 x86 Windows 解压缩表有多么混乱。)

(代码在这里并不特别有趣,它只是一堆断言,断言给定的输入字符串会产生给定的输出/错误。)

当然,我并不满足于仅仅提出自己的语义并对其进行测试:我还将 breakpad 的大多数堆栈遍历器测试移植到 rust-minidump!这确实发现了我的一些错误,但也教会了我 Breakpad 的堆栈遍历器中一些奇怪的怪癖,我不确定我是否真的同意。但就这种情况而言,我当时是瞎子摸象,即使与 Breakpad 保持错误兼容也是一种解脱。

这些测试还包括对非 CFI 路径的几个测试,这些路径同样不稳定且有怪癖。我仍然非常讨厌它们为堆栈扫描制定的许多奇怪的平台特定规则,但我被迫假设它们可能具有承重能力。(我确实遇到过好几个情况,我禁用了 breakpad 测试,因为它“显然是胡说八道”,然后在测试过程中在野外遇到了它。我很快学会接受胡说八道会发生,无法忽视。)

没有复制的一件事是 STACK WIN 的一些非常棘手的 hack。例如,在某些地方,它们引入了额外的堆栈扫描,试图处理以下事实:堆栈帧可能具有神秘的额外对齐方式,而 Windows 解压缩表不会告诉你?我猜?

几乎可以肯定的是,rust-minidump 在某些奇特的情况下会做得更差,因为它们可能存在这些情况,但这可能也意味着我们在其他一些随机情况下做得更好。我从未让两者完全一致,但到某个时候,分歧都出现在足够奇怪的情况下,在我看来,这两个堆栈遍历器在糟糕的情况下都产生了同样糟糕的结果。在没有任何理由偏好其中一个的情况下,分歧似乎是可以接受的,以保持实现更简洁。

如果你有兴趣,以下是一个移植的 breakpad 测试的简化版本(值得庆幸的是,minidump-synth 基于相同的二进制数据模拟框架,这些测试使用该框架)

#[test]
fn test_x86_frame_pointer() {
    let mut f = TestFixture::new();
    let frame0_ebp = Label::new();
    let frame1_ebp = Label::new();
    let mut stack = Section::new();

    // Setup the stack and registers so frame pointers will work
    stack.start().set_const(0x80000000);
    stack = stack
        .append_repeated(12, 0) // frame 0: space
        .mark(&frame0_ebp)      // frame 0 %ebp points here
        .D32(&frame1_ebp)       // frame 0: saved %ebp
        .D32(0x40008679)        // frame 0: return address
        .append_repeated(8, 0)  // frame 1: space
        .mark(&frame1_ebp)      // frame 1 %ebp points here
        .D32(0)                 // frame 1: saved %ebp (stack end)
        .D32(0);                // frame 1: return address (stack end)
    f.raw.eip = 0x4000c7a5;
    f.raw.esp = stack.start().value().unwrap() as u32;
    f.raw.ebp = frame0_ebp.value().unwrap() as u32;

    // Check the stackwalker's output:
    let s = f.walk_stack(stack).await;
    assert_eq!(s.frames.len(), 2);
    {
        let f0 = &s.frames[0];
        assert_eq!(f0.trust, FrameTrust::Context);
        assert_eq!(f0.context.valid, MinidumpContextValidity::All);
        assert_eq!(f0.instruction, 0x4000c7a5);
    }
    {
        let f1 = &s.frames[1];
        assert_eq!(f1.trust, FrameTrust::FramePointer);
        assert_eq!(f1.instruction, 0x40008678);
    }
}

专门的生产差异比较、模拟和调试工具

因为小型转储非常糟糕,而且存在很多角落情况,所以我花了大量时间担心那些细微的问题,如果我们真的尝试部署到生产环境中,这些问题将变成巨大的灾难。因此,我还花了很多时间构建socc-pair,它会获取 Mozilla 的崩溃报告系统中的崩溃报告 ID,并下载小型转储、基于旧 breakpad 的实现的输出以及额外的元数据。

然后,它在小型转储上运行本地 rust-minidump(minidump-stackwalk)实现,并在两个输入上进行特定于领域的差异比较。这部分中最重要的部分是对堆栈遍历进行模糊差异比较,尝试更好地处理以下情况:当一个实现添加一个额外的帧,但两者在其他方面一致时。它还使用每个实现报告的技术来尝试识别在两者完全不同时,哪个输出更可靠。

我还添加了一堆模拟和基准测试功能,因为我发现越来越多的场景下,我需要模拟一个生产环境。

哦,我还为栈步行器添加了非常详细的跟踪日志,以便我可以方便地事后调试它做出的决定。

这个工具发现了很多问题,更重要的是它帮助我快速隔离了问题根源。我非常高兴我做出了这个工具。由于它的存在,我们知道我们实际上修复了几个旧 breakpad 实现中出现的问题,这真是太棒了!

下面是 socc-pair 生成的报告的简化版本(是的,我滥用了 diff 语法来实现错误高亮。这是一个很棒的技巧,我像对待孩子一样爱它)

comparing json...

: {
    crash_info: {
        address: 0x7fff1760aca0
        crashing_thread: 8
        type: EXCEPTION_BREAKPOINT
    }
    crashing_thread: {
        frames: [
            0: {
                file: wrappers.cpp:1750da2d7f9db490b9d15b3ee696e89e6aa68cb7
                frame: 0
                function: RustMozCrash(char const*, int, char const*)
                function_offset: 0x00000010
-               did not match
+               line: 17
-               line: 20
                module: xul.dll

.....

    unloaded_modules: [
        0: {
            base_addr: 0x7fff48290000
-           local val was null instead of:
            code_id: 68798D2F9000
            end_addr: 0x7fff48299000
            filename: KBDUS.DLL
        }
        1: {
            base_addr: 0x7fff56020000
            code_id: DFD6E84B14000
            end_addr: 0x7fff56034000
            filename: resourcepolicyclient.dll
        }
    ]
~   ignoring field write_combine_size: "0"
}

- Total errors: 288, warnings: 39

benchmark results (ms):
    2388, 1986, 2268, 1989, 2353, 
    average runtime: 00m:02s:196ms (2196ms)
    median runtime: 00m:02s:268ms (2268ms)
    min runtime: 00m:01s:986ms (1986ms)
    max runtime: 00m:02s:388ms (2388ms)

max memory (rss) results (bytes):
    267755520, 261152768, 272441344, 276131840, 279134208, 
    average max-memory: 258MB (271323136 bytes)
    median max-memory: 259MB (272441344 bytes)
    min max-memory: 249MB (261152768 bytes)
    max max-memory: 266MB (279134208 bytes)

Output Files: 
    * (download) Minidump: b4f58e9f-49be-4ba5-a203-8ef160211027.dmp
    * (download) Socorro Processed Crash: b4f58e9f-49be-4ba5-a203-8ef160211027.json
    * (download) Raw JSON: b4f58e9f-49be-4ba5-a203-8ef160211027.raw.json
    * Local minidump-stackwalk Output: b4f58e9f-49be-4ba5-a203-8ef160211027.local.json
    * Local minidump-stackwalk Logs: b4f58e9f-49be-4ba5-a203-8ef160211027.log.txt

向生产环境过渡和部署

当我们对实现足够自信后,大部分剩余的测试工作由 Will Kahn-Greene 接手,他负责我们崩溃报告基础设施的大量服务器端细节。

Will 花费了大量时间搭建了大量机器来管理 rust-minidump 的部署和监控。他还做了大量艰苦的工作,清理了我们所有的服务器端配置脚本,以处理两种实现之间的任何差异。(虽然我在兼容性方面花费了大量时间,但我们都同意这是一个清理旧代码和错误的好机会。)

所有这些都设置完成后,他在过渡环境中开启了它,我们第一次看到了 rust-minidump 在接近生产环境中的实际运行情况。

糟糕透顶!

我们的过渡服务器接收的输入量约为生产服务器输入量的 10%,但即使在这种规模下,我们也很快发现了几个新的边缘情况,而且出现了大量的崩溃,这对于“处理其他人崩溃的工具”来说有点尴尬。

Will 在这里做了很好的监控和报告问题的工作。幸运的是,它们都非常容易修复。最终,一切都顺利了,一切似乎都像生产服务器上的旧实现一样可靠地工作。我们唯一完全无法生成任何输出的地方是对于那些严重截断的 minidump,这些 minidump 几乎等同于空文件。

我们最初确实雄心勃勃地想要在过渡服务器处理的所有内容上运行 socc-pair,或者做一些事情来真正确保结果的可靠性。但当我们达到这一点时,我们已经筋疲力尽,对新实现充满了信心。

最终,Will 只是说“让我们在生产环境中开启它”,我说“AAAAAAAAAAAAAAA”。

这一刻充满了恐惧。总会有更多的边缘情况。我们不可能就这么结束了。这可能会让整个 Mozilla 着火,并从互联网上删除 Firefox!

但 Will 说服了我。我们写了一些文档,详细介绍了所有细微的差异,并将其发送给了我们能找到的每个人。然后,真相的时刻终于到来:Will 在生产环境中开启了它,我真正看到了它在生产环境中的运行情况。

*戏剧性的鼓声*

一切正常。

在经历了所有这些压力和焦虑之后,我们开启了它,结果一切都很正常。

说真的,我可以说:它运行得很好。

它更快,崩溃更少,我们甚至知道它修复了一些问题。

我在那一周的剩余时间里都处于一种恍惚状态,因为我一直等着另一只鞋落下。我一直在等着有人从迷雾中走出,向我解释说我不知何故破坏了 Thunderbird 或者其他什么东西。但是没有,它只是正常工作。

所以我们去度假了,我一直在等着它崩溃,但它仍然正常工作。

老实说,我仍然对此感到震惊!

但嘿,事实证明,我们确实花了大量的精力来测试实现。在每一步,我们都发现了新的问题,但这是件好事,因为当我们到达最后一步时,就没有更多的问题会让我们感到惊讶了。

之后,模糊器仍然让我们吃尽苦头。

但这已经是第二部分了!感谢您的阅读!

 

关于 Aria Beingessner

更多来自 Aria Beingessner 的文章…


一条评论

  1. Ulrich

    非常有趣的文章!感谢您带我们一起游览沼泽地

    2022 年 6 月 14 日 下午 11:09

本文的评论已关闭。