前言
LLVM Pass是LLVM提供的用于优化/分析/处理IR的组件, 第三方可以自由开发Pass从而干涉编译过程, 实现代码优化/静态分析/代码混淆. 上一节学习了CMake的基本用法, 现在来用CMake实现最简单的LLVM Pass. 笔者的环境仍是MacOS. 需要注意的是LLVM Pass分为Legacy Pass和New Pass两种, 前者是历史遗留, 具体兼容性如下表.
LLVM |
默认 |
可选 |
5-12 |
LegacyPassManager |
-fexperimental-new-pass-manager 启用New, 该功能有限 |
13-14 |
NewPassManager |
-flegacy-pass-manager 启用 Legacy |
15-? |
NewPassManager |
|
编码
只需要demo.cpp
和CMakeLists.txt
两个文件. 因为LLVM的版本较多, 网上开源的LLVM Pass项目只支持部分版本, 笔者根据刚学习的CMake将其改造为兼容LLVM12-18.
demo.cpp
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Pass.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Passes/PassPlugin.h"
#include "llvm/Support/raw_ostream.h"
#if LLVM_VERSION_MAJOR < 16
#include "llvm/Transforms/IPO/PassManagerBuilder.h"
#endif
using namespace llvm;
#include <iostream>
#define PASSNAME "MyPassDemo"
// ---------------- Legacy Pass ---------------- //
class MyPassDemoLegacy : public FunctionPass {
public:
static char ID;
MyPassDemoLegacy() : FunctionPass(ID) {}
virtual bool runOnFunction(Function& F) override {
errs() << "LegacyPass: I saw a function called " << F.getName() << "!\n";
return false;
}
};
char MyPassDemoLegacy::ID = 0;
#if LLVM_VERSION_MAJOR < 16
static RegisterStandardPasses RegisterMyPass(PassManagerBuilder::EP_EarlyAsPossible,
[](const PassManagerBuilder &, legacy:assManagerBase &M) {
PM.add(new MyPassDemoLegacy());
}
);
#else
static RegisterPass<MyPassDemoLegacy> RegisterMyPass(PASSNAME, PASSNAME, false, false);
#endif
// ---------------- Legacy Pass ---------------- //
// ---------------- New Pass ---------------- //
class MyPassDemo : public PassInfoMixin<MyPassDemo> {
public:
PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM) {
errs() << "hello MyPassDemo\n";
return PreservedAnalyses::all();
}
};
extern "C" LLVM_ATTRIBUTE_WEAK ::llvm:assPluginLibraryInfo llvmGetPassPluginInfo() {
return {
.APIVersion = LLVM_PLUGIN_API_VERSION,
.PluginName = PASSNAME,
.PluginVersion = "1.0",
.RegisterPassBuilderCallbacks = [](PassBuilder &B) {
PB.registerPipelineStartEPCallback(
#if LLVM_VERSION_MAJOR < 14
[](ModulePassManager &MPM, PassBuilder::OptimizationLevel Level) {
#else
[](ModulePassManager &MPM, OptimizationLevel Level) {
#endif
MPM.addPass(MyPassDemo());
});
}
};
}
// ---------------- New Pass ---------------- //
CMakeLists.txt
cmake_minimum_required(VERSION 3.6)
project(MyPassDemo)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_BUILD_TYPE "Release")
find_package(LLVM REQUIRED CONFIG) # 引入LLVMConfig.cmake以初始化LLVM相关变量
list(APPEND CMAKE_MODULE_PATH "${LLVM_DIR}") # 不加会导致低版本LLVM cmake失败
include(AddLLVM) # 引入AddLLVM.cmake以导入add_llvm_pass_plugin函数
include(HandleLLVMOptions) # 不加会导致低版本LLVM cmake失败
add_definitions(${LLVM_DEFINITIONS})
include_directories(${LLVM_INCLUDE_DIRS})
link_directories(${LLVM_LIBRARY_DIRS})
add_llvm_pass_plugin(MyPassDemo${LLVM_VERSION_MAJOR}
demo.cpp
)
测试
编译:
export LLVM_DIR=/path/to/llvm12/build/lib/cmake/llvm
cmake -B build --fresh
cmake --build build
# for LLVM<=12 Legacy Pass
llvm12/build/bin/clang -isysroot `xcrun --sdk macosx --show-sdk-path` -Xclang -load -Xclang build/MyPassDemo12.dylib /tmp/1.cpp
# 等价于上面
llvm12/build/bin/clang -isysroot `xcrun --sdk macosx --show-sdk-path` -fplugin=build/MyPassDemo12.dylib /tmp/1.cpp
# for LLVM=13/14 Legacy Pass
llvm13/build/bin/clang -isysroot `xcrun --sdk macosx --show-sdk-path` -flegacy-pass-manager -fplugin=build/MyPassDemo13.dylib /tmp/1.cpp
# for LLVM=13/14 New Pass
llvm13/build/bin/clang -isysroot `xcrun --sdk macosx --show-sdk-path` -fpass-plugin=build/MyPassDemo13.dylib /tmp/1.cpp
# for LLVM>=15 New Pass
llvm15/build/bin/clang -isysroot `xcrun --sdk macosx --show-sdk-path` -fpass-plugin=build/MyPassDemo15.dylib /tmp/1.cpp
经过测试可以发现, Pass的LLVM版本需要和LLVM大版本一致, 否则也会产生错误, 但总体来说Pass解决了LLVM编译代码量过大的问题, 所以仍然值得采用, 测试结果:
hello MyPassDemo
AppleClang的Pass
AppleClang, 即XCode自带的Clang, 苹果因为安全性考虑阉割掉了LLVM Pass, 因此常规方法并不能加载起来.
当然如果如果逆向技术过关, 也很容易将Pass改为兼容AppleClang的.
ExtensionPointTy
// EP_Peephole PeepholeEPCallbacks
void registerPeepholeEPCallback(const std::function<void(FunctionPassManager&, OptimizationLevel)>&);
// EP_LoopOptimizerEnd LateLoopOptimizationsEPCallbacks
void registerLateLoopOptimizationsEPCallback(const std::function<void(LoopPassManager&, OptimizationLevel)>&);
// EP_LateLoopOptimizations LoopOptimizerEndEPCallbacks
void registerLoopOptimizerEndEPCallback(const std::function<void(LoopPassManager&, OptimizationLevel)>&);
// EP_ScalarOptimizerLate ScalarOptimizerLateEPCallbacks
void registerScalarOptimizerLateEPCallback(const std::function<void(FunctionPassManager&, OptimizationLevel)>&);
// EP_CGSCCOptimizerLate CGSCCOptimizerLateEPCallbacks
void registerCGSCCOptimizerLateEPCallback(const std::function<void(CGSCCPassManager&, OptimizationLevel)>&);
// EP_VectorizerStart VectorizerStartEPCallbacks
void registerVectorizerStartEPCallback(const std::function<void(FunctionPassManager&, OptimizationLevel)>&);
// EP_EarlyAsPossible PipelineStartEPCallbacks
void registerPipelineStartEPCallback(const std::function<void(ModulePassManager&, OptimizationLevel)>&);
// EP_ModuleOptimizerEarly PipelineEarlySimplificationEPCallbacks
void registerPipelineEarlySimplificationEPCallback(const std::function<void(ModulePassManager&, OptimizationLevel)>&);
//
void registerOptimizerEarlyEPCallback(const std::function<void(ModulePassManager&, OptimizationLevel)>&);
// EP_OptimizerLast OptimizerLastEPCallbacks
void registerOptimizerLastEPCallback(const std::function<void(ModulePassManager&, OptimizationLevel)>&);
//
void registerFullLinkTimeOptimizationEarlyEPCallback(const std::function<void(ModulePassManager&, OptimizationLevel)>&);
//
void registerFullLinkTimeOptimizationLastEPCallback(const std::function<void(ModulePassManager&, OptimizationLevel)>&);
使用helloworld测试EP顺序:
- Debug: PipelineStart -> PipelineEarlySimplification -> OptimizerLast
- Release: PipelineStart -> PipelineEarlySimplification -> Peephole -> Peephole -> Peephole -> ScalarOptimizerLate -> Peephole -> VectorizerStart -> OptimizerLast