1. LLVM 编译
LLVM 对 CMake版本有限制,可以在官网查看
Ubuntu 20.04
LLVM 12.0.1
CMake 3.16.3
第一步:下载LLVM-Core 和 Clang源码
在llvm-project 仓库的 Release界面,下载LLVM-Core和Clang源码
llvm-12.0.1.src.tar.xz
clang-12.0.1.src.tar.xz
在/home/llvm/Programs文件夹下创建llvm-project文件夹,将下载好的原代码解压缩到对应目录。创建build文件夹
/home/llvm/Programs
/llvm-project # llvm 项目文件夹
/llvm # 解压缩后的llvm代码
/clang # 解压缩后的clang代码
/build # 存放编译后的LLVM
第二步:编译LLVM
在/home/llvm/Programs/llvm-project文件夹下创建build.sh文件。执行build.sh开始编译,编译需要一段时间,记得执行build.sh时以root权限执行!!!。
cmake 参数解释:
-G “Unix Makefiles”:生成Unix下的Makefile
-DLLVM_ENABLE_PROJECTS=“clang”:除了 LLVM Core 外,还需要编译的子项目。
-DLLVM_BUILD_TYPE=Release:在cmake里,有四种编译模式:Debug, Release, RelWithDebInfo, 和MinSizeRel。使用 Release 模式编译会节省很多空间。
-DLLVM_TARGETS_TO_BUILD=“X86”:默认是ALL,选择X86可节约很多编译时间。
-DBUILD_SHARED_LIBS=On:指定动态链接 LLVM 的库,可以节省空间。
make install 指令:
是将编译好的二进制文件和头文件等安装到本机的 /usr/local/bin 和 /usr/local/include 目录,方便后续使用。
cd build
cmake -G "Unix Makefiles" -DLLVM_ENABLE_PROJECTS="clang" \
-DCMAKE_BUILD_TYPE=Release -DLLVM_TARGETS_TO_BUILD="x86" \
-DBUILD_SHARED_LIBS=On ../llvm
make
make install
2. CMake LLVM Pass 项目创建
在/home/llvm/Programs/llvm-project文件夹下创建一个项目文件夹,叫什么随意,这里用OLLVMPP。
整个项目目录结构如下:
OLLVMPP:OLLVM文件夹,项目根目录
Build:Build文件夹,存放编译后的LLVM Pass
Test:Test文件夹,存放编译生成的测试文件和需要优化的代码文件
Bin:Bin文件夹,编译生成的二进制文件
IR:IR文件夹,编译生成的IR文件
TestProgram.cpp:需要优化的代码文件
Transforms
include:include文件夹,存放LLVM Pass头文件
src:src文件夹,存放LLVM Pass源码
Demo.cpp:自己写的LLVM Pass代码
CMakeList.txt:整个 CMake 项目的配置文件
test.sh:编译LLVM Pass并对Test文件夹中的代码文件进行测试
CMakeLists.txt文件内容如下:
# 参考官方文档:https://llvm.org/docs/CMake.html#developing-llvm-passes-out-ofsource
project(OLLVMPP)
cmake_minimum_required(VERSION 3.13.4)
find_package(LLVM REQUIRED CONFIG)
list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}")
include(AddLLVM)
include_directories("./include") # 包含 ./include 文件夹中的头文件
separate_arguments(LLVM_DEFINITIONS_LIST NATIVE_COMMAND ${LLVM_DEFINITIONS})
add_definitions(${LLVM_DEFINITIONS_LIST})
include_directories(${LLVM_INCLUDE_DIRS})
add_llvm_library( LLVMObfuscator MODULE # 编译后的LLVM Pass模块名称 LLVMObfuscator.so
# 在此处添加cpp代码源文件,例如src下有一个源文件Demo.cpp
/src/Demo.cpp
)
test,sh文件内容如下:
cd ./Build
cmake ../Transforms
make
cd ../Test
# 将源代码转换成LLVM IR
clang -S -emit-llvm TestProgram.cpp -o IR/TestProgram.ll
# 优化LLVM IR,这里的 -test 参数时Pass中自己注册的
opt -load ../Build/LLVMObfuscator.so -test -S IR/TestProgram.ll -o IR/TestProgram_test.ll
# 将llvm IR 编译成二进制可执行文件
clang IR/TestProgram_test.ll -o Bin/TestProgram_test
LLVM Pass编写步骤如下:
创建一个类,继承FunctionPass
实现runOnFunction(Funtion &F)函数
向LLVM 注册自己的Pass类
#include "llvm/Pass.h"
#include "llvm/IR/Function.h"
#include "llvm/Support/raw_ostream.h"
using namespace llvm;
namespace{
// 创建一个类,继承FunctionPass
class Demo : public FunctionPass{
public:
static char ID;
Demo() : FunctionPass(ID){}
// 实现runOnFunction(Funtion &F)函数
bool runOnFunction(Function &F);
};
}
// runOnFunction 函数实现,获取函数名称
bool Demo::runOnFunction(Function &F){
// do something
outs() << "function name: " << F.getName() << "\n";
}
char Demo::ID = 0;
// 注册该 Demo Pass,这里写的test就是上面 test.sh 中 优化部分 -test参数
static RegisterPass<Demo> X("test", "LLVM Pass Demo");
3. LLVM Pass:基本块分割
基本块分割,也就是将一个基本块分割成若干个等价的基本块,在分割后的基本块之间加上无条件跳转。在许多基于基本块的代码混淆中,基本块数量越多,代码混淆后的复杂度就越大。
实现思路:
遍历函数中每个基本块,对每个基本块进行分割即可。
注意:有PHI指令的基本块我们选择跳过,因为PHI指令的值依赖于前驱,如果我们分割时改变了前驱,那么执行结果就错误了。
主要API:
可选参数:接收命令行中可选参数作为一个基本块块分割成多少个,使用
cl::opt
模板类来实现,这里的opt是option,不是优化器的意思。splitBasicBlock
:BasicBlock类中的一个成员函数,作用是分割基本块。isa<>
:一个模板函数,用于判断一个指针志昂的数据类型是不是给定类型,这里我们用来判断PHI块。
核心代码:
// 命令行参数split_num,用于指定一个基本块分割次数,默认为2
static cl::opt<int> splitNum("split_num", cl::init(2), cl::desc("Split <split_num> time(s) each BB"));
// 判断基本块中是否包含PHI指令
bool SplitBasicBlock::containsPHI(BasicBlock *BB){
for(Instruction &I : *BB){
if(isa<PHINode>(&I)){
return true;
}
}
return false;
}
// 基本块分割,
void SplitBasicBlock::split(BasicBlock *BB){
// 计算分裂后每个基本块的大小
// 原基本块的大小 / 分裂数目(向上取整)
int splitSize = (BB->size() + splitNum - 1) / splitNum;
for(int i = 1;i < splitNum;i ++){
BasicBlock *curBB = BB;
int cnt = 0;
for(Instruction &I : *curBB){
if(cnt++ == splitSize){
// 在 I 指令处对基本块进行分割
curBB = curBB->splitBasicBlock(&I);
break;
}
}
}
}
// runOnFunction 函数实现
bool SplitBasicBlock::runOnFunction(Function &F){
// 第一步:保存原先的所有基本块
vector<BasicBlock*> origBB;
for(BasicBlock &BB : F){
origBB.push_back(&BB);
}
// 第二步:对每个不包含 PHI 指令的基本块执行分裂操作
for(BasicBlock *BB : origBB){
if(!containsPHI(BB)){
split(BB);
}
}
return true;
}
// 注册
char SplitBasicBlock::ID = 0;
static RegisterPass<SplitBasicBlock> X("split", "Split a basic block into multiple basic blocks.");