llvm pass 插件必须匹配工具链版本,根本原因是llvm ir内存布局、passmanager接口、analysismanager生命周期及function/module等核心类的abi在不同版本间不保证二进制兼容,官方明确不提供跨版本abi支持。

LLVM Pass 插件必须匹配工具链版本,根本原因在于 LLVM IR 的内存布局、PassManager 接口、AnalysisManager 生命周期管理,以及核心类(如 Function、Module、Value)的 ABI 在不同 LLVM 版本间不保证二进制兼容。
为什么 clang 加载 .so 会直接崩溃
当你用 LLVM 15.0.7 编译的 libMyPass.so,试图在 LLVM 16.0.0 的 clang 进程中用 -fpass-plugin= 加载时,最常见现象是段错误或 std::bad_cast 异常。这不是插件代码写错了,而是:
-
clang运行时加载的libLLVM.so里,Function类的虚函数表偏移、成员变量顺序、RTTI type_info 名称都和你编译插件时链接的头文件/库不一致 -
PassBuilder注册流程在 LLVM 15 和 16 中改过多次:比如registerPipelineStartEPCallback参数签名变过,AnalysisManager模板参数从Function改为Function&等 - 即使你用
llvm-config --ldflags链接了正确路径,只要运行时LD_LIBRARY_PATH或系统默认libLLVM.so版本不一致,就等同于“用 C++17 编译的 shared_ptr 对象被 C++20 的 runtime 析构”
LLVM_VERSION_MAJOR 不是装饰,是强制约束
LLVM 官方明确不提供跨版本 ABI 兼容性保证。所有子项目(clang、llc、opt)都依赖同一份 llvm/include/llvm/Config/llvm-config.h 生成的宏定义。插件里若漏掉版本检查:
- 用
#include <llvm></llvm>时,实际包含的是你本地构建目录下的头文件,而非目标工具链安装路径下的头文件 - 若你本地是 LLVM 15 源码树,但目标环境是预编译的
clang+llvm-15.0.7-x86_64-linux-gnu-ubuntu-22.04.tar.xz,二者llvm-config --version虽然都是 15.0.7,但 patch 级别(如 15.0.7 vs 15.0.7-2)或构建选项(-DLLVM_ENABLE_RTTI=ON/OFF)不同,仍会导致dynamic_cast失败 - 必须在 CMakeLists.txt 里显式校验:
if(NOT "${LLVM_VERSION_MAJOR}" STREQUAL "15"),否则 CI 上一跑就挂
为什么 opt -load 比 clang -fpass-plugin 更容易出错
opt 工具本身是单体可执行文件,它静态链接了 libLLVMCore.a 等,而你的插件是动态链接的。这种混合链接模式下:
- 插件内部调用的
llvm::createFunctionPass,可能和opt自带的llvm::createFunctionPass符号冲突,导致构造函数被跳过 -
opt默认启用 new pass manager(-enable-new-pm),但旧版插件若只实现了legacy::Pass接口,在 LLVM 15+ 里默认已被禁用,需额外加-disable-new-pm才能触发——而这个开关在clang里根本不可用 - 调试时用
gdb --args opt -load ./MyPass.so -mypass input.bc,常看到PluginLoader::operator()返回空指针,本质是dlsym找不到符合当前optABI 的getPassPluginInfo符号
最稳妥的做法永远是:插件源码、构建环境、目标 clang/opt 三者全部来自同一份 llvm-project commit 或同一官方 tarball。任何“混用头文件 + 混用库”的尝试,本质上都在赌 ABI 碰巧没变——而 LLVM 的迭代节奏决定了,这个赌注每半年就会输一次。











