本文主要讲在Ubuntu22.04.5环境中如何配置编译环境和编写Pass,编译时直接在CLion的运行配置中找到对应的项目即可进行编译即可,或者在 llvm build 目录下执行ninja EncodeFunctionName。
操作环境:Ubuntu22.04.5
LLVM版本:22.1.5
编译环境:CLion2026.1 + clang
CMake配置选项如下:
-G Ninja
-DLLVM_ENABLE_PROJECTS="clang"
-DBUILD_SHARED_LIBS=ON
-DCMAKE_BUILD_TYPE=Release
-DCMAKE_C_COMPILER=clang
-DCMAKE_CXX_COMPILER=clang++
在llvm-project-22.1.5.src\llvm\lib\Transforms路径下新建一个目录,名称任意。
然后在新建的目录下创建一个cpp文件和CMakeLists.txt文件
在CMakeLists.txt中写入下面这些内容:
其中EncodeFunction决定着生成的插件文件的名称。EncodeFunctionName.cpp是Pass实现的源文件,根据实际情况来填。
这是 LLVM 自定义 CMake 宏,定义在:llvm/cmake/modules/AddLLVM.cmake,它是专门为 Pass 插件设计的宏
它会自动帮你做:
这是 插件目标名(Target Name)
它的作用包括:
在编译项目时,CMake会根据CMakeLists.txt文件生成对应的 Makefile / build.ninja文件,用于指导编译。而一个项目中有多个CMakeLists.txt,就需要建立CMake连接。
这里新建了一个CMakeLists.txt文件,但是并没有与原来项目的CMake连接起来,构建时不会自动寻找新建目录的CMakeLists.txt,但是llvm-project-22.1.5.src\llvm\lib\Transforms路径下的CMakeLists.txt文件是在CMake连接中的,所以我们可以将新建的CMakeLists.txt添加到这个CMakeLists.txt文件中。
在llvm-project-22.1.5.src\llvm\lib\Transforms路径下的CMakeLists.txt文件中添加一行add_subdirectory(新建的目录名)
add_subdirectory(新建的目录名)的作用是:让 CMake 在处理当前 CMakeLists.txt时,进入到新建的目录中,并执行该目录下的 CMakeLists.txt。
Pass的源码分成两部分:Pass功能源码和插件注册源码。
这段代码就是一个Function Pass的功能源码,它的功能就是打印出函数名。
static bool isRequired() { return true; }在 LLVM 新 Pass 管理器(New PM) 里是一个强制策略声明,它的作用是告诉 LLVM:这个 Pass 是“必需 Pass”,在任何情况下都不允许被跳过。
即使出现以下情况:
它是**插件入口函数,**由 llvmGetPassPluginInfo()调用,在 opt/ clang加载 .so时执行
这是一个 PassPluginLibraryInfo结构体,包含:
当插件被加载后:
它是 Lambda 表达式,在 C++中是 匿名函数对象。
registerPipelineStartEPCallback主要是为 clang(或任何构建默认优化管线的驱动)准备的。如果要通过opt来使用Pass,还是使用egisterPipelineParsingCallback。
回调函数内容:
MPM:当前正在构建的模块级 Pass 管理器
Level:-O0 ~ -O3
FunctionPassManager FPM;的作用是创建一个 Function Pass 管理器
FPM.addPass(HelloWorldPass());的作用是把你的 Pass 加入 Function Pass 管线
它的作用是把 ModuleToFunctionPassAdaptor对象存入 MPM 的内部容器
在 LLVM 新 PM 中:
这里的Pass 是 Function Pass 只能被FunctionPassManager FPM;管理。但这里的Pass要放到MPM 层面 ,也就是MPM.addPass(),这里的 MPM是ModulePassManager, 不能直接往 MPM 里塞 Function Pass。
createModuleToFunctionPassAdaptor的作用是把一个 FunctionPassManager适配成一个“看起来像 Module Pass 的东西”,也就是ModuleToFunctionPassAdaptor。
ModuleToFunctionPassAdaptor的声明:class ModuleToFunctionPassAdaptor : public ModulePass。
ModuleToFunctionPassAdaptor是 ModulePass,所以可以放进 ModulePassManager
FPM的声明:FunctionPassManager FPM;,它是 LLVM 的新 Pass 管理器。
而LLVM的新Pass管理器都具有如下特点:
所以不能createModuleToFunctionPassAdaptor(FPM);,即使是其它函数也不能直接将FPM作为参数传递给函数,因为在C++中直接以对象为参数时会触发拷贝构造函数,对对象进行拷贝,所以不能直接将FPM作为参数传递给函数。
createModuleToFunctionPassAdaptor(std::move(FPM));本质上是:
std::move的作用是将FunctionPassManager类型转换成FunctionPassManager&&类型,这时候不会新创建任何对象,FunctionPassManager&&还是原来的对象
FunctionPassManager&是“左值引用”,表示“我借用你,不动你”,通过它可以引用对象,来修改或读取对象数据
FunctionPassManager&&是“右值引用”,表示“我不仅可以借,还可以直接拿走你的资源”,通过它也可以引用对象,来修改或读取对象数据。但是它也可以移动对象。
在以它为参数调用构造函数时,就会触发移动构造函数,比如FunctionPassManager newFPM(std::move(oldFPM));这样,使用FunctionPassManager&&创建一个新的FunctionPassManager对象,它会将普通成员(int/bool等类型的成员)的值拷贝给新对象,并将指针成员所指的数据复制到新对象的指针成员所指的内存中,然后将旧对象的成员指针变成nullptr。这就是移动拷贝。
这里的:
可以直接和下面的一样变成:
MPM.addPass(createModuleToFunctionPassAdaptor(HelloWorldPass()))在执行过程中,内部一定会把 HelloWorldPass()加入到某个 FunctionPassManager中。
这段代码的作用是:注册一个“管线解析回调”,当 LLVM 解析 -passes=字符串时,可以获取这个字符串并调用回调函数。通过下面的指令使用Pass
llvm::StringRef Name:-passes=里的一个名字。
llvm::ModulePassManager &MPM:当前正在构建的模块级 Pass 管线
llvm::ArrayRef<llvm::PassBuilder::PipelineElement>表示管线元素的上下文(嵌套管线),大多数简单 Pass 用不到,可以忽略。
这段代码的作用是让 LLVM 在加载你的 .so / .dylib插件时能够发现并初始化这个 Pass 插件。
它的作用是:
传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 1小时前
被白衣23编辑
,原因: 修改一些错误