美文网首页
编写调试Pass

编写调试Pass

作者: 纯情_小火鸡 | 来源:发表于2020-03-27 16:11 被阅读0次

    LLVM Pass构成了LLVM编译器的转换和优化部分,构建这些转换使用的分析结果,是编译器的结构化技术。

    All LLVM passes are subclasses of the Pass class, which implement functionality by overriding virtual methods inherited from Pass. Depending on how your pass works, you should inherit from the ModulePass , CallGraphSCCPass, FunctionPass , or LoopPass, or RegionPass classes

    LLVM Pass框架的主要功能是,以高效的方式根据自己的约束执行调度。

    编写Pass

    首先,配置和构建LLVM。 您需要在LLVM源库中的某个位置创建一个新目录。 假设您制作的是lib / Transforms / Hello。 必须设置一个构建脚本,该脚本将为新遍编译源代码。 创建Hello.h 以及.cpp,将以下内容复制到CMakeLists.txt:

    add_llvm_library( LLVMHello MODULE
      Hello.cpp
    
      PLUGIN_TOOL
      opt
      )
    

    然后在lib/Transforms/CMakeLists.txt 中添加:

    add_subdirectory(Hello)
    

    此构建脚本指定要编译当前目录中的Hello.cpp文件并将其链接到共享对象$(LEVEL)/lib/LLVMHello.so中,该文件可以由opt工具通过其-load选项动态加载。

    .cpp中实现代码如下:

    #include "llvm/Pass.h"
    #include "llvm/IR/Function.h"
    #include "llvm/Support/raw_ostream.h"
    
    #include "llvm/IR/LegacyPassManager.h"
    #include "llvm/Transforms/IPO/PassManagerBuilder.h"
    
    using namespace llvm;
    
    namespace {
      //匿名空间。与C(在全局范围内)的“static”关键字相同。 它使在匿名空间内部声明的内容仅对当前文件可见。
    struct Hello : public FunctionPass {
      static char ID;   
      //声明了LLVM用于标识通过的通过标识符。 使LLVM避免使用昂贵的C++运行时信息。
      Hello() : FunctionPass(ID) {}
    
      //重写FunctionPass中的抽象虚函数
      bool runOnFunction(Function &F) override {
        errs() << "Hello: ";
        errs().write_escaped(F.getName()) << '\n';
        return false;
      }
    }; // end of struct Hello
    }  // end of anonymous namespace
    
    char Hello::ID = 0;
    //初始化pass ID
    static RegisterPass<Hello> X("hello", "Hello World Pass",
                                 false /* Only looks at CFG */,
                                 false /* Analysis Pass */);
    //注册®️当前类,给定一个命令行参数“hello”以及一个名字“Hello World Pass”。最后两个参数描述了它的行为:如果pass执行CFG而未修改它,则第三个参数设置为true; 如果pass是分析pass,例如控制树pass,则将true作为第四个参数。
    
    static RegisterStandardPasses Y(
        PassManagerBuilder::EP_EarlyAsPossible,
        [](const PassManagerBuilder &Builder,
           legacy::PassManagerBase &PM) { PM.add(new Hello()); });
    //如果我们要将pass注册为现有管道的一步,则可以使用提供的一些扩展,例如 PassManagerBuilder :: EP_EarlyAsPossible可以在任何优化之前应用我们的pass,或者PassManagerBuilder :: EP_FullLinkTimeOptimizationLast在链接时间优化后应用我们的pass。
    

    至此,从构建目录的顶层使用简单的“ gmake”命令编译文件,您会获得一个新文件“ lib / LLVMHello.so”。 注意,此文件中的所有内容都包含在一个匿名名称空间中,pass是自包含的单元,不需要外部接口才有用。

    实际上,内部的Pass并不是强连接的,他们经由PassManager持有相互之间的依赖关系并且在执行过程中解析。下图展示了每个Pass是如何被link到每个指定库中的指定文件。

    relationship.png

    Pass classes and requirements

    事实上,将C代码转换为IR的过程从词法分析开始,C代码会被分解为一个令牌流,每个令牌表示一个标识符、文字、操作符,等等。这个标记流被提供给解析器,解析器在Context free grammar (CFG)的帮助下为语言构建一个抽象语法树。然后进行语义分析,检查代码的语义是否正确,然后生成IR代码。

    AST在语义分析中被大量使用,在语义分析中,编译器检查程序元素和语言的正确用法。在语义分析过程中,编译器还根据AST生成符号表。完整的树遍历可以验证程序的正确性。在验证正确性之后,AST作为代码生成的基础。

    AST作为用于存储关于lexer给出的令牌的各种信息的数据结构。此信息在解析器逻辑中生成,并且根据所解析的令牌类型填充ASTs。

    The LLVM bit code file format (also known as bytecode) is actually two things: a bitstream container format and an encoding of LLVM IR into the container format.

    为pass选择父类时,应尽可能选择最具体的类,同时仍能满足列出的要求。 这为LLVM Pass基础结构提供了优化运行方式所必需的信息,从而使生成的编译器不会不必要地变慢。

    The ImmutablePass class

    最简单,最无聊的Pass类型是“ ImmutablePass”类。 此pass类型用于不必运行,不更改状态并且永远不需要更新。 这不是常规的转换或分析类型,但可以提供有关当前编译器配置的信息。

    The ModulePass class

    ModulePass是你可以使用的所有超类中最通用的类。 从ModulePass派生表示您的遍历将整个程序作为一个单元使用,以不可预测的顺序引用函数体,或者添加函数,删除函数。 由于无法知道ModulePass子类的行为,因此无法对其执行进行优化。

    正确使用该类需要重写:

    virtual bool runOnModule(Module &M) = 0;
    

    如果通过转换修改了模块,则应返回true,否则返回false。

    The CallGraphSCCPass class

    CallGraphSCCPass供需要在调用图上自下而上遍历程序的遍历使用(被调用者在调用者之前)。 从CallGraphSCCPass派生提供了一些构建和遍历CallGraph的机制,但是还允许系统优化CallGraphSCCPasses的执行。 如果您的pass满足以下概述的要求,并且不满足FunctionPass的要求,则应从CallGraphSCCPass派生。

    1. 除当前SCC以及SCC的直接调用方和直接被调用方中的功能外,不允许检查或修改其他任何Function。
    2. 需要保留当前的CallGraph对象,并对其进行更新以反映对该程序所做的任何更改。
    3. 不允许在当前Module中添加或删除SCC,尽管它们可能会更改SCC的内容。
    4. 允许在当前模块中添加或删除全局变量。
    5. 允许在runOnSCC调用之间维护状态(包括全局数据)。
    virtual bool doInitialization(CallGraph &CG);
    

    允许doInitialization方法执行大部分CallGraphSCCPasses不允许执行的操作。 可以添加和删除函数,获取函数的指针等。doInitialization方法旨在执行简单的初始化类型的工作,而这些工作不依赖于正在处理的SCC。 doInitialization方法调用不会与任何其他pass执行重叠(因此,它应该非常快)。

    virtual bool doFinalization(CallGraph &CG);
    

    doFinalization方法是一种不常用的方法,当通过框架完成对正在编译的程序中的每个SCC调用runOnSCC时,将调用该方法。

    The FunctionPass class

    与ModulePass子类相比,FunctionPass子类具有系统可以预期的可预测的局部行为。 所有FunctionPass在程序中的每个功能上执行,独立于程序中的所有其他功能。 FunctionPasses不需要以特定顺序执行,并且FunctionPasses不会修改外部函数。

    明确地说,FunctionPass子类不允许:

    1. 检查或修改当前正在处理的功能以外的其他功能。
    2. 从当前Module添加或移除Function。
    3. 从当前Module添加或移除global variables。
    4. 维护runOnFunction调用之间的状态(包括全局数据)。

    通常,实现FunctionPass很简单。 FunctionPasses可能会重载三种虚拟方法来完成其工作。 如果所有这些方法修改了程序,则应返回true;否则返回false。

    virtual bool doInitialization(Module &M);
    

    可以使用doInitialization方法执行FunctionPasses不允许执行的大多数操作。 他们可以添加和删除函数,获取指向函数的指针等。doInitialization方法旨在执行简单的初始化类型的工作,该类型不依赖于正在处理的函数。 doInitialization方法调用不会与任何其他pass执行重叠(因此,它应该非常快)。

    LowerAllocations 是如何使用此方法的一个很好的例子。 此过程将malloc和free指令转换为依赖于平台的malloc()和free()函数调用。 它使用doInitialization方法获取对其所需的malloc和free函数的引用,并在必要时向该模块添加原型。

    virtual bool runOnFunction(Function &F) = 0;
    
    virtual bool doFinalization(Module &M);
    

    Running a pass with opt

    当我们使用了RegisterPass方法注册了pass之后,可以用opt命令执行LLVM中的pass。

    $ opt -load lib/LLVMHello.so -hello < hello.bc > /dev/null
    Hello: __main
    Hello: puts
    Hello: main
    

    执行opt -help指令可查看其他注册的string:

    $ opt -load lib/LLVMHello.so -help
    OVERVIEW: llvm .bc -> .bc modular optimizer and analysis printer
    
    USAGE: opt [subcommand] [options] <input bitcode file>
    
    OPTIONS:
      Optimizations available:
    ...
        -guard-widening           - Widen guards
        -gvn                      - Global Value Numbering
        -gvn-hoist                - Early GVN Hoisting of Expressions
        -hello                    - Hello World Pass
        -indvars                  - Induction Variable Simplification
        -inferattrs               - Infer set function attributes
    

    PassManager提供了一个可选指令(-time-passes),可以允许你获取有关pass的执行时间以及排队的其他pass的信息:

    $ opt -load lib/LLVMHello.so -hello -time-passes < hello.bc > /dev/null
    Hello: __main
    Hello: puts
    Hello: main
    ===-------------------------------------------------------------------------===
                          ... Pass execution timing report ...
    ===-------------------------------------------------------------------------===
      Total Execution Time: 0.0007 seconds (0.0005 wall clock)
    
       ---User Time---   --User+System--   ---Wall Time---  --- Name ---
       0.0004 ( 55.3%)   0.0004 ( 55.3%)   0.0004 ( 75.7%)  Bitcode Writer
       0.0003 ( 44.7%)   0.0003 ( 44.7%)   0.0001 ( 13.6%)  Hello World Pass
       0.0000 (  0.0%)   0.0000 (  0.0%)   0.0001 ( 10.7%)  Module Verifier
       0.0007 (100.0%)   0.0007 (100.0%)   0.0005 (100.0%)  Total
    

    调试Pass

    我们可以在命令行执行bin目录下的Mach-O,配置参数即可输出Pass模块中的日志:

    clang XXX/CryptoUtils.cpp -mllvm -enable-symobf
    //或者使用Opt直接输出
    opt -instcount input.bc -S -o output.ll
    

    但是这种方式在开发阶段用于调试确实太低效了。如何做到像开发自己项目一样可以断点调试呢?

    可以采用如下步骤:

    1. 首先把工程编译成Xcode项目:
    > git clone --recursive -b release_80 https://github.com/HikariObfuscator/Hikari.git Hikari   
    > mkdir buildXcode  
    > cmake -G Xcode ../Hikari  
    

    以官方Demo为例,代码在lib/Transforms/Hello中。其通过RegisterPass注册了Pass,所以可以通过opt -load去加载动态库并指定参数hello

    假设我们的源文件是test.c:

    #include <stdio.h>
    
    int add(int x, int y) {
        return x + y;
    }
    
    int main(){
        printf("%d",add(3,4));
        return 0;
    }
    
    1. 首先需要编译源文件生成bitcode:
    2. potency is defined as a measure of how difficult is to
    clang -emit-llvm -c test.c -o test.bc
    // or
    clang -emit-llvm -S test.c -o test.ll
    
    1. 在 Xcode 中切换到 opt 的 Scheme,添加配置:


      Scheme.png
    2. 执行opt

    最后附上近期经常使用的一些好用的命令:

        //汇编反汇编相互转换
        - llvm−as example.ll −o example.bc
        - llvm−dis example.bc −o example.ll
        - clang -emit-llvm -S test.c -o test.ll     c to ll
        - clang -o test.c test.bc                               c to bc
        - clang -emit-llvm -o foo.bc -c foo.c       bc to c
        - lli test.ll                                                       执行ll代码
        - llc -filetype=obj test.ll                         ir生成.o
        - gcc test.o                                                        .o生成.out
        - clang -fobjc-arc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.2.sdk -emit-llvm -S ocFile.m -o ocIR.ll    将OC类转ll
        - swiftc –dump-ast main.swift                           Swift Abstract Syntax Tree (AST)
        - swiftc –emit-sil main.swift -o main.sil       Swift Intermediate Language (SIL)
        - swiftc –emit-ir main.swift -o main.ll         LLVM Intermediate Representation (LLVM IR)
        - swiftc –emit-assembly main.swift                  Assembly Language
        - swiftc main.swift -o hello                                swift to mach-O
        - swiftc -emit-bc main.swift -o hello.bc        swift to bc
    

    工具链命令

    • llvm-as - 汇编器,将 .ll 汇编成字节码。
    • llvm-dis - 反汇编器,将字节码编成可读的 .ll 文件。
    • opt - 字节码优化器。
    • llc - 静态编译器,将字节码编译成汇编代码。
    • lli - 直接执行 LLVM 字节码。
    • llvm-link - 字节码链接器,可以把多个字节码文件链接成一个。
    • llvm-ar - 字节码文件打包器。
    • llvm-lib - LLVM lib.exe 兼容库工具。
    • llvm-nm - 列出字节码和符号表。
    • llvm-config - 打印 LLVM 编译选项。
    • llvm-diff - 对两个进行比较。
    • llvm-cov - 输出 coverage infomation。
    • llvm-profdata - Profile 数据工具。
    • llvm-stress - 生成随机 .ll 文件。
    • llvm-symbolizer - 地址对应源码位置,定位错误。
    • llvm-dwarfdump - 打印 DWARF。

    调试工具

    • bugpoint - 自动测试案例工具
    • llvm-extract - 从一个 LLVM 的模块里提取一个函数。
    • llvm-bcanalyzer - LLVM 字节码分析器。

    开发工具

    • FileCheck - 灵活的模式匹配文件验证器。
    • tblgen - C++ 代码生成器。
    • lit - LLVM 集成测试器。
    • llvm-build - LLVM 构建工程时需要的工具。
    • llvm-readobj - LLVM Object 结构查看器。

    LLVM 源码工程目录介绍

    • llvm_examples_ - 使用 LLVM IR 和 JIT 的例子。
    • llvm_include_ - 导出的头文件。
    • llvm_lib_ - 主要源文件都在这里。
    • llvm_project_ - 创建自己基于 LLVM 的项目的目录。
    • llvm_test_ - 基于 LLVM 的回归测试,健全检察。
    • llvm_suite_ - 正确性,性能和基准测试套件。
    • llvm_tools_ - 基于 lib 构建的可以执行文件,用户通过这些程序进行交互,-help 可以查看各个工具详细使用。
    • llvm_utils_ - LLVM 源代码的实用工具,比如,查找 LLC 和 LLI 生成代码差异工具, Vim 或 Emacs 的语法高亮工具等。

    lib 目录介绍

    • llvm_lib_IR/ - 核心类比如 Instruction 和 BasicBlock。
    • llvm_lib_AsmParser/ - 汇编语言解析器。
    • llvm_lib_Bitcode/ - 读取和写入字节码
    • llvm_lib_Analysis/ - 各种对程序的分析,比如 Call Graphs,Induction Variables,Natural Loop Identification 等等。
    • llvm_lib_Transforms/ - IR-to-IR 程序的变换。
    • llvm_lib_Target/ - 对像 X86 这样机器的描述。
    • llvm_lib_CodeGen/ - 主要是代码生成,指令选择器,指令调度和寄存器分配。
    • llvm_lib_ExecutionEngine/ - 在解释执行和JIT编译场景能够直接在运行时执行字节码的库。

    相关文章

      网友评论

          本文标题:编写调试Pass

          本文链接:https://www.haomeiwen.com/subject/spaluhtx.html