美文网首页LLVM
转载:CLANG技术分享系列一:编写你的第一个CLANG插件

转载:CLANG技术分享系列一:编写你的第一个CLANG插件

作者: 三三At你 | 来源:发表于2019-10-07 17:04 被阅读0次

    原文链接

    引子

       以前遇到一个需求,检测iOS App代码中使用到的API是否存在开始支持的系统版本高于当前deploy target,或已经在当前支持的最新系统之前(包括当前系统)已经被标记为弃用(deprecated),以免发生Crash或者代码不能被调到的情况。
       这个问题可以看成时两个子问题:
       1.获取到所有的API的调用
       2.拿到所有的Documented公共API的(起始版本,废弃版本)记录.
       因OC:-[UIViewController viewDidLoad]的处理逻辑和C:CFNotificationCenterRemoveObserver或者C++ API等类似,而iOS开发中使用的主要是OC,此处只重点处理第一种情况(实际上也支持C函数调用)。
       为了获取到所有API的调用,就需要分析源代码(二进制相关记录中是基于selector的,而我们需要的是-/+[cls msg]).这就不得的不提到大名鼎鼎的llvm和clang了。
       Clang系列将推出多篇文章,基于Xcode和Clang,实现诸如代码风格规范,有效API检查,无用代码筛查等特性,欢迎关注和讨论。
    
    

    LLVM & CLANG

    ​ LLVM工程包含了一组模块化,可复用的编译器和工具链。和其名字的原意(Low level virtual machine)不同的是,LLVM不是一个首字母缩写,而是工程的名字。

    ​ 目前LLVM的主要子项目包括:

    1.LLVM Core:包含一个现在的源代码/目标设备无关的优化器,一集一个针对很多主流(甚至于一些非主流)的CPU的汇编代码生成支持。
    2.Clang:一个C/C++/Objective-C编译器,致力于提供令人惊讶的快速编译,极其有用的错误和警告信息,提供一个可用于构建很棒的源代码级别的工具.
    3.dragonegg: gcc插件,可将GCC的优化和代码生成器替换为LLVM的相应工具。
    4.LLDB:基于LLVM提供的库和Clang构建的优秀的本地调试器。
    5.libc++、libc++ ABI: 符合标准的,高性能的C++标准库实现,以及对C++11的完整支持。
    6.compiler-rt:针对"__fixunsdfdi"和其他目标机器上没有一个核心IR(intermediate representation)对应的短原生指令序列时,提供高度调优过的底层代码生成支持。
    7.OpenMP: Clang中对多平台并行编程的runtime支持。
    8.vmkit:基于LLVM的Java和.NET虚拟机实现
    9.polly: 支持高级别的循环和数据本地化优化支持的LLVM框架。
    10.libclc: OpenCL标准库的实现
    11.klee: 基于LLVM编译基础设施的符号化虚拟机
    12.SAFECode:内存安全的C/C++编译器
    13.lld: clang/llvm内置的链接器
    
    

    点击了解更多

    点击下载相关源代码

    CLANG插件支持

    ​ Clang作为LLVM提供的编译器前端,将用户的源代码(C/C++/Objective-C)编译成语言/目标设备无关的IR实现。并且提供良好的插件支持,容许用户在编译时,运行额外的自定义动作。

    要实现自定义的Clang插件(以C++为例),应按照以下步骤:
    1.自定义继承自
            clang::PluginASTAction(基于consumer的AST前端Action抽象基类)
            clang::ASTConsumer(用于客户读取AST的抽象基类),
            clang::RecursiveASTVisitor(前序或后续地深度优先搜索整个          AST,并访问每一个节点的基类)等基类,
    2.根据自身需要重载
            PluginASTAction::CreateASTConsumer,
            PluginASTAction::ParseArgs,
            ASTConsumer::HandleTranslationUnit
            RecursiveASTVisitor::VisitDecl
            RecursiveASTVisitor::VisitStmt
       等方法,实现自定义的分析逻辑。
    3.注册插件
        static FrontendPluginRegistry::Add<MyPlugin> X("my-plugin-        name", "my-plugin-description");
    4.编译时载入插件
        clang++ *** -Xclang -load -Xclang path-of-your-     
        plugin.dylib -Xclang -add-plugin -Xclang your-pluginName 
        -Xclang -plugin-arg-your-pluginName -Xclang your-
        pluginName-param
    
    

    注:AST->Abstract Syntax Tree抽象语法树 更多clang插件

    例子(分析某.M文件中用户定义的OC类)

    环境搭建

    1.下载clang相关源代码并编译

    cd /opt
    sudo mkdir llvm
    sudo chown `whoami` llvm
    cd llvm
    export LLVM_HOME=`pwd`
    
    git clone -b release_39 git@github.com:llvm-mirror/llvm.git llvm
    git clone -b release_39 git@github.com:llvm-mirror/clang.git llvm/tools/clang
    git clone -b release_39 git@github.com:llvm-mirror/clang-tools-extra.git llvm/tools/clang/tools/extra
    git clone -b release_39 git@github.com:llvm-mirror/compiler-rt.git llvm/projects/compiler-rt
    
    mkdir llvm_build
    cd llvm_build
    cmake ../llvm -DCMAKE_BUILD_TYPE:STRING=Release -DLLVM_ENABLE_RTTI:BOOL=ON
    make -j`sysctl -n hw.logicalcpu`
    
    

    PS.文件很多,也很大,如果clone缓慢,请先设置代理

    2.编写clang插件

    //printClsPlugin.cpp
    #include <iostream>
    #include "clang/Frontend/FrontendPluginRegistry.h"
    #include "clang/AST/AST.h"
    #include "clang/AST/ASTConsumer.h"
    #include "clang/Frontend/CompilerInstance.h"
    #include "clang/AST/RecursiveASTVisitor.h"
    using namespace clang;
    using namespace std;
    using namespace llvm;
    namespace ClangPlugin
    {
    class ClangPluginASTVisitor : public    
    RecursiveASTVisitor<ClangPluginASTVisitor>
    {
    private:
        ASTContext *context;
    public:
        void setContext(ASTContext &context){
            this->context = &context;
        }
        bool VisitDecl(Decl *decl){
            if(isa<ObjCImplDecl>(decl)){
                ObjCImplDecl *interDecl = (ObjCImplDecl*)decl;
                cout<<"[KWLM]Class Implementation Found:"
                <<interDecl->getNameAsString()<<endl;
                }
            return true;
        }
    };
    class ClangPluginASTConsumer : public ASTConsumer
    {
    private:
        ClangPluginASTVisitor visitor;
        void HandleTranslationUnit(ASTContext &context){
            visitor.setContext(context);
            visitor.TraverseDecl(context.getTranslationUnitDecl());
        }
    };
    class ClangPluginASTAction : public PluginASTAction
    {
    public:
        unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &Compiler,StringRef InFile){
            return unique_ptr<ClangPluginASTConsumer>(new ClangPluginASTConsumer);
        }
        bool ParseArgs(const CompilerInstance &CI, const std::vector<std::string>& args){
        return true;
        }
    };
    }
    static clang::FrontendPluginRegistry::Add
    <ClangPlugin::ClangPluginASTAction>X("ClangPlugin",     
    "ClangPlugin");
    
    

    3.编译生成插件(dylib)

    clang -std=c++11 -stdlib=libc++ -L/opt/local/lib -
    L/opt/llvm/llvm_build/lib  
    -I/opt/llvm/llvm_build/tools/clang/include -
    I/opt/llvm/llvm_build/include -
    I/opt/llvm/llvm/tools/clang/include -I/opt/llvm/llvm/include -
    dynamiclib -Wl,-headerpad_max_install_names -lclang -
    lclangFrontend -lclangAST -lclangAnalysis -lclangBasic -
    lclangCodeGen -lclangDriver -lclangFrontendTool -lclangLex -
    lclangParse -lclangSema -lclangEdit -lclangSerialization -
    lclangStaticAnalyzerCheckers -lclangStaticAnalyzerCore -
    lclangStaticAnalyzerFrontend -lLLVMX86CodeGen -
    lLLVMX86AsmParser -lLLVMX86Disassembler -lLLVMExecutionEngine 
    -lLLVMAsmPrinter -lLLVMSelectionDAG -lLLVMX86AsmPrinter -
    lLLVMX86Info -lLLVMMCParser -lLLVMCodeGen -lLLVMX86Utils -
    lLLVMScalarOpts -lLLVMInstCombine -lLLVMTransformUtils -
    lLLVMAnalysis -lLLVMTarget -lLLVMCore -lLLVMMC -lLLVMSupport -
    lLLVMBitReader -lLLVMOption -lLLVMProfileData -lpthread -
    lcurses -lz -lstdc++ -fPIC -fno-common -Woverloaded-virtual -
    Wcast-qual -fno-strict-aliasing -pedantic -Wno-long-long -Wall 
    -Wno-unused-parameter -Wwrite-strings -fno-rtti -fPIC   
    ./printClsPlugin.cpp -o ClangPlugin.dylib
    
    

    4.编写测试用oc文件 ​

    //ocClsDemo.m
    #import<UIKit/UIKit.h>
    @interface MyViewController : UIViewController
    @end
    
    @implementation MyViewController
    - (instancetype)init{
        if(self = [super init]){
        }
        return self;
    }
    @end
    
    

    5.使用1生成的clang编译oc文件(载入3生成的ClangPlugin.dylib)

    /opt/llvm/llvm_build/bin/clang -isysroot    /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator10.0.sdk -I/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1 
    -mios-version-min=8.0 -Xclang -load -Xclang         
    ~/Desktop/ClangPlugin.dylib -Xclang -add-plugin -Xclang 
    ClangPlugin -c ./ocClsDemo.m
    
    
    结果如下图所示: clang插件运行结果

    Xcode集成Clang插件

    要在Xcode中使用Clang插件,需要Hack Xcode.app。
    
    

    下载XcodeHacking.zip

    执行:
    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->Build Options->Compiler for C/C++/Objective-C选择Clang LLVM Trunk即可使得Xcode使用1生成的的Clang来编译。至于其他的命令行参数,均可以通过Xcode设置完成。 [Contact me](mailto:kang.wang1988@gmail.com)
    
    

    相关文章

      网友评论

        本文标题:转载:CLANG技术分享系列一:编写你的第一个CLANG插件

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