目录
LLVM中所有的Pass都继承自llvm::Pass类或其子类。
Ubuntu22.04.5
CLion2026.1
clang22
在llvm-project\llvm\lib\Transforms路径下新建一个目录,名称任意,如下:

然后在新建的目录下创建一个cpp文件和CMakeLists.txt文件如下:

在CMakeLists.txt中写入下面这些内容:
其中LLVMEncodeFunction就是生成的插件文件的名称。EncodeFunction.cpp是Pass实现的源文件。
这是 LLVM 自定义 CMake 宏,定义在:llvm/cmake/modules/AddLLVM.cmake,它是专门为 Pass 插件设计的宏
它会自动帮你做:
如果没有clang和clang++,那么需要下载clang/clang++。
在LLVM源码中编译Pass时,CLion的CMake会根据CMakeLists.txt文件生成对应的 Makefile / build.ninja文件,用于指导编译。而一个项目中有多个CMakeLists.txt,就需要建立CMake连接。
这里新建了一个CMakeLists.txt文件,但是并没有与原来项目的CMake连接起来,构建时不会自动寻找新建目录下的CMakeLists.txt进行加载,但是llvm-project\llvm\lib\Transforms路径下的CMakeLists.txt文件与LLVM的CMake是连接的,所以我们可以将新建目录添加到这个CMakeLists.txt文件中。
在llvm-project\llvm\lib\Transforms路径下的CMakeLists.txt文件中添加一行add_subdirectory(<新建的目录>),如下:

add_subdirectory(EncodeFunction)的作用是:让 CMake 在处理当前 CMakeLists.txt时,进入 EncodeFunction/这个子目录,并加载该目录下的 CMakeLists.txt。
Pass的源码分成两部分:Pass功能源码和插件注册源码。
在新 Pass 管理器中:
下面的源码都要在同一个cpp源文件中。
这段代码就是一个Function Pass的功能源码,它的功能就是打印出函数名。
static bool isRequired() { return true; }在 LLVM 新 Pass 管理器(New PM) 里是一个强制策略声明,它的作用是告诉 LLVM:这个 Pass 是“必需 Pass”,在任何情况下都不允许被跳过。
即使出现以下情况:
其中optnone函数是最常见的,**在 **-O0**(Debug 模式)下编译的函数,就有 ****optnone**。
-O0的语义是:不要做任何优化或变换,方便调试。
而使用clang/clang++将C/C++转换成ll文件时,如果不指定其它的优化等级,那么就会默认是-O0。
而LLVM自带的Pass,比如llvm/lib/Transforms/Utils中的HelloWorld.cpp,它就没有static bool isRequired() { return true; },这就导致使用opt -passes=helloworld test.ll -o test.bc不会报错,但是也不和预期一样打印出test.ll文件中的函数名。
它是**插件入口函数,**由 llvmGetPassPluginInfo()调用,在 opt/ clang加载 .so时执行
这是一个 PassPluginLibraryInfo结构体,包含:
当插件被加载后:
它是 Lambda 表达式,在 C++中是 匿名函数对象。
registerPipelineStartEPCallback主要是为 clang(或任何构建默认优化管线的驱动)准备的。只有clang才能触发这个Pass,如果要通过opt加载Pass,可以删除。
回调函数内容:
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
第一种只需要加载so文件就会直接使用Pass,这种还需要再指明Pass的名称。
llvm::StringRef Name:-passes=里的一个名字。
llvm::ModulePassManager &MPM:当前正在构建的模块级 Pass 管线
llvm::ArrayRef<llvm::PassBuilder::PipelineElement>表示管线元素的上下文(嵌套管线),大多数简单 Pass 用不到,可以忽略。
这段代码的作用是让 LLVM 在加载你的 .so / .dylib插件时能够发现并初始化这个 Pass 插件。
它的作用是:
LLVM 是通过 dlsym()/ LoadLibrary动态查找符号名的,如果不是 extern "C",函数名会被编译器改掉,导致找不到。
它的作用是:将该函数标记为 弱符号(weak symbol),允许插件中存在该函数,但主程序(opt / clang)中也可以没有该函数。可以避免链接冲突,并支持插件可选加载。
它表示从「最外层(全局)命名空间」开始,找到 llvm命名空间,再找到其中的 PassPluginLibraryInfo
PassPluginLibraryInfo是一个结构体,通常包含:
在 New Pass Manager 中,它的核心作用是告诉 LLVM:这个插件支持的 API 版本,以及如何注册 Pass(通常通过 PassBuilder)
这是 固定函数名,不能随便改。
LLVM 内部会做类似的事情:
自动调用llvmGetPassPluginInfo函数来注册Pass,如果你不提供这个函数,Pass插件将无法被识别。
getPluginInfo就是上面的插件注册源码,他会返回 PassPluginLibraryInfo结构体,包含:
用于让LLVM识别和注册插件。
如果在CLion的运行配置中可以找到Pass项目,那么直接选中项目,进行构建即可。
如果找不到的话,在llvm目录下的bulid(就是构建目录,名称未必是bulid)执行命令ninja EncodeFunctionName,在bulid目录下的lib目录中就会出现EcodeFunctionName.so文件。
通过这个指令可以使用插件Pass。
想要在LLVM源码外创建和编译LLVM Pass,需要先构建一个llvm项目,也就是构建llvm-project/llvm中的一个工具,比如clang或opt或者是前面的操作,如果已经做过了,可以跳过这一步。
先以项目的形式打开llvm-project/llvm/CMakeLists.txt,选择构建类型,在windows中工具链必须选择Visual Studio,Linux中使用默认的即可,并设置CMake选项为:
它的作用是:Configure(配置)具体干了这些事:
**注意:这一步并不会生成任何 ****.exe****或 ****.lib**文件!
然后设置构建目录,可以使用默认的(会创建cmake-bulid-release-visual-studio目录在llvm-project/llvm目录),也可以自己设置。如下所示:

然后点击确定即可,CLion会自动加载llvm项目的CMake
<font style="color:rgb(0, 0, 0);">可以从 LLVM 的源代码树外部开发 LLVM passes(针对于已安装或构建的 LLVM)。下面提供了一个项目布局的示例。</font>
然后使用CLion以项目形式打开<projecct dir>/CMakeLists.txt如下图所示:

<project dir>/CMakeLists.txt 的内容如下:
注意这里的<pass name>就是在项目下新建的目录名称HelloPass,如下图所示:

<project dir>/CMakeLists.txt 的内容如下:
<plugin name>是生成的Pass文件的名称,<source file name>是Pass的源文件,也就是<pass name>目录下的cpp文件。如下图所示:

使用的CMake配置如下:
如下所示:

然后点击确定,CLion会自动加载CMake,但是这是会出现许多报错如下:

这个报错只需要将project(ProjectName)添加到<project dir>/CMakeLists.txt中的find_package(LLVM ...)的上面,并将ProjectName改成<project dir>的名称。

整个报错只需要在<project dir>/CMakeLists.txt中添加include(AddLLVM),并且要添加到find_package(LLVM ...)之后。

这个报错只需呀将cmake_minimum_required(VERSION 4.2)添加到<project dir>/CMakeLists.txt中,并且要添加到project(ProjectName)上面。
修改后的<project dir>/CMakeLists.txt的内容如下:

可能还有其它的报错但是不用管,通常只要将这几个报错解决了就可以正常加载CMake了。
然后在CLion的运行配置中找到<plugin name>,选中然后点击确定,如下

然后点击构建。就会在build目录创建一个<plugin name>目录,在这个目录会有一个so文件,这个so文件的名称与在<project dir>/<pass name>/CMakeLists.txt中设置的名称一样,如下:

通过下面指令使用Pass:
其中-load-pass-plugin=的后面是生成的so文件的完整路径。
上面介绍了如何通过opt使用插件式Pass,在LLVM中clang/clang++也是可以使用插件式Pass的。
通过opt使用Pass,需要先使用clang/clang++将源码转换成ll文件。如果通过clang/clang++使用Pass,可以在生成可执行文件的过程中,执行Pass。想要让Pass的插件文件可以被clang/clang++使用,就必须在llvm::PassPluginLibraryInfo getPluginInfo() {}中添加下面这段代码
通过clang/clang++使用Pass,使用下面的指令:
我的Ubuntu22.04.5虚拟机太卡了,所以这一章节Windows系统中的CLion进行操作,Windows对内嵌式的Pass是支持的。
CMake的配置如下:
在Linux环境中,还使用上面的CMake配置应该是没有问题的。Linux和Windows的操作都是一样的。如果有难以解决的问题,可以发到评论区让我看一下。
在llvm-project/llvm/lib/Transforms/目录创建一个目录<Pass Dir>,名称随意,并在该目录下创建一个CMakeLists.txt文件和.cpp文件,如下所示:

注意:这里之所以选择llvm-project/llvm/lib/Transforms/是因为要想内建Pass,就必须将Pass项目加入到LLVM的CMake,而这个目录是LLVM官方推荐的目录,直接在llvm-project/llvm/lib/Transforms/CMakeLists.txt中添加下面这行内容:
这里的<Pass Dir>就是你在Transforms目录下新建的目录的名称。如果你比较熟悉LLVM项目,可以选择再其它目录下进行操作。
然后在llvm-project/llvm/include/llvm目录找到地方创建一个h文件,通常选择在llvm-project/llvm/include/llvm/Transforms/目录下创建一个目录,然后在新建目录中创建一个h文件。只要这个h文件在llvm-project/llvm/include/llvm目录下就行。如下:

h文件的内容如下:
llvm-project/llvm/lib/Transforms/EncodeFunctionName/EncodeFunctionName.cpp的内容如下:
llvm-project/llvm/lib/Transforms/EncodeFunctionName/CMakeLists.txt的内容如下:
注意这里的<target file name>是生成的库文件的名称,这里的这个名称必须以LLVM开头
add_llvm_component_library和add_llvm_pass_plugin的作用一样都是声明一个库文件,只是add_llvm_pass_plugin的作用倾向于生成一个插件动态库。而add_llvm_component_library就是生成库文件,包括静态库和动态库。
add_llvm_component_library最后生成的是动态库*(.so/.dll)还是静态库(.a/.lib),由 BUILD_SHARED_LIBS决定,如果BUILD_SHARED_LIBS=ON,那么最后生成的即使动态库,反之就是静态库。
要注意:当BUILD_SHARED_LIBS=ON时,虽然生成的是动态库,但是,这个动态库不一定是插件,除非导出llvmGetPassPluginInfo。这是插件与动态库的区别。
它通常用于“内置 Pass”(编进 LLVM / opt),因为内置的Pass都是静态链接到程序中的
要想将Pass嵌入到opt/clang/clang++,前面的步骤都是相同的,但是到注册Pass它们的操作是不一样的。
现在为了“注册” Pass,需要将其添加到几个地方。将以下内容添加到 llvm-project/llvm/lib/Passes/PassRegistry.def的FUNCTION_PASS 部分
这时EncodeFunctionName()会报错。还需要再在llvm-project/llvm/lib/Passes/PassBuilder.cpp中,引入EncodeFunctionName.h头文件,如下:

并在llvm-project\llvm\tools\opt\CMakeLists.txt文件中的set(LLVM_LINK_COMPONENTS ...)中将Pass库添加进去,如下:

这里的EncodeFunctionName要替换成<Pass Dir>/CMakeLists.txt中设置的库文件名称去掉LLVM后的名称。因为LLVM_LINK_COMPONENTS是 LLVM 自己的 component 依赖机制,它做的事情是:根据你写的 component 名自动找到对应的 LLVMXXX.lib,并设置 include 路径,处理 inter-library dependencies。
在llvm-project/llvm/lib/Passes/PassBuilderPipelines.cpp文件中引入Pass的头文件。如果是Function Pass就在llvm-project/llvm/lib/Passes/PassBuilderPipelines.cpp文件中寻找void PassBuilder::buildFunctionSimplificationPipelin函数,然后在该函数中FunctionPassManager FPM;的下面添加代码:
任何位置都可以,关键是要确保这段代码在FunctionPassManager FPM;下面,并函数执行时一定会执行这行代码。
[培训]《冰与火的战歌:Windows内核攻防实战》!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
最后于 2026-5-20 14:35
被白衣23编辑
,原因: 修改一些语言错误