LLVM-pass环境搭建

admin 2026-04-21 01:46:15 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 该文档详细介绍了在Windows环境下搭建LLVM-pass开发环境的完整流程,包括安装MSYS2、编译LLVM项目、配置VSCode开发环境以及编写Pass插件的方法。文档提供了Clang编译原理说明、CMake配置参数详解、调试模式设置技巧以及常见问题解决方案,具有较强的实操指导价值。 综合评分: 78 文章分类: 安全开发,编译技术,环境配置


cover_image

LLVM-pass环境搭建

原创

弥留 弥留

Relay学安全

2026年4月20日 07:28 陕西

在小说阅读器读本章

去阅读

原理简述

Clang的LLVM调用方式

  1. Clang吧原始c/cpp代码转换成前端ir(ll文件)或者bc (ir人类可看懂语言,bc机器二进制代码, ir和bc可以相互转换)
  2. 然后由后端的opt.exe开始优化代码
  3. opt加载pass的dll,根据dll控制ir进行混淆
  4. 由opt将ll转换成二进制代码win是.obj文件 linux是.o文件
  5. 由连接器链接obj文件最终生成可执行文件

整个编译过程中,Clang只负责将代码转换成ir中间层文件,  然后由opt操作ir和加载pass。

如何保证你的pass是在opt优化之后加载,已O3优化举例

opt.exe -O3 -flatten --dce input.ll -o output_obfuscated.ll

在只用opt命令的时候确保O3参数 在你的pass之前。

官网教程编写pass插件

https://llvm.org/docs/WritingAnLLVMNewPMPass.html

创建环境

https://llvm.org/docs/CMake.html

编写pass

https://llvm.org/docs/WritingAnLLVMNewPMPass.html

安装

windwos环境

编译环境:win10系统,8核心,16G内存

首先安装mysys2

下载之后配置好然后安装依赖

# 更新 pacmanpacman -Syu --noconfirm
# 下载工具链pacman -S mingw-w64-x86_64-llvm \mingw-w64-x86_64-clang \mingw-w64-x86_64-nasm \mingw-w64-x86_64-cmake \mingw-w64-x86_64-ninja \make \git --noconfirm

这是用来编译llvm的, llvm-link  opt 这些 , 需要切换mysys2的环境变量到系统环境变量。

下载llvm-project

#看下自己rust llvm的版本rustc -V -v# rust版本选1.79  llvm版本大概是 18.1.3左右https://github.com/llvm/llvm-project/tree/llvmorg-18.1.3
如果直接用clang开发,那么就不用关注这个 随便下一个就行。

进入llvm目录进行编译

#创建build 目录mkdir buildcd build

==然后把下面的命令复制到 msys2里面执行就行。

配置cmake

release模式

#编译最新版本可以用这个命令cmake -G Ninja ../llvm \      -DCMAKE_BUILD_TYPE=Release \      -DLLVM_ENABLE_PROJECTS="clang;lld" \      -DCMAKE_INSTALL_PREFIX=/mingw64/llvm_install2 \      -DLLVM_TARGETS_TO_BUILD="X86"

编译18.1.3版本的用这个命令cmake -G Ninja ../llvm \      -DCMAKE_BUILD_TYPE=Release \      -DCMAKE_C_COMPILER=clang \      -DCMAKE_CXX_COMPILER=clang++ \      -DCMAKE_CXX_FLAGS="-include cstdint -include cstddef" \      -DLLVM_ENABLE_PROJECTS="clang;lld" \      -DCMAKE_INSTALL_PREFIX=/mingw64/llvm_install2 \      -DLLVM_TARGETS_TO_BUILD="X86" \      -DLLVM_ENABLE_PLUGINS=ON \      -DLLVM_EXPORT_SYMBOLS_FOR_PLUGINS=ON \      -DLLVM_ENABLE_RTTI=OFF \      -DLLVM_ENABLE_EH=OFF \      -DLLVM_ENABLE_ZSTD=OFF

| 参数 | 解释 | | — | — | | -G Ninja | 必须,指定使用 Ninja 作为构建系统。 | | -DCMAKE_BUILD_TYPE=Release | 建议使用 Release 或 RelWithDebInfo 以获得优化。 | | -DLLVM_ENABLE_PROJECTS="..." | 指定要构建的组件,通常需要 clang 和 lld。 | | -DCMAKE_INSTALL_PREFIX=/mingw64/llvm_install | 指定安装路径。 使用 MSYS2 路径 (如 /mingw64/llvm_install) 比 Windows 路径 (如 C:\...) 更可靠。 | | -DLLVM_ENABLE_ZSTD=OFF | 解决高版本py找不到 zstd压缩库的bug | | -DCMAKE_CXX_FLAGS=”-include cstdint -include cstddef” | 解决高版本编译器,编译低版本代码,找不到头文件的问题。 |

这里编译完大概是2-3小时左右。

调试模式

#内存会爆掉。编译成功opt大小超过3g windows内核拒绝执行cmake -G Ninja ../llvm \      -DCMAKE_BUILD_TYPE=RelWithDebInfo \      -DLLVM_ENABLE_ASSERTIONS=ON \      -DLLVM_ENABLE_PROJECTS="clang;lld" \      -DCMAKE_INSTALL_PREFIX=/mingw64/llvm_dbg_install \      -DLLVM_TARGETS_TO_BUILD="X86" \      -DLLVM_OPTIMIZED_TABLEGEN=ON

编译18.1.3版本的用这个命令cmake -G Ninja ../llvm \      -DCMAKE_BUILD_TYPE=RelWithDebInfo \      -DCMAKE_C_COMPILER=clang \      -DCMAKE_CXX_COMPILER=clang++ \      -DCMAKE_CXX_FLAGS="-include cstdint -include cstddef" \      -DLLVM_ENABLE_PROJECTS="clang;lld" \      -DCMAKE_INSTALL_PREFIX=/mingw64/llvm_install_dbg \      -DLLVM_TARGETS_TO_BUILD="X86" \      -DLLVM_ENABLE_PLUGINS=ON \      -DLLVM_EXPORT_SYMBOLS_FOR_PLUGINS=ON \      -DLLVM_ENABLE_RTTI=OFF \      -DLLVM_ENABLE_EH=OFF \      -DLLVM_ENABLE_ZSTD=OFF \      -DLLVM_PARALLEL_LINK_JOBS=2
#只编译opt,节省编译时间ninja opt
编译完成后,您会在 build/bin/ 目录下找到 opt.exe。

| 参数 | 解释 | | — | — | | RelWithDebInfo | 生成带有调试符号的优化版本。这是开发者调试 LLVM 的首选方案。 | | DLLVM_ENABLE_ASSERTIONS=ON | 极其重要。开启断点检查,如果你的 Pass 逻辑弄乱了 IR 结构(例如指令类型不匹配),程序会立即在出错位置崩溃并给出提示,而不是无缘无故地 Segmentation Fault。 | | DLLVM_OPTIMIZED_TABLEGEN=ON | 加快编译速度,让 TableGen 工具以 Release 模式编译(它本身不需要调试)。 |

我们调试只需要调试opt加载pass操作ir的过程就行。所以这里只用编译一个调试版本的opt.exe就行。这个时间大概是45分钟左右。

为什么叫调试模式,而不是DEBUG

踩坑:这里编译debug版本的话,给的16G的内存会直接炸掉。即使增加内存编译,动态链接导出函数超过65535个无法链接成可执行文件。静态链接,程序大小会超过3G,windows内核不加载超过大小3G的可执行文件。

用ninja进行编译和安装

在 build 目录中执行编译和安装命令。

# 编译 LLVMninja
# 安装到指定的安装路径ninja install

添加LLVM运行的依赖

由于使用Mingw-w64的环境,编译出来的LLVM无法直接使用,需要去MSYS2的mingw64的bin目录复制下列dll到LLVM的bin目录。

libgcc_s_seh-1.dlllibstdc++-6.dlllibwinpthread-1.dllzlib1.dll

测试一下

export PATH="/c/msys64/mingw64/llvm_install/bin:$PATH"clang --version

这样编译环境就算搭建完成了。

Pass项目搭建

这里推荐使用VSCode进行开发

VSCode配置

1、安装插件

C/C++    #补全语法CMake    #cmake编译CodeLLDB #调试代码十六进制编辑器  #查看内存

VSCode 需要把默认的终端改为 MSYS2 MINGW64。具体步骤如下:可参考:

url:https://blog.csdn.net/liang_clg/article/details/121097676
操作步骤:title: "VSCode增加mysys2终端_vscode 设置msys2终端-CSDN博客"description: "文章浏览阅读2k次。使用VSCode时,增加mysys2的mingw64命令终端步骤:打开setting.json【文件】->【首选项目】->【设置】,查找shell windows ,点击在setting.json中编辑编辑setting.json在文件里加入如下内容:  \"terminal.integrated.defaultProfile.windows\": \"mysys2-mingw64\",  \"terminal.integrated.profiles.windows\": {  _vscode 设置msys2终端"host: blog.csdn.net

添加如下内容

// 设置默认的终端配置"terminal.integrated.defaultProfile.windows": "mysys2-mingw64","terminal.integrated.profiles.windows": {    "mysys2-mingw64": {     "path": "cmd.exe",     "args": [           "/c",           "C:\\msys64\\msys2_shell.cmd -defterm -mingw64 -no-start -here"     ]   }},

2、项目格式

3、cpp编译配置文件

直接用C/C++插件生成编辑配置json文件

{    "configurations": [        {            "name": "Win32",            "includePath": [                "${workspaceFolder}/**",                "C:\\msys64\\mingw64\\llvm_install\\include", //llvm头文件                "E:\\project\\llvm-project\\build\\include"  //构建llvm的build目录下的头文件            ],            "defines": [                "_DEBUG",                "UNICODE",                "_UNICODE"            ],            "compilerPath": "C:\\msys64\\mingw64\\llvm_install\\bin\\clang++.exe",  //使用的编辑器            "cStandard": "c17",            "cppStandard": "c++17",            "intelliSenseMode": "clang-x64"        }    ],    "version": 4}

4、pass的项目文件内容

llvm-pass -> CMakeLists.txt

cmake_minimum_required(VERSION 3.20)
#定义项目的名称为 llvm-pass,并声明该项目使用 C 和 C++ (CXX) 语言。这会让 CMake 去寻找对应的编译器(比如你的 g++.exe)。project(llvm-pass LANGUAGES C CXX)
SET (CMAKE_CXX_STANDARD 17)
#最核心的一句。 它会让 CMake 去你的系统中寻找 LLVM 的配置文件find_package(LLVM REQUIRED CONFIG)
#将 LLVM 官方提供的 CMake 脚本目录加入到 CMake 的搜索路径中,并引入 AddLLVM 模块。list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}")include(AddLLVM)
#LLVM_DEFINITIONS 通常是一个包含多个宏的字符串(比如 -D__STDC_CONSTANT_MACROS -D_DEBUG)。separate_arguments 把这个字符串按照空格拆分成一个 CMake 列表。然后 add_definitions 将这些宏应用到全局编译器中。separate_arguments(LLVM_DEFINITIONS_LIST NATIVE_COMMAND ${LLVM_DEFINITIONS})add_definitions(${LLVM_DEFINITIONS_LIST})include_directories(${LLVM_INCLUDE_DIRS})
#告诉 CMake 进入名为 HelloWorld 的子文件夹,并执行那里的 CMakeLists.txt 文件。add_subdirectory(HelloWorld)

llvm-pass ->build.bat

SET LLVM_DIR=C:\msys64\mingw64\llvm_installSET LLVN_DIR_BIN=%LLVM_DIR%\binSET PATH=%LLVN_DIR_BIN%;%PATH%

rmdir /s /q buildmkdir buildcd build
cmake -G "Ninja" -S .. -B . -DCMAKE_BUILD_TYPE=Release -DLLVM_DIR=%LLVM_DIR%cmake --build .

llvm-pass -> HelloWorld -> CMakeLists.txt

#告诉 CMake 编译 Pass.cpp,并生成一个名为 HelloWorld 的库。#这里的 MODULE 非常关键。在 CMake 中,库有三种类型:#STATIC:静态库(.a / .lib)#SHARED:动态链接库(.so / .dll),允许其他程序在链接时依赖它。#MODULE:这也是动态库(在 Windows 下依然是 .dll),但它不打算在编译时被其他程序链接,而是专门用于在程序运行时通过动态加载(如 LoadLibrary)引入。#opt.exe 使用 -load-pass-plugin 加载你的 Pass 正是这种模式。add_library(HelloWorld MODULE Pass.cpp)

#这是一个 LLVM 特有的宏。LLVM 内部有很多子模块,这句话的意思是:“我想要使用 LLVM 的 Support(基础工具)、Core(IR 核心数据结构)和 IRReader(读取 IR 的工具)模块,请帮我把它们转换成对应的静态库文件名,并存放到 llvm_libs 变量中。”llvm_map_components_to_libnames(llvm_libs support core irreader)
# Link against LLVM libraries#寻找系统级别的线程库find_package(Threads REQUIRED)
# Link against LLVM libraries and pthreads (winpthreads on mingw)#将前面找到的 LLVM 静态库和线程库,链接到你的 HelloWorld 动态库中。PRIVATE 表示这些依赖仅仅 HelloWorld 自己内部使用,不会暴露给依赖 HelloWorld 的其他target_link_libraries(HelloWorld PRIVATE ${llvm_libs} Threads::Threads)

llvm-pass -> HelloWorld ->Pass.cpp

#include "llvm/Passes/PassPlugin.h"#include "llvm/Passes/PassBuilder.h"#include "llvm/IR/Function.h"#include "llvm/Support/raw_ostream.h"
using namespace llvm;
namespace llvm {
class&nbsp;HelloWorldPass&nbsp;:&nbsp;public&nbsp;PassInfoMixin<HelloWorldPass> {&nbsp;&nbsp;public:&nbsp; &nbsp;&nbsp;PreservedAnalyses&nbsp;run(Function &F, FunctionAnalysisManager &AM);&nbsp; &nbsp;&nbsp;static&nbsp;bool&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;isRequired()&nbsp;{&nbsp;return&nbsp;true;}&nbsp;// opt必定去执行这个pass};
// 简单实现:打印函数名并声明不修改分析结果PreservedAnalyses&nbsp;HelloWorldPass::run(Function &F, FunctionAnalysisManager &AM)&nbsp;{&nbsp; &nbsp;&nbsp;errs() <<&nbsp;"HelloWorldPass running on function: "&nbsp;<< F.getName() <<&nbsp;"\n";&nbsp; &nbsp;&nbsp;return&nbsp;PreservedAnalyses::all();}
}&nbsp;// namespace llvm

/* 注册pass插件 */llvm::PassPluginLibraryInfo&nbsp;getHelloWorldPassPluginInfo()&nbsp;{&nbsp; &nbsp;&nbsp;return&nbsp;{LLVM_PLUGIN_API_VERSION,&nbsp;"hello", LLVM_VERSION_STRING, [](PassBuilder &PB) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PB.registerPipelineParsingCallback(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; [](StringRef Name, llvm::FunctionPassManager &PM, ArrayRef<llvm::PassBuilder::PipelineElement>) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(Name ==&nbsp;"hello") {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PM.addPass(HelloWorldPass());&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;true;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;false;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; });&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }};}
//导出插件让 opt找到extern&nbsp;"C"&nbsp;LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo&nbsp;llvmGetPassPluginInfo()&nbsp;{&nbsp; &nbsp;&nbsp;return&nbsp;getHelloWorldPassPluginInfo();}

执行build.bat 就会编译好pass的dll

5、测试编译好的pass

clang版本

测试c代码

#include&nbsp;<stdio.h>
int&nbsp;main(){
&nbsp; &nbsp;&nbsp;printf("1w3123");}

编译成bc

/c/msys64/mingw64/llvm_install/bin/clang.exe -emit-llvm -c test.c -o test.bcclang -fuse-ld=lld -emit-llvm -c test.c -o test.bc

用opt去加载pass

/c/msys64/mingw64/llvm_install/bin/opt.exe -load-pass-plugin=libHelloWorld.dll -passes=hello -disable-output test.bcopt -load-pass-plugin=libHelloWorld.dll -passes=hello -disable-output test.bc

看到这个就代表pass编译成功并且被成功加载了

rust版本

rust有两种版本,msvc版本和GNU版本,这俩编译的cmake指令有所不同。

==这里使用GNU版本。

查看rust版本

rustup&nbsp;show

下载指定的rust版本

rustup&nbsp;toolchain install nightly-2024-04-15-x86_64-pc-windows-gnu

切换到指定的rust版本

rustup&nbsp;default nightly-2024-04-15-x86_64-pc-windows-gnu

查看rust的llvm版本

rustc -vV &nbsp; //rustc&nbsp;--version&nbsp;--verbose

注意: 这里rust必须使用夜版,也就是nightly 开头的

==如果你使用的是msvc的rust和llvm那么上面的 CMake不需要更改

==使用gnu版本的rsut需要吧库静态链接到pass里面

llvm-pass -> HelloWorld -> CMakeLists.txt  (rust gnu版本要用静态链接,否者报错0x7F)

add_library(HelloWorld MODULE Pass.cpp)
# 获取 LLVM 组件的库名llvm_map_components_to_libnames(llvm_libs support core irreader)
# 注意:这里我们注销掉了 find_package(Threads REQUIRED) 和 Threads::Threads# 因为我们在下方的 target_link_options 中手动接管了 pthread 的静态链接,# 防止 CMake 画蛇添足导致双重链接。# find_package(Threads REQUIRED)
target_link_libraries(HelloWorld PRIVATE ${llvm_libs})
# 在 MinGW 下的专属链接配置if(MINGW)&nbsp; &nbsp; target_link_options(HelloWorld PRIVATE&nbsp; &nbsp; &nbsp; &nbsp; -fuse-ld=lld &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# 强行使用 lld 链接器,防止默认的 ld 崩溃&nbsp; &nbsp; &nbsp; &nbsp; -static-libgcc&nbsp; &nbsp; &nbsp; &nbsp; -static-libstdc++&nbsp; &nbsp; &nbsp; &nbsp; -Wl,-Bstatic&nbsp; &nbsp; &nbsp; &nbsp; -lwinpthread &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# 手动静态链接线程库&nbsp; &nbsp; &nbsp; &nbsp; -Wl,-Bdynamic&nbsp; &nbsp; )endif()

原因: windows特性惹的锅,上面需要拷贝到llvm_install下面的那几个dll。 rust自己也有一份但是版本比较落后。

  1. rust编译前端ir的时候就会去加载这几个dll到内存里面
  2. pass插件加载的时候,pass也会加载这几个dll,但是windwos看内存里面有一份了,就不去加载llvm里面的这些dll了,直接使用rust的落后版本的dll。
  3. 由于旧版dll缺少一些东西,pass直接就甩出0x7F 错误。

==要是给rust调用的pas,你必须重新编译一份,静态链接的pass出来。

环境目录

文件代码

test3 ==> .cargo ==> config.toml

这个目录个文件需要自己创建

[target.x86_64-pc-windows-gnu]rustflags&nbsp;= [&nbsp; &nbsp;&nbsp;"-Z",&nbsp;"llvm-plugins=E:/project/llvm-pass/build/HelloWorld/libHelloWorld.dll",&nbsp; &nbsp;&nbsp;"-Cpasses=function(hello)"]

test3 ==> main.rs

fn&nbsp;main() {&nbsp; &nbsp; println!("Hello, llvm-pass!");}

test3 ==> build.bat

@echo&nbsp;off

cargo cleancargo +nightly-2024-04-15-x86_64-pc-windows-gnu build --release --target &nbsp;x86_64-pc-windows-gnu
:: 检查上一条命令(build)的返回值,如果不等于 0 说明编译失败if&nbsp;%ERRORLEVEL% NEQ 0 (&nbsp; &nbsp;&nbsp;echo&nbsp;编译失败,请检查代码!&nbsp; &nbsp; pause&nbsp; &nbsp;&nbsp;exit&nbsp;/b %ERRORLEVEL%)
:: 如果编译成功,程序会走到这里target\x86_64-pc-windows-gnu\release\test3.exe

==编译脚本写makefile 用make编译也可以。

rust加载pass效果

LLVM IR 编程基本流程

  1. 创建一个模块 Module
  2. 在模块中创建一个函数 Function
  3. 在函数 Function 中创建基本块 BasicBlock
  4. 在基本块中创建指令 Instruction
  5. 测试运行

免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:Relay学安全 弥留 弥留《LLVM-pass环境搭建》

LLVM-pass环境搭建 网络安全文章

LLVM-pass环境搭建

文章总结: 该文档详细介绍了在Windows环境下搭建LLVM-pass开发环境的完整流程,包括安装MSYS2、编译LLVM项目、配置VSCode开发环境以及编
评论:0   参与:  0