美文网首页
浅谈LLVM

浅谈LLVM

作者: 住册新账号 | 来源:发表于2021-06-24 20:57 被阅读0次

何为LLVM

在LLVM的官网(https://llvm.org/)中写到The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.翻译过来的意思是“LLVM项目是模块化、可重用的编译器以及工具链技术的集合。”
在理解LLVM时,我们可以认为它包括了一个广义的LLVM和一个狭义的LLVM。广义的LLVM就是指整个LLVM编译器架构,包括了前端、后端、优化器、众多的库函数以及很多的模块;而狭义的LLVM其实就是聚焦于编译器后端功能(代码生成、代码优化、JIT等)的一系列模块和库。

LLVM背景

目前常见的编译器有以下两种:

  • GCC
  • LLVM

LLVM的作者是Chris Lattner,此人既是LLVM之父也是Swift之父同时还是Clang的主要贡献者。在使用LLVM之前,Apple公司一直使用GCC作为编译器,但是Apple对Objective-C新增的特性,GCC并未配合给予实现,Apple自己开发的GCC模块又很难得到GCC委员会的合并,因此Apple在Chris Lattner毕业时,把他招入靡下开发自己的编译器架构,即LLVM。

Xcode版本 应用编译器
<Xcode3 GCC
Xcode3 GCC+LLVM
Xcode4.2 默认LLVM-Clang
>Xcode5 废弃GCC

传统的编译器架构和LLVM架构

传统编译器架构.jpg
LLVM架构.jpg
  • 经典的编译器如GCC在设计上都是提供一条龙服务的: 你不需要知道它使用的IR是什么样的,它也不会暴露中间接口来给你操作它的IR。 换句话说,从前端到后端,这些编译器的大量代码都是强耦合的。这样做的好处是,因为不需要暴露中间过程的接口,它可以在内部做任何想做的平台相关的优化。 而坏处是,假如有N种语言(C、OC、C++、Swift...)的前端,同时也有M个架构(模拟器、arm64、x86...)的target,就需要N*M个编译器。
  • LLVM架构不同的前端后端使用统一的中间代码LLVM IR, 那么每当新增加一种语言,就只要添加一个这个语言到IR的前端; 每当新增加一种目标平台,就只要添加一个IR到这个目标平台的后端。 如果有M种语言、N种目标平台,那么最优情况下只要实现 M+N 个前后端。优化阶段是一个通用的阶段,它针对的是统一的LLVM IR,不论是支持新的编程语言,还是支持新的硬件设备,都不需要对优化阶段做修改。

Clang和LLVM

LLVM-Clang.jpg
因为LLVM只是一个编译器框架,所以还需要一个前端来支撑整个系统,所以Apple又拨款拨人一起研发了Clang(http://clang.llvm.org/),作为整个编译器的前端,Clang是LLVM项目的一个子项目,用来编译C、C++和Objective-C。
相比于GCC,Clang具有如下优点:
  • 编译速度快:在某些平台上,Clang的编译速度显著的快过GCC(Debug模式下编译OC速度比GGC快3倍)
  • 占用内存小:Clang生成的AST所占用的内存是GCC的五分之一左右
  • 模块化设计:Clang采用基于库的模块化设计,易于 IDE 集成及其他用途的重用
  • 诊断信息可读性强:在编译过程中,Clang 创建并保留了大量详细的元数据 (metadata),有利于调试和错误报告
  • 设计清晰简单,容易理解,易于扩展增强


    Clang和LLVM.jpg

    通过上面两张图可以清晰的描述Clang和LLVM之间的关系。

OC源文件的编译过程

对于iOS开发者来说,整个编译流程可以简要概括为 Clang对代码进行处理形成中间层作为输出,llvm把CLang的输出作为输入生成机器码
Clang的执行过程包含以下几步:

  • 宏替换,头文件导入
  • 语法分析,代码切割为token
  • 组成AST(抽象语法树)
  • 生成中间码(IR)
    下面我们以一个CommandLine工程来看一下编译的过程:测试代码如下
#import <stdio.h>
#define height 5

int main(int argc, const char * argv[]) {
    
    int a = 2;
    int b = 3;
    int c = a + b + height;
    
    return 0;
}
  • cd到测试工程目录下,命令行查看编译的过程:clang -ccc-print-phases main.m
    clang -ccc-print-phases main.jpg
  • 查看preprocessor(预处理)的结果:clang -E main.m
    可以看到,括号,符号,关键字等等都被切割成token。
    宏替换.jpg

词法分析

  • 词法分析,生成Token:clang -fmodules -E -Xclang -dump-tokens main.m
    词法分析.jpg
    新增测试代码:
void test(int a, int b) {
    int c = a + b - 3;
}

语法分析

  • 语法分析,生成语法树-AST: clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
    语法分析.jpg
    如果上面的图表现得还不够清晰,那下面这张图就非常明显的表达了语法树。
    语法树AST.jpg

LLVM IR

LLVM IR有三种表示,一种是便于阅读的文本格式,类似于汇编代码,但其实它介于高等语言和汇编之间,这种表示就是给人看的,磁盘文件后缀为.ll;第二种是不可读的二进制IR,被称作位码(bitcode),磁盘文件后缀为.bc;第三种表示是一种内存格式,只保存在内存中,所以谈不上文件格式和文件后缀,这种格式是LLVM之所以编译快的一个原因,它不像gcc,每个阶段结束会生成一些中间过程文件,它编译的中间数据都是这第三种表示的IR。三种格式是完全等价的,我们可以在Clang/LLVM工具的参数中指定生成这些文件,可以通过llvm-as和llvm-dis来在前两种文件之间做转换。
clang -c -emit-llvm main.m 编译产生字节码
clang -S -emit-llvm main.m 编译产生可视化字节码
llvm-dis main.bc main.ll bc字节码转为可视化字节码ll
llvm-as main.ll main.bc 可视化字节码转为字节码bc

源码下载

git clone https://git.llvm.org/git/llvm.git/
下载clang
cd llvm/tools
git clone https://git.llvm.org/git/clang.git/

源码编译

安装cmake和ninja(使用ninja编译LLVM比较快,大约10几分钟,否则需要一个多小时。先安装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】目录大概 20多G
ninja install
安装完毕后,安装目录大概 10多G

应用与实践

安装完毕,下面就可以开发一个Clang插件来练练手啦。

  • 在【clang/tools】源码目录下新建一个插件目录,例如我的叫【WJBPlugin】


    创建插件目录.jpg
  • 在【clang/tools/CMakeLists.txt】仿照最后一行加入内容: add_clang_subdirectory(WJBPlugin),小括号里是插件目录名


    CMakeLists添加插件目录名.jpg
  • 在【WJBPlugin】目录下touch【WJBPlugin.cpp】,再新建一个【CMakeLists.txt】,文件内容是:add_llvm_loadable_module(WJBPlugin WJBPlugin.cpp)
    WJBPlugin是插件名,WJBPlugin.cpp是源代码文件


    CMakeLists.jpg
    WJBPlugin.jpg
  • 在llvm同级目录下新建一个【llvm_xcode】目录
    cd llvm_xcode
    cmake -G Xcode ../llvm
    会生成LLVM的Xcode项目,打开项目,在【Loadable modules】目录下可以找到我们的插件【WJBPlugin】,这样就可以直接在Xcode里编写插件代码以及编译插件了。
    将下面这段c++代码拷入WJBPlugin.cpp
#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 WJBPlugin {
    class WJBHandler : public MatchFinder::MatchCallback {
    private:
        CompilerInstance &ci;
        
    public:
        WJBHandler(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, "报错:类名中不能带有下划线"));
                }
            }
        }
    };
    
    class WJBASTConsumer: public ASTConsumer {
    private:
        MatchFinder matcher;
        WJBHandler handler;
        
    public:
        WJBASTConsumer(CompilerInstance &ci) :handler(ci) {
            matcher.addMatcher(objcInterfaceDecl().bind("ObjCInterfaceDecl"), &handler);
        }
        
        void HandleTranslationUnit(ASTContext &context) {
            matcher.matchAST(context);
        }
    };

    class WJBASTAction: public PluginASTAction {
    public:
        unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &ci, StringRef iFile) {
            return unique_ptr<WJBASTConsumer> (new WJBASTConsumer(ci));
        }

        bool ParseArgs(const CompilerInstance &ci, const vector<string> &args) {
            return true;
        }
    };
}

static FrontendPluginRegistry::Add<WJBPlugin::WJBASTAction>
X("WJBPlugin", "The WJBPlugin is my first clang-plugin.");

选择WJBPlugin这个target进行编译,编译完会在Products生成一个动态库文件【WJBPlugin.dylib】

测试插件

  • Xcode新建一个测试工程,在项目中指定加载插件动态库:Build Settings > Other C Flags。

    -Xclang -load -Xclang 动态库路径 -Xclang -add-plugin -Xclang 插件名称 加载插件.jpg
  • 首先 hack Xcode,才能使 Xcode 指向我们自己编译的 Clang:

    下载XcodeHacking.zip并解压,里面有 HackedBuildSystem.xcspec 和 HackedClang.xcplugin 两个文件,这里可能需要修改一下 HackedClang.xcplugin/Contents/Resources/HackedClang.xcspec 文件,将 ExecPath 的值修改为你编译出来的 Clang 的目录: hackXcode.jpg
  • 然后 cd 到解压的 XcodeHacking 目录,将这两个文件用命令行移动到对应的目录下:
    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,点击 Target 的 Build Settings搜索c++,修改 Compiler for C/C++/Objective-C 项为 Clang LLVM Trunk(不进行 hack Xcode 操作的话是不会有这个选项的)


    compiler.jpg
  • 先clean一下,然后编译项目,就会在编译日志看到WJBPlugin插件的警告信息了,大功告成。

总结

文章不长,这看似简单的过程也花费了我很多的时间,主要是LLVM过于强大,以至于我不得不在标题前加上了“浅谈”二字,本文属实只是介绍了些皮毛和入门知识,更深层的汪洋大海还需慢慢探索。
参考文献:
LLVM框架/LLVM编译流程/Clang前端/LLVM IR/LLVM应用与实践
LLVM基本概念入门
初探 Clang
LLVM编译原理和使用

相关文章

  • 浅谈LLVM

    何为LLVM 在LLVM的官网(https://llvm.org/[https://llvm.org/])中写到T...

  • LLVM 和 Clang 浅谈

    LLVM 是什么?对标GCC在编译时、链接时、运行时甚至是在闲置时优化程序的编译器 Clang 是什么?LLVM自...

  • 浅谈LLVM编译器

    GCC Xcode 最早的编译器(Xcode 3.0及之前版本)不太智能,因为当时Xcode 当前的编译器是GCC...

  • iOS LLVM-Clang 浅谈

    LLVM概念 LLVM官网: https://llvm.org/ 编译器架构图:image Frontend:前端...

  • LLVM

    LLVM 什么是LLVM? 官网:https://llvm.org/ The LLVM Project is a ...

  • iOS_LLVM

    LLVM 官网:https://llvm.org/[https://llvm.org/] The LLVM Pro...

  • iOS 逆向 day 18 GCC LLVM Clang

    一、LLVM 1. 什么是 LLVM 官网:https://llvm.org/ The LLVM Project ...

  • iOS逆向-day10:LLVM 编译器

    一、LLVM的简单介绍 1.1、什么是LLVM官网:https://llvm.org/LLVM官网解释:The L...

  • 初识LLVM&Clang-开发Xcode插件

    初识LLVM&Clang-开发Xcode插件 LLVM Xcode现在使用的编译器就是LLVM。LLVM比以前使用...

  • LLVM 初始 ①

    什么是LLVM 官网:https://llvm.org/ The LLVM Project is a collec...

网友评论

      本文标题:浅谈LLVM

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