iOS逆向-day10:LLVM 编译器

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
      

五、应用与实践
  • 5.1、libclang、libTooling
    官方参考:https://clang.llvm.org/docs/Tooling.html
    应用:语法树分析、语言转换等

  • 5.2、 Clang插件开发
    官方参考
    https://clang.llvm.org/docs/ClangPlugins.html
    https://clang.llvm.org/docs/ExternalClangExamples.html
    https://clang.llvm.org/docs/RAVFrontendAction.html
    应用:代码检查(命名规范、代码规范)等

  • 5.3、Pass开发
    官方参考:https://llvm.org/docs/WritingAnLLVMPass.html
    应用:代码优化、代码混淆等

  • 5.4、开发新的编程语言
    https://llvm-tutorial-cn.readthedocs.io/en/latest/index.html
    https://kaleidoscope-llvm-tutorial-zh-cn.readthedocs.io/zh_CN/latest/

六、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 
      #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")) {
                            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 CreateASTConsumer(CompilerInstance &ci, StringRef iFile) {
                  return unique_ptr (new MJASTConsumer(ci));
              }
      
              bool ParseArgs(const CompilerInstance &ci, const vector &args) {
                  return true;
              }
          };
      }
      
      // 注册插件
      // 左边是插件的名称,右边是插件的描述,X 可以随便写,官方使用的是 X
      static FrontendPluginRegistry::Add
      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的资料

    • 资料一: https://clang.llvm.org/doxygen/namespaceclang.html
    • 资料二: https://clang.llvm.org/doxygen/classclang_1_1Decl.html
    • 资料三: https://clang.llvm.org/doxygen/classclang_1_1Stmt.html
最后推荐书籍
最后推荐书籍

你可能感兴趣的:(iOS逆向-day10:LLVM 编译器)