初探LLVM&clang&pass
字数 2424 2025-08-24 16:48:07
LLVM & Clang & Pass 深入解析
1. 编译器架构概述
1.1 经典三段式设计
现代编译器通常采用三段式设计架构:
-
前端 (Frontend):
- 进行词法分析、语法分析
- 生成抽象语法树(AST)
- 生成中间语言(IR)
- 示例:Java字节码、LLVM IR、GCC的GIMPLE Tuples
-
优化器 (Optimizer):
- 分析中间语言
- 避免多余计算,提高性能
- 通过Pass系统进行优化
-
后端 (Backend):
- 根据中间语言生成对应CPU架构指令
- 支持多种架构如X86、ARM等
这种设计实现了模块化,增加新语言只需实现新前端,增加新CPU架构只需实现新后端。
1.2 LLVM特点
- 模块化设计:前后端解耦,支持灵活扩展
- 统一的IR:所有语言前端生成相同的中间表示
- 强大的Pass系统:
- 自动对Pass进行排序
- 管道化提高效率
- 支持分析、转换和代码生成Pass
1.3 LLVM的广义与狭义定义
- 广义LLVM:指整个编译器框架,包括前端、优化器、后端
- 狭义LLVM:特指LLVM后端,即优化器对IR进行优化直到生成目标代码的过程
2. LLVM主要子项目
| 项目名称 | 描述 |
|---|---|
| LLVM Core | 包含源代码和目标架构无关的独立配置器,支持多种CPU的汇编代码生成 |
| Clang | C/C++/Objective-C编译器,提供高效编译和良好错误信息 |
| LLDB | 基于LLVM和Clang构建的本地调试器,支持多线程调试 |
| LLD | clang/llvm内置的链接器 |
| dragonegg | GCC插件,可将GCC优化和代码生成器替换为LLVM工具 |
| libc++, libc++ ABI | 高性能C++标准库实现,完整支持C++11 |
| compiler-rt | 为动态测试工具提供运行时库实现 |
| OpenMP | 提供OpenMP运行时 |
| vmkit | 基于LLVM的Java和.NET虚拟机实现 |
| polly | 支持高级循环和数据本地化优化的框架 |
| libclc | OpenCL标准库实现 |
| klee | 符号化虚拟机,用于发现错误和证明函数属性 |
| SAFECode | 用于C/C++程序的内存安全编译器 |
3. LLVM编译安装
3.1 环境准备
sudo apt-get install subversion
sudo apt-get install cmake
3.2 下载源码
建议使用8.0.0版本:
#!/usr/bin/env bash
cd ~ && mkdir LLVM && cd LLVM
wget http://releases.llvm.org/8.0.0/llvm-8.0.0.src.tar.xz
tar -xf llvm-8.0.0.src.tar.xz && rm llvm-8.0.0.src.tar.xz
mv ./llvm-8.0.0.src ./llvm-8.0.0
cd llvm-8.0.0/tools/
wget http://releases.llvm.org/8.0.0/cfe-8.0.0.src.tar.xz
tar -xf ./cfe-8.0.0.src.tar.xz && rm ./cfe-8.0.0.src.tar.xz
mv ./cfe-8.0.0.src ./clang
cd ./clang/tools
wget http://releases.llvm.org/8.0.0/clang-tools-extra-8.0.0.src.tar.xz
tar -xf ./clang-tools-extra-8.0.0.src.tar.xz && rm ./clang-tools-extra-8.0.0.src.tar.xz
mv ./clang-tools-extra-8.0.0.src ./clang-tools-extra
cd ../../../projects/
wget http://releases.llvm.org/8.0.0/compiler-rt-8.0.0.src.tar.xz
wget http://releases.llvm.org/8.0.0/libcxx-8.0.0.src.tar.xz
wget http://releases.llvm.org/8.0.0/libcxxabi-8.0.0.src.tar.xz
tar -xf ./compiler-rt-8.0.0.src.tar.xz && rm ./compiler-rt-8.0.0.src.tar.xz
tar -xf libcxx-8.0.0.src.tar.xz && rm libcxx-8.0.0.src.tar.xz
tar -xf libcxxabi-8.0.0.src.tar.xz && rm libcxxabi-8.0.0.src.tar.xz
mv compiler-rt-8.0.0.src ./compiler-rt
mv libcxx-8.0.0.src ./libcxx
mv libcxxabi-8.0.0.src ./libcxxabi
cd ../../ && mkdir build && cd build
cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release ../llvm-8.0.0
make -j4
sudo make install
3.3 验证安装
./build/bin/clang++ -v
3.4 使用Clang重新编译LLVM
CC=clang CXX=clang++ cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release ../llvm-8.0.0
make -j4
sudo make install
4. Clang使用
4.1 基本编译
#include <iostream>
using namespace std;
int main() {
cout << "Hello, world!" << endl;
return 0;
}
编译C++,使用C++11标准和libc++库:
clang++ -std=c++11 -stdlib=libc++ test.cpp
4.2 语法检查
clang test.cpp -fsyntax-only
4.3 编译选项
-
-c:仅进行预处理和编译,输出重定位ELFclang++ -std=c++11 -stdlib=libc++ ./test1.cpp -c -o test -
-S:仅进行预处理和编译,输出可读汇编文本clang++ -std=c++11 -stdlib=libc++ ./test1.cpp -S -o test
4.4 生成LLVM IR
生成未优化的LLVM IR:
clang++ -std=c++11 -stdlib=libc++ test.cpp -S -emit-llvm -o test
生成O3优化的LLVM IR:
clang++ -std=c++11 -stdlib=libc++ test.cpp -S -emit-llvm -o test -O3
4.5 编译过程分析
示例代码:
#include <stdio.h>
int main() {
printf("hello world\n");
return 0;
}
查看编译各阶段:
clang -ccc-print-phases test1.c
4.5.1 预处理
clang -E test1.c
4.5.2 词法分析
clang -fmodules -E -Xclang -dump-tokens test1.c
4.5.3 语法分析
clang -fmodules -fsyntax-only -Xclang -ast-dump test1.c
AST节点类型:
TranslationUnitDecl:根节点,表示编译单元FunctionDecl:函数声明ParmVarDecl:参数声明CompoundStmt:具体语句DeclStmt:语句声明VarDecl:变量声明IntegerLiteral:整数字面量BinaryOperator:操作符ImplicitCastExpr:隐式转换DeclRefExpr:引用类型声明ReturnStmt:返回语句
5. 中间语言IR
5.1 IR表示形式
-
文本格式:便于阅读的汇编格式,后缀为
.llclang test1.c -S -emit-llvm -o test.ll -
二进制格式:不可读,后缀为
.bcclang test1.c -c -emit-llvm -o test.bc
5.2 IR格式转换
-
.bc转.ll:llvm-dis test.bc -
.ll或.bc转汇编:llc ./test.ll -o test.s
5.3 IR语法
5.3.1 关键字
;:注释define:函数定义declare:函数声明i32:32位整数ret:函数返回alloca:栈空间分配内存align:内存对齐load:读取数据store:写入数据icmp:整数值比较br:选择分支label:代码标签
5.3.2 标识符
- 全局标识符:以
@开头(函数、全局变量) - 本地标识符:以
%开头(寄存器名称、类型)
5.3.3 示例
乘法运算的三种IR表示:
; 乘法运算
%result = mul i32 %X, 8
; 左移运算
%result = shl i32 %X, 3
; 三次加法运算
%0 = add i32 %X, %X
%1 = add i32 %0, %0
%result = add i32 %1, %1
5.3.4 其他语法元素
unnamed_addr:标记全局变量,表示地址不重要nsw:"No Signed Wrap",无符号值运算标识nuw:"No Unsigned Wrap",有符号值运算标识bitcast ... to ..:类型转换指令dso_local:表示解析为同一链接单元中的符号
6. Pass系统
6.1 Pass分类
- 分析Pass:收集信息用于调试或可视化
- 转换Pass:修改IR,进行优化或混淆
- 代码生成Pass:生成目标代码
6.2 编写简单Pass
6.2.1 项目结构
outpass/
├── CMakeLists.txt
└── Print_FuncPass/
├── CMakeLists.txt
└── Print_FuncPass.cpp
6.2.2 顶层CMakeLists.txt
cmake_minimum_required(VERSION 3.4)
set(ENV{LLVM_DIR} ~/LLVM/bulid/lib/cmake/llvm)
find_package(LLVM REQUIRED CONFIG)
add_definitions(${LLVM_DEFINITIONS})
include_directories(${LLVM_INCLUDE_DIRS})
link_directories(${LLVM_LIBRARY_DIRS})
add_compile_options(-std=c++14)
add_subdirectory(Print_FuncPass)
6.2.3 Pass目录CMakeLists.txt
add_library(PrintFunctions MODULE
Print_FuncPass.cpp)
set_target_properties(PrintFunctions PROPERTIES COMPILE_FLAGS "-fno-rtti")
if(APPLE)
set_target_properties(PrintFunctions PROPERTIES LINK_FLAGS "-undefined dynamic_lookup")
endif(APPLE)
6.2.4 Pass实现
#include "llvm/Pass.h"
#include "llvm/IR/Function.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Transforms/IPO/PassManagerBuilder.h"
using namespace llvm;
namespace {
struct Hello : public FunctionPass {
static char ID;
Hello() : FunctionPass(ID) {}
virtual bool runOnFunction(Function &F) {
errs() << "A function has been called: " << F.getName() << "!\n";
return false;
}
};
}
char Hello::ID = 0;
// 注册到opt中
static RegisterPass<Hello> X("print_func", "print func name PASS",
false /* Only looks at CFG */,
false /* Analysis Pass */);
// 注册到标准编译流程中
static RegisterStandardPasses Y(
PassManagerBuilder::EP_EarlyAsPossible,
[](const PassManagerBuilder &Builder, legacy::PassManagerBase &PM) {
PM.add(new Hello());
});
6.2.5 测试代码
#include <stdio.h>
int func2() {
int a, b = 1;
return a + b;
}
int func1() {
int a, b = 1;
func2();
return a + b;
}
int main() {
func1();
return 0;
}
6.2.6 使用Pass
-
直接使用clang:
clang -Xclang -load -Xclang ./outpass/Print_FuncPass/libPrintFunctions.so ./test.c -
使用opt:
clang -emit-llvm -c ./test.c opt -load ./outpass/Print_FuncPass/libPrintFunctions.so -print_func < ./test.bc
7. 总结
本文详细介绍了LLVM编译器框架的核心概念、安装配置、使用方法和Pass开发:
- 理解了LLVM的三段式架构设计优势
- 掌握了LLVM的编译安装过程
- 学习了Clang的基本使用和编译过程分析
- 深入了解了LLVM IR的语法和特性
- 实践了如何开发自定义Pass
LLVM强大的模块化设计和Pass系统使其成为编译器技术研究和开发的理想平台。通过自定义Pass,开发者可以实现代码优化、分析、混淆等多种功能。