llvm NewPassManager API分析及适配方案

admin 2022年6月11日18:18:35安全文章评论4 views8742字阅读29分8秒阅读模式

llvm NewPassManager API分析及适配方案

本文为看雪论坛优秀文章
看雪论坛作者ID:LeadroyaL


本文从 llvmGetPassPluginInfo 出发,深入解读 PassBuilder 的各个 API,记录从 LegacyPassManager 迁移到 NewPassManager 的过程。本文使用llvm13,低版本相关API会不一致。

llvmGetPassPluginInfo 出发

根据背景知识,llvm13以上通过 -fpass-plugin=libPass.so 指定被加载的 library,加载后调用该共享库的导出符号 llvmGetPassPluginInfo 获取相关信息,该函数需要返回结构体 llvm::PassPluginLibraryInfo,位于 llvm/include/llvm/Passes/PassPlugin.h。
struct PassPluginLibraryInfo {  /// The API version understood by this plugin, usually c  /// LLVM_PLUGIN_API_VERSION  uint32_t APIVersion;  /// A meaningful name of the plugin.  const char *PluginName;  /// The version of the plugin.  const char *PluginVersion;   /// The callback for registering plugin passes with a c PassBuilder  /// instance  void (*RegisterPassBuilderCallbacks)(PassBuilder &);};

直接看范例吧,很好理解。
extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfollvmGetPassPluginInfo() {    return {LLVM_PLUGIN_API_VERSION,            "Skeleton",            "1.0.0",            [](PassBuilder &PB) {                //xxxxxxx            }};}

第一个参数传递版本号,和编译依赖的llvm环境有关,从环境中取值即可。加载时会检查clang版本号和plugin版本号是否一致,不一致会加载失败。
 
第二个参数是插件名字,随便传。
 
第三个参数是插件版本号,随便传。
 
第四个参数非常重要,是一个回调函数,clang加载该插件后会调用它,传递 PassBuilder &PB 这个关键对象。
 
问题转化为了:PassBuilder &PB 如何使用。


PassBuilder 的 API

PassBuilder 声明位于 llvm/include/llvm/Passes/PassBuilder.h,有大量函数供调用,总结如下:
  • (xxx)Analyses,与加载pass不相关

  • build(xxx)Pipeline,与加载pass不相关

  • parsePassPipiline,尝试处理给定的处理串,可能用于测试

  • printPassNames,打印所有的Pass

  • registerAnalysisRegistrationCallback,与加载pass不相关

  • register(xxx)EPCallback,核心API,与Legacy比较像

  • registerPipelineParsingCallback,给opt用的,opt --load-pass-plugin=libPass.so -passes=passName1,passName2 可以触发到


阅读 register(xxx)EPCallback 系列的声明,EP表示ExtensionPoint,与 llvm/include/llvm/Transforms/IPO/PassManagerBuilder.h 的 ExtensionPointTy 对应,ExtensionPointTy这个枚举类,那味道可太熟悉了,LegacyPassManager的注册时刻。
 
随便选取一个,观察其实现,传入一个回调函数,将该函数添加到列表里。每个API传入的回调函数返回值都是 void,第一个参数是一个PassManager,第二个参数是 OptimizationLevel,很容易理解,pass开发者可以根据不同的优化等级走不同的逻辑,并使用 PassManager 完成 pass 的注册。
void registerPeepholeEPCallback(    const std::function<void(FunctionPassManager &, OptimizationLevel)> &C) {  PeepholeEPCallbacks.push_back(C);}

对我们有用的部分,总结如下表,这里有一个大变更,最早的回调 registerPipelineStartEPCallback 只能注册 ModulePass。

llvm NewPassManager API分析及适配方案

ModulePassManager 是llvm传给plugin的,只提供一个API,addPass,传递一个对象,但这里很复杂。

简单样例v1

经过一些测试,总结了一个最小的样例,先看结构,再分析原理。
➜  /tmp clang -fpass-plugin=libSkeletonPass.so test.cllvmGetPassPluginInfoisRequired invokedModule name is test.c!

PB.registerPipelineStartEPCallback(myCallback); void myCallback(llvm::ModulePassManager &PM, llvm::PassBuilder::OptimizationLevel Level) {    PM.addPass(TestPass());} class TestPass {public:    static StringRef name() {        errs() << "name invokedn";        return "TestPass";    }     static bool isRequired() {        errs() << "isRequired invokedn";        return true;    }     PreservedAnalyses run(Module &M, ModuleAnalysisManager &) {        errs() << "Module name is " << M.getName() << "!n";        return PreservedAnalyses::all();    }};

现在不需要继承任何类了,编译时llvm会通过模板来检查 TestPass 的方法,共有 3 个。
  • static StringRef name 必须,表示 pass 的名字。可以使用 PassInfoMixin 模板快速实现该API。

  • static bool isRequired 可选,如果返回true,则该pass不会被跳过。

  • PreservedAnalyses run(Module &M, ModuleAnalysisManager &) 必选,ModulePass 需要这里是 llvm::Module 和 ModuleAnalysisManager,FunctionPass 需要这里是 Function 和 FunctionAnalysisManager。


有了整体认知后,介绍相关的模板,llvm/include/llvm/IR/PassManager.h。

llvm::ModulePassManager &PM

这个对象由llvm传递给plugin,查看发现它是一个模板 PassManager。
extern template class PassManager<Module>; /// Convenience typedef for a pass manager over modules.using ModulePassManager = PassManager<Module>;
而 PassManager 也是一个模板,由 PassInfoMixin 提供一个 name。
 
IRUnitT 就是传入的 llvm::Module,然后推断出 AnalysisManagerT 的类型为 AnalysisManager<IRUnitT>,也就是AnalysisManager<Module>,恰好是 ModuleAnalysisManager。
 
AnalysisManager也是一个模板类,就不展开了。
template <typename IRUnitT,          typename AnalysisManagerT = AnalysisManager<IRUnitT>,          typename... ExtraArgTs>class PassManager : public PassInfoMixin<                        PassManager<IRUnitT, AnalysisManagerT, ExtraArgTs...>> {  //xxxxxxxx} ///////////////////// template <typename DerivedT> struct PassInfoMixin {  /// Gets the name of the pass we are mixed into.  static StringRef name() {    static_assert(std::is_base_of<PassInfoMixin, DerivedT>::value,                  "Must pass the derived type as the template argument!");    StringRef Name = getTypeName<DerivedT>();    if (Name.startswith("llvm::"))      Name = Name.drop_front(strlen("llvm::"));    return Name;  }}; ///////////////////// using ModuleAnalysisManager = AnalysisManager<Module>;

继续看 addPass的实现,有两个重载,第一个是传入 pass 时,将其加入到列表中;第二个是传入另一个 PassManager 时,将其内部的 passes 全部加入到列表中。
template <typename IRUnitT,          typename AnalysisManagerT = AnalysisManager<IRUnitT>,          typename... ExtraArgTs>class PassManager : public PassInfoMixin<                        PassManager<IRUnitT, AnalysisManagerT, ExtraArgTs...>> {   template <typename PassT>  std::enable_if_t<!std::is_same<PassT, PassManager>::value>  addPass(PassT &&Pass) {    using PassModelT =        detail::PassModel<IRUnitT, PassT, PreservedAnalyses, AnalysisManagerT,                          ExtraArgTs...>;     Passes.emplace_back(new PassModelT(std::forward<PassT>(Pass)));  }   template <typename PassT>  std::enable_if_t<std::is_same<PassT, PassManager>::value>  addPass(PassT &&Pass) {    for (auto &P : Pass.Passes)      Passes.emplace_back(std::move(P));  }}

重点关注第一个,detail::PassModel 位于 llvm/include/llvm/IR/PassManagerInternal.h,是一个模板类。IRUnitT 是传入的 llvm::Module,PassT是传入的用户自定义的结构体,PreservedAnalysesT是传入的 PreservedAnalyses,AnalysisManagerT是传入的ModuleAnalysisManager。
 
它的构造方法拿到用户自定义的结构体, run 方法调用传入的pass的run方法,需要满足 IRUnitT 和 AnalysisManagerT 的类型约束,这和上文提到的。
ModulePass 需要这里是 llvm::Module 和 ModuleAnalysisManager,FunctionPass 需要这里是 Function 和 FunctionAnalysisManager
 
相互印证。它的 name 方法调用传入的结构体的 static name 方法。它的 passIsRequiredImpl 会根据传入的结构体的 isRequired 是否声明而定,未声明时默认为 false,已声明则调用传入的结构体的 static isRequired
template <typename IRUnitT, typename PassT, typename PreservedAnalysesT,          typename AnalysisManagerT, typename... ExtraArgTs>struct PassModel : PassConcept<IRUnitT, AnalysisManagerT, ExtraArgTs...> {  explicit PassModel(PassT Pass) : Pass(std::move(Pass)) {}  // We have to explicitly define all the special member functions because MSVC  // refuses to generate them.  PassModel(const PassModel &Arg) : Pass(Arg.Pass) {}  PassModel(PassModel &&Arg) : Pass(std::move(Arg.Pass)) {}   //xxxxxxxxxxx  PreservedAnalysesT run(IRUnitT &IR, AnalysisManagerT &AM,                         ExtraArgTs... ExtraArgs) override {    return Pass.run(IR, AM, ExtraArgs...);  }   StringRef name() const override { return PassT::name(); }   template <typename T>  using has_required_t = decltype(std::declval<T &>().isRequired());   template <typename T>  static std::enable_if_t<is_detected<has_required_t, T>::value, bool>  passIsRequiredImpl() {    return T::isRequired();  }  template <typename T>  static std::enable_if_t<!is_detected<has_required_t, T>::value, bool>  passIsRequiredImpl() {    return false;  }   bool isRequired() const override { return passIsRequiredImpl<PassT>(); }   PassT Pass;};

而 PassModel 继承 PassConcept,PassConcept 也是模板,整体没什么东西,全部都是关键方法。
template <typename IRUnitT, typename AnalysisManagerT, typename... ExtraArgTs>struct PassConcept {  // Boiler plate necessary for the container of derived classes.  virtual ~PassConcept() = default;   /// The polymorphic API which runs the pass over a given IR entity.  ///  /// Note that actual pass object can omit the analysis manager argument if  /// desired. Also that the analysis manager may be null if there is no  /// analysis manager in the pass pipeline.  virtual PreservedAnalyses run(IRUnitT &IR, AnalysisManagerT &AM,                                ExtraArgTs... ExtraArgs) = 0;   /// Polymorphic method to access the name of a pass.  virtual StringRef name() const = 0;   /// Polymorphic method to to let a pass optionally exempted from skipping by  /// PassInstrumentation.  /// To opt-in, pass should implement `static bool isRequired()`. It's no-op  /// to have `isRequired` always return false since that is the default.  virtual bool isRequired() const = 0;};

经过多层的模板套娃,我们已经完完全全理清楚了 ModulePassManager.addPass 的原理,开发起来更有底气!


简单样例v2

通过 PassInfoMixin 可以省掉 static StringRef name()。
struct TestPass : PassInfoMixin<TestPass> {public:    static bool isRequired() {        errs() << "isRequired invokedn";        return true;    }     PreservedAnalyses run(Module &M, ModuleAnalysisManager &) {        errs() << "Module name is " << M.getName() << "!n";        return PreservedAnalyses::all();    }};

此时还差一个细节,返回值 PreservedAnalyses 是什么?阅读文档后认为是这样,不确定对不对:
  • 如果修改了IR,返回 PreservedAnalyses::none,表示之前的优化分析全部不需要保留

  • 如果修改了IR,返回一个集合,表示该集合中的pass不需要再次被执行了

  • 如果没有修改IR,则返回 PreservedAnalyses::all,表示分析集全部需要保留


这时,相当于 ModulePass 的注册方式已搞定,接下来是 FunctionPass。


处理 FunctionPass

直接贴方案吧,不可挑剔的方案,使用 createModuleToFunctionPassAdaptor,来自 https://groups.google.com/g/llvm-dev/c/e_4WobR9WP0 。只要保证下文的 HelloWorld 有 PreservedAnalyses run(Function &F, FunctionAnalysisManager &) 方法就行。
PB.registerPipelineStartEPCallback(    [](ModulePassManager &MPM, OptimizationLevel Level) {        FunctionPassManager FPM;                       FPM.addPass(HelloWorld());        MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM)));});


适配 opt

registerPipelineParsingCallback 有很多个重载,其有 FunctionPassManager 和 ModulePassManager,区别不大。回调函数的第一个参数是传入的 Pass 名字,只要有 Pass 需要被执行,就会传递进来,因此需要判断是不是在调用自己,有兴趣的自己试试吧。
void registerPipelineParsingCallback(    const std::function<bool(StringRef Name, ModulePassManager &,                            ArrayRef<PipelineElement>)> &C) {    ModulePipelineParsingCallbacks.push_back(C);}

迁移方案

搞不动了,Skeleton的迁移放在github了,https://github.com/LeadroyaL/llvm-pass-tutorial 
 
其他项目等什么时候不忙了或者有好心人帮忙适配一下。




llvm NewPassManager API分析及适配方案


看雪ID:LeadroyaL

https://bbs.pediy.com/user-home-735539.htm

*本文由看雪论坛 LeadroyaL 原创,转载请注明来自看雪社区

llvm NewPassManager API分析及适配方案

# 往期推荐

1.PWN入门-格式化字符串漏洞

2.2016腾讯游戏安全技术竞赛题PC第一题

3.Android APP漏洞之战——调试与反调试详解

4.Fuzzm: 针对WebAssembly内存错误的模糊测试

5.0rays战队2021圣诞校内招新赛题解

6.2022腾讯游戏安全初赛一题解析



llvm NewPassManager API分析及适配方案



llvm NewPassManager API分析及适配方案

球分享

llvm NewPassManager API分析及适配方案

球点赞

llvm NewPassManager API分析及适配方案

球在看



llvm NewPassManager API分析及适配方案

点击“阅读原文”,了解更多!

原文始发于微信公众号(看雪学苑):llvm NewPassManager API分析及适配方案

特别标注: 本站(CN-SEC.COM)所有文章仅供技术研究,若将其信息做其他用途,由用户承担全部法律及连带责任,本站不承担任何法律及连带责任,请遵守中华人民共和国安全法.
  • 我的微信
  • 微信扫一扫
  • weinxin
  • 我的微信公众号
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年6月11日18:18:35
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                  llvm NewPassManager API分析及适配方案 http://cn-sec.com/archives/1109699.html

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: