LLVM 是什么?
根据官方的描述 ,LLVM项目是一个模块化和可重用的编译器和工具链技术的集合。他主要包含几个模块:
- LLVM-Core: LLVM核心库可以将源程序语言转化为“LLVM IR”语言, IR语言独立于不同的CPU平台,由于其完善的文档,可以将各种不同的语言都加入到编译链当中。
- Clang: 基于LLVM的C/C++/Objective-C编译器, 跟C语言相关的语言都可以使用Clang来编译。
- LLDB: 一个高性能的调试器。
- libc++: C++标准库, 支持c++11和c++ 14。
- compiler-rt : 为目标平台提供其硬件不支持的低级功能的优化实现。
- MLIR: 目的是做一个通用、可复用的编译器框架,减少构建Domain Specific Compiler的开销,目前主要用于机器学习领域
概括的说 LLVM编译器 的工作流程就是 :
- 前端把源代码翻译成中间表示 (IR)。
- 后端把IR编译成目标平台的机器码。
编译LLVM源码
下载源码:
llvm源码地址 : github.com/llvm/llvm-p… , 下载各个版本中的llvm-xx.x.x.src.tar.xz 部分。
编译:
llvm分为Debug和 Release 两个版本的,默认编译选项为Debug,编译时间较长,且占用内存较大,
命令为
mkdir build;
cd build; cmake -DLLVM_INCLUDE_TESTS=off ../llvm;
ninja -j8
llvm默认的编译工具是ninja, 除此以外还可以选择make等
LLVM_INCLUDE_TESTS选项为是部分编译Test部分
如需编译Release版本,则添加Build-Type:
cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_INCLUDE_TESTS=off ../llvm
默认编译选项不包含llvm的子模块内容,可以使用 -DLLVM_ENABLE_PROJECTS='...' , 参数来添加 如:
cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_INCLUDE_TESTS=off -DLLVM_ENABLE_PROJECTS='clang;libc++' ../llvm
LLVM工具介绍
- llvm-ar:LLVM的静态库打包器,类似Linux系统中的ar。
- llvm-as:LLVM 汇编器,
- lli: 可以执行一个IR语言文件
- llvm-as: LLVM的汇编器,生成一个bc格式的二进制文件,可以被llvm识别
- llvm-dis: LLVM 反汇编器 ,可以将二进制文件转为 IR文件。
- llc: 将bc文件转成对应平台的汇编代码。
- llvm-link: llvm的链接器,用于链接多个文件.
- opt: llvm重要的指令,可以执行各种PASS。
IR语言示例
这里编写一个简单的HelloWorld代码
#include <stdio.h>
int main( ){
printf("hello world\n");
return 0 ;
}
使用Clang进行编译
clang -emit-llvm -S hello.c -o hello.ll
即可生成IR中间语言的输出文件 hello.ll
; ModuleID = 'hello.c'
source_filename = "hello.c"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"
@.str = private unnamed_addr constant [13 x i8] c"hello world\0A\00", align 1
; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @main() #0 {
%1 = alloca i32, align 4
store i32 0, i32* %1, align 4
%2 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str, i64 0, i64 0))
ret i32 0
}
declare dso_local i32 @printf(i8*, ...) #1
attributes #0 = { noinline nounwind optnone uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="all" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="all" "less-precise-fpmad"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
!llvm.module.flags = !{!0}
!llvm.ident = !{!1}
!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{!"clang version 10.0.0-4ubuntu1 "}
使用上面编译生成的 lli 程序 ,可以直接运行当前的hello.ll 文件
lli ./hello.ll
IR语法简介
根据上文输出的IR语言范例,简单总结一下IR语言的基础语法
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
| e | 小端序 |
| m:o | 符号表中使用Mach-O格式的name mangling |
| i64:64 | i64类型的变量采用64比特的ABI对齐 |
| f80:128 | 将long double类型的变量采用128比特的ABI对齐 |
| n8:16:32:64 | 目标CPU的原生整型包含8比特、16比特、32比特和64比特 |
| S128 | 栈以128比特自然对齐 |
以 分号 ; 开头的代码代表注释
; Function Attrs: noinline nounwind optnone uwtable
基本数据类型
- i32 代表 int
- i8 代表char
@ 代表全局符号,包括变量和函数 % 代表局部符号,包括局部变量
指令:
- alloca : 给函数分配栈空间,函数返回时自动回收。
- store : 数据存储到对应的变量中 。i32 0, i32* %1, align 4 带边将0存储到 变量 %1 中, 而%1正是alloca申请的栈空间。
- ret: 返回指令
- Add / Sub / Mul / Div / Rems : 加减乘除取余 操作
- icmp、fcmp : 比较指令
- call 函数调用
PASS
pass是llvm中非常重要的模块, 可以对整个编译过程添加各种自定义的操作,编译好的release版本中,官方提供了一个默认的pass
opt -load ../build_release/lib/LLVMHello.so -hello hello.bc
此Pass可以输出hello中定义的函数
Hello: main
自定义pass
官网中介绍了如何自行定义一个pass
releases.llvm.org/10.0.0/docs…
为了方便,我们直接在源码中添加一个Pass, 功能是函数名称混淆
Pass代码编写
-
改掉Hello模块代码
- 复制位于llvm中 Hello模块目录的代码( /llvm/lib/Transforms/Hello ),并改名为EncryptFunctionName
- 将Hello.cpp 改为 Test.cpp
- Hello.exports 改为 EncryptFuncName.exports
-
修改Transforms/CMakeList.txt ,追加一行
add_subdirectory(EncryptFuncName) -
修改EncryptFunctionName目录中的CMakeLists.txt
# If we don't need RTTI or EH, there's no reason to export anything # from the hello plugin. if( NOT LLVM_REQUIRES_RTTI ) if( NOT LLVM_REQUIRES_EH ) set(LLVM_EXPORTED_SYMBOL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/EncryptFuncName.exports) endif() endif() if(WIN32 OR CYGWIN) set(LLVM_LINK_COMPONENTS Core Support) endif() add_llvm_library( EncryptFuncName MODULE BUILDTREE_ONLY Test.cpp DEPENDS intrinsics_gen PLUGIN_TOOL opt ) -
修改Test.cpp
#include "llvm/ADT/Statistic.h" #include "llvm/IR/Function.h" #include "llvm/Pass.h" #include "llvm/Support/raw_ostream.h" #include <iostream> using namespace llvm; #define DEBUG_TYPE "encrypt" int Counter = 0 ; namespace { // Hello - The first implementation, without getAnalysisUsage. struct EncryptFuncName : public FunctionPass { static char ID; // Pass identification, replacement for typeid EncryptFuncName() : FunctionPass(ID) { } bool runOnFunction(Function &F) override { if( F.getName().compare("main") != 0){ char buff[30]; errs().write_escaped(F.getName()) << '\n'; std::sprintf(buff, "OOO00ooOOO%d" , Counter++); //将除main函数之外的所有函数名称都进行OOO00ooOOO前缀的混淆加密. F.setName(buff); errs().write_escaped(F.getName()) << '\n'; } return false; } }; } char EncryptFuncName::ID = 0; static RegisterPass<EncryptFuncName> X("encrypt", "Hello World Pass");
- 编写测试代码, hello.cpp
#include <stdio.h>
int fun1( ){
return 0;
}
int fun2( ){
return 1;
}
int main( ){
printf("hello world\n");
printf("fun1 %d \n" , fun1());
printf("fun2 %d\n" , fun2());
return 0 ;
}
-
执行pass并编译
opt -load ../build_release/lib/EncryptFuncName.so -encrypt hello.bc; clang hello.bc -o hello -
结果验证
当编译完成之后,在本地目录生成可执行文件hello,运行命令
nm hello可以看到下面的结果:
分别是将 fun1 =》 OOO00ooOOO0 , fun2 =》 OOO00ooOOO1
总结
本文档介绍了:
- llvm源码编译
- IR语言基础语法
- 自定义Pass