1.获取LLVM工程
官方github页:github.com/llvm/llvm-p…
git clone https://github.com/llvm/llvm-project.git
这里我直接使用了main分支,应该对应的是LLVM 13
2.基于legacy pass manager的pass编写
这里基本上可以参考官方文档LLVMHEllo案例 Writing an LLVM Pass
在llvm-project/llvm/lib/Transforms文件夹下新建自定义的pass文件夹,这里有官方的其他pass案例
cd llvm-project/llvm/lib/Transforms
mkdir MyPass
在MyPass文件夹下新建一个 CMakeLists.txt
写入
add_llvm_library( MYPass MODULE
MYPass.cpp
DEPENDS
intrinsics_gen
PLUGIN_TOOL
opt
)
复制代码
在llvm-project/llvm/lib/Transforms/CMakeLists.txt中,添加文件夹目录
add_subdirectory(MYPass)
复制代码
在MyPass文件夹下,编写pass代码MyPass.cpp
这里我直接从Hello文件下,把官方案例拷贝过来改了改。这个pass的作用是在处理方法时打印方法名。
想写插桩Pass的话,可以参考LLVM – Pass 实现 C函数 插桩
3.构建和编译LLVM
构建流程可以参考官方git
关于生成器的选择,我本机是MacOS,首先尝试了Ninja,因为没有选择target,也不知道有哪些target。执行cmake –build build后。整个build文件夹占了50G。之后还是Xcode选来构建和编译,使用Xcode对我来说更友好一些。可以更方便地选择需要的target进行编译。不至于占用那么大的磁盘空间。关于使用Xcode编译LLVM也可以参考这篇文章:开发 clang 插件:0 基础感受底层组
cd /yourPath/llvm-project
cmake -S llvm -B build -G Xcode -DLLVM_ENABLE_PROJECTS="clang;libcxx;libcxxabi"
关于DLLVM_ENABLE_PROJECTS参数的选择,有部分project无法MacOS上生成构建文件。我在选择libc的时报错了。
构建的这部分也可以参考 开发 clang 插件:0 基础感受底层组
等待一段时间后,可以在llvm-project目录下,找到build目录,就是刚刚用Xcode生成的构建文件。
打开llvm-project/build目录下的LLVM.xcodeproj,会跳出自动创建Schemes的弹窗。选择自动生成Automatically Create Schemes会生成一大堆Scheme。如果明确知道自己需要哪些,可以选择手动配置。这里我选择自动。
点击当前Scheme的最底下的Manage Schemes
通过filter过滤,查找想要的Scheme,双击选择,然后进行编译
这里我选择了clang、opt和MyPass
编译完成后,可在llvm-project/build/Debug/bin目录下找到编译后的可执行文件,以及llvm-project/build/Debug/lib目录下找到LLVM pass的动态库
4.测试
新建一个c文件来测试pass。
touch ctest.c
文件内容:
int main() {
return 0;
}
复制代码
touch ctest.c
先编译成IR
yourpath/llvm-project/build/Debug/bin/clang -emit-llvm -S ctest.c -o ctest.ll
使用opt测试
yourpath/llvm-project/build/Debug/bin/opt -load yourpath/llvm-project/build/Debug/lib/MYPass.dylib -mypass ctest.ll -enable-new-pm=0
成功打印
使用clang测试
yourpath/llvm-project/build/Debug/bin/clang ctest.c -Xclang -load -Xclang yourpath/llvm-project/build/Debug/lib/MYPass.dylib -flegacy-pass-manager
成功打印
特别注意opt的参数enable-new-pm和clang的参数flegacy-pass-manager。启用flegacy pass manager。这里也可以参考 LLVM IR 的第一个 Pass:上手官方文档 Hello Pass
flegacy-pass-manager参数会在将来移除
5.在Xcode中使用
新建一个Xcode iPhone APP工程。
在Build Settings中添加User-Defined
CC yourpath/llvm-project/build/Debug/bin/clang
CXX yourpath/llvm-project/build/Debug/bin/clang++
复制代码
让Xcode使用我们编译的clang
在Build Settings的Other C Flags添加clang加载pass动态库的参数
-Xclang -load -Xclang yourpath/llvm-project/build/Debug/lib/MYPass.dylib -flegacy-pass-manager
修改Build Settings中的Eable Index-While-Building Funcitonality为NO
配置完成 command+B开始编译项目
生效
自此,基于legacy pass manager的pass使用结束。
6.基于new pass manager的pass编写
这里可以参考官方文档,以下都是官方文档的步骤
新建文件 llvm-project/llvm/include/llvm/Transforms/Utils/MyNewPass.h
可以参考同级目录下的HelloWorld.h
#ifndef LLVM_TRANSFORMS_UTILS_MyNewPass_H
#define LLVM_TRANSFORMS_UTILS_MyNewPass_H
#include "llvm/IR/PassManager.h"
namespace llvm {
class MyNewPass : public PassInfoMixin<MyNewPass> {
public:
PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
};
} // namespace llvm
#endif // LLVM_TRANSFORMS_UTILS_HELLOWORLD_H
复制代码
新建文件llvm-project/llvm/lib/Transforms/Utils/MyNewPass.cpp,代码作用还是一样,打印方法名
#include "llvm/Transforms/Utils/MyNewPass.h"
using namespace llvm;
PreservedAnalyses MyNewPass::run(Function &F,
FunctionAnalysisManager &AM) {
errs() << F.getName() << "\n";
return PreservedAnalyses::all();
}
复制代码
修改文件llvm-project/llvm/lib/Passes/PassRegistry.def,
新增一条
FUNCTION_PASS("mynewpass", MyNewPass())
修改文件llvm-project/llvm/lib/Passes/PassBuilder.cpp,
新增头文件引入
#include "llvm/Transforms/Utils/MyNewPass.h"
在Xcode的LLVMTransformUtils文件夹下引入刚刚新建pass头文件和代码文件
在Xcode中重新编译opt
7.测试
新建一个IR文件
touch a.ll
写入官方案例IR代码
define i32 @foo() {
%a = add i32 2, 3
ret i32 %a
}
define void @bar() {
ret void
}
复制代码
让重新编译的opt使用pass
llvm-project/build/Debug/bin/opt -disable-output a.ll -passes=mynewpass
复制代码
测试通过
使用opt使用pass处理上一个例子的ctest.ll
llvm-project/build/Debug/bin/opt -disable-output ctest.ll -passes=mynewpass
测试失败,没有输出
这里的ctest.ll是在上一个例子中,用clang编译出来的。在官方文档的最后有提到,pass有被跳过的可能性,需要增加isRequired方法
修改llvm-project/llvm/include/llvm/Transforms/Utils/MyNewPass.h,增加isRequired方法
#ifndef LLVM_TRANSFORMS_UTILS_MyNewPass_H
#define LLVM_TRANSFORMS_UTILS_MyNewPass_H
#include "llvm/IR/PassManager.h"
namespace llvm {
class MyNewPass : public PassInfoMixin<MyNewPass> {
public:
PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
static bool isRequired() { return true; }
};
} // namespace llvm
#endif // LLVM_TRANSFORMS_UTILS_HELLOWORLD_H
复制代码
重新编译opt
再次测试ctest.ll,测试通过
8.在Xcode中使用new pass manager的pass
在legcy pass manager中,我们是通过Xlang给clang传参-Xclang -load -Xclang llvm-project/build/Debug/lib/MYPass.dylib,来使用pass。所以可否把-passes=mynewpass传过去呢?
经过尝试,是不行的
查看llvm-project/build/Debug/bin/clang –help-hidden,感觉应该是-fpass-plugin
在stackoverflow上也有一个帖子的朋友是使用-fpass-plugin来加载的
这里采用另一种方式把pass加入到clang的编译流程中,详见官方文档
官方文档中写到,可以参考BackendUtil.cpp中的Clang添加pass的方式。
为了让打印更明显,我在llvm-project/llvm/lib/Transforms/Utils/MyNewPass.cpp加了一行
#include "llvm/Transforms/Utils/MyNewPass.h"
using namespace llvm;
PreservedAnalyses MyNewPass::run(Function &F,
FunctionAnalysisManager &AM) {
errs() << "MyNewPass: ";
errs() << F.getName() << "\n";
return PreservedAnalyses::all();
}
复制代码
修改项目中的BackendUtil.cpp
引入头文件
#include "llvm/Transforms/Utils/MyNewPass.h"
搜索 registerPipelineStartEPCallback找到添加其他pass的位置
添加我的pass
PB.registerPipelineStartEPCallback(
[](ModulePassManager &MPM, OptimizationLevel Level) {
MPM.addPass(
createModuleToFunctionPassAdaptor(MyNewPass()));
});
复制代码
这里可以看到其他pass都是有条件地添加。比如判断优化等级OptimizationLevel。这里我选择把我的pass无条件添加。
重新编译clang
回到之前创建的Xcode iPhone 测试工程
删除Build Settings的Other C Flags中加载legacy pass的参数
先清理一下编译缓存,Product-Clean Build Folder,然后编译项目
测试成功
9.最后
我本意是想做一下swift的代码插桩,在swift函数中插入一些代码。考虑到swift的编译前端是swiftc,以及LLVM pass是作用于LLVM IR的,所以想看一下如何使用LLVM pass。实际看下来,
LLVM pass的使用和编译器前端密不可分。要么是需要clang -load要么需要修改clang加载pass代码。看来还是得看一下swiftc。