在搭建LLVM的环境之前有着一些对编译原理的盲区,以及对gcc、g++、clang、clang++、makefile、make、cmake、cmakelist、ninja这些定义有些模糊不清,所以先补充一下这些前置知识

简单认识编译原理

编译过程分为五个步骤,按照执行流程的话又分为前端、中端、后端

编译器实际上也是一种电脑程序,作用就是把我们输入的某种编程语言写的源代码(原始语言)转换成另一种编程语言(目标语言)

编译各个阶段的展示

image

词法分析

image

我们编写的源代码实际上就是一串线性字符流、扫描器通过接收线性字符流,将其组合成一系列类似单词的东西。在编程语言中,每个词都被称作词法单元。有些词法单元是单个字符,也有的是很多字符。(生成的词法单元也被称作token)

源代码中的注释和空格实际上没有任何意义

语法分析

通过将扁平的词法单元序列转化为树形结构,树形结构能更好的反应嵌套本质。这些树被抽象语法树(AST, Abstract Syntax Tree)

image

语义分析

通过抽象语法树来作上下文相关的分析,获得一些信息(类型信息、引用信息),通过会直接存储到语法树本身的属性中。

image

但有时候可能会将这些信息存储在外部的查找表中。该表的关键字是标识符,即变量和声明的名称。也被称作符号表。

至此,编译器完成了理解源代码的工作、统称为编译器的前端

中间代码生成

在中间阶段,代码可能会被存储在一些中间代码(IR)中,IR充当了这源代码和目标代码的中间接口。

IR的优点在于,如果想实现一种语言到另一种语言的转化,实际上只需要重新写一个前端和一个后端,而不用再重新实现一个编译器。

而且后续的优化工作使用IR的话,就可以用专门针对IR的统一算法去进行优化而不用再专门实现一种针对其它中间代码的算法。

优化

因为我们编写的代码不一定是最优的,所以编译器会选择最优的代码进行优化,优化是基于IR代码的。

目标代码生成

当前面的优化过程完成后,接下来就是将其转换为类似于汇编的原始指令。(简单来说就是汇编代码)

生成目标代码后,编译过程基本上就完成了,像gcc编译器的话后面再执行汇编(转化为机器语言)、链接两个步骤就能生成可执行文件。

image

gcc、g++、Clang、Clang++的区别

GCC、G++

gcc这种现代“编译器”是一个工具集合,包含了预处理器、编译器,而且会自己调用汇编器、连接器或加载器等多种工具,而不是单单的一个编译器

那编译器是什么?

简单来说,编译器就是一个程序能读取某种语言的一个程序(这个语言是源语言),然后将其翻译转换成另一种语言的等价程序(这个语言被称为目标语言)。编译器最重要的一个规定就是报告在翻译过程中发现“源”程序的错误。
如果这个目标程序是机器语言,那么这个目标程序就是可执行程序。

也就是说,编译器实际上是将一种语言转换成另一种语言的程序

g++则是跟gcc一样,只不过针对的是C++

Clang、Clang++

Clang是一个编译器,同时也是一个前端。

前端是对于从源代码可执行程序过程的前端,Clang因为可以实现从源代码变成类似的汇编代码的功能,所以也是一个编译器。

Clang++则是针对C++

make、makefile、ninja、Cmake、Cmakelist的关系

这里引入大佬的流程图,也是非常清晰明了

image-20240912222326342

Make是一个构建自动化工具,用于自动化编译和构建过程,减少重复的编译工作。
Makefile是一个文本文件,里面包含了Make工具需要遵循的指令,描述了如何编译和链接程序。
开发者可以在Makefile中定义项目的构建规则和依赖关系,然后通过执行make命令来自动构建项目。

CMake是一个跨平台的构建系统生成工具,它可以生成标准的构建文件,如Unix的Makefile或Windows的工程文件。
CMake使用CMakeLists.txt文件来定义项目的构建规则和依赖关系。
CMake并不直接构建项目,而是生成适用于特定平台的构建文件,然后依赖于底层的构建系统(如Make或Ninja)来实际构建项目。

Ninja是一个小巧且专注于速度的构建系统,类似于Make,但设计上更加注重快速构建。
CMake可以生成Ninja的构建文件(类似于Makefile),然后使用Ninja来实际构建项目,通常比Make更快。

CMakeLists.txt是CMake的配置文件,它告诉CMake如何构建你的项目(类似于Makefile,但更为高级和可移植)。
综上所述,GCC是实际的编译器;Make和Ninja是构建系统,用于自动化编译过程;CMake是生成构建文件的工具,用于生成Makefile或Ninja构建文件;Makefile和CMakeLists.txt是相应构建系统的配置文件。开发者可以使用这些工具来简化和自动化编译和构建过程,提高开发效率。

Linux下编译LLVM源码

1
2
3
4
5
6
7
8
9
10
11
12
git clone https://github.com/llvm/llvm-project.git
cd llvm-project
mkdir build
cd build
cmake -G Ninja -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TARGETS_TO_BUILD="X86" -DCMAKE_BUILD_TYPE=Debug -DLLVM_INCLUDE_TESTS=OFF -DLLVM_ENABLE_RTTI=ON ../llvm
ninja -j1

#配置环境变量
vim ~/.bashrc
export LLVM_HOME=/home/zhuyuan/llvm-project/build // 这个是生成的build目录
export PATH=${LLVM_HOME}/bin:$PATH
source ~/.bashrc

如果ninja编译不动,那就创建交换分区,线程-j1即可,虽然慢但不会死机

Vscode配置调试pass环境

任意目录下创建mypass文件夹,之后建立一个.vscode文件夹后存放三个json以及makefile文件。

c_cpp_properties.json

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
// c_cpp_properties.json  用来配置要包含的头文件和宏
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"/home/zhuyuan/llvm-project/llvm/include", // 这里设置为LLVM源码目录的include目录
"/home/zhuyuan/llvm-project/build/include" // 这里设置为LLVM_HOME的include目录
],
"defines": [
"_GNU_SOURCE",
"__STDC_CONSTANT_MACROS",
"__STDC_FORMAT_MACROS",
"__STDC_LIMIT_MACROS"
],
"compilerPath": "/home/zhuyuan/llvm-project/build/bin/clang", // 这里设置为clang的绝对路径(vscode不加载环境变量,必须要用绝对路径)
"cStandard": "c17",
"cppStandard": "c++23",
"intelliSenseMode": "linux-clang-x64",
"compilerArgs": [
"-fno-exceptions",
"-fno-rtti"
]
}
],
"version": 4
}

launch.json

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
// launch.json   配置调试设置
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Pass",
"type": "cppdbg",
"request": "launch",
"program": "/home/zhuyuan/llvm-project/build/bin/opt", // 这里设置为opt的绝对路径
"args": [
"-load-pass-plugin=${fileDirname}/build/${fileBasenameNoExtension}.so",
"-passes=${fileBasenameNoExtension}",
"${fileDirname}/build/1-target_before.bc",
"-o",
"${fileDirname}/build/2-target_after.bc",
],
"stopAtEntry": false,
"cwd": "${fileDirname}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
],
"sourceFileMap":{
// "./": ""
},
//"preLaunchTask": "build",
}
]
}

tasks.json

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
// tasks.json
{
"version": "2.0.0",
"tasks": [
{
"label": "build", // 设置build任务 即 make build -j2
"type": "shell",
"command": "make",
"args": [
"build",
"-j2",
],
"options": {
"cwd": "${workspaceFolder}",
"env": {
"PATH": "/home/zhuyuan/llvm-project/build/bin:${env:PATH}",
"FILE": "${relativeFile}",
"FILE_DIR_NAME": "${fileDirname}",
"FILE_EXT_NAME": "${fileExtname}",
"FILE_BASENAME_NO_EXTENSION": "${fileBasenameNoExtension}"
}
},
"isBackground": false,
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "dedicated",
"showReuseMessage": true,
"clear": true
},
"problemMatcher": {
"owner": "cpp",
"fileLocation": [
"absolute"
],
"pattern": {
"regexp": "^(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$",
"file": 1,
"line": 2,
"column": 3,
"severity": 4,
"message": 5
}
}
},
{
"label": "run", // 设置run任务
"type": "shell",
"command": "make",
"group": {
"kind": "build",
"isDefault": true
},
"args": [
"run",
"-j2"
],
"options": {
"cwd": "${workspaceFolder}",
"env": {
"PATH": "/home/zhuyuan/llvm-project/build/bin:${env:PATH}",
"FILE": "${relativeFile}",
"FILE_DIR_NAME": "${fileDirname}",
"FILE_EXT_NAME": "${fileExtname}",
"FILE_BASENAME_NO_EXTENSION": "${fileBasenameNoExtension}"
}
},
"isBackground": false,
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "dedicated",
"showReuseMessage": true,
"clear": true
},
"problemMatcher": []
},
{
"label": "clean", // 设置clean任务
"type": "shell",
"command": "make",
"args": [
"clean"
],
"options": {
"cwd": "${workspaceFolder}",
"env": {
"FILE": "${relativeFile}",
"FILE_DIR_NAME": "${fileDirname}",
"FILE_EXT_NAME": "${fileExtname}",
"FILE_BASENAME": "${fileBasename}"
}
},
"isBackground": true,
"presentation": {
"reveal": "never",
},
"problemMatcher": []
}
]
}

makefile

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
TARGRT_FILE=./TestProgram.cpp	# 指定目标源文件名

cxxflags=`llvm-config --cxxflags`
ldflags=`llvm-config --ldflags`
libflags=`llvm-config --libs`

build-pass:
@bash -c "if [[ ! -d $(FILE_DIR_NAME)/build ]];then mkdir $(FILE_DIR_NAME)/build;fi"
@g++ $(cxxflags) -ggdb -shared $(ldflags) $(libflags) -fPIC $(FILE) -o $(FILE_DIR_NAME)/build/$(FILE_BASENAME_NO_EXTENSION).so
@echo "\033[32mBuild $(FILE) Successfully.\033[0m"

build-target:
@clang -emit-llvm -Xclang -disable-O0-optnone -c $(TARGRT_FILE) -o $(FILE_DIR_NAME)/build/1-target_before.bc
@llvm-dis $(FILE_DIR_NAME)/build/1-target_before.bc
@echo "\033[32mBuild target.cpp successfully.\033[0m"

clean:
rm -f ./build/*.o ./build/*.so ./build/*.bc ./build/*.ll*

build: build-pass build-target

run: build
@clear
@opt -load-pass-plugin=$(FILE_DIR_NAME)/build/$(FILE_BASENAME_NO_EXTENSION).so \
-passes=$(FILE_BASENAME_NO_EXTENSION) \
$(FILE_DIR_NAME)/build/1-target_before.bc -o $(FILE_DIR_NAME)/build/2-target_after.bc
@llvm-dis $(FILE_DIR_NAME)/build/2-target_after.bc

下断点之后先F5准备进行调试,然后执行Ctrl+Shift+B生成调试所需要的文件即可

image-20240912224412972

gdb调试

1
2
3
4
5
6
gdb opt
set args -load-pass-plugin='/home/zhuyuan/llvm_Mypass/ollvm_flapass/build/Flatten.so' -passes=Flatten ./build/1-target_before.bc -o ./build/2-target_after.bc
b main
r
#llvm::cl::ParseCommandLineOptions一般这个函数用来加载so
#等到将pass.so文件加载进入之后根据基地址和偏移定位pass的入口函数处,下断之后run过去即可断到so中

image-20240925110536360

image-20240925110604149