1. LLVM Pass开发环境准备
在开始编写第一个LLVM Pass之前,我们需要先搭建好开发环境。LLVM作为一个模块化的编译器框架,其Pass系统是进行代码分析和转换的核心机制。
1.1 LLVM安装与配置
推荐使用官方预编译的LLVM发行版进行安装,对于Ubuntu/Debian系统可以执行:
bash复制wget https://github.com/llvm/llvm-project/releases/download/llvmorg-16.0.0/clang+llvm-16.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz
tar xvf clang+llvm-16.0.0*.tar.xz
sudo mv clang+llvm-16.0.0*/ /opt/llvm
echo 'export PATH=/opt/llvm/bin:$PATH' >> ~/.bashrc
对于其他操作系统,可以从LLVM官网下载对应的预编译包。安装完成后验证:
bash复制llvm-config --version
应该输出类似"16.0.0"的版本号。
1.2 开发工具链准备
除了LLVM本身,我们还需要:
- CMake(3.13+版本)
- 现代C++编译器(GCC 9+/Clang 10+)
- Ninja或Make构建工具
在Ubuntu上可以一键安装:
bash复制sudo apt install cmake ninja-build g++ python3-pip
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 第一个LLVM Pass的实现
2.1 Pass的基本结构
一个最简单的LLVM Pass需要继承自Pass基类并实现几个关键方法。下面是一个打印函数名的示例Pass:
cpp复制#include "llvm/Pass.h"
#include "llvm/IR/Function.h"
#include "llvm/Support/raw_ostream.h"
using namespace llvm;
namespace {
struct MyFirstPass : public FunctionPass {
static char ID;
MyFirstPass() : FunctionPass(ID) {}
bool runOnFunction(Function &F) override {
errs() << "Found function: " << F.getName() << "\n";
return false; // 没有修改IR
}
};
} // namespace
char MyFirstPass::ID = 0;
static RegisterPass<MyFirstPass> X("my-first-pass",
"My First LLVM Pass");
这个Pass会遍历程序中的所有函数,并打印它们的名称。关键点:
- 继承自
FunctionPass表示这是一个函数级别的Pass runOnFunction是主要的处理逻辑RegisterPass宏注册Pass使其可被LLVM识别
2.2 CMake构建配置
为了编译这个Pass,我们需要配置CMakeLists.txt:
cmake复制cmake_minimum_required(VERSION 3.13)
project(MyLLVMPass)
find_package(LLVM REQUIRED CONFIG)
include_directories(${LLVM_INCLUDE_DIRS})
add_definitions(${LLVM_DEFINITIONS})
add_library(MyFirstPass MODULE
MyFirstPass.cpp
)
target_link_libraries(MyFirstPass
PRIVATE
LLVMCore
LLVMSupport
)
构建命令:
bash复制mkdir build && cd build
cmake -G Ninja -DLLVM_DIR=/opt/llvm/lib/cmake/llvm ..
ninja
成功构建后会生成MyFirstPass.so(Linux)或MyFirstPass.dll(Windows)动态库。
3. 运行LLVM Pass的多种方式
3.1 使用opt工具运行Pass
opt是LLVM提供的Pass运行工具,基本用法:
bash复制opt -load ./build/MyFirstPass.so -my-first-pass input.bc -o output.bc
其中:
-load指定Pass动态库路径-my-first-pass是我们在代码中注册的Pass名称input.bc是LLVM字节码输入文件-o output.bc指定输出文件
3.2 集成到Clang编译流程
我们可以将Pass集成到常规的编译流程中:
bash复制clang -Xclang -load -Xclang ./build/MyFirstPass.so test.c
这种方式会在编译过程中自动运行我们的Pass。
3.3 调试Pass输出
为了查看Pass的详细执行情况,可以启用LLVM的调试输出:
bash复制opt -load ./build/MyFirstPass.so -my-first-pass input.bc -debug-only=my-first-pass
需要在Pass代码中添加调试支持:
cpp复制#define DEBUG_TYPE "my-first-pass"
#include "llvm/Support/Debug.h"
bool runOnFunction(Function &F) override {
LLVM_DEBUG(dbgs() << "Processing: " << F.getName() << "\n");
// ...
}
4. 实际案例:函数调用统计Pass
让我们实现一个更实用的Pass,统计程序中的函数调用次数:
cpp复制struct CallCounterPass : public ModulePass {
static char ID;
CallCounterPass() : ModulePass(ID) {}
bool runOnModule(Module &M) override {
std::map<std::string, unsigned> callCounts;
for (auto &F : M) {
for (auto &BB : F) {
for (auto &I : BB) {
if (auto *Call = dyn_cast<CallInst>(&I)) {
if (Function *CalledFunc = Call->getCalledFunction()) {
callCounts[CalledFunc->getName()]++;
}
}
}
}
}
errs() << "Function call counts:\n";
for (const auto &pair : callCounts) {
errs() << pair.first << ": " << pair.second << "\n";
}
return false;
}
};
这个Pass展示了:
- 使用
ModulePass处理整个模块 - 三层循环遍历函数、基本块和指令
- 使用
dyn_cast进行安全的类型转换 - 收集并输出统计信息
5. 常见问题与调试技巧
5.1 段错误(Segmentation Fault)
这是新手最常见的问题,通常是因为:
- 访问了空指针
- LLVM API版本不匹配
- 错误的IR遍历方式
调试方法:
- 使用
gdb调试opt工具 - 添加大量日志输出
- 检查LLVM版本一致性
5.2 Pass没有被执行
可能原因:
- Pass注册名称拼写错误
- 没有正确指定
-load参数 - Pass类型选择错误(如应该用ModulePass却用了FunctionPass)
5.3 构建失败
常见构建问题:
- 缺少LLVM开发包:确保安装了
llvm-dev或对应系统的开发包 - CMake找不到LLVM:显式指定
-DLLVM_DIR=/path/to/llvm/cmake - C++标准不兼容:在CMake中设置
CMAKE_CXX_STANDARD=17
5.4 性能优化技巧
当处理大型项目时:
- 避免在Pass中创建不必要的容器
- 使用
SmallVector等LLVM高效容器 - 减少字符串操作
- 使用
LLVM_DEBUG而非errs()输出大量日志
6. Pass开发进阶方向
掌握了基础Pass开发后,可以探索:
6.1 分析Pass vs 转换Pass
- 分析Pass:只收集信息不修改IR(如我们的示例)
- 转换Pass:会修改IR,需要正确处理所有Use-Def链
6.2 使用Pass管道
LLVM支持Pass管道,可以组合多个Pass:
bash复制opt -passes='my-pass1,my-pass2' input.bc -o output.bc
6.3 基于New Pass Manager
LLVM新版Pass Manager提供更好的性能:
cpp复制struct MyPass : public PassInfoMixin<MyPass> {
PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) {
// 实现逻辑
return PreservedAnalyses::all();
}
};
6.4 集成到Clang静态分析器
可以将Pass集成到Clang的静态分析框架中,实现更复杂的代码检查功能。
