自定义LLVM Pass
LLVM实现了一系列的分析和转换Pass,所有的LLVM Pass都是pass类的子类,并且通过覆写了从pass类继承的虚函数以实现其功能。任何一个Pass都是Pass LLVM的实例
先看看最简单的Pass
一般,用户自定义的Pass放在llvm-project-9.0.1/llvm/lib/Transforms 目录下
在Transforms目录下有一个Hello目录,这是一个demo Pass
图中划框的含义:
1、用户自定义的Pass一般是放在 llvm/lib/Transforms目录下
2、引入必要的头文件,引入llvm命名空间,以使用其中的LLVM函数
3、由于是要对函数做处理,所以Hello Pass 需要继承 llvm::FunctionPass
4、重写llvm::FunctionPass中的runOnFunction方法,来实现自定义的处理要求 : Hello Pass 的逻辑是在终端打印函数名称
5、注册Hello Pass ,在opt命令调用Hello Pass时,需要提供什么样的参数才能调用到这个Hello Pass
(5之后的代码:可以在cpp文件中实现多个Pass,不需关心)
如何使用这个Hello Pass
1、在编译llvm基础库和clang时,hello pass已经编译好了,位置:
/home/showme/androidSec/llvm/llvm-project-9.0.1/llvm/cmake-build-debug/lib/LLVMHello.so
2、将clang、opt等指令添加到环境变量中
export PATH="/home/showme/androidSec/llvm/llvm-project-9.0.1/llvm/cmake-build-debug/bin:$PATH"
3、写一个测试代码 test.c
#include<stdio.h>
void test_func2();
void test_func1()
{
printf("test_func1 content\r\n");
}
void test_func2()
{
printf("test_func2 context\r\n");
}
int main(int argc,char** argv)
{
printf("main func\r\n");
test_func2();
test_func1();
}
4、将测试代码编译为 IR 或者 llvm bitcode
clang -emit-llvm -S test.c -o test.ll
llvm-as test.ll -o test.bc
5、调用 hello pass , 可以看到hello pass是以函数实现的顺序进行处理的,(不会优化外部的导出函数,printf,也优化不到,没有相关的IR、llvm bitcode代码)
opt -load /home/showme/androidSec/llvm/llvm-project-9.0.1/llvm/cmake-build-debug/lib/LLVMHello.so -hello test.ll
opt -load /home/showme/androidSec/llvm/llvm-project-9.0.1/llvm/cmake-build-debug/lib/LLVMHello.so -hello test.bc
6、结果
Hello: test_func1
Hello: test_func2
Hello: main
如何调试opt 和 opt调用的pass
1、点击 Edit Configurations
2、搜索 opt , 并填写Program arguments
参数就是 调用opt时,后面跟的参数(注意文件写绝对地址)
-load /home/showme/androidSec/llvm/llvm-project-9.0.1/llvm/cmake-build-debug/lib/LLVMHello.so -hello /home/showme/tmpp/test.ll
3、下断点
找到 opt的源码位置 llvm/tools/opt/opt.cpp下断点
再到我们调用的 hello pass中下断点
创建Pass:对函数中的基本快进行计数
参考 https://llvm.org/docs/WritingAnLLVMPass.html#quick-start-writing-hello-world (文档很详细)
设置clion : File->Setting->Build,Execution,Deployment->CMake:选中Reload Cmake project on editing CMakeLists.txt
模仿Hello Pass,创建FuncBlockCount Pass
1、在llvm-project-9.0.1/llvm/lib/Transforms目录下新建FuncBlockCount文件夹
2、在FuncBlockCount文件夹下创建CMakeLists.txt文件
将hello pass的 CMakeLists.txt 复制到 FuncBlockCount pass的 CMakeLists.txt中
并修改如下
add_llvm_library( LLVMFuncBlockCount MODULE BUILDTREE_ONLY
FuncBlockCount.cpp
DEPENDS
intrinsics_gen
PLUGIN_TOOL
opt
)
3、在FuncBlockCount文件夹下创建FuncBlockCount.cpp文件
#include "llvm/ADT/Statistic.h"
#include "llvm/IR/Function.h"
#include "llvm/Pass.h"
#include "llvm/Support/raw_ostream.h"
#include <llvm/Analysis/LoopInfo.h>
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Transforms/IPO/PassManagerBuilder.h"
using namespace llvm;
namespace {
struct FuncBlockCount : public FunctionPass {
static char ID; // Pass identification, replacement for typeid
FuncBlockCount() : FunctionPass(ID) {}
void countBlocksInLoop(Loop* L,unsigned nest){
unsigned num_blocks = 0;
Loop::block_iterator bb;
for(bb = L->block_begin();bb != L->block_end();++bb){
num_blocks++;
errs() << "Loop level " << nest << " has " << num_blocks << "blocks\n";
std::vector<Loop*> subLoops = L->getSubLoops();
Loop::iterator j,f;
for(j=subLoops.begin(),f=subLoops.end();j!=f;++j){
countBlocksInLoop(*j,nest+1);
}
}
}
bool runOnFunction(Function &F) override {
LoopInfo *LI = &getAnalysis<LoopInfoWrapperPass>().getLoopInfo();
errs() << "Funcion = " << F.getName() + " ";
for(Loop* L : *LI){
countBlocksInLoop(L,0);
}
return false;
}
// 参考 :https://blog.csdn.net/qq_36681922/article/details/103395313
// 其中getAnalysisUsage函数重载了原来Pass中的对应函数,告诉Pass管理器,我们的Pass依赖于LoopInfoWrapperPass
void getAnalysisUsage(AnalysisUsage &AU) const override {
AU.addRequired<LoopInfoWrapperPass>();
//非常重要的代码段
}
};
}
char FuncBlockCount::ID = 0;
static RegisterPass<FuncBlockCount> X("funcBlockCount", "func Block Count Pass");
static llvm::RegisterStandardPasses Y(
llvm::PassManagerBuilder::EP_EarlyAsPossible,
[](const llvm::PassManagerBuilder &Builder,
llvm::legacy::PassManagerBase &PM) { PM.add(new FuncBlockCount()); });
4、llvm-project-9.0.1/llvm/lib/Transforms/CMakeLists.txt 中添加
add_subdirectory(FuncBlockCount)
5、clion Build->Build Project 重现编译项目
6、测试Pass
opt -load /home/showme/androidSec/llvm/llvm-project-9.0.1/llvm/cmake-build-debug/lib/LLVMFuncBlockCount.so -funcBlockCount /home/showme/tmpp/example.ll
7、结果
Funcion = main Loop level 0 has 1blocks
Loop level 1 has 1blocks
Loop level 1 has 2blocks
Loop level 1 has 3blocks
Loop level 1 has 1blocks
创建Pass : 加密函数名
1、在llvm-project-9.0.1/llvm/lib/Transforms目录下新建TestEncodeFuncName文件夹
2、在TestEncodeFuncName文件夹下创建CMakeLists.txt文件
将hello pass的 CMakeLists.txt 复制到 TestEncodeFuncName pass的 CMakeLists.txt中
add_llvm_library( LLVMTestEncodeFuncName MODULE BUILDTREE_ONLY
TestEncodeFuncName.cpp
DEPENDS
intrinsics_gen
PLUGIN_TOOL
opt
)
3、在TestEncodeFuncName文件夹下创建TestEncodeFuncName.cpp文件
#include "llvm/ADT/Statistic.h"
#include "llvm/IR/Function.h"
#include "llvm/Pass.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Transforms/IPO/PassManagerBuilder.h"
using namespace llvm;
namespace {
struct TestEncodeFuncName : public FunctionPass {
static char ID; // Pass identification, replacement for typeid
TestEncodeFuncName() : FunctionPass(ID) {}
bool runOnFunction(Function &F) override {
errs() << "TestEncodeFuncName: " << F.getName() << " -> ";
if(F.getName().compare("main") != 0){ //过滤的main方法
// MD5的使用就抄llvm源码中的使用
llvm::MD5 Hasher;
llvm::MD5::MD5Result Hash;
Hasher.update("antiy_");
Hasher.update(F.getName());
SmallString<32> HexString;
llvm::MD5::stringifyResult(Hash,HexString);
F.setName(HexString); //将加密后的函数名字设置回去
}
SmallString<32> smallString;
errs().write_escaped(F.getName()) << '\n';
return false;
}
};
}
char TestEncodeFuncName::ID = 0;
static RegisterPass<TestEncodeFuncName> X("encode", "encode function name Pass");
static llvm::RegisterStandardPasses Y(
llvm::PassManagerBuilder::EP_EarlyAsPossible,
[](const llvm::PassManagerBuilder &Builder,
llvm::legacy::PassManagerBase &PM) { PM.add(new TestEncodeFuncName()); });
4、llvm-project-9.0.1/llvm/lib/Transforms/CMakeLists.txt 中添加
add_subdirectory(TestEncodeFuncName)
5、编译,测试
opt -load /home/showme/androidSec/llvm/llvm-project-9.0.1/llvm/cmake-build-debug/lib/LLVMTestEncodeFuncName.so -encode /home/showme/tmpp/test.ll
6、结果
TestEncodeFuncName: test_func1 -> 20c5ee24ff7f0000659787bec5550000
TestEncodeFuncName: test_func2 -> 20c5ee24ff7f0000659787bec5550000.1
TestEncodeFuncName: main -> main
函数名称加密原理比较好理解:
1、FunctionPass 是作用于函数的
2、在优化 IR 或 LLVM Bitcode 的时候,获取函数的名称
3、将函数的名称进行MD5加密
4、再将加密后的函数名称替换掉原先的函数名
在llvm源码之外开发Pass (参考 llvm 官方文档,写的很细 https://llvm.org/docs/CMake.html#developing-llvm-passes-out-of-source)
基本目录结构如下
<project dir>/
|
CMakeLists.txt
<pass name>/
|
CMakeLists.txt
Passname.cpp
1、在testOutllvmPass/CMakeLists.txt中填写 编译内容
find_package(LLVM REQUIRED CONFIG)
add_definitions(${LLVM_DEFINITIONS})
include_directories(${LLVM_INCLUDE_DIRS})
add_subdirectory(testOutllvmPass) # 这里修改为我们的pass Name
2、填写testOutllvmPass/testOutllvmPass/CMakeLists.txt
1)当不需要将这个Pass集成到llvm源码中时,可以简单填写为
add_library(LLVMtestOutllvmPass MODULE testOutllvmPass.cpp)
2)当需要在之后将该Pass集成到llvm源码时,testOutllvmPass/testOutllvmPass/CMakeLlists.txt编写如下
add_llvm_library(LLVMtestOutllvmPass MODULE
testOutllvmPass.cpp
)
通过需要将testOutllvmPass/CMakeLists.txt修改为如下内容
find_package(LLVM REQUIRED CONFIG)
//就是新增下面两行,手册上说必须放到 find_package(LLVM REQUIRED CONFIG) 的下方
list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}")
include(AddLLVM)
add_definitions(${LLVM_DEFINITIONS})
include_directories(${LLVM_INCLUDE_DIRS})
add_subdirectory(testOutllvmPass)
本次使用的是第二种方式编写testOutllvmPass/testOutllvmPass/CMakeLlists.txt
3、testOutllvmPass/testOutllvmPass/testOutllvmPass.cpp使用TestEncodeFuncName.cpp中的代码 :记得把类名改成testOutllvmPass
4、同clion打开项目:根据报错,修补项目代码
1)
No project() command is present. The top-level CMakeLists.txt file must
contain a literal, direct call to the project() command. Add a line of
code such as
project(ProjectName)
在testOutllvmPass/CMakeLists.txt文件中,添加内容
project(testOutllvmPass)
2)
CMake Warning (dev) in CMakeLists.txt:
No cmake_minimum_required command is present. A line of code such as
cmake_minimum_required(VERSION 3.17)
在testOutllvmPass/CMakeLists.txt文件中,添加内容
cmake_minimum_required(VERSION 3.17)
3)
Could not find a package configuration file provided by "LLVM" with any of
the following names:
LLVMConfig.cmake
llvm-config.cmake
Add the installation prefix of "LLVM" to CMAKE_PREFIX_PATH or set
"LLVM_DIR" to a directory containing one of the above files. If "LLVM"
provides a separate development package or SDK, be sure it has been
installed.
查找LLVMConfig.cmake或LLVM-Config.cmake文件所在的目录
找到
/home/showme/androidSec/llvm/llvm-project-9.0.1/llvm/cmake-build-debug/lib/cmake/llvm/LLVMConfig.cmake
在testOutllvmPass/CMakeLists.txt中添加内容
set(LLVM_DIR /home/showme/androidSec/llvm/llvm-project-9.0.1/llvm/cmake-build-debug/lib/cmake/llvm/LLVMConfig.cmake)
最终 testOutllvmPass/CMakeLists.txt
project(testOutllvmPass)
cmake_minimum_required(VERSION 3.17)
set(LLVM_DIR /home/showme/androidSec/llvm/llvm-project-9.0.1/llvm/cmake-build-debug/lib/cmake/llvm/)
# 顺序也很重要
find_package(LLVM REQUIRED CONFIG)
# 就是新增下面两行,手册上说必须放到 find_package(LLVM REQUIRED CONFIG) 的下方
list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}")
include(AddLLVM)
add_definitions(${LLVM_DEFINITIONS})
include_directories(${LLVM_INCLUDE_DIRS})
add_subdirectory(testOutllvmPass)
5、进行编译
Build -> Build Project
6、生成文件
./cmake-build-debug/testOutllvmPass/LLVMtestOutllvmPass.so
7、进行测试
opt -load /home/showme/androidSec/llvm/outLLVMPass/testOutllvmPass/cmake-build-debug/testOutllvmPass/LLVMtestOutllvmPass.so -encode /home/showme/tmpp/test.ll
8、结果
testOutllvmPass: test_func1 -> 70754e79fe7f0000656780d6db550000
testOutllvmPass: test_func2 -> 70754e79fe7f0000656780d6db550000.1
testOutllvmPass: main -> main
在llvm源码之外开发Pass-调试
1、配置Clion,点击Edit Configuration
2、CMake Application 选择 LLVMtestOutllvmPass
3、Executable : 选择为 llvm源码中编译生成的opt
/home/showme/androidSec/llvm/llvm-project-9.0.1/llvm/cmake-build-debug/bin/opt
4、Program arguments :
-load /home/showme/androidSec/llvm/outLLVMPass/testOutllvmPass/cmake-build-debug/testOutllvmPass/LLVMtestOutllvmPass.so -encode /home/showme/tmpp/test.ll
5、testOutllvmPass.cpp 中 runOnFunction方法内下断点,可以调试,可以跟踪到llvm源码中
clang直接使用pass
clang -Xclang -load -Xclang /home/showme/androidSec/llvm/outLLVMPass/testOutllvmPass/cmake-build-debug/testOutllvmPass/LLVMtestOutllvmPass.so /home/showme/tmpp/test.c
结果
testOutllvmPass: test_func1 -> 807c376efd7f00009d01538ba6550000
testOutllvmPass: test_func2 -> 807c376efd7f00009d01538ba6550000.3
testOutllvmPass: main -> main
将自定义pass注册到llvm中
从上面的🌰中看到,在clang或者opt调用自定义Pass都需要指定自定位Pass库文件的全路径,下面将Pass注册到llvm中,免去命令load Pass库的操作
llvm源码中 /workspace/llvm-project/llvm/include/llvm/和/workspace/llvm-project/llvm/lib中的目录结构是大致相仿,include/llvm用于防止头文件,lib用于放置实现代码。
将自定义Pass注册进llvm,就是在这两个目录下进行修改
目标:下面将之前在llvm源码中编写的TestEncodeFuncName 注册进llvm源码
1、先在llvm-project/llvm/include/llvm/文件夹下创建目录TestEncodeFuncName,再在TestEncodeFuncName目录中创建TestEncodeFuncName.h头文件
#ifndef LLVM_TESTENCODEFUNCNAME_H
#define LLVM_TESTENCODEFUNCNAME_H
#include "llvm/Pass.h"
namespace llvm {
Pass* createTestEncodeFuncName();
}
#endif // LLVM_TESTENCODEFUNCNAME_H
2、llvm-project/llvm/lib/Transforms/TestEncodeFuncName/TestEncodeFuncName.cpp 包含上面创建的头文件,并实现createTestEncodeFuncName函数
#include "llvm/Transforms/TestEncodeFuncName/TestEncodeFuncName.h"
// 删除代码中如下的代码
/*
static llvm::RegisterStandardPasses Y(
llvm::PassManagerBuilder::EP_EarlyAsPossible,
[](const llvm::PassManagerBuilder &Builder,
llvm::legacy::PassManagerBase &PM) { PM.add(new TestEncodeFuncName()); });
*/
// 添加如下代码
Pass* llvm::createTestEncodeFuncName(){return new TestEncodeFuncName();}
3、在llvm-project/llvm/lib/Transforms/IPO/PassManagerBuilder.cpp 中添加内容
#include "llvm/Transforms/TestEncodeFuncName/TestEncodeFuncName.h"
// 设置TestEncodeFuncName Pass在默认情况下不会被调用,在命令行指定encode_fucntion_name参数时才会被调用
static cl::opt<bool>
EnableEncodeFunctionName("encode_fucntion_name", cl::init(false),
cl::Hidden,
cl::desc("Encode Function Name Pass"));
// void PassManagerBuilder::populateModulePassManager(
if(EnableEncodeFunctionName){
MPM.add(createTestEncodeFuncName());
}
4、llvm-project/llvm/lib/Transforms/TestEncodeFuncName/CMakeLists.txt
add_llvm_library( LLVMTestEncodeFuncName
TestEncodeFuncName.cpp
ADDITIONAL_HEADER_DIRS
${LLVM_MAIN_INCLUDE_DIR}/llvm/Transforms
${LLVM_MAIN_INCLUDE_DIR}/llvm/Transforms/TestEncodeFuncName
DEPENDS
intrinsics_gen
)
5、llvm-project/llvm/lib/Transforms/LLVMBuild.txt
[common]
subdirectories = AggressiveInstCombine Coroutines HelloNew IPO InstCombine Instrumentation Scalar Utils Vectorize ObjCARC CFGuard TestEncodeFuncName
[component_0]
type = Group
name = Transforms
parent = Libraries
6、llvm-project/llvm/lib/Transforms/IPO/LLVMBuild.txt
[component_0]
type = Library
name = IPO
parent = Transforms
library_name = ipo
required_libraries = AggressiveInstCombine Analysis BitReader BitWriter Core FrontendOpenMP InstCombine IRReader Linker Object ProfileData Scalar Support TransformUtils Vectorize Instrumentation TestEncodeFuncName
7、llvm-project/llvm/lib/Transforms/TestEncodeFuncName/LLVMBuild.txt
[component_0]
type = Library
name = TestEncodeFuncName
parent = Transforms
library_name = TestEncodeFuncName
8、重新编译
llvm-project/llvm/cmake-build-debug$ make clang
9、会生成libLLVMTestEncodeFuncName.a 文件
/home/showme/androidSec/llvm/llvm-project-9.0.1/llvm/cmake-build-debug/lib/libLLVMTestEncodeFuncName.a
10、用clang尝试调用
export PATH="/home/showme/androidSec/llvm/llvm-project-9.0.1/llvm/cmake-build-debug/bin:$PATH"
clang -mllvm -encode_fucntion_name hello_clang.c