LLVM15加载自定义pass的方法

​ 最近学习LLVM,尝试编写LLVM Pass,发现网上的许多博客文章加载自定义Pass的方式仍然停留在旧版方法,LLVM13开始对PassManager进行了变更,在LLVM13-14版本可以采用临时方案-flegacy-pass-manager解决,但是到了LLVM15+,旧版的LegacyPassManager只能用opt加载,所以最好是学习使用新版的PassManager。

1. 编写自定义Pass(LLVM15+opt适配旧版PassManager)

参考LLVM官方Developing LLVM passes out of source

(1)创建目录结构,当前目录llvmPass下:

1
2
3
4
5
.
├── CMakeLists.txt
└── FuncName
├── CMakeLists.txt
└── FuncEncrypt.cpp

(2)llvmPass/CMakeLists:

先添加环境变量$LLVM_HOME 指向LLVM15下的LLVM-llvmorg-15.0.6/llvm/build,编写pass时需要用到该目录下的库文件等。

1
2
3
4
5
6
7
8
9
10
11
12
if(NOT DEFINED ENV{LLVM_HOME})
message(FATAL_ERROR "$LLVM_HOME is not defined")
endif()
if(NOT DEFINED ENV{LLVM_DIR})
set(ENV{LLVM_DIR} $ENV{LLVM_HOME}/lib/cmake/llvm)
endif()
find_package(LLVM REQUIRED CONFIG)
add_definitions(${LLVM_DEFINITIONS})
include_directories(${LLVM_INCLUDE_DIRS})
link_directories(${LLVM_LIBRARY_DIRS})

add_subdirectory(FuncName)

(3)llvmPass/FuncName/CMakeList.txt:

1
2
3
4
5
SET(CMAKE_CXX_FLAGS "-Wall -fno-rtti")

add_library(FuncEncryptPass MODULE
FuncEncrypt.cpp
)

(4)llvmPass/FuncEncrypt.cpp:

简单实现自定义Pass的功能:将程序函数名修改为encryptedXXX

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include "llvm/Pass.h"
#include "llvm/IR/Function.h"
#include "llvm/Support/raw_ostream.h"

#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Transforms/IPO/PassManagerBuilder.h"
#include "string.h"

using namespace llvm;

int i = 0;
namespace {
struct FuncEncrypt : public FunctionPass {
static char ID;
FuncEncrypt() : FunctionPass(ID) {}

bool runOnFunction(Function &F) override {
if(F.getName().compare("main")){
char name[30] = {0};
sprintf(name, "encrypted%d", i++);
F.setName(name);
}
return false;
}
}; // end of struct Hello
} // end of anonymous namespace

char FuncEncrypt::ID = 0;
static RegisterPass<FuncEncrypt> X("FuncEncrypt", "A Pass to Encrypt Function Name",
false /* Only looks at CFG */,
false /* Analysis Pass */);

static RegisterStandardPasses Y(
PassManagerBuilder::EP_EarlyAsPossible,
[](const PassManagerBuilder &Builder,
legacy::PassManagerBase &PM) { PM.add(new FuncEncrypt()); });

cmake构建后,生成libFuncEncryptPass.so,查看下注册结果:

1
2
opt -load ./llvmPass/build/FuncName/libFuncEncryptPass.so -help | grep Encrypt
--FuncEncrypt - A Pass to Encrypt Function Name

编写一个test.c文件,随便定义几个函数:

1
2
3
4
5
6
7
8
9
func1(){
...
}
func2(){
...
}
int main(){
...
}

采用llvm15方案加载自定义pass:

clang:

1
2
$ clang -fpass-plugin=./llvmPass/build/FuncName/libFuncEncryptPass.so test.c                             error: unable to load plugin './llvmPass/build/FuncName/libFuncEncryptPass.so': 'Plugin entry point not found in './llvmPass/build/FuncName/libFuncEncryptPass.so'. Is this a legacy plugin?'
1 error generated.

opt:

先编成IR:

1
clang -emit-llvm test.c -S -o test.ll
1
2
3
4
5
$ opt -load-pass-plugin ./llvmPass/build/FuncName/libFuncEncryptPass.so -S -passes=FuncEncrypt test.ll -o test1.ll
Failed to load passes from './llvmPass/build/FuncName/libFuncEncryptPass.so'. Request ignored.
Expected<T> must be checked before access or destruction.
Unchecked Expected<T> contained error:
Plugin entry point not found in './llvmPass/build/FuncName/libFuncEncryptPass.so'. Is this a legacy plugin?PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace.

可以看到两者都提示没有找到plugin entry point

如开头所说,这是因为LLVM13开始采用了新的PassManager,对于opt可以增加-enable-new-pm=0解决:

1
2
3
4
$ opt -load ./llvmPass/build/FuncName/libFuncEncryptPass.so -S --FuncEncrypt -enable-new-pm=0 test.ll -o test1.ll
$ clang test1.ll -o test && nm test | grep encrypted
0000000000001140 T encrypted0
0000000000001180 T encrypted1

可以看到test.c中两个函数名都被成功修改。

2. NewPassManager方案(LLVM13+)

可能存在的问题

(1)执行clang 或 opt 后 提示 undefined symbol: _ZTIN4llvm12FunctionPassE:

参考:https://stackoverflow.com/questions/17225956/developing-an-llvm-pass-with-cmake-out-of-llvm-source-directory

Undefined Symbol: _ZTIN4llvm12FunctionPassE There is an inconsistency between LLVM main build system and the cmake support for building out-of-source. The LLVM binaries are built without runtime type info “-fno-rtti”. Therefore, the out-of-source passes have to be built the same way otherwise opt will complain that symbol “_ZTIN4llvm12FunctionPassE” is undefined.

To solve this problem, LLVM must be compile with RTTI enabled. Add “-DLLVM_REQUIRES_RTTI=1” to cmake, or add “REQUIRES_RTTI=1” to make.

意思是编译LLVM时没有开启RTTI 所以自定义pass也应该以相同方式构建,在CMakeLists中加一行:SET(CMAKE_CXX_FLAGS “-Wall -fno-rtti”)

参考链接

https://releases.llvm.org/15.0.0/docs/WritingAnLLVMPass.html#introduction-what-is-a-pass

https://github.com/banach-space/llvm-tutor/

https://www.leadroyal.cn/categories/llvm/