LLVM从小白到放弃(一)- LLVM概述与LLVM环境搭建

LLVM的历史

  • LLVM计划启动于2000年,开始由美国UIUC大学的Chris Lattner博士主持开展,后来Apple也加入其中。最初的目的是开发一套提供中间代码和编译基础设施的虚拟系统。
  • LLVM命名最早源自于底层虚拟机(Low Level Virtual Machine)的缩写,随着LLVM项目的不断发展,原先的全称已不再适用,目前LLVM就是该项目的全称。

什么是LLVM

  • 广义:LLVM是一个包括了很多模块的编译器框架。
  • 狭义:LLVM特指LLVM项目中的LLVM Core和Clang子模块。
  • 简单来收,可以将LLVM理解成为一个现代化、可扩展的编译器。

GCC的编译流程

  • GCC分为三个模块:前端、优化器和后端

暂时无法在文档外展示此内容

  • LLVM本质上也是三段式:

暂时无法在文档外展示此内容

LLVM的编译流程

  • 一个具体的例子:

暂时无法在文档外展示此内容

LLVM相对于GCC的优势

优势1:模块化
  • LLVM:LLVM是高度模块化设计的,每一个模块都可以从LLVM项目中抽离出来单独使用。
  • GCC:而GCC虽然也是三段式编译,但各个模块之间是难以抽离出来单独使用的。
优势2:可扩展
  • LLVM:LLVM为开发者提供了丰富的API,例如开发者可以通过LLVM Pass框架干预中间代码优化过程,并且配备了完善的文档
  • GCC:虽然GCC是开源的,但要在GCC的基础上进行扩展门槛很高、难度很大

LLVM编译过程总结

  • 对于C/C++程序来说,LLVM的编译过程如图所示:

LLVM环境搭建

Ubuntu/LLVM/CMake版本
  • Ubuntu 20.04
  • LLVM 12.0.1 / 9.0.9svn(ndkr21e)
  • Cmake 3.21.1
第一步:下载LLVM-Core和Clang源代码

https://github.com/llvm/llvm-project/releases/tag/llvmorg-12.0.1

clang-12.0.1.src.tar.xz

llvm-12.0.1.src.tar.xz

下载、解压、并重命名,并在同一目录下新建build文件夹,如下:

第二步:编译LLVM项目

在同一文件夹内创建build.sh文件,内容如下:

1
2
3
4
5
6
7
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

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 的库,可以节省空间。

-

LLVM基本用法

第一步:将源代码转化成LLVM IR
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "iostream"



using namespace std;



int main() {

cout << "Hello World!" << endl;



return 0;

}

LLVM IR 有两种表现形式,一种是人类可阅读的文本形式,对应文件后缀为 .ll ;另一种是方便机器处 理的二进制格式,对应文件后缀为 .bc 。使用以下命令将源代码转化为 LLVM IR:

clang -S -emit-llvm hello.cpp -o hello.ll

clang -c -emit-llvm hello.cpp -o hello.bc

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
; ModuleID = 'hello.cpp'

source_filename = "hello.cpp"

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-unknown-linux-gnu"



%"class.std::ios_base::Init" = type { i8 }

%"class.std::basic_ostream" = type { i32 (...)**, %"class.std::basic_ios" }

%"class.std::basic_ios" = type { %"class.std::ios_base", %"class.std::basic_ostream"*, i8, i8, %"class.std::basic_streambuf"*, %"class.std::ctype"*, %"class.std::num_put"*, %"class.std::num_get"* }

%"class.std::ios_base" = type { i32 (...)**, i64, i64, i32, i32, i32, %"struct.std::ios_base::_Callback_list"*, %"struct.std::ios_base::_Words", [8 x %"struct.std::ios_base::_Words"], i32, %"struct.std::ios_base::_Words"*, %"class.std::locale" }

%"struct.std::ios_base::_Callback_list" = type { %"struct.std::ios_base::_Callback_list"*, void (i32, %"class.std::ios_base"*, i32)*, i32, i32 }

%"struct.std::ios_base::_Words" = type { i8*, i64 }

%"class.std::locale" = type { %"class.std::locale::_Impl"* }

%"class.std::locale::_Impl" = type { i32, %"class.std::locale::facet"**, i64, %"class.std::locale::facet"**, i8** }

%"class.std::locale::facet" = type <{ i32 (...)**, i32, [4 x i8] }>

%"class.std::basic_streambuf" = type { i32 (...)**, i8*, i8*, i8*, i8*, i8*, i8*, %"class.std::locale" }

%"class.std::ctype" = type <{ %"class.std::locale::facet.base", [4 x i8], %struct.__locale_struct*, i8, [7 x i8], i32*, i32*, i16*, i8, [256 x i8], [256 x i8], i8, [6 x i8] }>

%"class.std::locale::facet.base" = type <{ i32 (...)**, i32 }>

%struct.__locale_struct = type { [13 x %struct.__locale_data*], i16*, i32*, i32*, [13 x i8*] }

%struct.__locale_data = type opaque

%"class.std::num_put" = type { %"class.std::locale::facet.base", [4 x i8] }

%"class.std::num_get" = type { %"class.std::locale::facet.base", [4 x i8] }



@_ZStL8__ioinit = internal global %"class.std::ios_base::Init" zeroinitializer, align 1

@__dso_handle = external hidden global i8

@_ZSt4cout = external dso_local global %"class.std::basic_ostream", align 8

@.str = private unnamed_addr constant [13 x i8] c"Hello World!\00", align 1

@llvm.global_ctors = appending global [1 x { i32, void ()*, i8* }] [{ i32, void ()*, i8* } { i32 65535, void ()* @_GLOBAL__sub_I_hello.cpp, i8* null }]



; Function Attrs: noinline uwtable

define internal void @__cxx_global_var_init() #0 section ".text.startup" {

call void @_ZNSt8ios_base4InitC1Ev(%"class.std::ios_base::Init"* nonnull dereferenceable(1) @_ZStL8__ioinit)

%1 = call i32 @__cxa_atexit(void (i8*)* bitcast (void (%"class.std::ios_base::Init"*)* @_ZNSt8ios_base4InitD1Ev to void (i8*)*), i8* getelementptr inbounds (%"class.std::ios_base::Init", %"class.std::ios_base::Init"* @_ZStL8__ioinit, i32 0, i32 0), i8* @__dso_handle) #3

ret void

}



declare dso_local void @_ZNSt8ios_base4InitC1Ev(%"class.std::ios_base::Init"* nonnull dereferenceable(1)) unnamed_addr #1



; Function Attrs: nounwind

declare dso_local void @_ZNSt8ios_base4InitD1Ev(%"class.std::ios_base::Init"* nonnull dereferenceable(1)) unnamed_addr #2



; Function Attrs: nounwind

declare dso_local i32 @__cxa_atexit(void (i8*)*, i8*, i8*) #3



; Function Attrs: noinline norecurse optnone uwtable mustprogress

define dso_local i32 @main() #4 {

%1 = alloca i32, align 4

store i32 0, i32* %1, align 4

%2 = call nonnull align 8 dereferenceable(8) %"class.std::basic_ostream"* @_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc(%"class.std::basic_ostream"* nonnull align 8 dereferenceable(8) @_ZSt4cout, i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str, i64 0, i64 0))

%3 = call nonnull align 8 dereferenceable(8) %"class.std::basic_ostream"* @_ZNSolsEPFRSoS_E(%"class.std::basic_ostream"* nonnull dereferenceable(8) %2, %"class.std::basic_ostream"* (%"class.std::basic_ostream"*)* @_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_)

ret i32 0

}



declare dso_local nonnull align 8 dereferenceable(8) %"class.std::basic_ostream"* @_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc(%"class.std::basic_ostream"* nonnull align 8 dereferenceable(8), i8*) #1



declare dso_local nonnull align 8 dereferenceable(8) %"class.std::basic_ostream"* @_ZNSolsEPFRSoS_E(%"class.std::basic_ostream"* nonnull dereferenceable(8), %"class.std::basic_ostream"* (%"class.std::basic_ostream"*)*) #1



declare dso_local nonnull align 8 dereferenceable(8) %"class.std::basic_ostream"* @_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_(%"class.std::basic_ostream"* nonnull align 8 dereferenceable(8)) #1



; Function Attrs: noinline uwtable

define internal void @_GLOBAL__sub_I_hello.cpp() #0 section ".text.startup" {

call void @__cxx_global_var_init()

ret void

}



attributes #0 = { noinline uwtable "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"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" "unsafe-fp-math"="false" "use-soft-float"="false" }

attributes #1 = { "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"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" "unsafe-fp-math"="false" "use-soft-float"="false" }

attributes #2 = { nounwind "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"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" "unsafe-fp-math"="false" "use-soft-float"="false" }

attributes #3 = { nounwind }

attributes #4 = { noinline norecurse optnone uwtable mustprogress "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"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" "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 12.0.1"}
第二步:优化LLVM IR

使用opt指令对LLVM IR进行优化

opt -load LLVMObfuscator.so -hlw -S hello.ll -o hello_opt.ll

  • -load 加载特定的LLVM Pass(集合)进行优化(通常为.so文件)
  • -hlw是LLVM Pass中自定义的参数,用来指定使用哪个Pass进行优化
第三步:编译LLVM IR为可执行文件

这一步我们通过Clang完成,从LLVM IR到可执行文件中间还有一系列复杂的流程,Clang帮助我们整合了这个过程

clang hello_opt.ll -o hello

参考