从零构建LLVM Sanitizer:编译器插桩技术实战指南

本文详细解析如何基于LLVM框架构建自定义代码检测工具(Sanitizer),涵盖编译器驱动架构、LLVM IR转换、运行时库集成等核心技术,为静态分析和动态检测提供实践指导。

从希望与梦想构建LLVM Sanitizer - Trail of Bits博客

Carson Harmon
2019年6月25日
编译器, 实习项目, 静态分析

每年,Trail of Bits都会举办为期一个月的冬季实习项目(称为"winternship")。今年我们很高兴接待了4名实习生,他们参与了3个项目。本项目来自Carson Harmon,普渡大学新任毕业生,对编译器和系统工程感兴趣,现为我们研究实践部门的全职成员。

我最初计划在LLVM中实现动态指向分析(points-to analysis)作为实习项目。指向分析能够告诉我们,在程序中给定地址或指针处,内存中存储的数据类型,以及可能的内存分配位置。这项技术很有价值,因为它有助于评估静态分析的准确性,为静态分析补充额外信息,并提供特定对象创建时机和位置的上下文。

LLVM sanitizer基础设施为实现此类分析提供了天然平台。Sanitizers可以在编译时对程序进行插桩,并包含一个运行时支持库,其中提供libc实现、函数替换接口、内存和线程管理、操作系统特定的系统调用支持等功能。事实上,许多开发者日常使用的现有bug检测工具都是作为sanitizer实现的,包括:

  • AddressSanitizer:检测无效的栈和堆访问、释放后使用等内存访问错误
  • MemorySanitizer:检测未初始化的读取操作
  • LeakSanitizer:定位内存泄漏
  • UndefinedBehaviorSanitizer:检测未定义行为(如整数溢出和位移越界)

遗憾的是,由于缺乏LLVM sanitizers的文档,我的指向分析没有取得很大进展。相反,我将概述LLVM的高级架构以及如何使用它创建sanitizers,重现在实习期间经历的步骤。

如何编写自己的LLVM sanitizer

我首先研究了Eli Bendersky的博客和GitHub仓库、Adrian Sampson博士的博客、EuroLLVM的会议记录以及LLVM庞大的工具链文档。部分信息已经过时,但这些资料帮助我确定了构建自定义sanitizer需要交互的模块。

编译器驱动(compiler driver)是LLVM中所有模块之间的粘合剂;任何需要在模块之间传递的信息都通过驱动进行。构建sanitizer可能需要修改这些组件中的任意部分。我发现理解驱动设计对于系统开发至关重要。

clang前端、LLVM IR、compiler-rt和编译器驱动之间的高层关系

如果分析过程或sanitizer想要修改LLVM类型生成过程,它们需要修改驱动的代码生成过程。

代码生成过程中clang类型的数据流

驱动还负责调度和运行LLVM通道(passes)。LLVM通道通过修改IR来插入、删除或替换指令,这特别有用,因为它允许sanitizers修改指令并插入函数调用,而无需开发者额外努力。驱动根据传递给前端的配置设置注册通道。Sanitizer通道应该最后运行,因为驱动可能会运行优化通道、分析通道或其他可能影响插桩的转换通道。

通道管理器与驱动之间的交互

最后,驱动负责链接sanitizer的运行时组件compiler-rt。Compiler-rt是一个库,为sanitizers提供在执行期间与目标程序交互的能力。这种交互可以通过LLVM转换通道插入对compiler-rt中定义函数的调用来实现,或者使用compiler-rt的函数钩子接口。

转换通道与运行时组件之间的交互

创建自己的sanitizer

我创建了一个教程来帮助构建自己的sanitizer,其中包括预构建的测试sanitizer、关于开发和集成通道及运行时组件的分步指南,以及在LLVM上开发的其他有用资源。我认为winternship是在寒假学习新知识的绝佳方式,希望更多公司提供类似项目。

如果你喜欢这篇文章,请分享: Twitter LinkedIn GitHub Mastodon Hacker News


近期文章
使用Deptective调查依赖项
做好准备,AIxCC评分轮正在进行中!
让智能合约超越私钥风险的成熟之路
Go解析器中意想不到的安全陷阱
我们从审查首批DKLs23库中学到的经验
来自Silence Laboratories的23个库

© 2025 Trail of Bits.
使用Hugo和Mainroad主题生成。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计