☰
Flutter Engine 的 Sanitizer 使用指南:用 TSan/ASan/MSan/UBSan/LSan 定位引擎内存与并发缺陷
2026/9/28 8:44:27 网站建设 项目流程
  • 跨平台
  • 图形学
  • 前端

【免费下载链接】engine

The Flutter engine

项目地址:https://gitcode.com/gh_mirrors/eng/engine
点击查看免费下载

Flutter Engine 的构建系统从根上接入了 Clang 生态的五大 Sanitizer(Thread/Address/Memory/Undefined Behavior/Leak),用于在开发与调试阶段定点隔离引擎内部的并发数据竞争、内存错误、未初始化内存读取等构造性问题。本文将基于 docs/Using-Sanitizers-with-the-Flutter-Engine.md 的完整流程,结合仓库中的 tools/gn、testing/sanitizer_suppressions.sh 及各 suppressions 文件,为你梳理从配置构建、运行单元测试到维护抑制规则的一整套可落地操作。读完本文,你将能针对具体故障反向选择合适的 Sanitizer,在本地完成一次带抑制规则(suppression)的引擎测试构建,并学会如何安全地新增、裁剪抑制条目。

一、五大 Sanitizer 概览与选型思路

Flutter Engine 的所有构建变体都支持开启以下 Sanitizer 的任意组合,每种工具针对一类特定的“构造性问题”(construction issues):

Sanitizer检测目标典型问题示例GN 开关
Thread Sanitizer(TSan)数据竞争(data races)多线程同时读写同一共享变量且无同步--tsan
Address Sanitizer(ASan)内存错误use-after-free、缓冲区溢出--asan
Memory Sanitizer(MSan)读取未初始化内存读取未赋值的堆/栈内存--msan
Undefined Behavior Sanitizer(UBSan)未定义行为有符号溢出、空指针解引用、对齐错误--ubsan
Leak Sanitizer(LSan)内存泄漏堆对象未释放--lsan

这些开关在 tools/gn 中会被翻译为对应的 GN 参数:is_msan、is_asan、is_tsan、is_lsan、is_ubsan,随后作用于整个构建根(buildroot)。

选型建议(原文档核心方法论):不是所有“目标架构 × 宿主平台 × Sanitizer”的组合都受支持,且每种 Sanitizer 在不同工具链版本上的支持度差异很大。同时受构建/测试基础设施限制,这些 Sanitizer默认不会在 presubmit 或 build-bot 上开启。因此官方推荐的做法是从问题反推工具(work backwards from a problem):遇到疑似数据竞争就用 TSan,遇到崩溃伴随内存访问错误就用 ASan,遇到未初始化读取就用 MSan,先用最有希望定位该问题的那一个。

从仓库的实际 suppressions 文件可以看出这套方法论的应用痕迹:例如 tsan_suppressions.txt 中抑制了flutter::Shell::OnAnimatorBeginFrame在引擎关闭时的竞争,lsan_suppressions.txt 中抑制了MakeSkSurfaceFromBackingStore在引擎关闭时未回收的 SkSurface——这些正是引擎生命周期边界上的已知问题。

二、通用使用准则:一次配置,全量生效

开启 Sanitizer 的构建变体通常会在构建阶段直接失败,或在运行特定单元测试目标时失败。这些失败要么是未被正确标注的误报(false positive),要么是 Flutter Engine 或 Dart VM 的真实缺陷。官方策略是:允许在不修复全部问题的情况下继续发现新问题——通过选择性抑制(suppression)已知问题,把噪音排除在外。

2.1 一键加载全部 Sanitizer 选项

所有抑制规则与 Sanitizer 运行选项都通过环境变量注入,可在当前 shell 中一次性加载:

source ./flutter/testing/sanitizer_suppressions.sh

执行后脚本会打印各 Sanitizer 使用的抑制文件路径,示例输出:

Using Thread Sanitizer suppressions in ./flutter/testing/tsan_suppressions.txt Using Leak Sanitizer suppressions in ./flutter/testing/lsan_suppressions.txt

查看仓库中 testing/sanitizer_suppressions.sh 的源码,可以看到它实际完成的工作:

  • 根据宿主系统(Linux/Darwin)定位 buildtools 目录;
  • 为TSAN_OPTIONS、LSAN_OPTIONS、UBSAN_OPTIONS分别设置suppressions=指向 tsan_suppressions.txt、lsan_suppressions.txt、ubsan_suppressions.txt;
  • 设置ASAN_OPTIONS="symbolize=1:detect_leaks=1:intercept_tls_get_addr=0",并指定ASAN_SYMBOLIZER_PATH指向 buildtools 中的llvm-symbolizer。

脚本加载后,直接在终端运行具体的单元测试二进制即可,所有 Sanitizer 选项和抑制规则都会自动生效。

2.2 抑制规则的使用与维护

各抑制文件顶部都写有针对该 Sanitizer 的具体添加说明(例如 lsan_suppressions.txt 顶部指向 AddressSanitizer/LeakSanitizer 的 suppressions 官方文档)。新增抑制条目时,须遵循两条铁律:

  1. 抑制规则要尽量详细(as detailed as possible),过于宽泛的抑制会连真实的新问题一起屏蔽;
  2. 如果预期的问题没有被 Sanitizer 捕获,先检查抑制列表里是否有看起来相关的条目,必要时选择性禁用部分抑制以复核。

运行时 LSan 会输出如下形式的“Suppressions used”统计,便于你判断哪些抑制条目在本次运行中实际命中了多少字节:

----------------------------------------------------- Suppressions used: count bytes template 1 120 class_createInstance 5 80 MakeSkSurfaceFromBackingStore 3 128 _dispatch_once_callout -----------------------------------------------------

2.3 其他注意事项

  • Goma 兼容性:Sanitizer 构建对 Goma(分布式编译)的支持不完整,官方建议在问题解决前禁用 Goma 以获得更可靠的构建(构建命令中统一带--no-goma)。
  • 问题追踪:所有被 Sanitizer 捕获的问题都带[sanitizer]标签归档追踪。仓库内的抑制条目也大量引用对应 issue 编号,如flutter::MessageLoop::EnsureInitializedForCurrentThread(LSan 抑制,见 lsan_suppressions.txt)、fml::MessageLoopTaskQueues::Dispose(TSan 抑制,见 tsan_suppressions.txt),新发现的问题也应照此登记。

三、各 Sanitizer 的启用方法与实战命令

以下每种 Sanitizer 的命令模式完全一致:用tools/gn生成带开关的构建目录 →autoninja编译 → 加载 suppressions → 运行测试。仓库统一使用out/host_debug_unopt作为主机调试(unoptimized)构建目录。

3.1 Leak Sanitizer:检测内存泄漏

LSan 检测内存泄漏,启用开关为--lsan。最佳实践是与 unoptimized 构建变体搭配使用——构建根(buildroot)在这种模式下会配置理想参数(如关闭优化,便于符号化与准确回溯)。

$ ./flutter/tools/gn --runtime-mode debug --lsan --unoptimized --no-goma $ autoninja -C out/host_debug_unopt $ source ./flutter/testing/sanitizer_suppressions.sh $ ./out/host_debug_unopt/embedder_unittests

LSan 既支持独立运行,也可与 ASan 联合使用(联合时由 ASan 隐式附带)。从 lsan_suppressions.txt 可以看到引擎侧维护的典型泄漏抑制类别:Dart VM 误报(leak:dart::*)、Objective-C 类定义(leak:class_createInstance)、Embedder backing store 的 SkSurface(leak:MakeSkSurfaceFromBackingStore)、TLS 槽中的 MessageLoop(leak:fml::MessageLoop::EnsureInitializedForCurrentThread)、平台消息未处理导致的泄漏(leak:flutter::PlatformViewEmbedder::HandlePlatformMessage)、mock 引擎测试中的有意分配(leak:*flutter/shell/platform/linux/testing/mock_engine.cc)等。

3.2 Address Sanitizer:检测内存错误

ASan 检测 use-after-free、缓冲区溢出等内存错误,启用开关为--asan。注意:启用 ASan 会隐式启用 LSan,因此sanitizer_suppressions.sh会在 ASan 构建中同时开启泄漏检测(这正是脚本中ASAN_OPTIONS设置detect_leaks=1的原因)。

$ ./flutter/tools/gn --runtime-mode debug --asan --unoptimized --no-goma $ autoninja -C out/host_debug_unopt $ source ./flutter/testing/sanitizer_suppressions.sh $ ./out/host_debug_unopt/embedder_unittests

平台限制:ASan 对 aarch64 目标的支持不完整(spotty support),官方建议在x64 目标上使用以获得最佳效果。

3.3 Undefined Behavior Sanitizer:检测未定义行为

UBSan 捕获未定义行为,启用开关为--ubsan。sanitizer_suppressions.sh会为其指定抑制文件(ubsan_suppressions.txt),其中抑制了 Dart VM(third_party/dart)的对齐与空指针类错误,以及 SwiftShader(flutter/third_party/swiftshader)的未定义行为。

$ ./flutter/tools/gn --runtime-mode debug --ubsan --unoptimized --no-goma $ autoninja -C out/host_debug_unopt $ source ./flutter/testing/sanitizer_suppressions.sh $ ./out/host_debug_unopt/embedder_unittests

UBSan 的各类错误(如 alignment、null、signed-integer-overflow 等)可在运行时按类别禁用。

3.4 Thread Sanitizer:检测数据竞争

TSan 捕获数据竞争,启用开关为--tsan。抑制文件 tsan_suppressions.txt 中的条目直观反映了引擎的并发热点:Dart VM 内部(race:dart::*、thread:dart::*)、引擎关闭时 animator 开始帧的竞争(race:flutter::Shell::OnAnimatorBeginFrame、race:flutter::Shell::OnAnimatorNotifyIdle)、以及fml::MessageLoopTaskQueues::Dispose的竞争。

$ ./flutter/tools/gn --runtime-mode debug --tsan --unoptimized --no-goma $ autoninja -C out/host_debug_unopt $ source ./flutter/testing/sanitizer_suppressions.sh $ ./out/host_debug_unopt/embedder_unittests

3.5 Memory Sanitizer:检测未初始化内存读取

MSan 检测对未初始化内存的读取,启用开关为--msan。这是唯一一个仅支持 Linux 的 Sanitizer(因其依赖特定工具链对全程序的内存插桩)。

$ ./flutter/tools/gn --runtime-mode debug --msan --unoptimized --no-goma $ autoninja -C out/host_debug_unopt $ source ./flutter/testing/sanitizer_suppressions.sh $ ./out/host_debug_unopt/embedder_unittests

四、源码级佐证:开关如何从 CLI 抵达构建系统

--asan/--lsan/--msan/--tsan/--ubsan五个开关在 tools/gn 中由 argparse 声明为布尔参数,随后在同一文件的 GN 参数组装阶段(tools/gn)逐一映射为is_asan、is_lsan、is_msan、is_tsan、is_ubsan。这些is_*参数会传递给引擎的 BUILD 系统(GN),进而作用于编译与链接阶段,为对应目标注入 sanitizer 插桩与运行库。

换句话说,你在命令行写的--tsan并不只是“给某个测试加个 flag”,而是整个构建根(buildroot)级别的配置切换——这也解释了为什么文档强调“构建根已为所有目标接线了 sanitizer 构建尝试”,以及为何不是所有“架构 × 平台 × sanitizer”组合都能编译通过:插桩会递归应用到引擎与 Dart VM 的全部依赖目标上。

五、故障排查速查

现象排查方向
Sanitizer 构建失败或特定测试目标运行失败多为未标注的误报或引擎/Dart VM 真实缺陷;先用 suppressions 过滤已知问题,再判断剩余失败是否为新问题
预期的问题没被 Sanitizer 捕获检查对应 suppressions 文件,确认是否被已有条目(可能过宽)屏蔽;考虑选择性禁用抑制后重跑
运行出现大量重复已知问题查看 “Suppressions used” 统计,判断命中条目;对照 issue 编号核实是否为已跟踪问题
Goma 下构建不稳定按官方建议禁用 Goma(--no-goma),等待兼容性问题修复
目标平台选择MSan 仅 Linux;ASan 在 aarch64 上支持不完整,优先 x64

六、小结

Flutter Engine 的 Sanitizer 支持是一套完整的“发现问题 → 抑制已知 → 持续发现”工作流:通过 tools/gn 的--asan/--lsan/--msan/--tsan/--ubsan开关一键生成带插桩的构建,通过 testing/sanitizer_suppressions.sh 一键加载全部抑制与环境选项,再通过维护 tsan_suppressions.txt、lsan_suppressions.txt、ubsan_suppressions.txt 三个抑制文件来沉淀已知问题、聚焦新缺陷。这套方法论既适用于 Flutter Engine 的本地调试,也适用于任何“全量插桩 + 精细抑制”的 C++ 大型项目质量建设。

  • 跨平台
  • 图形学
  • 前端

【免费下载链接】engine

The Flutter engine

项目地址:https://gitcode.com/gh_mirrors/eng/engine
点击查看免费下载
上一篇:如何在鸿蒙系统上打造完全自定义的纯净阅读体验:开源阅读鸿蒙版终极指南
下一篇:OpenCore Legacy Patcher深度解析:让老款Mac重获新生的完整实战指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询