首页
社区
课程
招聘
[原创]使用LLVM的New Pass Manager编写和使用Pass
发表于: 2026-5-16 01:15 17688

[原创]使用LLVM的New Pass Manager编写和使用Pass

2026-5-16 01:15
17688

目录

LLVM中所有的Pass都继承自llvm::Pass类或其子类。

Ubuntu22.04.5
CLion2026.1
clang22

llvm-project\llvm\lib\Transforms路径下新建一个目录,名称任意,如下:

图片描述

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

图片描述

在CMakeLists.txt中写入下面这些内容:

其中LLVMEncodeFunction就是生成的插件文件的名称。EncodeFunction.cppPass实现的源文件。

这是 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 中:

这里的PassFunction Pass 只能被FunctionPassManager FPM;管理。但这里的Pass要放到MPM 层面 ,也就是MPM.addPass(),这里的 MPMModulePassManager, 不能直接往 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中的一个工具,比如clangopt或者是前面的操作,如果已经做过了,可以跳过这一步。

先以项目的形式打开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配置应该是没有问题的。LinuxWindows的操作都是一样的。如果有难以解决的问题,可以发到评论区让我看一下。

llvm-project/llvm/lib/Transforms/目录创建一个目录<Pass Dir>,名称随意,并在该目录下创建一个CMakeLists.txt文件和.cpp文件,如下所示:

图片描述

注意:这里之所以选择llvm-project/llvm/lib/Transforms/是因为要想内建Pass,就必须将Pass项目加入到LLVMCMake,而这个目录是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_libraryadd_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.defFUNCTION_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_COMPONENTSLLVM 自己的 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编辑 ,原因: 修改一些语言错误
收藏
免费 38
打赏
分享
最新回复 (11)
雪    币: 76
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
1
2026-5-16 10:58
0
雪    币: 3737
活跃值: (4904)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
学习一下
2026-5-16 15:01
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
111
2026-5-16 15:40
0
雪    币: 191
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
666
2026-5-17 23:39
0
雪    币: 6223
活跃值: (11177)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
有空还要再复习下,很久没用了
2026-5-18 09:15
0
雪    币: 234
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
66
2026-5-22 20:17
0
雪    币: 1341
活跃值: (1130)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
表哥牛逼
2026-5-23 16:25
0
雪    币: 10416
活跃值: (8445)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
9
666
2026-5-24 23:46
0
雪    币: 209
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
10
666
2026-5-26 11:07
0
雪    币: 87
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
11
mark
2026-5-28 13:52
0
雪    币: 3
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
12
6999999
4天前
0
游客
登录 | 注册 方可回帖
返回