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

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

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

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

我原本计划在winternship期间实现LLVM的动态指向分析(points-to analysis)。指向分析可以告诉我们,在程序中给定地址或指针处,内存中存储的数据类型,以及可能的内存分配位置。这很有用,因为它有助于评估静态分析的准确性,用额外事实增强静态分析,并提供某些对象创建时间和位置的上下文。

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

  • AddressSanitizer:识别无效的堆栈和堆访问、释放后使用和其他类型的内存访问错误
  • MemorySanitizer:检测未初始化的读取
  • LeakSanitizer:定位内存泄漏
  • UndefinedBehaviorSanitizer:检测未定义行为(例如整数溢出和过度移位)

不幸的是,由于缺乏关于LLVM sanitizers的文档,我的指向分析没有取得很大进展。相反,我将提供LLVM的高层概述以及如何使用它来制作sanitizers,重述我在winternship期间经历的步骤。

如何编写自己的LLVM sanitizer

我首先回顾了Eli Bendersky的博客和GitHub仓库、Adrian Sampson博士的博客、EuroLLVM的会议记录以及LLVM的广泛工具链文档。我找到的一些信息已经过时,但它帮助确定了构建自己的sanitizer需要交互的模块。

编译器驱动程序是LLVM中所有模块之间的粘合剂;需要在模块之间传递的任何信息都通过驱动程序传递。构建sanitizer可能需要修改这些组件中的任意数量。我发现理解驱动程序设计对于系统开发很重要。

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

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

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

驱动程序还负责调度和运行LLVM passes。LLVM passes修改IR以插入、删除或替换指令,这特别有用,因为它允许sanitizers修改指令和插入函数调用,而无需开发人员任何额外努力。驱动程序基于传递给前端的配置设置注册passes。Sanitizer passes应注册为最后运行,因为驱动程序可能会运行优化passes、分析passes或其他可能影响你插桩的转换passes。

传递管理器和驱动程序之间的交互

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

转换传递和运行时组件之间的交互

制作自己的sanitizer

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

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