在Xcode中使用自定义LLVM Pass

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

111.png

写入

add_llvm_library( MYPass MODULE
  MYPass.cpp

  DEPENDS
  intrinsics_gen
  PLUGIN_TOOL
  opt
  )
复制代码

在llvm-project/llvm/lib/Transforms/CMakeLists.txt中,添加文件夹目录

add_subdirectory(MYPass)
复制代码

222.png

在MyPass文件夹下,编写pass代码MyPass.cpp
这里我直接从Hello文件下,把官方案例拷贝过来改了改。这个pass的作用是在处理方法时打印方法名。
想写插桩Pass的话,可以参考LLVM – Pass 实现 C函数 插桩

333.png

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。如果明确知道自己需要哪些,可以选择手动配置。这里我选择自动。

444.png

点击当前Scheme的最底下的Manage Schemes

666.png

通过filter过滤,查找想要的Scheme,双击选择,然后进行编译

777.png

这里我选择了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

成功打印
22.png

使用clang测试

yourpath/llvm-project/build/Debug/bin/clang ctest.c -Xclang -load -Xclang yourpath/llvm-project/build/Debug/lib/MYPass.dylib -flegacy-pass-manager

成功打印

33.png

特别注意opt的参数enable-new-pm和clang的参数flegacy-pass-manager。启用flegacy pass manager。这里也可以参考 LLVM IR 的第一个 Pass:上手官方文档 Hello Pass

11.png
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++
复制代码

44.png

让Xcode使用我们编译的clang

在Build Settings的Other C Flags添加clang加载pass动态库的参数

-Xclang -load -Xclang yourpath/llvm-project/build/Debug/lib/MYPass.dylib -flegacy-pass-manager

55.png

修改Build Settings中的Eable Index-While-Building Funcitonality为NO

66.png

配置完成 command+B开始编译项目
生效

77.png

自此,基于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())

55.png

修改文件llvm-project/llvm/lib/Passes/PassBuilder.cpp,
新增头文件引入
#include "llvm/Transforms/Utils/MyNewPass.h"

在Xcode的LLVMTransformUtils文件夹下引入刚刚新建pass头文件和代码文件

22.png

33.png
在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
复制代码

测试通过

44.png

使用opt使用pass处理上一个例子的ctest.ll

llvm-project/build/Debug/bin/opt -disable-output ctest.ll -passes=mynewpass

测试失败,没有输出

这里的ctest.ll是在上一个例子中,用clang编译出来的。在官方文档的最后有提到,pass有被跳过的可能性,需要增加isRequired方法

55.png

修改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,测试通过

66.png

8.在Xcode中使用new pass manager的pass

在legcy pass manager中,我们是通过Xlang给clang传参-Xclang -load -Xclang llvm-project/build/Debug/lib/MYPass.dylib,来使用pass。所以可否把-passes=mynewpass传过去呢?

77.png

经过尝试,是不行的

查看llvm-project/build/Debug/bin/clang –help-hidden,感觉应该是-fpass-plugin

88.png

在stackoverflow上也有一个帖子的朋友是使用-fpass-plugin来加载的

99.png

这里采用另一种方式把pass加入到clang的编译流程中,详见官方文档

11.png

官方文档中写到,可以参考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()));
        });
复制代码

22.png

这里可以看到其他pass都是有条件地添加。比如判断优化等级OptimizationLevel。这里我选择把我的pass无条件添加。

重新编译clang

回到之前创建的Xcode iPhone 测试工程
删除Build Settings的Other C Flags中加载legacy pass的参数

33.png

先清理一下编译缓存,Product-Clean Build Folder,然后编译项目

44.png

测试成功

9.最后
我本意是想做一下swift的代码插桩,在swift函数中插入一些代码。考虑到swift的编译前端是swiftc,以及LLVM pass是作用于LLVM IR的,所以想看一下如何使用LLVM pass。实际看下来,
LLVM pass的使用和编译器前端密不可分。要么是需要clang -load要么需要修改clang加载pass代码。看来还是得看一下swiftc。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享