LLVM编译及加载pass

1. LLVM和clang

LLVM

LLVM是一个工具链的大集合,包含常用的clang等等。LLVM编译过程:

image-20230108163843359

可以理解为,LLVM将C、C++代码,生成统一的IR,这里的IR可以理解为可读性比较强的汇编,介于高级语言与机器码之间。LLVM后端将IR最终转化为机器码执行。

常说的编译器优化 -O2 等等,就是发生在对IR的处理上,LLVM提供多种Pass,每个Pass输入为IR格式,输出也为IR格式,从而实现对代码的优化。

所以如果我们想实现一个简单的基于LLVM的编程语言,其实大概思路只要做好前端部分,即源代码 -> 词法、语法分析等等 -> IR,剩下的过程就可以交给LLVM后端来解决。

IR是统一的,也就是说可以利用C++生成的IR,和其他语言生成的LLVM IR通过控制LLVM中间过程,最终编译到一个可执行文件中。

image-20230114164208644

clang

clang简单来说是C/C++的编译器,类似于gcc,针对一个C文件test.c,使用clang test.c即可以编译生成a.out可执行文件,跟gcc一样。但狭义上其实clang仅仅指该编译器的前端部分,即由C文件生成中间表现IR的过程,而从IR到可执行文件,是由中后端LLVM完成的。

image-20230108163801430

2. 安装编译LLVM

LLVM 15.0.3 源代码:https://github.com/llvm/llvm-project/releases/tag/llvmorg-15.0.3

编译:

1
cmake ./llvm-project-15.0.3.src/llvm -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=ON -DLLVM_ENABLE_PROJECTS='clang;lld;libcxxabi;libcxx;clang-tools-extra;openmp' -DLLVM_TARGETS_TO_BUILD="AArch64;ARM;X86"

然后 make 即可

把bin加入环境变量:

1
2
export LLVM_HOME=/Users/USERNAME/Desktop/llvm/bin
export PATH=$LLVM_HOME:$PATH

检查安装:

1
2
3
4
5
➜  clang --version
clang version 15.0.3
Target: x86_64-apple-darwin21.1.0
Thread model: posix
InstalledDir: /Users/USERNAME/Desktop/llvm/bin

编译下代码试试:

1
2
3
➜  test clang helloworld.c
➜ test ./a.out
Hello world!

3. LLVM编译程序过程

(1)源代码编译成IR

test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stdio.h>

void func(int a){
if(a > 10){
printf("AAA\n");
}
else{
printf("BBB\n");
}
}

int main(){
int a = 100;
func(a);
return 0;
}

生成bc:

1
clang -emit-llvm -c test.c -o test.bc

生成ll:

1
clang -emit-llvm -c -S test.c -o test.ll

bc和ll都是LLVM IR的格式,区别在于ll格式是可读的:

test.ll
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
; ModuleID = 'test.c'
source_filename = "test.c"
target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx12.0.0"

@.str = private unnamed_addr constant [5 x i8] c"AAA\0A\00", align 1
@.str.1 = private unnamed_addr constant [5 x i8] c"BBB\0A\00", align 1

; Function Attrs: noinline nounwind optnone ssp uwtable
define void @func(i32 noundef %a) #0 {
entry:
%a.addr = alloca i32, align 4
store i32 %a, ptr %a.addr, align 4
%0 = load i32, ptr %a.addr, align 4
%cmp = icmp sgt i32 %0, 10
br i1 %cmp, label %if.then, label %if.else

if.then: ; preds = %entry
%call = call i32 (ptr, ...) @printf(ptr noundef @.str)
br label %if.end

if.else: ; preds = %entry
%call1 = call i32 (ptr, ...) @printf(ptr noundef @.str.1)
br label %if.end

if.end: ; preds = %if.else, %if.then
ret void
}

declare i32 @printf(ptr noundef, ...) #1

; Function Attrs: noinline nounwind optnone ssp uwtable
define i32 @main() #0 {
entry:
%retval = alloca i32, align 4
%a = alloca i32, align 4
store i32 0, ptr %retval, align 4
store i32 100, ptr %a, align 4
%0 = load i32, ptr %a, align 4
call void @func(i32 noundef %0)
ret i32 0
}

attributes #0 = { noinline nounwind optnone ssp uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "tune-cpu"="generic" }
attributes #1 = { "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "tune-cpu"="generic" }

!llvm.module.flags = !{!0, !1, !2, !3, !4}
!llvm.ident = !{!5}

!0 = !{i32 2, !"SDK Version", [2 x i32] [i32 12, i32 0]}
!1 = !{i32 1, !"wchar_size", i32 4}
!2 = !{i32 7, !"PIC Level", i32 2}
!3 = !{i32 7, !"uwtable", i32 2}
!4 = !{i32 7, !"frame-pointer", i32 2}
!5 = !{!"clang version 15.0.3"}

(2)IR生成可执行文件

可以lli直接执行bc、ll:

1
lli test.ll

image-20230108174712422

llc编译成汇编:

1
llc test.ll -o test.s
test.s
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
	.section	__TEXT,__text,regular,pure_instructions
.build_version macos, 12, 0 sdk_version 12, 0
.globl _func ## -- Begin function func
.p2align 4, 0x90
_func: ## @func
.cfi_startproc
## %bb.0: ## %entry
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
subq $16, %rsp
movl %edi, -4(%rbp)
cmpl $10, -4(%rbp)
jle LBB0_2
## %bb.1: ## %if.then
leaq L_.str(%rip), %rdi
movb $0, %al
callq _printf
jmp LBB0_3
LBB0_2: ## %if.else
leaq L_.str.1(%rip), %rdi
movb $0, %al
callq _printf
LBB0_3: ## %if.end
addq $16, %rsp
popq %rbp
retq
.cfi_endproc
## -- End function
.globl _main ## -- Begin function main
.p2align 4, 0x90
_main: ## @main
.cfi_startproc
## %bb.0: ## %entry
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
subq $16, %rsp
movl $0, -8(%rbp)
movl $100, -4(%rbp)
movl -4(%rbp), %edi
callq _func
xorl %eax, %eax
addq $16, %rsp
popq %rbp
retq
.cfi_endproc
## -- End function
.section __TEXT,__cstring,cstring_literals
L_.str: ## @.str
.asciz "AAA\n"

L_.str.1: ## @.str.1
.asciz "BBB\n"

.subsections_via_symbols

编译汇编:

1
2
3
➜  test clang test.s -isysroot `xcrun --show-sdk-path` -o ./test
➜ test ./test
AAA

(3)LLVM Pass

以上基本执行了LLVM编译C程序的过程。文章开头说到,编译器对代码的优化等处理过程是由一个个Pass来处理,每个Pass输入为IR,输出也是IR,这里尝试对test.ll这个IR增加覆盖率统计sancov。

还是原本的test.ll
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
; ModuleID = 'test.c'
source_filename = "test.c"
target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx12.0.0"

@.str = private unnamed_addr constant [5 x i8] c"AAA\0A\00", align 1
@.str.1 = private unnamed_addr constant [5 x i8] c"BBB\0A\00", align 1

; Function Attrs: noinline nounwind optnone ssp uwtable
define void @func(i32 noundef %a) #0 {
entry:
%a.addr = alloca i32, align 4
store i32 %a, ptr %a.addr, align 4
%0 = load i32, ptr %a.addr, align 4
%cmp = icmp sgt i32 %0, 10
br i1 %cmp, label %if.then, label %if.else

if.then: ; preds = %entry
%call = call i32 (ptr, ...) @printf(ptr noundef @.str)
br label %if.end

if.else: ; preds = %entry
%call1 = call i32 (ptr, ...) @printf(ptr noundef @.str.1)
br label %if.end

if.end: ; preds = %if.else, %if.then
ret void
}

declare i32 @printf(ptr noundef, ...) #1

; Function Attrs: noinline nounwind optnone ssp uwtable
define i32 @main() #0 {
entry:
%retval = alloca i32, align 4
%a = alloca i32, align 4
store i32 0, ptr %retval, align 4
store i32 100, ptr %a, align 4
%0 = load i32, ptr %a, align 4
call void @func(i32 noundef %0)
ret i32 0
}

attributes #0 = { noinline nounwind optnone ssp uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "tune-cpu"="generic" }
attributes #1 = { "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "tune-cpu"="generic" }

!llvm.module.flags = !{!0, !1, !2, !3, !4}
!llvm.ident = !{!5}

!0 = !{i32 2, !"SDK Version", [2 x i32] [i32 12, i32 0]}
!1 = !{i32 1, !"wchar_size", i32 4}
!2 = !{i32 7, !"PIC Level", i32 2}
!3 = !{i32 7, !"uwtable", i32 2}
!4 = !{i32 7, !"frame-pointer", i32 2}
!5 = !{!"clang version 15.0.3"}

加载pass:sancov-module 并且添加相应的–sanitizer-coverage-trace-pc-guard、–sanitizer-coverage-level选项

1
➜  test opt --passes=sancov-module test.ll -o test.cov.ll --sanitizer-coverage-trace-pc-guard --sanitizer-coverage-level=3 -S
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
; ModuleID = 'test.ll'
source_filename = "test.c"
target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx12.0.0"

@.str = private unnamed_addr constant [5 x i8] c"AAA\0A\00", align 1
@.str.1 = private unnamed_addr constant [5 x i8] c"BBB\0A\00", align 1
@__sancov_lowest_stack = external thread_local(initialexec) global i64
@__sancov_gen_ = private global [3 x i32] zeroinitializer, section "__DATA,__sancov_guards", align 4
@__sancov_gen_.1 = private global [1 x i32] zeroinitializer, section "__DATA,__sancov_guards", align 4
@"\01section$start$__DATA$__sancov_guards" = extern_weak hidden global i32
@"\01section$end$__DATA$__sancov_guards" = extern_weak hidden global i32
@llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 2, ptr @sancov.module_ctor_trace_pc_guard, ptr null }]
@llvm.used = appending global [3 x ptr] [ptr @sancov.module_ctor_trace_pc_guard, ptr @__sancov_gen_, ptr @__sancov_gen_.1], section "llvm.metadata"

; Function Attrs: noinline nounwind optnone ssp uwtable
define void @func(i32 noundef %a) #0 {
entry:
%a.addr = alloca i32, align 4
call void @__sanitizer_cov_trace_pc_guard(ptr @__sancov_gen_) #3
store i32 %a, ptr %a.addr, align 4
%0 = load i32, ptr %a.addr, align 4
%cmp = icmp sgt i32 %0, 10
br i1 %cmp, label %if.then, label %if.else

if.then: ; preds = %entry
call void @__sanitizer_cov_trace_pc_guard(ptr inttoptr (i64 add (i64 ptrtoint (ptr @__sancov_gen_ to i64), i64 4) to ptr)) #3
%call = call i32 (ptr, ...) @printf(ptr noundef @.str)
br label %if.end

if.else: ; preds = %entry
call void @__sanitizer_cov_trace_pc_guard(ptr inttoptr (i64 add (i64 ptrtoint (ptr @__sancov_gen_ to i64), i64 8) to ptr)) #3
%call1 = call i32 (ptr, ...) @printf(ptr noundef @.str.1)
br label %if.end

if.end: ; preds = %if.else, %if.then
ret void
}

declare i32 @printf(ptr noundef, ...) #1

; Function Attrs: noinline nounwind optnone ssp uwtable
define i32 @main() #0 {
entry:
%retval = alloca i32, align 4
%a = alloca i32, align 4
call void @__sanitizer_cov_trace_pc_guard(ptr @__sancov_gen_.1) #3
store i32 0, ptr %retval, align 4
store i32 100, ptr %a, align 4
%0 = load i32, ptr %a, align 4
call void @func(i32 noundef %0)
ret i32 0
}

declare void @__sanitizer_cov_trace_pc_indir(i64)

declare void @__sanitizer_cov_trace_cmp1(i8 zeroext, i8 zeroext)

declare void @__sanitizer_cov_trace_cmp2(i16 zeroext, i16 zeroext)

declare void @__sanitizer_cov_trace_cmp4(i32 zeroext, i32 zeroext)

declare void @__sanitizer_cov_trace_cmp8(i64, i64)

declare void @__sanitizer_cov_trace_const_cmp1(i8 zeroext, i8 zeroext)

declare void @__sanitizer_cov_trace_const_cmp2(i16 zeroext, i16 zeroext)

declare void @__sanitizer_cov_trace_const_cmp4(i32 zeroext, i32 zeroext)

declare void @__sanitizer_cov_trace_const_cmp8(i64, i64)

declare void @__sanitizer_cov_load1(ptr)

declare void @__sanitizer_cov_load2(ptr)

declare void @__sanitizer_cov_load4(ptr)

declare void @__sanitizer_cov_load8(ptr)

declare void @__sanitizer_cov_load16(ptr)

declare void @__sanitizer_cov_store1(ptr)

declare void @__sanitizer_cov_store2(ptr)

declare void @__sanitizer_cov_store4(ptr)

declare void @__sanitizer_cov_store8(ptr)

declare void @__sanitizer_cov_store16(ptr)

declare void @__sanitizer_cov_trace_div4(i32 zeroext)

declare void @__sanitizer_cov_trace_div8(i64)

declare void @__sanitizer_cov_trace_gep(i64)

declare void @__sanitizer_cov_trace_switch(i64, ptr)

declare void @__sanitizer_cov_trace_pc()

declare void @__sanitizer_cov_trace_pc_guard(ptr)

declare void @__sanitizer_cov_trace_pc_guard_init(ptr, ptr)

; Function Attrs: nounwind uwtable
define internal void @sancov.module_ctor_trace_pc_guard() #2 {
call void @__sanitizer_cov_trace_pc_guard_init(ptr @"\01section$start$__DATA$__sancov_guards", ptr @"\01section$end$__DATA$__sancov_guards")
ret void
}

attributes #0 = { noinline nounwind optnone ssp uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "tune-cpu"="generic" }
attributes #1 = { "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "tune-cpu"="generic" }
attributes #2 = { nounwind uwtable "frame-pointer"="all" }
attributes #3 = { nomerge }

!llvm.module.flags = !{!0, !1, !2, !3, !4}
!llvm.ident = !{!5}

!0 = !{i32 2, !"SDK Version", [2 x i32] [i32 12, i32 0]}
!1 = !{i32 1, !"wchar_size", i32 4}
!2 = !{i32 7, !"PIC Level", i32 2}
!3 = !{i32 7, !"uwtable", i32 2}
!4 = !{i32 7, !"frame-pointer", i32 2}
!5 = !{!"clang version 15.0.3"}

可以看到成功插入了__sanitizer_cov_trace_pc_guard等函数,用于对分支等的记录。

也可以用nm看一下符号信息:

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
➜  test opt --passes=sancov-module test.ll -o test.cov.bc --sanitizer-coverage-trace-pc-guard --sanitizer-coverage-level=3
➜ test llvm-nm test.cov.bc
U ___sancov_lowest_stack
U ___sanitizer_cov_load1
U ___sanitizer_cov_load16
U ___sanitizer_cov_load2
U ___sanitizer_cov_load4
U ___sanitizer_cov_load8
U ___sanitizer_cov_store1
U ___sanitizer_cov_store16
U ___sanitizer_cov_store2
U ___sanitizer_cov_store4
U ___sanitizer_cov_store8
U ___sanitizer_cov_trace_cmp1
U ___sanitizer_cov_trace_cmp2
U ___sanitizer_cov_trace_cmp4
U ___sanitizer_cov_trace_cmp8
U ___sanitizer_cov_trace_const_cmp1
U ___sanitizer_cov_trace_const_cmp2
U ___sanitizer_cov_trace_const_cmp4
U ___sanitizer_cov_trace_const_cmp8
U ___sanitizer_cov_trace_div4
U ___sanitizer_cov_trace_div8
U ___sanitizer_cov_trace_gep
U ___sanitizer_cov_trace_pc
U ___sanitizer_cov_trace_pc_guard
U ___sanitizer_cov_trace_pc_guard_init
U ___sanitizer_cov_trace_pc_indir
U ___sanitizer_cov_trace_switch
---------------- T _func
---------------- T _main
U _printf
---------------- t _sancov.module_ctor_trace_pc_guard
w section$end$__DATA$__sancov_guards
w section$start$__DATA$__sancov_guards

可能存在的问题

(1)使用MacOS编译LLVM源代码,然后编译出来的clang编译前端C程序,会出现找不到stdio.h

1
2
3
4
5
➜  test clang helloworld.c
helloworld.c:1:9: fatal error: 'stdio.h' file not found
#include<stdio.h>
^~~~~~~~~
1 error generated.

增加编译选项: -isysroot `xcrun –show-sdk-path` ,例:

1
2
3
➜  test clang helloworld.c -isysroot `xcrun --show-sdk-path`
➜ test ./a.out
Hello world!

参考链接

https://www.cnblogs.com/LittleSec/p/12757964.html