LLVM是一套模块化的编译器基础设施,Clang是基于LLVM的C++前端,二者结合可以让开发者灵活定制编译流程,实现代码分析、优化、转换等自定义功能。基于这套框架开发工具,不需要从零构建编译器,只需要复用现有组件即可快速实现需求。

开发环境准备
首先需要安装LLVM和Clang的开发依赖,不同系统的安装方式略有差异,以Ubuntu系统为例,执行以下命令即可完成基础环境搭建:
# 安装LLVM和Clang相关开发包 sudo apt-get update sudo apt-get install llvm-14 llvm-14-dev clang-14 clang-14-dev cmake build-essential # 验证安装是否成功 clang-14 --version llvm-config-14 --version
安装完成后需要确认llvm-config和clang的可执行文件路径已加入环境变量,后续编译自定义工具时会用到这些路径。
开发自定义Clang插件
Clang插件可以在编译过程中访问C++代码的抽象语法树(AST),实现代码检查、自动修改等功能。下面是一个简单的插件示例,功能是打印代码中所有函数的名称。
插件代码实现
创建FunctionPrinterPlugin.cpp文件,代码如下:
#include "clang/Frontend/FrontendPluginRegistry.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Frontend/CompilerInstance.h"
#include "llvm/Support/raw_ostream.h"
using namespace clang;
// 遍历AST的Visitor类
class FunctionVisitor : public RecursiveASTVisitor<FunctionVisitor> {
public:
explicit FunctionVisitor(ASTContext *Context) : Context(Context) {}
// 访问函数声明的回调
bool VisitFunctionDecl(FunctionDecl *FD) {
// 跳过内置函数和没有函数体的函数
if (FD->isThisDeclarationADefinition() && FD->hasBody()) {
llvm::outs() << "找到函数: " << FD->getNameInfo().getName().getAsString() << "n";
}
return true;
}
private:
ASTContext *Context;
};
// AST消费类
class FunctionPrinterASTConsumer : public ASTConsumer {
public:
explicit FunctionPrinterASTConsumer(ASTContext *Context) : Visitor(Context) {}
void HandleTranslationUnit(ASTContext &Context) override {
Visitor.TraverseDecl(Context.getTranslationUnitDecl());
}
private:
FunctionVisitor Visitor;
};
// 插件前端动作类
class FunctionPrinterPluginAction : public PluginASTAction {
public:
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef InFile) override {
return std::make_unique<FunctionPrinterASTConsumer>(&CI.getASTContext());
}
bool ParseArgs(const CompilerInstance &CI, const std::vector<std::string> &Args) override {
return true;
}
};
// 注册插件
static FrontendPluginRegistry::Add<FunctionPrinterPluginAction>
X("function-printer", "打印所有函数名称的Clang插件");
编译与测试插件
编写CMakeLists.txt文件用于编译插件:
cmake_minimum_required(VERSION 3.10)
project(FunctionPrinterPlugin)
# 查找LLVM和Clang包
find_package(LLVM 14 REQUIRED CONFIG)
find_package(Clang 14 REQUIRED CONFIG)
# 设置C++标准
set(CMAKE_CXX_STANDARD 17)
# 包含LLVM和Clang的头文件目录
include_directories(${LLVM_INCLUDE_DIRS} ${CLANG_INCLUDE_DIRS})
# 编译插件为共享库
add_library(FunctionPrinterPlugin SHARED FunctionPrinterPlugin.cpp)
target_link_libraries(FunctionPrinterPlugin PRIVATE clangAST clangFrontend clangBasic)
执行编译命令:
mkdir build && cd build cmake .. make
编译完成后会得到libFunctionPrinterPlugin.so文件,创建测试用的C++代码test.cpp:
#include <iostream>
int add(int a, int b) {
return a + b;
}
void print_hello() {
std::cout << "Hello LLVM" << std::endl;
}
int main() {
print_hello();
return add(1, 2);
}
使用Clang加载插件编译测试代码:
clang-14 -fplugin=./libFunctionPrinterPlugin.so -fplugin-arg-function-printer= test.cpp -o test
编译过程中会输出找到的函数名称:
找到函数: add 找到函数: print_hello 找到函数: main
开发自定义LLVM Pass
LLVM Pass用于对LLVM中间表示(IR)进行分析或转换,下面是一个简单的Pass示例,功能是统计IR中加法指令的数量。
Pass代码实现
创建AddInstCounterPass.cpp文件:
#include "llvm/Pass.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/Instruction.h"
#include "llvm/Support/raw_ostream.h"
using namespace llvm;
namespace {
// 定义Pass结构体
struct AddInstCounterPass : public FunctionPass {
static char ID;
AddInstCounterPass() : FunctionPass(ID) {}
// 对每个函数执行Pass逻辑
bool runOnFunction(Function &F) override {
int addCount = 0;
// 遍历函数中的所有基本块
for (auto &BB : F) {
// 遍历基本块中的所有指令
for (auto &Inst : BB) {
// 判断是否为加法指令
if (Inst.getOpcode() == Instruction::Add) {
addCount++;
}
}
}
if (addCount > 0) {
llvm::outs() << "函数 " << F.getName() << " 中有 " << addCount << " 条加法指令n";
}
return false; // 返回false表示没有修改IR
}
};
}
// 初始化Pass ID
char AddInstCounterPass::ID = 0;
// 注册Pass,指定Pass名称和命令行参数
static RegisterPass<AddInstCounterPass> X("add-counter", "统计加法指令数量的LLVM Pass");
编译与测试Pass
编写编译Pass的CMakeLists.txt:
cmake_minimum_required(VERSION 3.10)
project(AddInstCounterPass)
find_package(LLVM 14 REQUIRED CONFIG)
set(CMAKE_CXX_STANDARD 17)
include_directories(${LLVM_INCLUDE_DIRS})
add_library(AddInstCounterPass SHARED AddInstCounterPass.cpp)
target_link_libraries(AddInstCounterPass PRIVATE LLVM)
编译得到libAddInstCounterPass.so后,使用之前的test.cpp生成LLVM IR文件:
clang-14 -S -emit-llvm test.cpp -o test.ll
使用opt工具加载Pass运行:
opt-14 -load ./libAddInstCounterPass.so -add-counter test.ll -o test_opt.ll
运行后会输出统计结果:
函数 add 中有 1 条加法指令 函数 main 中有 1 条加法指令
常见问题与注意事项
- LLVM和Clang的版本需要匹配,不同大版本之间的API差异较大,开发时尽量使用相同版本的开发包。
- Clang插件的共享库路径需要正确指定,否则Clang无法加载插件。
- LLVM Pass分为函数级、模块级等不同类型,开发时需要根据需求选择合适的Pass基类。
- 操作AST或IR时需要注意空指针判断,避免出现段错误。
总结
基于LLVM和Clang开发自定义工具的核心流程是明确需求、选择合适的开发组件(插件或Pass)、实现对应逻辑、编译测试。Clang插件适合处理C++源码层面的逻辑,LLVM Pass适合处理编译中间层的逻辑,二者可以结合使用满足复杂的定制需求。开发者可以根据实际场景灵活扩展,实现代码分析、自动优化、自定义语法检查等多种功能。
LLVMClangC++_compilerAST自定义工具开发修改时间:2026-06-26 14:15:28