01-自定义Pass

自定义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

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值