首页 13-LLVM
文章
取消

13-LLVM

什么是LLVM

  • 官网 LLVM
  • LLVM项目是模块化、可重用的编译器以及工具链技术的集合。
  • LLVM最早的时候是Illinois的一个研究项目,主要负责人是Chris Lattner(Swift之父),他现在就职于Apple. Apple 目前也是LLVM项目的主要赞助者之一。

传统编译器架构

1
source code -> Frontend -> Optimizer -> backend -> Machine code
  • Frontend: 前端
    • 词法分析、语法分析、语义分析、生成中间代码
  • Optimizer: 优化器
    • 中间代码优化
  • Backend: 后端
    • 生成机器码

LLVM架构

llvm01

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

Clang

  • 什么是Clang

    • LLVM项目的一个子项目
    • 基于LLVM架构的CC++Objective-C编译器前端
    • 官网
  • Clang优点

    • 编译速度快:在某些平台上,Clang的编译速度显著的快过GCC(Debug模式下编译OC速度比GGC快3倍)
    • 占用内存小: Clang生成的AST所占用的内存是GCC的五分之一左右
    • 模块化设计: Clang采用基于库的模块化设计,易于 IDE 集成及其他用途的重用
    • 诊断信息可读性强:在编译过程中,Clang 创建并保留了大量详细的元数据 (metadata),有利于调试和错误报告
    • 设计清晰简单,容易理解,易于扩展增强
  • ClangLLVM

    llvm02

文件编译

  • OC源文件的编译过程

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

      llvm03

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

  • 词法分析

    • 词法分析,生成Token: $ clang -fmodules -E -Xclang -dump-tokens main.m

      llvm04

  • 语法树-AST

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

      llvm05

  • LLVM IR

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

      • text:便于阅读的文本格式,类似于汇编语言,拓展名.ll, $ clang -S -emit-llvm main.m
      • memory: 内存格式
      • bitcode: 二进制格式,拓展名.bc$ clang -c -emit-llvm main.m
    • IR基本语法

      • 注释以分号 ; 开头
      • 全局标识符以@开头,局部标识符以%开头
      • alloca,在当前函数栈帧中分配内存
      • i3232bit,4个字节的意思
      • align,内存对齐
      • store,写入数据
      • load,读取数据
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      
      define void @test(i32, i32) #0 {
      	%3 = alloca i32, align 4
      	%4 = alloca i32, align 4
      	%5 = alloca i32, align 4
      	store i32 %0, i32* %3, align 4
      	store i32 %1, i32* %4, align 4
      	%6 = load i32, i32* %3, align 4
      	%7 = load i32, i32* %4, align 4
      	%8 = add nsw i32 %6, %7
      	%9 = sub nsw i32 %8, 3
      	store i32 %9, i32* %5, align 4
      	ret void
      }
      
    • 官方语法参考

  • 源码下载

    • 下载LLVM
    • $ git clone https://git.llvm.org/git/llvm.git/
    • 大小 648.2 M,仅供参考
    • 下载clang
      • $ cd llvm/tools
      • $ git clone https://git.llvm.org/git/clang.git/
      • 大小240.6M,仅供参考
  • 源码编译

    • 安装cmakeninja(先安装brew,https://brew.sh/)
      • $ brew install cmake
      • $ brew install ninja
    • ninja如果安装失败,可以直接从github获取release版放入/usr/local/bin
      • https://github.com/ninja-build/ninja/releases
    • 在LLVM源码同级目录下新建一个llvm_build目录(最终会在llvm_build目录下生成build.ninja)
      • $ cd llvm_build
      • $ cmake -G Ninja ../llvm -DCMAKE_INSTALL_PREFIX=LLVM的安装路径
      • 更多cmake相关选项,可以参考: https://llvm.org/docs/CMake.html
    • 依次执行编译、安装指令
      • $ ninja
      • 编译完毕后, llvm_build目录大概 21.05 G(仅供参考)
      • $ ninja install
      • 安装完毕后,安装目录大概 11.92 G(仅供参考)
  • 源码编译

    • 也可以生成Xcode项目再进行编译,但是速度很慢(可能需要1个多小时)

      llvm06

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

      • $ cd llvm_xcode
      • $ cmake -G Xcode ../llvm

      llvm07

  • 应用与实践

    • libclanglibTooling
    • Clang插件开发
      • 官方参考
        • https://clang.llvm.org/docs/ClangPlugins.html
        • https://clang.llvm.org/docs/ExternalClangExamples.html
        • https://clang.llvm.org/docs/RAVFrontendAction.html
      • 应用:代码检查(命名规范、代码规范)等
    • Pass开发
    • 官方参考
    • 应用:代码优化、代码混淆等
    • 开发新的编程语言
      • https://llvm-tutorial-cn.readthedocs.io/en/latest/index.html
      • https://kaleidoscope-llvm-tutorial-zh-cn.readthedocs.io/zh_CN/latest/
  • clang插件开发

    • 插件目录

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

        • ../llvm/clang/tools/tb-plugin
      • clang/tools/CMakeLists.txt最后加入内容: add_clang_subdirectory(mj-plugin),小括号里是插件目录名

        llvm08

    • 插件必要文件

      • jv-plugin目录下新建一个CMakeLists.txt,文件内容是: add_llvm_loadable_module(JVPlugin JVPlugin.cpp)
      • TBPlugin是插件名,TBPlugin.cpp是源代码文件
      1
      
      add_llvm_loadable_module(JVPlugin JVPlugin.cpp)
      
    • 编写插件源码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      
      #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 JVPlugin {
          class Handler : public MatchFinder::MatchCallback {
          private:
              CompilerInstance &ci;
                  
          public:
              JVHandler(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, "Jovins:类名中不能带有下划线"));
                      }
                  }
              }
          };
              
          class JVASTConsumer: public ASTConsumer {
          private:
              MatchFinder matcher;
              JVHandler handler;
                  
          public:
              JVASTConsumer(CompilerInstance &ci) :handler(ci) {
                  matcher.addMatcher(objcInterfaceDecl().bind("ObjCInterfaceDecl"), &handler);
              }
                  
              void HandleTranslationUnit(ASTContext &context) {
                  matcher.matchAST(context);
              }
          };
          
          class JVASTAction: public PluginASTAction {
          public:
              unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &ci, StringRef iFile) {
                  return unique_ptr<JVASTConsumer> (new JVASTConsumer(ci));
              }
          
              bool ParseArgs(const CompilerInstance &ci, const vector<string> &args) {
                  return true;
              }
          };
      }
          
      static FrontendPluginRegistry::Add<JVPlugin::JVASTAction>
      X("JVPlugin", "The JVPlugin is my first clang-plugin.");
      
    • 编译插件

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

      生成的动态库可以在项目Products -> show in Finder找到 JVPlugin.dylib

    • 加载插件

      • 在Xcode项目中指定加载插件动态库: BuildSettings > OTHER_CFLAGS
      • -Xclang -load -Xclang: 动态库路径
      • -Xclang -add-plugin -Xclang: 插件名称
    • Hack Xcode

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

      • 下载XcodeHacking.zip,解压,修改HackedClang.xcplugin/Contents/Resources/HackedClang.xcspec的内容,设

        置一下自己编译好的clang的路径

      llvm09

      • 然后在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

    • 修改Xcode的编译器

      llvm10

    • 编译项目

      • 编译项目后,会在编译日志看到JVPlugin插件的打印信息(如果插件更新了,最好先Clean一下项目)

      llvm11

    • 更多

      • 想要实现更复杂的插件功能,就需要利用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
本文由作者按照 CC BY 4.0 进行授权

12-iOS签名机制

14-代码混淆