1. 编写第一个LLVM Pass
任务目标
实现一个LLVM FunctionPass,遍历函数内所有指令并打印每条指令的操作码名称,通过该最小示例掌握LLVM Pass的编写、注册与加载流程。
环境准备
需要LLVM开发库(版本≥14,本示例兼容至18)。Ubuntu/Debian安装:llvm-dev、llvm包;macOS使用Homebrew:llvm。确保llvm-config在PATH中。
完整实现代码
// OpcodePrinter.cpp
#include "llvm/IR/Function.h"
#include "llvm/IR/Instructions.h"
#include "llvm/Pass.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Passes/PassPlugin.h"
#include "llvm/Support/raw_ostream.h"
using namespace llvm;
namespace {
struct OpcodePrinter : public PassInfoMixin<OpcodePrinter> {
PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) {
for (BasicBlock &BB : F) {
for (Instruction &I : BB) {
errs() << F.getName() << ":\t"
<< I.getOpcodeName() << "\n";
}
}
return PreservedAnalyses::all();
}
};
} // namespace
// 注册插件入口
extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() {
return {LLVM_PLUGIN_API_VERSION, "OpcodePrinter", LLVM_VERSION_STRING,
[](PassBuilder &PB) {
PB.registerPipelineParsingCallback(
[](StringRef Name, FunctionPassManager &FPM,
ArrayRef<PassBuilder::PipelineElement>) {
if (Name == "print-opcodes") {
FPM.addPass(OpcodePrinter());
return true;
}
return false;
});
}};
}
编译与运行
编译Pass为共享库:
clang++ -shared -fPIC -fno-rtti OpcodePrinter.cpp \
$(llvm-config --cxxflags --ldflags --libs) -o libOpcodePrinter.so
准备一个测试C文件test.c:
int add(int a, int b) { return a + b; }
生成LLVM IR并运行Pass:
clang -S -emit-llvm test.c -o test.ll
opt -load-pass-plugin=./libOpcodePrinter.so \
-passes="print-opcodes" test.ll -o /dev/null
验证方法
运行后标准错误输出应包含类似以下内容(顺序可能因优化级别不同而异):
add: alloca
add: store
add: load
add: load
add: add
add: ret
判定通过条件:输出中add函数内包含add和ret操作码,且无编译错误。若使用-O0生成IR,指令序列与上述示例一致。
常见问题
- 链接错误:确认
llvm-config --cxxflags包含-fno-rtti,且LLVM版本与编译命令一致。 - Pass未注册:检查插件加载路径是否正确,
-passes="print-opcodes"名称需与代码中注册字符串完全匹配。 - 输出为空:确认输入IR包含函数定义(非仅声明),可用
cat test.ll检查。