rustc 的 Sanitizer 支持:内存错误检测与 CFI 防护的完整实现指南

rustc 的 Sanitizer 支持:内存错误检测与 CFI 防护的完整实现指南 rustc 的 Sanitizer 支持内存错误检测与 CFI 防护的完整实现指南【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust导读本文以 rustc-dev-guide 的 sanitizers 章节为骨架系统讲解 rustc 编译器内建的 Sanitizer 支持体系涵盖 AddressSanitizer、ThreadSanitizer、MemorySanitizer、LeakSanitizer、HWAddressSanitizer、CFI/KCFI 等工具的启用方式、实现原理、测试方法与在新目标平台上扩展的完整流程。读完本文你将掌握如何用-Z sanitizer...为 Rust 程序开启内存错误与数据竞争检测、如何通过#[sanitize]属性按函数粒度控制插桩以及 sanitizer 运行时库如何随 rustc 构建、分发与链接的底层机制。一、rustc 内建的 Sanitizer 家族rustc 编译器对以下 sanitizer 提供了内建支持依据 sanitizers.md 所列并可在当前仓库源码中得到印证Sanitizer简称检测能力AddressSanitizeraddress更快速的内存错误检测器可检测堆、栈、全局变量的越界访问、释放后使用use after free、返回后使用use after return、双重释放、非法释放以及内存泄漏ControlFlowIntegritycfiLLVM 前向边forward-edge控制流完整性保护阻止间接调用被劫持Hardware-assisted AddressSanitizerhwaddress与 ASan 类似但基于部分硬件辅助利用 Top-Byte-Ignored 等硬件特性KernelControlFlowIntegritykcfi面向操作系统内核的前向边控制流完整性保护LeakSanitizerleak运行时内存泄漏检测器MemorySanitizermemory未初始化读操作检测器ThreadSanitizerthread快速的数据竞争data race检测器值得注意的是从当前仓库的选项定义看rustc 的 sanitizer 家族比开发指南文档列出的还要更大。compiler/rustc_session/src/options.rs 中parse_sanitizers的说明列出了完整的合法取值address、cfi、dataflow、hwaddress、kcfi、kernel-address、kernel-hwaddress、leak、memory、memtag、safestack、shadow-call-stack、thread以及realtime。这其中的dataflowDataFlowSanitizer、kernel-addressKASan、kernel-hwaddressKHWASan、memtagMemory Tagging、safestackSafeStack、shadow-call-stack与realtimeRealtimeSanitizer面向特定场景本文主体仍以开发指南文档重点介绍的七类为准。二、如何启用 Sanitizer2.1 基础用法-Z sanitizer...启用某个 sanitizer 只需在编译时传递rustc -Z sanitizeraddress main.rs # 或者针对特定目标平台 rustc --target x86_64-unknown-linux-gnu -Z sanitizeraddress main.rs其中sanitizer的取值就是上文表格中的address、cfi、hwaddress、kcfi、leak、memory、thread之一。多个 sanitizer 也可以用逗号分隔组合例如同时开启 ASan 与 LSanrustc -Z sanitizeraddress,leak main.rs该选项在 compiler/rustc_session/src/options.rs 中被定义为sanitizer: SanitizerSet通过parse_sanitizers解析为位集合SanitizerSet并与目标平台的default_sanitizers合并见 options.rs。2.2 与 Cargo 配合使用在实际项目中更常见的做法是通过 Cargo 的 rustflags 传递该标志RUSTFLAGS-Z sanitizeraddress cargo run RUSTFLAGS-Z sanitizerthread cargo test需要注意的是-Z是 rustc 的 unstable 选项需要 nightly 工具链才能使用同时目标平台必须带有对应的 sanitizer 运行时库见下文第三节。关于该标志的详细语义还可查阅 unstable book 的 sanitizer 条目。三、Sanitizer 在 rustc 中的实现机制开发指南明确指出除 CFI 外所有 sanitizer 的实现几乎完全依赖 LLVM。rustc 的角色是 LLVM 编译期插桩 pass 与运行时库之间的集成点integration point。整个实现链路可以拆解为以下五个关键环节。3.1 运行时库的构建与分发compiler-rtSanitizer 的运行时库来自 [compiler-rt] 项目。在当前仓库中bootstrap 负责为目标平台编译这些运行时库并安装到 rustc 的 libdir 中。启用方式是在bootstrap.toml即仓库根目录的 bootstrap.example.toml 模板中设置build.sanitizers true具体构建逻辑位于 src/bootstrap/src/core/build_steps/llvm.rsbootstrap 会先检查supported_sanitizers(out_dir, self.target, builder.config.channel)得出该目标平台需要构建哪些运行时然后调用 CMake 构建clang_rt.*产物最终把产物重命名为librustc-{channel}_rt.{component}.aLinux 等或.dylibDarwin并放置到目标平台的 libdir。supported_sanitizers的匹配表llvm.rs直观展示了各平台可用的运行时组合例如x86_64-unknown-linux-gnuasan、dfsan、lsan、msan、safestack、tsan、rtsan、ubsanaarch64-unknown-linux-gnuasan、lsan、msan、tsan、hwasan、rtsan、ubsanx86_64-apple-darwinasan、lsan、tsan、rtsanx86_64-unknown-freebsdasan、msan、tsan注意事项build.sanitizers true只是让 rustc 自己构建运行时库如果你使用官方预编译工具链运行时库由官方发布流程CI 中--enable-sanitizers预置在 libdir 内。3.2 函数的 LLVM 属性标记在 LLVM 代码生成阶段rustc 会为所有需要插桩的函数打上对应的 LLVM 属性attribute。核心实现在 compiler/rustc_codegen_llvm/src/attributes.rs 的sanitize_attrs函数启用 AddressSanitizer → 打SanitizeAddress属性启用 MemorySanitizer → 打SanitizeMemory属性启用 ThreadSanitizer → 打SanitizeThread属性启用 HWAddressSanitizer → 打SanitizeHWAddress属性默认情况下所有函数都会被插桩但可以通过#[sanitize(xyz on|off|other)]属性改变这一行为。该属性的解析结果存放在 compiler/rustc_middle/src/middle/codegen_fn_attrs.rs 的CodegenFnAttrs::sanitizers字段类型为SanitizerFnAttrs随后被sanitize_attrs读取let mut enabled tcx.sess.sanitizers() - sanitizer_fn_attr.disabled;即全局启用的 sanitizer 集合减去该函数显式关闭的部分。例如为某个函数单独关闭 ASan#[sanitize(address off)] fn sensitive_ffi_region() { // 该函数不会被打上 SanitizeAddress 属性 }sanitize_attrs中还集成了 ignorelist 机制SanitizerIgnoreList命中 ignorelist 的函数同样不会被打上 sanitizer 属性CFI/KCFI 场景下则会补上no-sanitize-cfi/no-sanitize-kcfi属性。3.3 函数粒度的决策限制与内联抑制是否插桩的决策只能在函数粒度上进行。当同一 crate 内不同函数的插桩决策不一致时例如全局开启 ASan 但某个函数显式关闭内联inlining可能导致未被标记的函数被内联进被标记的函数或反之从而破坏决策边界。因此需要抑制内联在 MIR 层面抑制内联rustc_mir的 inline pass 会检查函数属性在 LLVM 层面抑制内联通过 LLVM 的noinline等属性配合LLVM IR Attributes 定义中与 sanitizer 相关的部分确保插桩决策边界不被内联穿透。这也是文档中特别强调在决策不一致时可能需要在 MIR 级别和 LLVM 级别同时抑制内联的原因。3.4 专属 LLVM 插桩 Passrustc 生成的 LLVM IR 会交给 LLVM 中各自专属的插桩 pass进行改写每个 sanitizer 对应一个 pass。关键实现在 compiler/rustc_codegen_llvm/src/back/write.rsrustc 构造一个llvm::SanitizerOptions结构体把SanitizerSet逐位映射为sanitize_address、sanitize_cfi、sanitize_kcfi、sanitize_memory、sanitize_thread、sanitize_hwaddress、sanitize_dataflow、sanitize_realtime等布尔字段同时携带sanitize_memory_track_origins、sanitize_dataflow_abilist等细粒度参数然后在代码生成阶段把该配置传给 LLVM。源码注释揭示了一个重要细节插桩 pass 是在优化 pass 之后调用的Sanitizer instrumentation is only inserted during the pre-link optimization stage见 write.rs。这意味着插桩作用于优化后的 IR并且与 LTO 存在交互——代码中明确在非 LTO!is_lto时才填充sanitizer_optionsSanitizeHWAddress的某些组合场景还存在待完善的 FIXME。3.5 运行时库的链接当最终生成可执行文件时rustc 会把对应 sanitizer 的运行时库链接进来。链接逻辑位于rustc_codegen_ssa的 link 模块对应文档所述 [sanitizer-link] 部分当前仓库对应实现路径为 compiler/rustc_codegen_ssa/src/back/link.rs。搜索策略如下在目标平台的 libdir 中查找librustc-*_rt.*运行时库首先相对于被覆盖的 sysrootoverridden system root搜索随后回退到默认 sysroot 搜索。最后一步的回退机制至关重要它保证了在使用 cargo-Z build-std或 xargo 构造的 sysroot 覆盖场景下sanitizer 运行时依然可用——因为这类覆盖构建通常不会重复复制运行时库回退到默认 sysroot 可以避免链接失败。四、测试验证体系Sanitizer 功能在 rustc 仓库中通过两层测试验证见 sanitizers.md 的 Testing 章节代码生成测试位于 tests/codegen-llvm 目录文档所述sanitize*.rs测试验证生成的 LLVM IR 中是否正确出现 sanitizer 属性与插桩结构。端到端功能测试位于 tests/ui/sanitizer/ 目录真实编译并运行被插桩的程序验证 sanitizer 能否正确报告问题。当前仓库中该目录包含address.rs、thread.rs、memory.rs、leak.rs、hwaddress.rs、dataflow.rs、kcfi/、cfi/等子目录与测试文件以及若干交互/诊断类测试例如incompatible.rs验证不兼容的 sanitizer 组合被拒绝inline-always-sanitize.rs验证#[inline(always)]与 sanitizer 的交互crt-static.rs验证静态 CRT 场景unsupported-target-khwasan.rs验证不支持的平台上 KASan/KHWASan 的报错issue-72154-address-lifetime-markers.rs、issue-111184-cfi-coroutine-witness.rs回归历史 bug。运行 sanitizer 测试有两大前提已构建 sanitizer 运行时库即bootstrap.toml中build.sanitizers true并且目标平台支持对应 sanitizer。当 sanitizer 在该目标上不受支持时相关测试会被自动忽略——这一行为由 compiletest 的needs-sanitizer-*指令控制。五、在新目标平台上启用 Sanitizer如果目标平台已被 LLVM 支持为它启用某个 sanitizer 需要完成以下五步严格对应 sanitizers.md 的流程在目标定义中声明支持将该 sanitizer 加入目标定义结构体的supported_sanitizers字段。该字段位于 compiler/rustc_target/src/spec/mod.rs同文件还有default_sanitizers用于设置默认开启的 sanitizer。完成后rustc --target .. -Zsanitizer..即可识别该 sanitizer 为受支持状态。为目标构建运行时库并放入 libdir在 src/bootstrap/src/core/build_steps/llvm.rs 的supported_sanitizers匹配表中为新目标的三元组添加运行时组件列表。让 compiletest 识别新目标的支持情况修改 compiletest 的needs-sanitizer-*判定逻辑对应文档所述 [compiletest-definition]当前仓库实现位于 src/tools/compiletest/src/util.rs使带needs-sanitizer-*标记的测试能在新目标上运行。运行测试验证./x test --force-rerun tests/ui/sanitizer/注意文档原文写的是tests/ui/sanitize/而当前仓库中端到端测试实际位于 tests/ui/sanitizer/ 目录运行时应以仓库实际目录为准。在 CI 配置中启用分发在 CI 的 Dockerfile 等配置中增加--enable-sanitizers标志使 sanitizer 运行时随官方发布流程一起构建并分发。六、进阶配置项速览围绕-Z sanitizer...rustc 还提供了一组配套的 unstable 选项全部定义在 compiler/rustc_session/src/options.rs选项作用-Z sanitizer...启用指定 sanitizer逗号分隔可组合-Z sanitizer-ignorelist文件提供 sanitizer ignorelist 文件命中函数不插桩-Z sanitizer-cfi-canonical-jump-tablesCFI 规范跳转表默认开启-Z sanitizer-cfi-generalize-pointersCFI 指针泛化-Z sanitizer-cfi-normalize-integers归一化整数类型KCFI 场景下为 target modifier-Z sanitizer-cfi-diagCFI 诊断信息-Z sanitizer-cfi-recoverCFI 恢复模式-Z sanitizer-dataflow-abilist列表DataFlowSanitizer 的 ABI 列表-Z sanitizer-kcfi-arityKCFI 函数签名 arity 检查-Z sanitizer-memory-track-origins0/1/2MSan 的未初始化来源追踪级别-Z sanitizer-recover...对选中的 sanitizer 启用恢复模式检测到问题后继续执行此外部分 sanitizer 被标记为target modifier见 options.rs即它们的设置必须与目标平台一致跨目标增量编译时若 sanitizer 集合不同会触发一致性检查——这主要是为了保障增量编译缓存增量产物按 sanitizer 集合区分的正确性。源码中还体现了一个细节sanitize_memory_track_origins的合法取值是 0、1、2其中 2 会记录完整的未初始化来源调用栈。七、总结rustc 的 sanitizer 支持是一套LLVM 插桩 compiler-rt 运行时的成熟集成通过-Z sanitizer...一行启用通过#[sanitize]属性按函数精细调控由 bootstrap 按目标平台构建并分发运行时库最终由链接阶段智能搜索 sysroot 完成装配。对于开发者而言日常排查内存错误用address、数据竞争用thread、未初始化读用memory、内核与底层系统用kcfi/kernel-address即可获得与 C/C 生态同级的动态分析能力。若希望在新平台启用 sanitizer参照第五节五步流程即可将支持面扩展到任意 LLVM 已支持的目标。【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考