消毒用品

设计初衷

排错程序是一种用于检测代码中特定类别的 bug 的工具。消毒器的运行原理各不相同,但通常(但不总是)依赖于某种形式的编译时插桩来更改代码,以便在运行时发现 bug。 Fuchsia 使用各种清理器来发现和诊断难以找到的危险 bug。

通过 build 时标志启用 Sanitizers。Sanitizer build 在 Fuchsia 的持续集成 (CI) 和提交队列 (CQ) 上持续运行,为 Fuchsia C/C++ 和 Rust 开发者提供服务。

开发者通常可以从清理器中受益,而无需执行任何特殊操作,只有在清理器检测到 bug 时才需要注意。不过,也存在一些限制。请继续阅读,了解支持哪些清理器以及如何使用它们。

支持的清理器

Fuchsia 目前支持以下清理器:

  • AddressSanitizer (ASan) 可检测到越界访问、释放后使用 / 返回 / 范围和双重释放的情况。
  • LeakSanitizer (LSan) 可检测内存泄漏。 LeakSanitizer 在检查内存泄漏方面的工作方式类似于保守的垃圾收集器。无法从引用根(线程堆栈、线程寄存器、全局变量和线程局部变量)访问的任何分配都被视为泄漏。
  • ThreadSanitizer (TSan) 可检测数据争用(仅限主机)。
  • UndefinedBehaviorSanitizer (UBSan) 会检测依赖于未定义的程序行为的具体问题。

Zircon 内核中提供以下清理器行为:

  • 物理内存管理器 (PMM) 检查器 (pmm_checker) 可检测释放后使用 bug 和游离 DMA。
  • 内核地址排错程序 (KASan) 通过与 PMM 协作,将地址排错程序扩展到内核代码。
  • Lockdep 是一种运行时锁验证器,可检测死锁等锁危险。

默认情况下,系统会添加以下 C/C++ 编译选项,以在运行时检测或防止 bug:

  • -ftrivial-auto-var-init=pattern(请参阅 RFC)将自动变量初始化为非零模式,以暴露与从未初始化的内存读取相关的 bug。
  • ShadowCallStackSafeStack 可强化生成的代码,防止出现堆栈溢出。

最后,Fuchsia 使用 libFuzzersyzkaller 执行覆盖率导向的模糊测试。模糊测试器与清理器类似,都会尝试在运行时发现代码中的 bug,并且通常会结合使用。模糊测试器与清理器的不同之处在于,模糊测试器会尝试强制执行生产代码,使其进入可能会暴露 bug 的路径。

支持的配置

目前,在本地 build 和 CI/CQ 中,以下配置支持 Sanitizers:

  • bringup.x64
  • bringup.arm64(注意:使用 bringup.qemu-arm64 进行本地模拟)
  • core.x64
  • core.arm64(注意:使用 core.qemu-arm64 进行本地模拟)
  • zbi_tests.x64
  • zbi_tests.arm64

此外,清理器还适用于主机工具。

上述所有测试都在 CI/CQ 上通过 qemuIntel NUC 进行。由于资源和容量问题,我们未在其他平台上测试过 Sanitizers,但您可以使用以下构建工作流在这些平台上进行本地测试。

对于某些已登录的用户,Gerrit 和 CI 控制台中可能会显示在 //vendor 下定义的配置的其他 tryjob。查找名称中包含 -asan 的配置。

上述清理器适用于 C/C++ 代码。此外,LSan 还应用于 Rust 代码,用于检测 Rust 内存泄漏

排查清理器问题

构建

Fuchsia 平台 build(树内)

如需重现 Sanitizer build,您可以使用build 变体来启用 Sanitizer:

fx set product --variant asan-ubsan --variant host_asan-ubsan

或者,您也可以选择仅检测某些二进制文件:

fx set product --variant asan-ubsan/executable_name

选择性插桩工作流程非常适合在硬件上进行本地测试,因为完全插桩的 build 无法安装在设备上。

具体来说,如需检测内核代码中的释放后使用 bug,您需要启用内核 PMM 检查器

树外 build

使用 Fuchsia 工具链进行编译时,只需传递 -fsanitize= 标志即可指明要使用的 Sanitizer。 请参阅编译器文档

创建包含插桩组件的 Fuchsia 软件包时,您需要确保该软件包包含所有运行时依赖项,包括作为 Clang 工具链一部分分发的清理器运行时,以及作为 Fuchsia SDK 一部分在 sysroot 下分发的插桩 C 库。

测试

像往常一样在本地工作流程或启用了清理器的 CQ 构建器(tryjob 的名称中包含 asan)中进行测试。如果清理器检测到问题,则会在日志中输出包含以下字符串之一的消息:

  • ERROR: AddressSanitizer
  • ERROR: LeakSanitizer
  • SUMMARY: UndefinedBehaviorSanitizer
  • WARNING: ThreadSanitizer

在这些消息之后,您会找到堆栈轨迹,其中会指明问题的性质并指向根本原因。您可以在 fx log 中找到这些消息。

请注意,触发清理器的测试可能仍显示为通过。 清理器问题不会表现为测试失败。

检测到的清理器问题通常具有类似的根本原因。您或许可以搜索 Fuchsia bug,查找包含与清理器输出中显示的某些关键字相同的 bug,从而找到之前工作的参考资料。

已知问题

#[should_panic]

Fuchsia 的 Rust build 会在 panic!中止。这可显著减小二进制文件的大小。一个不幸的副作用是,使用 #[should_panic] 属性的测试可能会错误地检测到内存泄漏。这些测试会发出预期的 panic,然后退出而不进行展开,这意味着它们不会释放其 堆分配。对于 LeakSanitizer 而言,这与真正的内存泄漏没有区别。

如果此问题影响到您的测试,您可以:

  • 按照此示例切换到 #[fuchsia::test] 属性。这是首选属性,因为 #[fuchsia::test] 只会停用 LeakSanitizer,但会保持其他 sanitizer 处于启用状态。

  • 如果难以切换到 #[fuchsia::test],请按照此示例在 Sanitizer build 中停用测试。

请参阅:问题 88496:应该出现 panic 的 Rust 测试触发了 leaksanitizer

最佳做法

确保代码通过测试运行

排错程序会在运行时发现 bug。除非您的代码在测试中或通常在 Fuchsia 上以 CI/CQ 运行的方式运行,否则清理器将无法发现您代码中的 bug。

确保代码的清理器覆盖率的最佳方法是确保在相同配置下的测试覆盖率。请参阅有关测试覆盖率的指南。

请勿在代码中抑制清理器

可以针对特定 build 目标抑制 Sanitizers。最常见的是,这用于在引入清理器支持之前就存在的问题,尤其是 Fuchsia 项目不拥有的第三方代码中的问题。

被抑制的清理器应被视为技术债务,因为它们不仅会隐藏旧 bug,还会阻止您发现代码中引入的新 bug。 理想情况下,不应添加新的抑制,而应移除现有抑制并修复底层 bug。

如需抑制 sanitizer,请按如下方式修改定义可执行目标文件的 BUILD.gn 文件:

executable("please_fix_the_bugs") {
  ...
  # TODO(https://fxbug.dev/42074368): delete the below and fix the memory bug.
  deps += [ "//build/config/sanitizers:suppress-asan-stack-use-after-return" ]
  # TODO(https://fxbug.dev/42074368): delete the below and fix the memory bug.
  deps += [ "//build/config/sanitizers:suppress-asan-container-overflow" ]
  # TODO(https://fxbug.dev/42074368): delete the below and fix the memory leak.
  deps += [ "//build/config/sanitizers:suppress-lsan.DO-NOT-USE-THIS" ]
}

以上示例展示了如何抑制所有清理器。不过,您最多只能抑制导致失败的清理器。请通过提交 bug 并按上图所示在注释中引用该 bug 来跟踪抑制情况。

另一种停用清理器的常见方法如下:

executable("too_slow_when_built_with_asan") {
  ...
  exclude_toolchain_tags = [ "asan" ]
}

上述两个示例均以整个可执行文件的粒度进行抑制。 如需进行更精细的抑制,您可以检测代码中是否存在排错程序。例如,这对于在特定测试用例中抑制清理器非常有用,但不能更广泛地使用。例如,有意引入内存错误并测试清理器运行时的测试会使用此功能。

对于 C/C++,请参阅:

对于 Rust,您可以遵循以下模式:

#[cfg(test)]
mod tests {
    #[test]
    // TODO(https://fxbug.dev/42074368): delete the below and fix the leak
    #[cfg_attr(feature = "variant_asan", ignore)]
    fn test_that_leaks() {
        // ...
    }
}

测试是否不稳定

如果被测代码的行为不确定,则清理器错误可能会不稳定。例如,内存泄漏可能只在某些竞态条件下发生。如果清理器错误时有时无,请参阅有关在 CQ 中测试不稳定情况的指南。

提交优质 bug

遇到清理器问题时,请提交包含所有可用问题排查信息的 bug。

示例:问题 73214:blobfs 中的 ASAN use-after-scope

bug 报告包含以下内容:

  • 由清理器(在本例中为 ASan)提供的错误。
  • 有关如何构建和测试以重现错误的说明。
  • 后续调查的详细信息,包括所需的具体代码指针。
  • 对相关更改的引用,例如在本例中,对用于修复 bug 根本原因的更改的引用。

路线图

持续性工作:

未来工作领域:

  • ThreadSanitizer (TSan):检测数据争用。
  • 内核支持检测并发 bug。
  • 扩展对 Rust 的 Sanitizer 支持,例如检测 Rust unsafe {} 代码块或跨 FFI 调用的内存安全 bug,或检测未定义的行为 bug。
  • MemorySanitizer (MSan):检测从未初始化的内存读取的情况。

另请参阅:2021 年路线图中的清理器