iOS逆向-day10:LLVM 编译器

作者: IIronMan | 来源:发表于2020-07-13 20:41 被阅读0次
    LLVM
    一、LLVM的简单介绍
    • 1.1、什么是LLVM
      官网:https://llvm.org/

      • LLVM官网解释:The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
      • 翻译为中文:LLVM 项目是模块化、可重用的编译器以及工具链技术的集合
      • 美国计算机协会 (ACM) 将其2012 年软件系统奖项颁给了LLVM,之前曾经获得此奖项的软件和技术包括:JavaApache
        Mosaicthe World Wide WebSmalltalkUNIXEclipse等等
    • 1.2、LLVM 的创始人
      Chris Lattner,亦是Swift之父

      LLVM 的创始人
    • 1.3、LLVM命名的由来,有些文章把LLVM当做Low Level Virtual Machine(低级虚拟机)的缩写简称,官方描述如下

      • The name LLVMitself is not an acronym; it is the full name of the project.
      • LLVM 这个名称本身不是首字母缩略词; 它是项目的全名
    二、编译器架构
    • 2.1、传统的编译器架构


      • Frontend:前端
        词法分析、语法分析、语义分析、生成中间代码
      • Optimizer:优化器
        中间代码优化
      • Backend:后端
        生成机器码
    • 2.2、LLVM架构

      LLVM架构
      • 不同的前端后端使用统一的中间代码LLVM Intermediate Representation (LLVM IR)
      • 如果需要支持一种新的编程语言,那么只需要实现一个新的前端
      • 如果需要支持一种新的硬件设备,那么只需要实现一个新的后端
      • 优化阶段是一个通用的阶段,它针对的是统一的LLVM IR,不论是支持新的编程语言,还是支持新的硬件设备,都不需要对优化阶段做修改
      • 相比之下,GCC的前端和后端没分得太开,前端后端耦合在了一起。所以GCC为了支持一门新的语言,或者为了支持一个新的目标平台,就
        变得特别困难
      • LLVM 现在被作为实现各种静态和运行时编译语言的通用基础结构(GCC家族、Java、.NET、Python、Ruby、Scheme、Haskell、D等)
    • 2.3、Clang 编译器

      • 什么是Clang?
        • LLVM项目的一个子项目
        • 基于LLVM架构的C/C++/Objective-C编译器前端
        • 官网:http://clang.llvm.org/
      • 相比于GCC,Clang 具有如下优点
        • 编译速度快:在某些平台上,Clang的编译速度显著的快过GCC(Debug模式下编译OC速度比GGC快3倍)
        • 占用内存小:Clang生成的AST所占用的内存是GCC的五分之一左右
        • 模块化设计:Clang采用基于库的模块化设计,易于 IDE 集成及其他用途的重用
        • 诊断信息可读性强:在编译过程中,Clang 创建并保留了大量详细的元数据 (metadata),有利于调试和错误报告
        • 设计清晰简单,容易理解,易于扩展增强
    • 2.4、Clang 与 LLVM

      • 广义的LLVM:整个LLVM架构
      • 狭义的LLVM:LLVM后端(代码优化、目标代码生成等)
    三、OC源文件的编译过程

    创建一个命令行项目

    • 3.1、命令行查看编译的过程:clang -ccc-print-phases main.m

    • 3.2、查看 preprocessor(预处理)的结果:clang -E main.m

      查看 preprocessor(预处理)的结果
    • 3.3、词法分析
      词法分析,生成Token: clang -fmodules -E -Xclang -dump-tokens main.m

      词法分析
    • 3.4、语法树-AST
      语法分析,生成语法树(AST,Abstract Syntax Tree): clang -fmodules -fsyntax-only -Xclang -ast-dump main.m

      语法树-AST
      语法树-AST
    • 3.5、LLVM IR

      • LLVM IR有3种表示形式(但本质是等价的,就好比水可以有气体、液体、固体3种形态)

        • text:便于阅读的文本格式,类似于汇编语言,拓展名 .ll, $ clang -S -emit-llvm main.m
        • memory:内存格式
        • bitcode:二进制格式,拓展名 .bcclang -c -emit-llvm main.m
        • IR基本语法
          • 注释以分号 ; 开头
          • 全局标识符以@开头,局部标识符以%开头
          • alloca,在当前函数栈帧中分配内存
          • i32,32bit,4个字节的意思
          • align,内存对齐
          • store,写入数据
          • load,读取数据
      • 官方语法参考: https://llvm.org/docs/LangRef.html

    四、LLVM 源码下载
    • 4.1、下载LLVM 和 下载clang

      • 创建一个文件夹 LLVM_ALL ,其他的名字也可以

      • 先下载 LLVM 到 LLVM_ALL 文件夹下 ,大小 648.2 M,仅供参考

        git clone https://github.com/llvm-mirror/llvm.git
        
      • 再下载clang,大小 240.6 M,仅供参考,放到 /LLVM_ALL /llvm/tools 目录下

        //  clang 放到  /LLVM_ALL /llvm/tools 目录下
        cd llvm/tools
        // 下载 clang
        git clone https://github.com/llvm-mirror/clang.git
        
    • 4.2、源码编译

      • 安装 cmakeninja(先安装brew,https://brew.sh/

        brew install cmake
        brew install ninja
        

        提示:如果 ninja 如果安装失败,可以直接从github获取release版放入【/usr/local/bin】中

        下载ninja
      • llvm 源码同级目录下新建一个【llvm_build】目录(最终会在【llvm_build】目录下生成【build.ninja】)

        llvm_build
        • cd llvm_build
        • cmake -G Ninja ../llvm -DCMAKE_INSTALL_PREFIX=llvm的安装路径

        提示:更多cmake相关选项,可以参考: https://llvm.org/docs/CMake.html

      • 依次执行编译、安装指令

        • 编译完毕后, 【llvm_build】目录大概 21.05 G(仅供参考)

          ninja
          
        • 安装完毕后,安装目录大概 11.92 G(仅供参考)

          ninja install
          
    • 4.3、如果不想按照4.2的编译方式,也可以生成Xcode项目再进行编译,但是速度很慢(可能需要1个多小时)

      • 在 llvm 同级目录下新建一个【llvm_xcode】目录

        llvm_xcode
        cd llvm_xcode
        cmake -G Xcode ../llvm
        

    五、应用与实践
    六、clang插件开发
    • 6.1、clang插件开发1 – 插件目录

      • clang/tools 源码目录下新建一个插件目录,假设叫做mj-plugin

        在 clang/tools 源码目录下新建一个插件目录,假设叫做 mj-plugin
      • clang/tools/CMakeLists.txt 最后加入内容: add_clang_subdirectory(mj-plugin),小括号里是 插件目录名

    • 6.2、插件必要文件


      插件必要文件目录
      • mj-plugin 下创建 MJPlugin.cpp 文件,插件使用 C++ 编写

        touch MJPlugin.cpp
        
      • mj-plugin 里面也要有一份 CMakeLists.txt 文件,里面写清插件需要加载哪些 C++ 代码,文件内容如下

        add_llvm_loadable_module(MJPlugin MJPlugin.cpp)
        

        提示:如果多个 C++ 文件,可以如下


        如果多个 C++ 文件目录
    • 6.3、clang插件开发3 – 编写插件源码


      • 在 llvm 同级目录下新建一个【llvm_xcode】目录

        cd llvm_xcode
        cmake -G Xcode ../llvm
        

        image
      • 找到我们的插件文件进行开发,如下

        找到我们的插件文件进行开发
      • 具体的源码

        #include <iostream>
        #include "clang/AST/AST.h"
        #include "clang/AST/ASTConsumer.h"
        #include "clang/ASTMatchers/ASTMatchers.h"
        #include "clang/ASTMatchers/ASTMatchFinder.h"
        #include "clang/Frontend/CompilerInstance.h"
        #include "clang/Frontend/FrontendPluginRegistry.h"
        
        using namespace clang;
        using namespace std;
        using namespace llvm;
        using namespace clang::ast_matchers;
        
        namespace MJPlugin {
            class MJHandler : public MatchFinder::MatchCallback {
                private:
                CompilerInstance &ci;
        
               public:
               MJHandler(CompilerInstance &ci) :ci(ci) {}
        
                    void run(const MatchFinder::MatchResult &Result) {
                         if (const ObjCInterfaceDecl *decl = Result.Nodes.getNodeAs<ObjCInterfaceDecl>("ObjCInterfaceDecl")) {
                              size_t pos = decl->getName().find('_');
                              if (pos != StringRef::npos) {
                                  DiagnosticsEngine &D = ci.getDiagnostics();
                                  SourceLocation loc = decl->getLocation().getLocWithOffset(pos);
                                  D.Report(loc, D.getCustomDiagID(DiagnosticsEngine::Error, "M了个J:类名中不能带有下划线"));
                              }
                         }
                    }
               };
        
               class MJASTConsumer: public ASTConsumer {
                   private:
                   MatchFinder matcher;
                   MJHandler handler;
        
                   public:
                   MJASTConsumer(CompilerInstance &ci) :handler(ci) {
                       matcher.addMatcher(objcInterfaceDecl().bind("ObjCInterfaceDecl"), &handler);
                   }
        
                   void HandleTranslationUnit(ASTContext &context) {
                      matcher.matchAST(context);
                   }
            };
        
            class MJASTAction: public PluginASTAction {
            public:
                unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &ci, StringRef iFile) {
                    return unique_ptr<MJASTConsumer> (new MJASTConsumer(ci));
                }
        
                bool ParseArgs(const CompilerInstance &ci, const vector<string> &args) {
                    return true;
                }
            };
        }
        
        // 注册插件
        // 左边是插件的名称,右边是插件的描述,X 可以随便写,官方使用的是 X
        static FrontendPluginRegistry::Add<MJPlugin::MJAction>
        X("MJPlugin", "The MJPlugin is my first clang-plugin.");
        
    • 6.4、clang插件开发4 – 编译插件

      • 利用cmake生成的Xcode项目来编译插件(第一次编写完插件,需要利用cmake重新生成一下Xcode项目)
      • 插件源代码在【Sources/Loadable modules】目录下可以找到,这样就可以直接在Xcode里编写插件代码
      • 选择MJPlugin这个target进行编译,编译完会生成一个动态库文件
      编译插件
    • 6.5、clang插件开发5 – 加载插件

      • 在Xcode项目中指定加载插件动态库:Build Settings > OTHER_CFLAGS
      • -Xclang -load -Xclang 动态库路径 -Xclang -add-plugin -Xclang 插件名称
      加载插件
    • 6.6、clang插件开发6 – Hack Xcode (Xcode 破解 由于制作Xcode插件)

      • 首先要对Xcode进行Hack,才能修改默认的编译器

      • 下载【XcodeHacking.zip】,解压,修改HackedClang.xcplugin/Contents/Resources/HackedClang.xcspec】的内容,设
        置一下自己编译好的clang的路径

        设置一下自己编译好的clang的路径
      • 然后在XcodeHacking目录下进行命令行,将XcodeHacking的内容剪切到Xcode内部

        // 命令一
        sudo mv HackedClang.xcplugin `xcode-select-print�path`/../PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins
        // 命令二
        sudo mv HackedBuildSystem.xcspec `xcode-select-print�path`/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Specifications
        
    • 6.7、clang插件开发7 – 修改Xcode的编译器,修改位置如下,使用我们自己编译的 clang

    • 6.8、clang插件开发8 – 编译项目
      编译项目后,会在编译日志看到MJPlugin插件的打印信息(如果插件更新了,最好先Clean一下项目)

    • 6.9、clang插件开发9 – 更多
      想要实现更复杂的插件功能,就需要利用clang的API针对语法树(AST)进行相应的分析和处理
      关于AST的资料

    最后推荐书籍
    最后推荐书籍

    相关文章

      网友评论

        本文标题:iOS逆向-day10:LLVM 编译器

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