news 2026/11/4 9:07:34

LLVM Pass开发入门:环境配置与基础实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LLVM Pass开发入门:环境配置与基础实现

1. LLVM Pass开发环境准备

在开始编写第一个LLVM Pass之前,我们需要确保开发环境配置正确。LLVM Pass开发通常需要以下组件:

  • LLVM工具链(包括clang、opt等)
  • CMake构建系统(建议3.13或更高版本)
  • C++17兼容的编译器(如GCC 7+或MSVC 2017+)
  • Ninja构建工具(可选但推荐)

1.1 LLVM安装与配置

对于Windows平台,建议使用预编译的LLVM发行版。可以从LLVM官网下载安装包,安装时注意勾选"Add LLVM to the system PATH"选项。安装完成后,可以通过以下命令验证:

clang --version opt --version

如果使用Linux系统,可以通过包管理器安装(如apt install llvm clang),但建议从源码编译以获得完整开发环境。

1.2 项目目录结构

一个典型的LLVM Pass项目目录结构如下:

/MyLLVMPass ├── CMakeLists.txt # 项目构建配置文件 ├── build/ # 构建目录(建议外部构建) │ └── test.c # 测试用例 └── MyPass.cpp # Pass实现代码

注意:强烈建议将构建目录(build)与源代码分离,这符合CMake的最佳实践,可以避免污染源代码目录。

2. LLVM Pass基础概念

2.1 Pass层级体系

LLVM Pass按照作用范围分为五个层级:

  1. 模块(Module)层级:处理整个LLVM IR模块(对应一个源文件)
  2. 调用图(CallGraph)层级:分析函数间的调用关系
  3. 函数(Function)层级:处理单个函数
  4. 基本块(BasicBlock)层级:处理函数内的基本代码块
  5. 指令(Instruction)层级:处理单个IR指令

2.2 Pass的类型

LLVM Pass主要分为两大类:

  • 分析(Analysis) Pass:只分析IR不修改,如支配树分析
  • 转换(Transform) Pass:会修改IR,如内联优化

我们即将实现的函数名打印Pass属于分析Pass,因为它只读取函数名而不修改IR。

3. 实现第一个LLVM Pass

3.1 Pass代码结构

下面是一个完整的函数名打印Pass实现(MyPass.cpp):

#include "llvm/IR/PassManager.h" #include "llvm/Passes/PassBuilder.h" #include "llvm/Passes/PassPlugin.h" #include "llvm/Support/raw_ostream.h" using namespace llvm; namespace { struct MyPass : public PassInfoMixin<MyPass> { // 主处理函数,每个函数都会调用一次 PreservedAnalyses run(Function &F, FunctionAnalysisManager &) { // 输出当前函数名 errs() << "Found Function: " << F.getName() << "\n"; // 表示没有修改IR,保持所有分析结果有效 return PreservedAnalyses::all(); } }; } // Pass插件注册入口 extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() { return { LLVM_PLUGIN_API_VERSION, "MyPass", // Pass名称 "v1.0", // 版本号 [](PassBuilder &PB) { PB.registerPipelineParsingCallback( [](StringRef Name, FunctionPassManager &FPM, ArrayRef<PassBuilder::PipelineElement>) { if (Name == "my-pass") { // 命令行使用的Pass名称 FPM.addPass(MyPass()); return true; } return false; } ); } }; }

3.2 关键代码解析

  1. Pass主体结构:

    • 继承自PassInfoMixin模板类
    • 必须实现run()方法作为入口点
  2. run()方法:

    • 参数Function &F是当前处理的函数
    • 返回值PreservedAnalyses表示Pass是否修改了IR
    • errs()是LLVM提供的错误输出流
  3. 插件注册:

    • llvmGetPassPluginInfo()是必须实现的入口函数
    • 通过PassBuilder注册Pass名称和构造器

4. 构建与测试Pass

4.1 CMake配置

创建CMakeLists.txt文件:

cmake_minimum_required(VERSION 3.13) project(MyPass) # 设置LLVM路径(根据实际安装位置修改) set(LLVM_DIR "D:/LLVM/llvm-project/build/lib/cmake/llvm") find_package(LLVM REQUIRED CONFIG) # 添加LLVM模块路径 list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}") include(AddLLVM) # 包含LLVM头文件 include_directories(${LLVM_INCLUDE_DIRS}) add_definitions(${LLVM_DEFINITIONS}) # 设置C++标准 set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 构建Pass插件 add_library(MyPass MODULE MyPass.cpp) set_target_properties(MyPass PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON PREFIX "") # 链接LLVM库 target_link_libraries(MyPass PRIVATE LLVMCore LLVMSupport) # Windows下设置UTF-8编码 if(MSVC) target_compile_options(MyPass PRIVATE /utf-8) endif()

4.2 构建Pass

在build目录下执行:

cmake -G "Ninja" -DCMAKE_BUILD_TYPE=RelWithDebInfo .. ninja

构建成功后会在build目录生成MyPass.dll(Windows)或MyPass.so(Linux)。

4.3 测试用例

创建简单的测试程序test.c:

#include <stdio.h> void secret_function() { printf("I am secret\n"); } int main() { secret_function(); return 0; }

4.4 运行Pass

  1. 首先将C代码编译为LLVM IR:
clang -S -emit-llvm -O1 -Xclang -disable-llvm-passes test.c -o test.ll
  1. 然后通过opt工具运行我们的Pass:
opt -load-pass-plugin=MyPass.dll -passes=my-pass test.ll -o /dev/null

4.5 预期输出

运行Pass后,你应该能看到类似以下输出:

Found Function: secret_function Found Function: main Found Function: printf

5. 常见问题与调试技巧

5.1 编译问题排查

问题1:CMake找不到LLVM

  • 确保LLVM_DIR路径正确指向LLVM的cmake目录
  • 检查LLVM是否安装完整

问题2:C++17特性不支持

  • 更新编译器版本
  • 确保CMake中正确设置了C++17标准

5.2 运行时问题

问题1:Pass没有执行

  • 检查opt命令中的Pass名称是否与注册时一致
  • 确保使用了正确的-load-pass-plugin参数

问题2:中文乱码

  • Windows下添加/utf-8编译选项
  • 避免在Pass代码中使用中文

5.3 调试技巧

  1. 使用LLVM_DEBUG宏:
#define DEBUG_TYPE "mypass" LLVM_DEBUG(dbgs() << "Debug info: " << F.getName() << "\n");

编译时添加-DCMAKE_BUILD_TYPE=Debug并运行时加-debug参数

  1. 打印完整IR:
F.print(errs());
  1. 使用GDB/LLDB:
gdb --args opt -load-pass-plugin=MyPass.so -passes=my-pass test.ll

6. Pass进阶开发建议

6.1 扩展Pass功能

当前Pass只是打印函数名,可以扩展以下功能:

  1. 统计函数基本信息(参数数量、基本块数等)
  2. 分析函数调用关系
  3. 检测特定模式(如未使用的函数)

6.2 性能优化建议

  1. 避免在Pass中频繁创建/销毁对象
  2. 使用LLVM提供的分析结果(如支配树)
  3. 对大型项目考虑并行处理

6.3 集成到构建系统

可以将Pass集成到:

  • 编译器流水线(通过PassBuilder)
  • 静态分析工具链
  • 自定义编译工具

7. 实际应用案例

7.1 函数调用图生成

扩展我们的Pass来生成函数调用关系:

PreservedAnalyses run(Function &F, FunctionAnalysisManager &FAM) { errs() << "Function: " << F.getName() << " calls:\n"; for (auto &BB : F) { for (auto &I : BB) { if (auto *Call = dyn_cast<CallInst>(&I)) { if (Function *Callee = Call->getCalledFunction()) { errs() << " -> " << Callee->getName() << "\n"; } } } } return PreservedAnalyses::all(); }

7.2 简单代码度量

统计函数的基本信息:

PreservedAnalyses run(Function &F, FunctionAnalysisManager &) { unsigned NumBB = 0, NumInst = 0; for (auto &BB : F) { NumBB++; NumInst += BB.size(); } errs() << F.getName() << ": " << NumBB << " BB, " << NumInst << " Inst\n"; return PreservedAnalyses::all(); }

8. 进一步学习资源

  1. 官方文档:

    • LLVM Programmer's Manual
    • Writing an LLVM Pass
  2. 开源项目参考:

    • LLVM自带的Pass(如SimplifyCFG)
    • 知名开源项目中的Pass实现
  3. 调试工具:

    • opt -print-after-all
    • LLVM调试工具(bugpoint等)
  4. 社区资源:

    • LLVM邮件列表
    • LLVM Discord频道
    • 相关技术博客和会议演讲
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/4 9:05:59

Conda离线部署三方案:conda-pack、本地channel与缓存预热

内网服务器要跑一个 Python 项目&#xff0c;联网的开发机上conda create一路绿灯&#xff0c;拷到机房那台机器上&#xff0c;conda install直接甩你一脸PackagesNotFoundError。这不是运气问题&#xff0c;是 Conda 环境离线部署这件事本身有几个容易被忽略的前提。我在过去几…

作者头像 李华
网站建设 2026/11/4 9:04:24

Agent开发排查指南:从错误码还原失败链,精准定位根因

1. 为什么“模型不行”往往是排查方向跑偏了做 Agent 开发的人&#xff0c;大概都经历过这种场景&#xff1a;线上告警突然炸了&#xff0c;任务成功率从 95% 掉到 60%&#xff0c;第一反应是“模型又抽风了”&#xff0c;于是换模型、调 temperature、加 few-shot&#xff0c;…

作者头像 李华
网站建设 2026/11/4 9:03:49

HTML零基础入门:从创建第一个网页开始

1. 项目概述记得我第一次接触HTML时&#xff0c;那种既兴奋又忐忑的心情至今难忘。那是一个周末的下午&#xff0c;我对着电脑屏幕&#xff0c;试图用几行简单的代码创造出属于自己的第一个网页。对于完全零基础的我来说&#xff0c;这就像第一次拿起画笔面对空白画布的感觉。H…

作者头像 李华
网站建设 2026/11/4 9:03:28

跨平台主机隔离:为什么开了沙箱不等于隔离可信

1. 沙箱到底在防什么&#xff0c;为什么大家总觉得开了就万事大吉 很多人第一次接触“沙箱”这个词&#xff0c;是在浏览器里。Chrome 每个标签页一个进程&#xff0c;崩了一个不影响其他&#xff0c;这叫进程隔离&#xff1b;再往后一点&#xff0c;做后端或者做安全的朋友会接…

作者头像 李华