ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

附录 B · OLLVM(Obfuscator-LLVM,基于 LLVM 的开源代码混淆框架)自定义 Pass 开发环境搭建

附录 B · OLLVM(Obfuscator-LLVM,基于 LLVM 的开源代码混淆框架)自定义 Pass 开发环境搭建

附录导读

本附录手把手教你写一个自定义的 LLVM 混淆插件(Pass)。OLLVM 是最知名的开源混淆框架,学会写 Pass,你就能定制自己的混淆规则——比如决定哪些函数混淆、用什么方式混淆。

什么是 Pass?Pass 就是 LLVM 编译流水线上的一个"加工工位":它读入 IR(Intermediate Representation,中间表示,编译器内部对程序的统一描述)代码,做某种变换(例如把a + b替换成等价但难懂的表达式),再输出修改后的 IR。多个 Pass 串联起来,就构成完整的编译/混淆流程。

本附录的结构(建议按顺序阅读): 1. 第 1 节:从源码编译 LLVM(一次性环境准备,需要较长时间); 2. 第 2 节:选择 LLVM 版本——不同版本 Pass 接口差异很大,选错版本代码编不过; 3. 第 3–4 节:从一个最简 Pass 到完整的 MBA 替换 Pass,含完整可编译源码; 4. 第 5 节:把 Pass 集成到 Android NDK 构建中; 5. 第 6 节:调试 Pass 的各种方法(打印 IR 对比、FileCheck 回归测试、GDB 断点); 6. 第 7 节:新手最容易踩的坑(PHI 节点、异常处理、metadata、迭代器失效)。

前置知识提醒:你需要基本的 C++ 阅读能力,并了解 LLVM IR 的基本样子(可通过第 4.3 节的test.ll实例直观感受)。不需要精通编译原理。

附录知识定位:本附录整体属于实践应用——从编译 LLVM、写 Pass 代码到调试,每一步都配有可以直接执行的命令和完整源码,是手把手教程。但它的受众是"打算自己开发混淆 Pass 的人":如果你是分析方、只想理解防御方的工作流程和混淆原理是怎么从代码变成二进制的,可以把本附录当作背景参考来通读,不必强制跟着每一步敲命令、搭编译环境。换句话说,加固方开发者请逐节动手实操;分析方/旁观学习者可以当理论阅读,重点理解"思路"而非"操作"。


1. LLVM 源码编译(以 LLVM 18 为例)

知识定位:实践应用(面向打算自己开发 Pass 的读者)。这是搭建开发环境的一次性操作,照着命令跑即可;如果只想理解混淆原理,知道"开发 Pass 前需要先编译一套 LLVM"这个事实即可,不必亲自跑一遍耗时的编译过程。

# 获取源码 git clone --depth 1 --branch llvmorg-18.1.0 https://github.com/llvm/llvm-project.git # 编译(Release 模式,节省空间和时间) cd llvm-project cmake -S llvm -B build -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DLLVM_ENABLE_PROJECTS="clang" \ -DLLVM_TARGETS_TO_BUILD="AArch64;ARM;X86" ninja -C build -j$(nproc)

2. LLVM 版本选择建议

知识定位:实践应用中的决策参考。真正动手开发 Pass 时需要先按决策树选定版本号;如果只是了解背景,知道"LLVM 不同版本的 Pass 接口不兼容,选错版本会编译失败"这个结论就够了,不需要记住每个版本的具体 API 差异细节。

不同 LLVM 版本的 Pass 接口存在显著差异,选择合适的版本至关重要。

LLVM 17 (LTS 级稳定)

  • 推荐场景:需要最大兼容性和稳定性的生产项目。
  • Pass 接口:New Pass Manager 已完全取代 Legacy Pass Manager(Legacy PM 在 LLVM 17 中被移除)。
  • 关键变化:
  • llvm::PassInfoMixin<T>作为标准 Pass 基类。
  • registerPipelineParsingCallback用于注册 Pass 名称。
  • Opaque Pointer(不透明指针)默认启用,typed pointer 不再支持。
  • 注意事项:所有使用getPointerElementType()的旧代码必须重写。

LLVM 18 (推荐)

  • 推荐场景:新项目开发的最佳平衡点,兼顾现代特性与社区支持。
  • Pass 接口变化:
  • PassBuilder::registerPipelineStartEPCallback等 EP(Extension Point)回调签名微调,Lambda 参数增加OptimizationLevel。
  • FunctionAnalysisManager的getResult<>语义不变,但部分内置 Analysis 的返回类型有细微调整。
  • 新增CoroSplitPass等协程相关 Pass,混淆 Pass 需注意与协程 IR 的交互。
  • Android NDK 兼容:NDK r27 使用 LLVM 18,Pass 插件可直接兼容。

LLVM 19 (前沿)

  • 推荐场景:需要最新特性(如改进的向量化、新后端支持)的研究项目。
  • Pass 接口变化:
  • PassBuilder的parsePassPipeline支持更细粒度的控制。
  • 部分 Analysis Pass 接口重构(如LoopAnalysis相关)。
  • 引入实验性的SandboxIR,用于在受限环境中运行变换。
  • TargetTransformInfo接口有 breaking change,涉及成本模型查询。
  • 注意事项:API 可能在后续 patch 中变化,不建议用于需要长期维护的项目。

版本选择决策树

需要兼容 Android NDK r27? ──→ 选 LLVM 18 │ ↓ 否 需要最大稳定性? ──→ 选 LLVM 17 │ ↓ 否 需要前沿特性? ──→ 选 LLVM 19(但要做好 API 适配工作)

3. 编写第一个 Function Pass

知识定位:实践应用。这是一个可以直接复制、编译、运行的最小 Pass 示例,适合亲手敲一遍建立"Pass 到底长什么样"的直觉;如果只是想理解概念,读懂代码里的注释(遍历指令、识别 ADD、替换)即可,不要求真的去编译它。

// MyObfuscationPass.cpp #include "llvm/Passes/PassBuilder.h" #include "llvm/Passes/PassPlugin.h" #include "llvm/IR/Function.h" using namespace llvm; struct MyObfuscationPass : public PassInfoMixin<MyObfuscationPass> { PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) { for (auto &BB : F) { for (auto &I : BB) { // 在这里实现混淆逻辑 // 例如:识别 ADD 指令并替换为等价的 MBA 表达式 if (auto *BinOp = dyn_cast<BinaryOperator>(&I)) { if (BinOp->getOpcode() == Instruction::Add) { // TODO: 替换为 MBA } } } } return PreservedAnalyses::none(); } }; // 注册插件 extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() { return {LLVM_PLUGIN_API_VERSION, "MyObfuscation", "0.1", [](PassBuilder &PB) { PB.registerPipelineParsingCallback( [](StringRef Name, FunctionPassManager &FPM, ArrayRef<PassBuilder::PipelineElement>) { if (Name == "my-obf") { FPM.addPass(MyObfuscationPass()); return true; } return false; }); }}; }

4. 完整的 MBA(Mixed Boolean-Arithmetic,混合布尔-算术)替换 Pass 示例

知识定位:实践应用,本附录的核心实操内容。这是一个从源码、CMake 构建到运行验证全套可复现的完整案例,建议真正打算开发混淆工具的读者按 4.1→4.4 顺序逐步敲一遍;只是想理解 MBA 替换原理的读者,重点看 4.1 中两条数学恒等式(a+b=(a^b)+2*(a&b)等)和 4.3 的运行结果说明即可,不必亲自搭建编译环境。

下面是一个完整可编译的 MBA 替换 Pass,将a + b替换为(a ^ b) + 2 * (a & b),将a - b替换为(a ^ b) - 2 * (~a & b)。

4.1 完整源码:MBASubstitutionPass.cpp

// MBASubstitutionPass.cpp // 将加法和减法替换为等价的 MBA (Mixed Boolean-Arithmetic) 表达式 // // a + b → (a ^ b) + 2 * (a & b) // a - b → (a ^ b) - 2 * (~a & b) // // 编译: // clang++ -shared -fPIC -o libMBASub.so MBASubstitutionPass.cpp \ // $(llvm-config --cxxflags --ldflags --libs core support passes) \ // -fno-rtti // // 使用: // opt -load-pass-plugin=./libMBASub.so -passes=mba-sub input.ll -S -o output.ll #include "llvm/IR/Function.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/PassManager.h" #include "llvm/Passes/PassBuilder.h" #include "llvm/Passes/PassPlugin.h" #include "llvm/Support/raw_ostream.h" #include "llvm/ADT/Statistic.h" #include "llvm/Support/Debug.h" #include <random> #define DEBUG_TYPE "mba-sub" STATISTIC(NumAddSubstituted, "Number of ADD instructions substituted"); STATISTIC(NumSubSubstituted, "Number of SUB instructions substituted"); using namespace llvm; namespace { /// 将 ADD 指令 (a + b) 替换为 MBA 等价形式: (a ^ b) + 2 * (a & b) /// 数学证明: /// a + b = (a ^ b) + ((a & b) << 1) /// 因为 XOR 给出不进位的加法结果,AND 给出进位,左移 1 即乘以 2 static bool substituteAdd(BinaryOperator *BinOp) { IRBuilder<> Builder(BinOp); Value *A = BinOp->getOperand(0); Value *B = BinOp->getOperand(1); // (a ^ b) Value *Xor = Builder.CreateXor(A, B, "mba.xor"); // (a & b) Value *And = Builder.CreateAnd(A, B, "mba.and"); // 2 * (a & b) 即 (a & b) << 1 Value *Shl = Builder.CreateShl(And, ConstantInt::get(And->getType(), 1), "mba.shl"); // (a ^ b) + 2 * (a & b) Value *Result = Builder.CreateAdd(Xor, Shl, "mba.add"); BinOp->replaceAllUsesWith(Result); ++NumAddSubstituted; return true; } /// 将 SUB 指令 (a - b) 替换为 MBA 等价形式: (a ^ b) - 2 * (~a & b) /// 数学证明: /// a - b = a + (~b + 1) = (a ^ b) - 2 * (~a & b) static bool substituteSub(BinaryOperator *BinOp) { IRBuilder<> Builder(BinOp); Value *A = BinOp->getOperand(0); Value *B = BinOp->getOperand(1); // (a ^ b) Value *Xor = Builder.CreateXor(A, B, "mba.xor"); // ~a Value *NotA = Builder.CreateNot(A, "mba.not"); // (~a & b) Value *And = Builder.CreateAnd(NotA, B, "mba.and"); // 2 * (~a & b) 即 (~a & b) << 1 Value *Shl = Builder.CreateShl(And, ConstantInt::get(And->getType(), 1), "mba.shl"); // (a ^ b) - 2 * (~a & b) Value *Result = Builder.CreateSub(Xor, Shl, "mba.sub"); BinOp->replaceAllUsesWith(Result); ++NumSubSubstituted; return true; } struct MBASubstitutionPass : public PassInfoMixin<MBASubstitutionPass> { /// 可选:控制替换概率(0.0 ~ 1.0),避免全部替换导致性能下降过于明显 double SubstitutionProbability = 1.0; PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) { bool Changed = false; std::mt19937 RNG(42); // 固定种子保证可重现 std::uniform_real_distribution<double> Dist(0.0, 1.0); // 收集需要替换的指令(不能在迭代中修改) SmallVector<BinaryOperator *, 16> AddInsts; SmallVector<BinaryOperator *, 16> SubInsts; for (auto &BB : F) { for (auto &I : BB) { if (auto *BinOp = dyn_cast<BinaryOperator>(&I)) { // 只处理整数类型 if (!BinOp->getType()->isIntegerTy()) continue; if (BinOp->getOpcode() == Instruction::Add) { if (Dist(RNG) < SubstitutionProbability) AddInsts.push_back(BinOp); } else if (BinOp->getOpcode() == Instruction::Sub) { if (Dist(RNG) < SubstitutionProbability) SubInsts.push_back(BinOp); } } } } // 执行替换 for (auto *BinOp : AddInsts) { substituteAdd(BinOp); BinOp->eraseFromParent(); Changed = true; } for (auto *BinOp : SubInsts) { substituteSub(BinOp); BinOp->eraseFromParent(); Changed = true; } LLVM_DEBUG(dbgs() << "MBASub: " << F.getName() << " — substituted " << NumAddSubstituted << " ADDs, " << NumSubSubstituted << " SUBs\n"); return Changed ? PreservedAnalyses::none() : PreservedAnalyses::all(); } }; } // anonymous namespace // 注册为 out-of-tree Pass 插件 extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() { return { LLVM_PLUGIN_API_VERSION, "MBASubstitution", LLVM_VERSION_STRING, [](PassBuilder &PB) { // 注册 Pass 名称 "mba-sub" PB.registerPipelineParsingCallback( [](StringRef Name, FunctionPassManager &FPM, ArrayRef<PassBuilder::PipelineElement>) { if (Name == "mba-sub") { FPM.addPass(MBASubstitutionPass()); return true; } return false; }); // 也可以注册为 O0 优化管线的 EP,自动触发 PB.registerOptimizerEarlyEPCallback( [](ModulePassManager &MPM, OptimizationLevel Level) { if (Level == OptimizationLevel::O0) { // O0 时不自动启用,需显式指定 return; } FunctionPassManager FPM; FPM.addPass(MBASubstitutionPass()); MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM))); }); } }; }

4.2 CMakeLists.txt

cmake_minimum_required(VERSION 3.20) project(MBASubstitution) find_package(LLVM REQUIRED CONFIG) message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}") message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}") include_directories(${LLVM_INCLUDE_DIRS}) separate_arguments(LLVM_DEFINITIONS_LIST NATIVE_COMMAND ${LLVM_DEFINITIONS}) add_definitions(${LLVM_DEFINITIONS_LIST}) # Pass 必须禁用 RTTI(与 LLVM 保持一致) if(NOT LLVM_ENABLE_RTTI) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti") endif() add_library(MBASub MODULE MBASubstitutionPass.cpp) # 不链接 LLVM 库(作为插件加载时由 opt/clang 提供符号) set_target_properties(MBASub PROPERTIES CXX_STANDARD 17 PREFIX "lib" SUFFIX ".so" )

4.3 编译与运行

# 构建 Pass 插件 mkdir build && cd build cmake .. -DLLVM_DIR=/path/to/llvm/build/lib/cmake/llvm make -j$(nproc) # 生成测试 IR cat > test.c << 'EOF' int add_example(int a, int b) { return a + b; } int sub_example(int a, int b) { return a - b; } EOF clang -O0 -emit-llvm -S test.c -o test.ll # 运行 Pass opt -load-pass-plugin=./libMBASub.so -passes=mba-sub test.ll -S -o output.ll # 查看替换结果 cat output.ll # 原来的 add 指令将变成 xor + and + shl + add 的序列 # 原来的 sub 指令将变成 xor + not + and + shl + sub 的序列

4.4 多层嵌套增强

对输出再次运行 Pass,MBA 表达式会继续展开,指数级增加复杂度:

# 第二轮替换(已有的 MBA 中的 add/sub 会被再次展开) opt -load-pass-plugin=./libMBASub.so -passes=mba-sub output.ll -S -o output2.ll # 第三轮 opt -load-pass-plugin=./libMBASub.so -passes=mba-sub output2.ll -S -o output3.ll

5. 在 Android NDK 中集成

知识定位:实践应用,面向需要把自定义 Pass 真正用到移动端项目构建中的开发者。如果你不是在做 Android native 混淆集成,这里只需知道"写好的 Pass 可以通过-fpass-plugin挂到 NDK 编译流程里"这个事实。

# CMakeLists.txt set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fpass-plugin=${CMAKE_SOURCE_DIR}/libs/libMyObfPass.so")

注意:Pass 插件需要针对 NDK 使用的 LLVM 版本编译。


6. 调试 Pass 的方法

知识定位:实践应用。这是开发 Pass 过程中排错的工具箱(命令行调试、IR dump 对比、FileCheck 回归测试、GDB 断点),只有在你真正写 Pass 遇到问题时才会用到;了解性阅读可以跳过具体命令,只需知道"Pass 出 bug 时有这几类调试手段可用"。

6.1 使用 opt 命令行调试

# 以 debug 模式运行 Pass(需要 LLVM Debug 构建或 STATISTIC 宏) opt -load-pass-plugin=./libMBASub.so -passes=mba-sub \ -debug -stats test.ll -S -o /dev/null # 只打印特定 Pass 的调试信息 opt -load-pass-plugin=./libMBASub.so -passes=mba-sub \ -debug-only=mba-sub test.ll -S -o /dev/null # 打印 Pass 前后的 IR diff(非常有用) opt -load-pass-plugin=./libMBASub.so \ -passes='print<module>,mba-sub,print<module>' \ test.ll -S -o /dev/null 2>&1 | less

6.2 Dump IR 进行对比

# 使用 -print-after-all 打印每个 Pass 执行后的 IR opt -load-pass-plugin=./libMBASub.so -passes=mba-sub \ -print-after-all test.ll -S -o output.ll 2> ir_dumps.txt # 使用 -print-before / -print-after 精确控制 opt -load-pass-plugin=./libMBASub.so \ -passes=mba-sub \ -print-before=mba-sub \ -print-after=mba-sub \ test.ll -S -o output.ll 2> before_after.txt # diff 对比 Pass 前后变化 diff <(opt -passes='print<module>' test.ll -S 2>&1) \ <(opt -load-pass-plugin=./libMBASub.so -passes='mba-sub' test.ll -S 2>&1) \ --color

6.3 使用 FileCheck 做回归测试

FileCheck 是 LLVM 内置的模式匹配测试工具,用于验证 Pass 的输出是否符合预期。

; RUN: opt -load-pass-plugin=%plugindir/libMBASub.so \ ; RUN: -passes=mba-sub -S %s | FileCheck %s ; 测试 ADD 替换 define i32 @test_add(i32 %a, i32 %b) { entry: %result = add i32 %a, %b ret i32 %result } ; CHECK-LABEL: @test_add ; CHECK-NOT: add i32 %a, %b ; CHECK: %mba.xor = xor i32 %a, %b ; CHECK: %mba.and = and i32 %a, %b ; CHECK: %mba.shl = shl i32 %mba.and, 1 ; CHECK: %mba.add = add i32 %mba.xor, %mba.shl ; CHECK: ret i32 %mba.add ; 测试 SUB 替换 define i32 @test_sub(i32 %a, i32 %b) { entry: %result = sub i32 %a, %b ret i32 %result } ; CHECK-LABEL: @test_sub ; CHECK-NOT: sub i32 %a, %b ; CHECK: %mba.xor = xor i32 %a, %b ; CHECK: %mba.not = xor i32 %a, -1 ; CHECK: %mba.and = and i32 %mba.not, %b ; CHECK: %mba.shl = shl i32 %mba.and, 1 ; CHECK: %mba.sub = sub i32 %mba.xor, %mba.shl ; CHECK: ret i32 %mba.sub ; 测试:浮点数不应被替换 define float @test_float_add(float %a, float %b) { entry: %result = fadd float %a, %b ret float %result } ; CHECK-LABEL: @test_float_add ; CHECK: fadd float %a, %b

运行测试:

# 单个测试文件 llvm-lit -v test/mba_sub_test.ll # 批量运行 llvm-lit -v test/

6.4 使用 GDB/LLDB 调试 Pass 本身

# 使用 gdb 调试 opt 进程中的 Pass 逻辑 gdb --args opt -load-pass-plugin=./libMBASub.so -passes=mba-sub test.ll -S # 在 gdb 中设置断点 (gdb) break MBASubstitutionPass::run (gdb) run # 到达断点后可以检查 Function、BasicBlock、Instruction 等 LLVM 数据结构 (gdb) p F.getName().str() (gdb) p BinOp->getOpcodeName()

7. 常见 Pass 编写陷阱

知识定位:实践应用,是开发者踩坑后总结的检查清单(PHI 节点、异常处理、metadata、迭代器失效)。真正写 Pass 时建议对照检查;非开发读者可以把它当作"原来混淆 Pass 要考虑这么多编译器内部细节"的背景认知,不需要记住每条规则的具体写法。

7.1 PHI 节点处理

PHI 节点是 SSA 形式中合并不同前驱基本块值的特殊指令,必须位于基本块的最前面。

常见错误:在 PHI 节点前插入指令

// 错误示范:直接在 BasicBlock 开头插入,可能破坏 PHI 节点 IRBuilder<> Builder(&*BB.begin()); // 如果 BB 以 PHI 开头,这里就出问题了 Builder.CreateXor(A, B); // 正确做法:跳过 PHI 节点 IRBuilder<> Builder(&*BB.getFirstInsertionPt()); Builder.CreateXor(A, B);

常见错误:替换指令时未更新 PHI 的 incoming value

// 如果被替换的值在其他基本块的 PHI 中被引用, // replaceAllUsesWith 会自动处理,但如果你手动遍历 use,要注意 PHI for (auto &U : OldVal->uses()) { if (auto *PHI = dyn_cast<PHINode>(U.getUser())) { // PHI 节点的 operand 与 BasicBlock 是成对的, // 不能单独修改 value 而不考虑对应的 block } } // 最安全的方式:始终使用 replaceAllUsesWith OldInst->replaceAllUsesWith(NewVal); OldInst->eraseFromParent();

常见错误:在 PHI 节点所在的块前面分裂边时未更新 PHI

// 如果你的 Pass 会分裂关键边(critical edge), // 需要调用 SplitCriticalEdge 并确保 PHI 自动更新 // 使用 LLVM 提供的工具函数而非手动操作: #include "llvm/Transforms/Utils/BasicBlockUtils.h" SplitCriticalEdge(TermInst, SuccIdx);

7.2 异常处理兼容性

C++ 异常处理引入了invoke、landingpad、resume、catchswitch、catchpad、cleanuppad等特殊指令,混淆 Pass 必须正确处理。

规则 1:不要将 invoke 当作普通 call

// invoke 有两个后继:正常路径和异常路径 // 错误:只检查 CallInst if (auto *CI = dyn_cast<CallInst>(&I)) { ... } // 正确:同时处理 CallInst 和 InvokeInst,或使用 CallBase if (auto *CB = dyn_cast<CallBase>(&I)) { // 这同时匹配 call、invoke 和 callbr }

规则 2:不要在 landingpad 的基本块中插入非法指令

// landingpad 必须是基本块的第一条非 PHI 指令 // 如果你的 Pass 需要在这类块中操作,使用 getFirstInsertionPt() if (BB.isLandingPad()) { // 谨慎操作,或直接跳过此块 continue; }

规则 3:控制流平坦化不能破坏 EH (Exception Handling) 结构

// 如果你的 Pass 实现 CFF(Control Flow Flattening,控制流平坦化), // 遇到包含 invoke/landingpad 的函数时,最安全的做法是跳过 bool hasEH = false; for (auto &BB : F) { if (BB.isEHPad() || isa<InvokeInst>(BB.getTerminator())) { hasEH = true; break; } } if (hasEH) { LLVM_DEBUG(dbgs() << "Skipping " << F.getName() << ": contains exception handling\n"); return PreservedAnalyses::all(); }

7.3 Metadata 保留

LLVM IR 中的 metadata(如!dbg、!tbaa、!range、!prof)对调试信息、类型别名分析和 PGO 至关重要。

规则 1:新指令应继承原指令的调试位置

// 错误:新指令没有 debug location,调试时无法定位源码行 Value *NewInst = Builder.CreateXor(A, B); // 正确:复制原指令的 debug location IRBuilder<> Builder(OrigInst); Builder.SetCurrentDebugLocation(OrigInst->getDebugLoc()); Value *NewInst = Builder.CreateXor(A, B); // 自动继承 Builder 的 debug location

规则 2:不要丢失 TBAA metadata

// 如果替换 load/store 指令,新指令应保留 TBAA 信息 if (auto *LI = dyn_cast<LoadInst>(OrigInst)) { auto *NewLoad = Builder.CreateLoad(LI->getType(), NewPtr); // 复制所有 metadata NewLoad->copyMetadata(*LI); }

规则 3:正确声明 Preserved Analyses

// 如果你的 Pass 只修改指令内容但不改变 CFG: return PreservedAnalyses::allInSet<CFGAnalyses>(); // 如果你的 Pass 修改了 CFG(如 CFF): return PreservedAnalyses::none(); // 不要过度声明 Preserved(导致后续 Pass 使用过时的 Analysis 缓存) // 也不要过度声明 Invalidated(导致不必要的重新计算,拖慢编译)

7.4 迭代器失效

// 错误:在遍历过程中删除/替换指令会导致迭代器失效 for (auto &I : BB) { if (shouldReplace(&I)) { I.eraseFromParent(); // 崩溃!迭代器已失效 } } // 正确:先收集再处理 SmallVector<Instruction *, 16> ToReplace; for (auto &I : BB) { if (shouldReplace(&I)) ToReplace.push_back(&I); } for (auto *I : ToReplace) { // 替换并删除 I->replaceAllUsesWith(NewVal); I->eraseFromParent(); } // 或者使用 make_early_inc_range(C++20 / LLVM 提供) for (auto &I : llvm::make_early_inc_range(BB)) { if (shouldReplace(&I)) { I.replaceAllUsesWith(NewVal); I.eraseFromParent(); } }

参考资料

  1. LLVM 官方文档 — Writing an LLVM Passhttps://llvm.org/docs/WritingAnLLVMNewPMPass.html 新 Pass Manager 下编写 Pass 的权威指南。

  2. LLVM 官方文档 — Using New Pass Managerhttps://llvm.org/docs/NewPassManager.html New PM 的架构设计和使用方式详解。

  3. LLVM Programmer's Manualhttps://llvm.org/docs/ProgrammersManual.html LLVM IR 数据结构(Value、Instruction、BasicBlock 等)的完整参考。

  4. LLVM 官方文档 — LLVM Testing Infrastructure Guidehttps://llvm.org/docs/TestingGuide.html FileCheck、llvm-lit 等测试工具的使用说明。

  5. Hikari 源码https://github.com/HikariObfuscator/Hikari 最活跃的 OLLVM 分支,是学习混淆 Pass 工程实践的优秀参考。

  6. banach-space/llvm-tutorhttps://github.com/banach-space/llvm-tutor 一系列由浅入深的 LLVM Pass 教学示例,强烈推荐初学者。

  7. Trail of Bits — Building a Pass for LLVMhttps://blog.trailofbits.com/ Trail of Bits 博客中有多篇关于 LLVM Pass 开发的实战文章。

  8. OLLVM 原始论文J. Junod et al., "Obfuscator-LLVM — Software Protection for the Masses", SPRO 2015. 理解 OLLVM 三大核心 Pass(CFF、BCF(Bogus Control Flow,虚假控制流)、Sub)的设计思路。

返回列表