llvm Pass
简介
- Pass就是“遍历一遍IR,可以同时对它做一些操作”的意思。翻译成中文应该叫“传递”。
- 在实现上,LLVM的核心库中会给你一些 Pass类 去继承。你需要实现它的一些方法。
- ModulePass , CallGraphSCCPass, FunctionPass , or LoopPass, or RegionPass
- 最后使用LLVM的编译器会把它翻译得到的IR传入Pass里,给你遍历和修改。
作用
- 插桩: 在Pass遍历LLVM IR的同时,自然就可以往里面插入新的代码。
- 机器无关的代码优化:编译原理一课说:IR在被翻译成机器码前会做一些机器无关的优化。 但是不同的优化方法之间需要解耦,所以自然要各自遍历一遍IR,实现成了一个个LLVM Pass。 最终,基于LLVM的编译器会在前端生成LLVM IR后调用一些LLVM Pass做机器无关优化, 然后再调用LLVM后端生成目标平台代码。
- 静态分析: 像VSCode的C/C++插件就会用LLVM Pass来分析代码,提示可能的错误 (无用的变量、无法到达的代码等等)。
理解 llvm Pass
理解Pass API
Pass类是实现优化的主要资源。然而,我们从不直接使用它,而是通过清楚的子类使用它。当实现一个Pass时,你应该选择适合你的Pass的最佳粒度,适合此粒度的最佳子类,例如基于函数、模块、循环、强联通区域,等等。常见的这些子类如下:
ModulePass
:这是最通用的Pass;它一次分析整个模块,函数的次序不确定。它不限定使用者的行为,允许删除函数和其它修改。为了使用它,你需要写一个类继承ModulePass,并重载runOnModule()方法。FunctionPass
:这个子类允许一次处理一个函数,处理函数的次序不确定。这是应用最多的Pass类型。它禁止修改外部函数、删除函数、删除全局变量。为了使用它,需要写一个它的子类,重载runOnFunction()方法。BasicBlockPass
:这个类的粒度是基本块。FunctionPass类禁止的修改在这里也是禁止的。它还禁止修改或者删除外部基本块。使用者需要写一个类继承BasicBlockPass,并重载它的runOnBasicBlock()方法。
被重载的入口函数runOnModule()、runOnFunction()、runOnBasicBlock()返回布尔值false,如果被分析的单元(模块、函数和基本块)保持不变,否则返回布尔值true。
Pass的执行顺序/依赖
- ChatGPT说默认顺序是:FunctionPass -> Module Pass -> LoopPass ?
- 当然我们是可以修改插入Pass的执行顺序的。
1 | char PIMProf::AnnotationInjection::ID = 0; |
流程
- 编写LLVM pass代码
- 配置编译环境(cmake or make)
- 运行(opt or clang)
1 代码框架
最简单框架hello.cpp如下,注意Important
一定需要:
1 |
|
2 编译动态库
使用cmake
参考官方文档。
An example of a project layout is provided below.
1 | <project dir>/ |
Contents of <project dir>/CMakeLists.txt
:
1 | find_package(LLVM REQUIRED CONFIG) |
Contents of <project dir>/<pass name>/CMakeLists.txt
:
1 | add_library(LLVMPassname MODULE Pass.cpp) |
运行cmake编译。产生LLVMPassname.so
文件
1 | mkdir build && cd build |
使用命令行
请阅读知乎的文章
3 使用
opt加载Pass
1 | clang -c -emit-llvm main.c -o main.bc # 随意写一个C代码并编译到bc格式 |
把源代码编译成IR代码,然后用opt运行Pass实在麻烦且无趣。
clang加载Pass
1 | clang -Xclang -load -Xclang path/to/LLVMHello.so main.c -o main |
实践
插入代码
1 | void InjectSimMagic2(Module &M, Instruction *insertPt, uint64_t arg0, uint64_t arg1, uint64_t arg2) |
这段代码使用内联汇编嵌入到 LLVM IR 中,指令如下:
1 | mov $0, %rax |
其中:
- mov $0, %rax 将立即数 arg0 装载到通用寄存器 %rax 中。
- mov $1, %rbx 将立即数 arg1 装载到通用寄存器 %rbx 中。
- mov $2, %rcx 将立即数 arg2 装载到通用寄存器 %rcx 中。
- xchg %bx, %bx 是一条无操作指令,用于保证该汇编代码的原子性。
打印每个BBL内的汇编指令
由于直接打印的是llvm IR的表示,想要打印特定架构比如x86的汇编代码,其实需要进行llvm后端的转换。(取巧,可执行文件反汇编,然后根据插入的汇编桩划分)
1 |
需要进一步的研究学习
暂无
遇到的问题
暂无
开题缘由、总结、反思、吐槽~~
复现PIMProf论文时,用到了使用 llvm pass来插入特殊汇编