- 图形学
- 图像处理
【免费下载链接】skia
Skia is a complete 2D graphic library for drawing Text, Geometries, and Images.
本文以 Skia 官方测试文档 site/docs/dev/testing/xsan.md 为核心脉络,完整讲解如何使用最新版 Clang 为 Skia 构建 MSAN(内存消毒器)、ASAN(地址消毒器)与 TSAN(线程消毒器)构建,并结合仓库源码(GN 构建配置、CI 任务定义、工具链脚本)说明其底层实现原理。读完本文,你将掌握三种 sanitizer 构建的完整配置步骤、sanitizeGN 参数的展开规则、运行期环境变量调优,以及 Skia CI 中针对 sanitizer 的已知限制与测试裁剪策略。
消毒器总览:Skia 支持哪些运行时检查
Skia 使用 Clang 提供的运行时消毒器(sanitizer)来发现普通构建难以察觉的内存错误与数据竞争。官方文档明确指出,用最新版 Clang 即可完成 ASAN、UBSAN、TSAN 的编译,各消毒器的平台支持情况如下:
| 消毒器 | 检测重点 | 支持平台 |
|---|---|---|
| UBSAN | 未定义行为(越界、除零、对齐问题等) | Linux、Mac、Android、Windows(部分检查与平台相关) |
| ASAN | 堆/栈/全局缓冲区越界、悬垂指针、内存泄漏等 | Linux、Mac、Android、Windows |
| TSAN | 多线程数据竞争、happens-before 关系违例 | Linux、Mac |
| MSAN | 未初始化内存读取 | Linux(需配合 MSAN 插桩的 libc++) |
从仓库中的 CI 任务定义 infra/bots/jobs.json 可以看到,Skia 持续集成中实际运行着Build-Debian10-Clang-x86_64-Debug-SwiftShader_MSAN、Build-Debian10-Clang-x86_64-Release-MSAN、Test-Debian10-Clang-GCE-CPU-AVX2-x86_64-Release-All-MSAN等 sanitizer 任务,说明这些配置是经过生产环境验证的官方流程。此外,Android 平台还额外支持 HWASAN(硬件辅助地址消毒器),由 infra/bots/recipe_modules/build/android.py 中的sanitize = "HWASAN"注入。
为什么 sanitizer 构建要用 libc++
官方文档特别强调:用 libc++ 测试 sanitizer 构建,比用系统自带的 libstdc++ 能发现更多问题。原因是 libc++ 会主动"挂钩"(hook)到 sanitizer 的检测逻辑中,帮助分析更彻底。Skia 的 Linux 工具链中随附了一份 libc++,位于工具链目录的/lib下。
这一点在 CI 代码中得到了印证:运行测试的环境准备逻辑 infra/bots/recipe_modules/flavor/default.py 中有同样的注释说明,并且所有 sanitizer 任务都会把clang_linux/lib/x86_64-unknown-linux-gnu(即工具链内置 libc++ 所在目录)追加到LD_LIBRARY_PATH。而 GN 构建层在 Linux 上强制使用 libc++ 编译与链接:
# gn/skia/BUILD.gn#L379-L382 if (is_linux) { cflags_cc += [ "-stdlib=libc++" ] ldflags += [ "-stdlib=libc++" ] }因此,使用 Linux 工具链构建 sanitizer 版本时,链接目标默认就是随附的 libc++,而非系统 libstdc++。
准备工作:获取 Clang 与 MSAN 插桩的 libc++
要编译和运行 MSAN 构建,必须准备一份MSAN 插桩(instrumented)版本的 libc++。官方文档给出了两条路径,任选其一即可得到位于CLANGDIR下的 Clang 工具链:普通bin/目录存放 clang/clang++ 与符号化工具,/msan目录存放 MSAN 插桩版 libc++,/tsan目录存放 TSAN 插桩版 libc++。
方式一:直接下载预编译 Clang(仅限 Googler)
该方式依赖gsutil(Google Cloud SDK 组件),适用于有内部下载权限的 Google 员工:
gcloud auth application-default login CLANGDIR="${HOME}/clang" ./bin/sk asset download clang_linux $CLANGDIR其中sk是 Skia 的资产(asset)管理工具(仓库中另有 bin/fetch-sk 可获取),clang_linux是仓库定义的 Linux Clang 工具链资产。
方式二:从源码自建 Clang 与插桩 libc++(其他用户)
没有内部权限的用户可以完全从源码构建。官方文档给出的步骤如下:
CLANGDIR="${HOME}/clang" python3 tools/git-sync-deps CC= CXX= infra/bots/assets/clang_linux/create.py -t "$CLANGDIR"先运行 tools/git-sync-deps 同步依赖,再以空CC/CXX环境变量(强制使用系统编译器)执行 infra/bots/assets/clang_linux/create.py 生成工具链。这个脚本的实际构建逻辑记录在 infra/bots/assets/clang_linux/Dockerfile 中,其关键环节包括:
- 用 CMake 引导编译(bootstrap)clang、lld、clang-tools-extra 等组件;
- 用新编译出的 clang 从
runtimes子目录构建 libc++/libc++abi:- TSAN 版:
LLVM_USE_SANITIZER=Thread,产物拷贝到工具链的/tsan; - MSAN 版:
LLVM_USE_SANITIZER=MemoryWithOrigins,产物拷贝到工具链的/msan;
- TSAN 版:
- 将
llvm-symbolizer、llvm-profdata、llvm-cov放入bin/,供 sanitizer 报告符号化使用。
Dockerfile 注释还提醒:引导 libc++ 时必须从runtimes子目录开始构建,而不是llvm子目录,这与 LLVM 官方的 bootstrapping 文档一致。
配置与编译 MSAN 构建
MSAN 用于检测"读取未初始化内存"这类问题,是三类消毒器中配置要求最高的:它要求整个依赖链(包括 C++ 标准库)都经过 MSAN 插桩。官方文档给出的完整配置如下:
CLANGDIR="${HOME}/clang" mkdir -p out/msan cat > out/msan/args.gn <<- EOF cc = "${CLANGDIR}/bin/clang" cxx = "${CLANGDIR}/bin/clang++" extra_cflags = [ "-B${CLANGDIR}/bin" ] extra_ldflags = [ "-B${CLANGDIR}/bin", "-fuse-ld=lld", "-L${CLANGDIR}/msan", "-Wl,-rpath,${CLANGDIR}/msan" ] sanitize = "MSAN" skia_use_fontconfig = false EOF python3 tools/git-sync-deps bin/gn gen out/msan ninja -C out/msan各参数作用说明:
cc/cxx:指定工具链的 clang 与 clang++;extra_cflags中的-B${CLANGDIR}/bin:把工具链 bin 目录加入编译器的搜索路径,确保使用配套的汇编器/链接器等子工具;extra_ldflags中的-fuse-ld=lld:使用 LLVM 的 lld 链接器;-L${CLANGDIR}/msan与-Wl,-rpath,${CLANGDIR}/msan:链接并运行期定位 MSAN 插桩版 libc++(即文档脚注 [1] 所说的/msan目录);sanitize = "MSAN":GN 构建参数,告诉构建系统启用内存消毒器(其底层展开规则见下文"sanitize 参数的展开"一节);skia_use_fontconfig = false:关闭 fontconfig,因为它在 MSAN 下会产生大量误报——CI 构建脚本 infra/bots/recipe_modules/build/default.py 对 MSAN 任务也是强制设置这一项。
GN 构建层还会为 MSAN 自动追加-fsanitize-memory-track-origins(见 gn/skia/BUILD.gn),用于在报告中追踪未初始化值的来源(origin),这对定位问题至关重要。此外,从dm_flags.go的注释看,MSAN 任务默认不启用--randomProcessorTest随机种子测试(infra/bots/gen_tasks_logic/dm_flags.go),并会跳过Once、Shared两类测试(dm_flags.go),这些都是在 CI 中经过实践沉淀的规避策略。
配置与编译 ASAN 构建
ASAN 主要检测缓冲区越界、use-after-free 等地址类内存错误。官方配置:
CLANGDIR="${HOME}/clang" mkdir -p out/asan cat > out/asan/args.gn <<- EOF cc = "${CLANGDIR}/bin/clang" cxx = "${CLANGDIR}/bin/clang++" sanitize = "ASAN" extra_ldflags = [ "-fuse-ld=lld", "-Wl,-rpath,${CLANGDIR}/lib/x86_64-unknown-linux-gnu" ] EOF python3 tools/git-sync-deps bin/gn gen out/asan ninja -C out/asan与 MSAN 不同,ASAN 不需要插桩版的 libc++,因此只需把-Wl,-rpath指向工具链随附的普通 libc++ 所在目录lib/x86_64-unknown-linux-gnu。注意ASAN 实际会隐式联动 UBSAN:在 gn/skia/BUILD.gn 中,sanitize = "ASAN"会被展开为undefined,address,float-divide-by-zero,即同时开启未定义行为与浮点除零检测;仅当目标为 Android 时才退回纯address(原因是 UBSAN 存在链接错误,见代码中的 skia:13344 备注)。
CI 构建脚本 default.py 还显示,ASAN 任务会关闭 SPIR-V 校验(skia_enable_spirv_validation = false),以规避消毒器与校验层之间的冲突。
配置与编译 TSAN 构建
TSAN 用于检测多线程数据竞争。官方配置:
CLANGDIR="${HOME}/clang" mkdir -p out/tsan cat > out/tsan/args.gn <<- EOF cc = "${CLANGDIR}/bin/clang" cxx = "${CLANGDIR}/bin/clang++" sanitize = "TSAN" is_debug = false extra_ldflags = [ "-Wl,-rpath,${CLANGDIR}/lib" ] EOF python3 tools/git-sync-deps bin/gn gen out/tsan ninja -C out/tsan这里把is_debug设为false(即 Release 构建),rpath 指向工具链/lib目录。与 MSAN 类似,CI 中 TSAN 也使用独立的插桩版 libc++:测试环境会把clang_linux/tsan追加到LD_LIBRARY_PATH(flavor/default.py)。
sanitize 参数在 GN 构建系统中的展开
sanitize是 Skia GN 构建的顶层参数(默认空字符串,定义于 gn/BUILDCONFIG.gn),核心展开逻辑集中在 gn/skia/BUILD.gn。理解这段代码,就能明白上面三个配置背后到底发生了什么:
sanitize取值 | 实际传给编译器的-fsanitize=值 | 备注 |
|---|---|---|
MSAN | memory | 附加-fsanitize-memory-track-origins |
ASAN | undefined,address,float-divide-by-zero | Android 上退化为address(skia:13344) |
TSAN | thread | — |
HWASAN | hwaddress | 用于 Android 硬件辅助地址消毒 |
| 其他任意值 | 原样透传(如address,undefined) | 支持直接传入逗号分隔的列表 |
其他关键规则:
- 仅默认工具链生效:
sanitize只作用于默认 toolchain,交叉编译等场景下会被清空(BUILD.gn); - 禁用恢复模式:统一追加
-fno-sanitize-recover=$sanitizers,即一旦检测到问题立即终止程序并报错,而不是继续运行(BUILD.gn); - 抑制列表:通过
-fsanitize-blacklist挂载 tools/xsan.supp 抑制文件,其中豁免了 libpng 的filter_sse2_intrinsics.c与 zlib 的deflate.c(两者会解引用未对齐指针);注意该文件修改后需要gn clean全量重编才能生效(文件头部注释明确说明); - 帧指针:非 Windows 平台追加
-fno-omit-frame-pointer,保证栈回溯信息完整(BUILD.gn); - Windows/MSVC 特例:非 Clang 编译器下 ASAN 使用 MSVC 的
/fsanitize=address并链接clang_rt.asan-x86_64.lib(BUILD.gn); - 指针溢出可恢复:ASAN 构建下指针溢出检测被设为可恢复模式(
-fsanitize-recover=pointer-overflow),见recover_pointer_overflow配置(BUILD.gn,对应 skia:9731); - RTTI 依赖:由于
-fsanitize=vptr需要 RTTI,除 ASAN 外的构建默认不开启该检查(BUILD.gn)。
运行测试:sanitizer 运行期环境与日志解读
构建完成后即可运行 Skia 测试二进制(如dm、nanobench)。CI 测试环境配置(infra/bots/recipe_modules/flavor/default.py)为我们总结了经过验证的运行期最佳实践:
动态库路径(Linux)
- MSAN 构建:
LD_LIBRARY_PATH需包含clang_linux/msan(MSAN 插桩 libc++); - TSAN 构建:
LD_LIBRARY_PATH需包含clang_linux/tsan(TSAN 插桩 libc++); - 所有 sanitizer 构建:还需包含
clang_linux/lib/x86_64-unknown-linux-gnu(普通 libc++),并把clang_linux/bin加入PATH,以便 sanitizer 报告调用llvm-symbolizer完成符号化。
环境变量调优
- ASAN:
ASAN_OPTIONS=symbolize=1 detect_leaks=1(Mac/Windows 不支持detect_leaks,仅保留symbolize=1);LSAN_OPTIONS=symbolize=1 print_suppressions=1;UBSAN_OPTIONS=symbolize=1 print_stacktrace=1; - TSAN:
TSAN_OPTIONS=report_signal_unsafe=0(不报告信号处理器中的 malloc/fprintf 等调用,因为此时程序本就在崩溃边缘); - 若报告中栈帧显示
<unknown module>,可尝试为dm(尤其是 Vulkan 后端)追加fast_unwind_on_malloc=0,代价是 2~25 倍速度下降,仅在确实需要时启用。
CI 还会对 Linux 上的dm、nanobench使用 symbolize_stack_trace.py 包装运行,自动对 sanitizer 输出做二次符号化,便于阅读(flavor/default.py)。另外,ASAN/TSAN 任务会关闭 Vulkan SDK 的 debug 层(ASAN 会在其中报告泄漏、TSAN 会内存不足),见 flavor/default.py。
CI 中的 sanitizer:已知限制与测试裁剪
把 sanitizer 构建接入日常测试并非开箱即用,Skia CI 沉淀了大量规避经验,可作为本地实践的参考:
- 结果不上传:ASAN、MSAN、TSAN 均列入
no_upload列表(infra/bots/cfg.json),即这类任务的 GM/性能数据不上传 Golden/Perf 服务器,因为 sanitizer 构建本身用于找 bug 而非做基准对比; - TSAN 裁剪:移除
svg测试源(skbug.com/10848,dm_flags.go);在特定的TSAN_Vulkan任务上整体跳过测试,以避免 OOM 与大量误报(注释中提到 NUC11TZi5 等 GPU 组合);TSAN+Vulkan 场景下强制同时开启 GL(default.py),作为 NVIDIA 驱动超时的工作区; - MSAN 裁剪:跳过
Once/Shared测试、不启用--randomProcessorTest,并关闭 fontconfig; - ASAN 裁剪:关闭 SPIR-V 校验、关闭 Vulkan debug 层;
- 超时放宽:MSAN 任务的超时时间被放宽到 9 小时(gen_tasks_logic.go),说明插桩构建+测试的耗时远高于普通构建。
常见问题与排错提示
结合文档与源码,总结几个高频问题:
- MSAN 报错"使用未初始化值"但无法定位来源:确认
args.gn中sanitize = "MSAN"正确设置——构建系统会自动追加-fsanitize-memory-track-origins,若自行覆盖了 cflags 请保留该选项; - 运行时找不到 libc++ 动态库:MSAN/TSAN 构建需确保
-Wl,-rpath与运行期LD_LIBRARY_PATH都指向工具链的msan/tsan目录; - 修改了 tools/xsan.supp 但无效:该抑制文件只在 GN 构建的干净重建后生效,请先执行
gn clean(文件头部注释 tools/xsan.supp 明确要求); - 栈回溯显示
<unknown module>:按上文建议设置fast_unwind_on_malloc=0; - 误报集中在字体/图像解码库:优先确认
skia_use_fontconfig = false是否已设置(MSAN 必配),并检查相关第三方库是否命中 xsan.supp 的豁免列表。
延伸阅读
- 官方测试文档原文:site/docs/dev/testing/xsan.md
- sanitize 参数展开与编译器标志:gn/skia/BUILD.gn
- sanitizer 抑制列表:tools/xsan.supp
- Linux Clang 工具链(含 MSAN/TSAN libc++)构建脚本:infra/bots/assets/clang_linux/Dockerfile
- CI 构建参数(sanitize 注入、SwiftShader 插桩):infra/bots/recipe_modules/build/default.py
- 测试运行期环境变量与符号化:infra/bots/recipe_modules/flavor/default.py
- sanitizer 任务的测试裁剪策略:infra/bots/gen_tasks_logic/dm_flags.go
- Android ASAN/HWASAN 注入:infra/bots/recipe_modules/build/android.py
- 图形学
- 图像处理
【免费下载链接】skia
Skia is a complete 2D graphic library for drawing Text, Geometries, and Images.
相关推荐
Skia 构建与测试中的 MSAN、ASAN、TSAN 与 UBSAN 消毒器实战指南
Skia 构建与测试中的 MSAN、ASAN、TSAN 与 UBSAN 消毒器实战指南 本指南基于 Skia 官方开发文档 xsan.md https://li
图形学rippled(xrpld)Sanitizer 配置与实战指南:ASAN/TSan/UBSan 构建、测试与抑制规则
rippled(xrpld)Sanitizer 配置与实战指南:ASAN/TSan/UBSan 构建、测试与抑制规则 本指南基于 rippled(xrpld)仓
区块链Julia 源码构建中的 Sanitizer 支持:ASAN / MSAN / TSAN 检测环境搭建全指南
Julia 源码构建中的 Sanitizer 支持:ASAN / MSAN / TSAN 检测环境搭建全指南 导读 本指南面向需要在 Julia 自身 C/C+
编程语言编译器语言运行时标准库JIT编译
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考