iOS llvm-2

本文主要是理解LLVM的編譯流程以及clang插件的開(kāi)發(fā)

LLVM

  • LLVM是架構(gòu)編譯器的框架系統(tǒng),以C++編寫(xiě)而成淑翼,用于優(yōu)化任意程序語(yǔ)言編寫(xiě)的程序的編譯時(shí)間(compile-time)缤骨、鏈接時(shí)間(link-time)磁玉、運(yùn)行時(shí)間(run-time)以及空閑時(shí)間(idle-time)苛坚。對(duì)開(kāi)發(fā)者保持開(kāi)放妓羊,并兼容已有腳本

傳統(tǒng)編譯器設(shè)計(jì)

  • 源碼 Source Code -> 前端 Frontend -> 優(yōu)化器 Optimizer -> 后端 Backend(代碼生成器 CodeGenerator)-> 機(jī)器碼 Machine Code胯究,如下圖所示

ios的編譯器架構(gòu)

  • OC、C躁绸、C++使用的編譯器前端是Clang裕循,Swift是swift,后端都是LLVM净刮,如下圖所示

模塊說(shuō)明

  • 前端 Frontend:編譯器前端的任務(wù)是解析源代碼(編譯階段)剥哑,它會(huì)進(jìn)行 詞法分析、語(yǔ)法分析淹父、語(yǔ)義分析株婴、檢查源代碼是否存在錯(cuò)誤,然后構(gòu)建抽象語(yǔ)法樹(shù)(Abstract Syntax Tree AST)暑认,LLVM的前端還會(huì)生成中間代碼(intermediate representation困介,簡(jiǎn)稱(chēng)IR),可以理解為llvm是編譯器 + 優(yōu)化器蘸际, 接收的是IR中間代碼座哩,輸出的還是IR,給后端粮彤,經(jīng)過(guò)后端翻譯成目標(biāo)指令集

  • 優(yōu)化器 Optimizer:優(yōu)化器負(fù)責(zé)進(jìn)行各種優(yōu)化八回,改善代碼的運(yùn)行時(shí)間酷愧,例如消除冗余計(jì)算等

  • 后端 Backend(代碼生成器 Code Generator):將代碼映射到目標(biāo)指令集,生成機(jī)器代碼缠诅,并且進(jìn)行機(jī)器代碼相關(guān)的代碼優(yōu)化

LLVM的設(shè)計(jì)

LLVM設(shè)計(jì)的最重要方面是溶浴,使用通用的代碼表示形式(IR),它是用來(lái)在編譯器中表示代碼的形式管引,所有LLVM可以為任何編程語(yǔ)言獨(dú)立編寫(xiě)前端士败,并且可以為任意硬件架構(gòu)獨(dú)立編寫(xiě)后端,如下所示

LLVM的設(shè)計(jì)

通俗的一句話(huà)理解就是:LLVM的設(shè)計(jì)是前后端分離的褥伴,無(wú)論前端還是后端發(fā)生變化谅将,都不會(huì)影響另一個(gè)

Clang簡(jiǎn)介

clang是LLVM項(xiàng)目中的一個(gè)子項(xiàng)目,它是基于LLVM架構(gòu)圖的輕量級(jí)編譯器重慢,誕生之初是為了替代GCC饥臂,提供更快的編譯速度,它是負(fù)責(zé)C似踱、C++隅熙、OC語(yǔ)言的編譯器,屬于整個(gè)LLVM架構(gòu)中的 編譯器前端核芽,對(duì)于開(kāi)發(fā)者來(lái)說(shuō)囚戚,研究Clang可以給我們帶來(lái)很多好處

LLVM編譯流程
  • 新建一個(gè)文件,寫(xiě)下如下代碼
int test(int a,int b){
    return a + b + 3;
}


int main(int argc, const char * argv[]) {
    int a = test(1, 2);
    printf("%d",a);
    return 0;
}

  • 通過(guò)命令可以打印源碼的編譯流程
//************命令************
 clang -ccc-print-phases main.m
 
 //************編譯流程************
 //0 - 輸入文件:找到源文件
+- 0: input, "main.m", objective-c

//1 - 預(yù)處理階段:這個(gè)過(guò)程處理包括宏的替換轧简,頭文件的導(dǎo)入
+- 1: preprocessor, {0}, objective-c-cpp-output

//2 - 編譯階段:進(jìn)行詞法分析驰坊、語(yǔ)法分析、檢測(cè)語(yǔ)法是否正確哮独,最終生成IR
+- 2: compiler, {1}, ir

//3 - 后端:這里L(fēng)LVM會(huì)通過(guò)一個(gè)一個(gè)的pass去優(yōu)化拳芙,每個(gè)pass做一些事情,最終生成匯編代碼
+- 3: backend, {2}, assembler

//4 - 匯編代碼生成目標(biāo)文件
+- 4: assembler, {3}, object

//5 - 鏈接:鏈接需要的動(dòng)態(tài)庫(kù)和靜態(tài)庫(kù)皮璧,生成可執(zhí)行文件
+- 5: linker, {4}, image(鏡像文件)

//6 - 綁定:通過(guò)不同的架構(gòu)态鳖,生成對(duì)應(yīng)的可執(zhí)行文件
6: bind-arch, "x86_64", {5}, image

下面分別針對(duì)上述流程來(lái)解釋?zhuān)渲?主要是輸入文件,即找到源文件恶导。這里不做過(guò)多說(shuō)明

一浆竭、預(yù)處理編譯階段

  • 這個(gè)階段主要是處理包括宏的替換,頭文件的導(dǎo)入惨寿,可以執(zhí)行如下命令邦泄,執(zhí)行完畢可以看到文件的導(dǎo)入和的替換
//在終端直接查看替換結(jié)果
clang -E main.m

//生成對(duì)應(yīng)的文件查看替換后的源碼
clang -E main.m >> main2.m

需要注意的是:

  • typedef 在給數(shù)據(jù)類(lèi)型取別名時(shí),在預(yù)處理階段不會(huì)被替換掉

  • define則在預(yù)處理階段會(huì)被替換裂垦,所以經(jīng)常被是用來(lái)進(jìn)行代碼混淆顺囊,目的是為了app安全,實(shí)現(xiàn)邏輯是:將app中核心類(lèi)蕉拢、核心方法等用系統(tǒng)相似的名稱(chēng)進(jìn)行取別名了害幅,然后在預(yù)處理階段就被替換了,來(lái)達(dá)到代碼混淆的目的

二杆逗、編譯階段

編譯階段主要是進(jìn)行詞法、語(yǔ)法等的分析和檢查站宗,然后生成中間代碼IR

1、詞法分析

預(yù)處理完成后就會(huì)進(jìn)行詞法分析益愈,這里會(huì)把代碼切成一個(gè)個(gè)token梢灭,比如大小括號(hào)、等于號(hào)還有字符串等

  • 可以通過(guò)下面的命令查看
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
  • 如果頭文件找不到蒸其,指定sdk
clang -isysroot (自己SDK路徑) -fmodules -fsyntax-only -Xclang -dump-tokens main.m

 clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.1.sdk/ -fmodules -fsyntax-only -Xclang -dump-tokens main.m

以下是代碼的詞法分析結(jié)果


2敏释、語(yǔ)法分析

詞法分析完成后就是語(yǔ)法分析,它的任務(wù)是驗(yàn)證語(yǔ)法是否正確摸袁,在詞法分析的基礎(chǔ)上將單詞序列組合成各類(lèi)此法短語(yǔ)钥顽,如程序、語(yǔ)句靠汁、表達(dá)式 等等蜂大,然后將所有節(jié)點(diǎn)組成抽象語(yǔ)法樹(shù)(Abstract Syntax Tree AST),語(yǔ)法分析程序判斷程序在結(jié)構(gòu)上是否正確

  • 可以通過(guò)下面命令查看語(yǔ)法分析的結(jié)果
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
  • 如果導(dǎo)入頭文件找不到膀曾,可以指定SDK
 clang -isysroot (自己SDK路徑) -fmodules -fsyntax-only -Xclang -ast-dump main.m

 clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.1.sdk/ -fmodules -fsyntax-only -Xclang -ast-dump main.m

下面是語(yǔ)法分析的結(jié)果


其中,主要說(shuō)明幾個(gè)關(guān)鍵字的含義
-F unctionDecl 函數(shù)

  • ParmVarDecl 參數(shù)
  • CallExpr 調(diào)用一個(gè)函數(shù)
  • BinaryOperator 運(yùn)算符

3阳啥、生成中間代碼IR

完成以上步驟后添谊,就開(kāi)始生成中間代碼IR了,代碼生成器(Code Generation)會(huì)將語(yǔ)法樹(shù)自頂向下遍歷逐步翻譯成LLVM IR察迟,

  • 可以通過(guò)下面命令可以生成.ll的文本文件斩狱,查看IR代碼。OC代碼在這一步會(huì)進(jìn)行runtime橋接扎瓶,:property合成所踊、ARC處理等
clang -S -fobjc-arc -emit-llvm main.m

//以下是IR基本語(yǔ)法
@ 全局標(biāo)識(shí)
% 局部標(biāo)識(shí)
alloca 開(kāi)辟空間
align 內(nèi)存對(duì)齊
i32 32bit,4個(gè)字節(jié)
store 寫(xiě)入內(nèi)存
load 讀取數(shù)據(jù)
call 調(diào)用函數(shù)
ret 返回

下面是生成的中間代碼.ll文件

  • IR文件在OC中是可以進(jìn)行優(yōu)化的概荷,一般設(shè)置是在target - Build Setting - Optimization Level(優(yōu)化器等級(jí))中設(shè)置秕岛。LLVM的優(yōu)化級(jí)別分別是-O0 -O1 -O2 -O3 -Os(第一個(gè)是大寫(xiě)英文字母O),下面是帶優(yōu)化的生成中間代碼IR的命令
clang -Os -S -fobjc-arc -emit-llvm main.m -o main.ll

這是優(yōu)化后的中間代碼

  • xcode7以后開(kāi)啟bitcode误证,蘋(píng)果會(huì)做進(jìn)一步優(yōu)化(一般不使用)继薛,生成.bc的中間代碼,我們通過(guò)優(yōu)化后的IR代碼生成.bc代碼
clang -emit-llvm -c main.ll -o main.bc

三愈捅、后端

LLVM在后端主要是會(huì)通過(guò)一個(gè)個(gè)的Pass去優(yōu)化遏考,每個(gè)Pass做一些事情,最終生成匯編代碼

生成匯編代碼
  • 我們通過(guò)最終的.bc或者.ll代碼生成匯編代碼
 clang -S -fobjc-arc main.ll -o main.s 
 或
 clang -S -fobjc-arc main.bc -o main.s
  • 生成匯編代碼也可以進(jìn)行優(yōu)化
clang -Os -S -fobjc-arc main.m -o main.s

四蓝谨、生成目標(biāo)文件

  • 目標(biāo)文件的生成灌具,是匯編器以匯編代碼作為插入青团,將匯編代碼轉(zhuǎn)換為機(jī)器代碼,最后輸出目標(biāo)文件(object file)
clang -fmodules -c main.s -o main.o

可以通過(guò)nm命令咖楣,查看下main.o中的符號(hào)

$xcrun nm -nm main.o
  • _printf函數(shù)是一個(gè)是undefined 督笆、external 的

  • undefined表示在當(dāng)前文件暫時(shí)找不到符號(hào)_printf

  • external表示這個(gè)符號(hào)是外部可以訪(fǎng)問(wèn)的


五、鏈接

鏈接主要是鏈接需要的動(dòng)態(tài)庫(kù)和靜態(tài)庫(kù)截歉,生成可執(zhí)行文件胖腾,其中

  • 態(tài)庫(kù)會(huì)和執(zhí)行文件

  • 動(dòng)態(tài)庫(kù)是獨(dú)立的

連接器把編譯生成的.o文件和 .dyld .a文件鏈接,生成一個(gè)mach-o文件

clang main.o -o main

查看鏈接之后的符號(hào)

xcrun nm -nm main

結(jié)果如下所示瘪松,其中的undefined表示會(huì)在運(yùn)行時(shí)進(jìn)行動(dòng)態(tài)綁定

  • 通過(guò)命令查看 main是什么格式咸作,此時(shí)是 mach-o可執(zhí)行文件
file main 
// 打印結(jié)果:main:Mach-O 64-bit executable x86_64

./main
// 打印結(jié)果 :6%

六、綁定

綁定主要是通過(guò)不同的架構(gòu)宵睦,生成對(duì)應(yīng)的mach-o格式執(zhí)行文件

總結(jié)

綜上记罚,所述,LLVM的編譯流程如下圖所示

Clang插件開(kāi)發(fā)

1壳嚎、準(zhǔn)備工作

由于國(guó)內(nèi)網(wǎng)絡(luò)限制桐智,需要借助鏡像下載llvm的源碼,此處為鏡像鏈接

  • 下載LLVM項(xiàng)目
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/llvm.git
  • 在LLVM的projects目錄下下載compiler-rt烟馅、libcxx说庭、libcxxabi
cd ../projects

git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/compiler-rt.git

git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/libcxx.git 

git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/libcxxabi.git
  • 在Clang的tools下安裝extra工具
cd ../tools/clang/tools

git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/clang-tools-extra.git

2、LLVM編譯

由于最新的LLVM只支持cmake來(lái)編譯郑趁,所以需要安裝cmake

安裝cmake
  • 查看brew是否安裝cmake刊驴,如果已經(jīng)安裝,則跳過(guò)下面步驟
brew list
  • 通過(guò)brew安裝cmake
brew install cmake
編譯LLVM

有兩種編譯方式:

  • 通過(guò)xcode編譯LLVM
  • 通過(guò)ninja編譯LLVM

通過(guò)xcode編譯LLVM(方式一)

  • cmake編譯成Xcode項(xiàng)目
mkdir build_xcode

cd build_xcode

cmake -G Xcode ../llvm

使用xcode編譯Clang

  • 選擇自動(dòng)創(chuàng)建Schemes
  • 編譯(CMD + B)寡润,選擇ALL_BUILD Secheme進(jìn)行編譯捆憎,預(yù)計(jì)1+小時(shí)
  • :這里通過(guò)ALL_BUILD Secheme編譯會(huì)報(bào)以下錯(cuò)誤The i386 architecture is deprecated. You should update your ARCHS build setting to remove the i386 architecture,嘗試著去解決梭纹,但是目前尚未找到好的解決方案(后續(xù)會(huì)補(bǔ)充)

替代方案:選擇手動(dòng)創(chuàng)建Schemes躲惰,然后編譯編譯Clang + ClangTooling即可

通過(guò)ninja編譯LLVM(方式二)

  • 使用ninja進(jìn)行編譯則還需要安裝ninja,使用以下命令安裝ninja
brew install ninja
  • 在LLVM源碼根目錄下新建一個(gè)build_ninja目錄变抽,最終會(huì)在build_ninja目錄下生成``build.ninja`

  • 在LLVM源碼根目錄下新建llvm_release目錄础拨,最終編譯文件會(huì)在llvm_release文件夾路徑下

cd llvm_build

//注意DCMAKE_INSTALL_PREFIX后面不能有空格
cmake -G Ninja ../llvm -DCMAKE_INSTALL_PREFIX= 安裝路徑(本機(jī)為/ Users/xxx/xxx/LLVM/llvm_release)
  • 一次執(zhí)行編譯,安裝指令
ninja

ninja install

3绍载、創(chuàng)建插件

  • 在/llvm/tools/clang/tools下新建插件HKPlugin


  • 在/llvm/tools/clang/tools目錄下的CMakeLists.txt文件太伊,新增add_clang_subdirectory(HKPlugin),此處的HKPlugin即為上一步創(chuàng)建的插件名稱(chēng)

在HKPlugin目錄下新建兩個(gè)文件逛钻,分別是HKPlugi.cpp 和CMakeLists.txt僚焦,并在CMakeLists.txt中加上以下代碼


//1、通過(guò)終端在HKPlugin目錄下的創(chuàng)建
touch HKPlugin.cpp

touch CMakeLists.txt

//2曙痘、CMakeLists.txt中添加以下代碼
add_llvm_library( HKPlugin MODULE BUILDTREE_ONLY 
    HKPlugin.cpp
)
  • 接下來(lái)利用cmake重新生成Xcode項(xiàng)目芳悲,在build_xcode目錄下執(zhí)行以下命令
cmake -G Xcode ../llvm
  • 最后可以在LLVM的xcode項(xiàng)目中可以看到Loadable modules目錄下由自定義的HKPlugin目錄了立肘,然后可以在里面編寫(xiě)插件代碼了
編寫(xiě)插件代碼

在HKPlugin目錄下的HKPlugin.cpp文件中,加入以下代碼


#include <iostream>
#include "clang/AST/AST.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Frontend/FrontendPluginRegistry.h"

using namespace clang;
using namespace std;
using namespace llvm;
using namespace clang::ast_matchers;

//命名空間名扛,和插件同名
namespace HKPlugin {

//第三步:掃描完畢的回調(diào)函數(shù)
//4谅年、自定義回調(diào)類(lèi),繼承自MatchCallback
class HKMatchCallback: public MatchFinder::MatchCallback {
    
private:
    //CI傳遞路徑:HKASTAction類(lèi)中的CreateASTConsumer方法參數(shù) - HKConsumer的構(gòu)造函數(shù) - HKMatchCallback的私有屬性肮韧,通過(guò)構(gòu)造函數(shù)從CJLASTConsumer構(gòu)造函數(shù)中獲取

    CompilerInstance &CI;
    
    //判斷是否是用戶(hù)源文件
    bool isUserSourceCode(const string filename) {
        //文件名不為空
        if (filename.empty()) return  false;
        //非xcode中的源碼都認(rèn)為是用戶(hù)的
        if (filename.find("/Applications/Xcode.app/") == 0) return false;
        return  true;
    }

    //判斷是否應(yīng)該用copy修飾
    bool isShouldUseCopy(const string typeStr) {
        //判斷類(lèi)型是否是NSString | NSArray | NSDictionary
        if (typeStr.find("NSString") != string::npos ||
            typeStr.find("NSArray") != string::npos ||
            typeStr.find("NSDictionary") != string::npos/*...*/)
        {
            return true;
        }
        
        return false;
    }
    
public:
    HKMatchCallback(CompilerInstance &CI) :CI(CI) {}
    
    //重寫(xiě)run方法
    void run(const MatchFinder::MatchResult &Result) {
        //通過(guò)result獲取到相關(guān)節(jié)點(diǎn) -- 根據(jù)節(jié)點(diǎn)標(biāo)記獲热邗濉(標(biāo)記需要與CJLASTConsumer構(gòu)造方法中一致)
        const ObjCPropertyDecl *propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("objcPropertyDecl");
        //判斷節(jié)點(diǎn)有值,并且是用戶(hù)文件
        if (propertyDecl && isUserSourceCode(CI.getSourceManager().getFilename(propertyDecl->getSourceRange().getBegin()).str()) ) {
            //15弄企、獲取節(jié)點(diǎn)的描述信息
            ObjCPropertyDecl::PropertyAttributeKind attrKind = propertyDecl->getPropertyAttributes();
            //獲取節(jié)點(diǎn)的類(lèi)型超燃,并轉(zhuǎn)成字符串
            string typeStr = propertyDecl->getType().getAsString();
//            cout<<"---------拿到了:"<<typeStr<<"---------"<<endl;
            
            //判斷應(yīng)該使用copy,但是沒(méi)有使用copy
            if (propertyDecl->getTypeSourceInfo() && isShouldUseCopy(typeStr) && !(attrKind & ObjCPropertyDecl::OBJC_PR_copy)) {

                //使用CI發(fā)警告信息
                //通過(guò)CI獲取診斷引擎
                DiagnosticsEngine &diag = CI.getDiagnostics();

                //通過(guò)診斷引擎 report報(bào)告 錯(cuò)誤拘领,即拋出異常
                /*
                錯(cuò)誤位置:getBeginLoc 節(jié)點(diǎn)開(kāi)始位置
                錯(cuò)誤:getCustomDiagID(等級(jí)意乓,提示)
                 */
                diag.Report(propertyDecl->getBeginLoc(), diag.getCustomDiagID(DiagnosticsEngine::Warning, "%0 - 這個(gè)地方推薦使用copy!!"))<< typeStr;
            }
        }
    }
};


//第二步:掃描配置完畢
//3、自定義HKASTConsumer约素,繼承自ASTConsumer届良,用于監(jiān)聽(tīng)AST節(jié)點(diǎn)的信息 -- 過(guò)濾器
class HKASTConsumer: public ASTConsumer {
private:
    //AST節(jié)點(diǎn)的查找過(guò)濾器
    MatchFinder matcher;
    //定義回調(diào)類(lèi)對(duì)象
    HKMatchCallback callback;
    
public:
    //構(gòu)造方法中創(chuàng)建matcherFinder對(duì)象
    HKASTConsumer(CompilerInstance &CI) : callback(CI) {
        //添加一個(gè)MatchFinder,每個(gè)objcPropertyDecl節(jié)點(diǎn)綁定一個(gè)objcPropertyDecl標(biāo)識(shí)(去匹配objcPropertyDecl節(jié)點(diǎn))
        //回調(diào)callback圣猎,其實(shí)是在HKMatchCallback里面重寫(xiě)run方法(真正回調(diào)的是回調(diào)run方法)
        matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callback);
    }
    
    //實(shí)現(xiàn)兩個(gè)回調(diào)方法 HandleTopLevelDecl 和 HandleTranslationUnit
    //解析完一個(gè)頂級(jí)的聲明士葫,就回調(diào)一次(頂級(jí)節(jié)點(diǎn),相當(dāng)于一個(gè)全局變量送悔、函數(shù)聲明)
    bool HandleTopLevelDecl(DeclGroupRef D){
//        cout<<"正在解析..."<<endl;
        return  true;
    }
    
    //整個(gè)文件都解析完成的回調(diào)
    void HandleTranslationUnit(ASTContext &context) {
//        cout<<"文件解析完畢!"<<endl;
        //將文件解析完畢后的上下文context(即AST語(yǔ)法樹(shù)) 給 matcher
        matcher.matchAST(context);
    }
};

//2慢显、繼承PluginASTAction,實(shí)現(xiàn)我們自定義的Action放祟,即自定義AST語(yǔ)法樹(shù)行為
class HKASTAction: public PluginASTAction {
    
public:
    //重載ParseArgs 和 CreateASTConsumer方法
    bool ParseArgs(const CompilerInstance &ci, const std::vector<std::string> &args) {
        return true;
    }
    
    //返回ASTConsumer類(lèi)型對(duì)象鳍怨,其中ASTConsumer是一個(gè)抽象類(lèi)呻右,即基類(lèi)
    /*
     解析給定的插件命令行參數(shù)跪妥。
     - param CI 編譯器實(shí)例,用于報(bào)告診斷声滥。
     - return 如果解析成功眉撵,則為true;否則落塑,插件將被銷(xiāo)毀纽疟,并且不執(zhí)行任何操作。該插件負(fù)責(zé)使用CompilerInstance的Diagnostic對(duì)象報(bào)告錯(cuò)誤憾赁。
     */
    unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef iFile) {
        //返回自定義的CJLASTConsumer,即ASTConsumer的子類(lèi)對(duì)象
        /*
         CI用于:
         - 判斷文件是否使用戶(hù)的
         - 拋出警告
         */
        return unique_ptr<HKASTConsumer> (new HKASTConsumer(CI));
    }
    
};

}

//第一步:注冊(cè)插件污朽,并自定義AST語(yǔ)法樹(shù)Action類(lèi)
//1、注冊(cè)插件
static FrontendPluginRegistry::Add<HKPlugin::HKASTAction> HK("HKPlugin", "This is HKPlugin");

其原理主要分為三步

  • 【第一步】注冊(cè)插件龙考,并自定義AST語(yǔ)法樹(shù)Action類(lèi)

    • 繼承自PluginASTAction蟆肆,自定義ASTAction矾睦,需要重載個(gè)方法ParseArgsCreateASTConsumer,其中的重點(diǎn)方法是CreateASTConsumer炎功,方法中有個(gè)參數(shù)CI編譯實(shí)例對(duì)象枚冗,主要用于以下兩個(gè)方面

    • 用于判斷文件是否是用戶(hù)的

    • 用于拋出警告

    • 通過(guò)FrontendPluginRegistry注冊(cè)插件,需要關(guān)聯(lián)插件名與自定義的ASTAction類(lèi)

  • 【第二步】掃描配置完畢

    • 繼承ASTConsumer類(lèi)蛇损,實(shí)現(xiàn)自定義的子類(lèi)HKASTConsumer,有兩個(gè)數(shù)MatchFinder對(duì)象matcher以及HKMatchCallback自定義的回調(diào)對(duì)象callback

    • 實(shí)現(xiàn)構(gòu)造函數(shù)赁温,主要是創(chuàng)建MatchFinder對(duì)象,以及將CI傳給回調(diào)對(duì)象

    • 實(shí)現(xiàn)兩個(gè)回調(diào)方法

      • HandleTopLevelDecl:解析完個(gè)頂級(jí)的聲明淤齐,就回調(diào)一次
      • HandleTranslationUnit個(gè)文件都解析完成的回調(diào)股囊,將文件解析完畢后的上下文context(即AST語(yǔ)法樹(shù)) 給 matcher
  • 【第三步】掃描完畢的回調(diào)函數(shù)

    • 繼承自MatchFinder::MatchCallback,自定義回調(diào)類(lèi)HKMatchCallback
    • 定義CompilerInstance私有屬性床玻,用于接收ASTConsumer類(lèi)傳遞過(guò)來(lái)的CI信息
    • 重寫(xiě)run方法
      • 1毁涉、通過(guò)result,根據(jù)節(jié)點(diǎn)標(biāo)記锈死,獲取相應(yīng)節(jié)點(diǎn)贫堰,此時(shí)的標(biāo)記需要與HKASTConsumer構(gòu)造方法中一致
      • 2、判斷節(jié)點(diǎn)有值待牵,并且是用戶(hù)文件即isUserSourceCode私有方法
      • 3其屏、獲取節(jié)點(diǎn)的描述信息
      • 4、獲取節(jié)點(diǎn)的類(lèi)型缨该,并轉(zhuǎn)成字符串
      • 5偎行、判斷應(yīng)該使用copy,但是沒(méi)有使用copy
      • 6贰拿、通過(guò)CI獲取診斷引擎
      • 7蛤袒、通過(guò)診斷引擎報(bào)告錯(cuò)誤
        所以,綜上所述膨更,clang插件開(kāi)發(fā)的流程圖如下


        clang插件開(kāi)發(fā)細(xì)節(jié)圖

然后在終端中測(cè)試插件

//命令格式
自己編譯的clang文件路徑  -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.1.sdk/ -Xclang -load -Xclang 插件(.dyld)路徑 -Xclang -add-plugin -Xclang 插件名 -c 源碼路徑

//例子
/Users/XXX/Desktop/build_xcode/Debug/bin/clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.1.sdk/ -Xclang -load -Xclang /Users/XXXX/Desktop/build_xcode/Debug/lib/CJLPlugin.dylib -Xclang -add-plugin -Xclang CJLPlugin -c /Users/XXXX/Desktop/XXX/XXXX/測(cè)試demo/testClang/testClang/ViewController.m
測(cè)試插件

4妙真、Xcode集成插件

加載插件

  • 打開(kāi)測(cè)試項(xiàng)目,在target->Build Settings -> Other C Flags 添加以下內(nèi)容
 -Xclang -load -Xclang (.dylib)動(dòng)態(tài)庫(kù)路徑 -Xclang -add-plugin -Xclang HKPlugin

設(shè)置編譯器

  • 由于clang插件需要使用對(duì)應(yīng)的版本去加載荚守,如果版本不一致會(huì)導(dǎo)致編譯失敗珍德,如下所示
編譯錯(cuò)誤
  • 在Build Settings欄目中新增兩項(xiàng)用戶(hù)定義的設(shè)置,分別是CC和CXX
    • CC 對(duì)應(yīng)的是自己編譯的clang的絕對(duì)路徑
    • CXX 對(duì)應(yīng)的是自己編譯的clang++的絕對(duì)路徑
  • 接下來(lái)在Build Settingsindex矗漾,將Enable Index-Wihle-Building Functionality的DefaultNO
  • 最后锈候,重新編譯測(cè)試項(xiàng)目,會(huì)出現(xiàn)下面的效果


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末敞贡,一起剝皮案震驚了整個(gè)濱河市泵琳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖获列,帶你破解...
    沈念sama閱讀 221,820評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件琳钉,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡蛛倦,警方通過(guò)查閱死者的電腦和手機(jī)歌懒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)溯壶,“玉大人及皂,你說(shuō)我怎么就攤上這事∏腋模” “怎么了验烧?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,324評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)又跛。 經(jīng)常有香客問(wèn)我碍拆,道長(zhǎng),這世上最難降的妖魔是什么慨蓝? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,714評(píng)論 1 297
  • 正文 為了忘掉前任感混,我火速辦了婚禮,結(jié)果婚禮上礼烈,老公的妹妹穿的比我還像新娘弧满。我一直安慰自己,他們只是感情好此熬,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布庭呜。 她就那樣靜靜地躺著,像睡著了一般犀忱。 火紅的嫁衣襯著肌膚如雪募谎。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,328評(píng)論 1 310
  • 那天阴汇,我揣著相機(jī)與錄音数冬,去河邊找鬼。 笑死鲫寄,一個(gè)胖子當(dāng)著我的面吹牛吉执,可吹牛的內(nèi)容都是我干的疯淫。 我是一名探鬼主播地来,決...
    沈念sama閱讀 40,897評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼熙掺!你這毒婦竟也來(lái)了未斑?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,804評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤币绩,失蹤者是張志新(化名)和其女友劉穎蜡秽,沒(méi)想到半個(gè)月后府阀,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,345評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡芽突,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評(píng)論 3 340
  • 正文 我和宋清朗相戀三年试浙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片寞蚌。...
    茶點(diǎn)故事閱讀 40,561評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡田巴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出挟秤,到底是詐尸還是另有隱情壹哺,我是刑警寧澤,帶...
    沈念sama閱讀 36,238評(píng)論 5 350
  • 正文 年R本政府宣布艘刚,位于F島的核電站管宵,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏攀甚。R本人自食惡果不足惜箩朴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望秋度。 院中可真熱鬧隧饼,春花似錦、人聲如沸静陈。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,417評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)鲸拥。三九已至拐格,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間刑赶,已是汗流浹背捏浊。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,528評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留撞叨,地道東北人金踪。 一個(gè)月前我還...
    沈念sama閱讀 48,983評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像牵敷,于是被迫代替她去往敵國(guó)和親胡岔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評(píng)論 2 359