Clang LLVM 簡(jiǎn)介


本文將簡(jiǎn)單介紹 Clang LLVM 相關(guān)的知識(shí)闰非,然后介紹一下代碼是如何一步步的編譯運(yùn)行的莫秆,以及可以利用 clang 能做些什么芳绩。

簡(jiǎn)介

編譯器就是語(yǔ)言翻譯器葱她,把高級(jí)語(yǔ)言翻譯成計(jì)算機(jī)能夠執(zhí)行的機(jī)器語(yǔ)言薄啥。

語(yǔ)言翻譯主要工作流程:
源代碼 (source code) → 預(yù)處理器 (preprocessor) → 編譯器 (compiler) → 目標(biāo)代碼 (object code) → 鏈接器 (linker) → 可執(zhí)行程序 (executables)

LLVM (Low Level Virtual Machine) 是一個(gè)開(kāi)源的編譯器架構(gòu)辕羽。Clang 是 LLVM 的一個(gè)編譯器前端,它目前支持 C, C++, Objective-C 等編程語(yǔ)言垄惧。

Clang 對(duì)源程序進(jìn)行預(yù)處理刁愿、詞法分析、語(yǔ)法分析到逊,并將分析結(jié)果轉(zhuǎn)換為 Abstract Syntax Tree ( 抽象語(yǔ)法樹(shù) ) 铣口,最后使用 LLVM 作為編譯器后端代碼的生成器。

Clang 的開(kāi)發(fā)目標(biāo)是提供一個(gè)可以替代 GCC 的前端編譯器觉壶。Apple 對(duì) Objective-C 新增很多特性脑题,想做的很多功能,比如更好的IDE支持铜靶,GCC 不能很好的支持叔遂,于是,蘋(píng)果開(kāi)發(fā)了 Clang 與 LLVM 來(lái)完全取代GCC旷坦。Clang作為編譯器前端掏熬,LLVM作為編譯器后端。

與 GCC 相比秒梅,Clang 是一個(gè)重新設(shè)計(jì)的編譯器前端旗芬,具有一系列優(yōu)點(diǎn),例如模塊化捆蜀,代碼簡(jiǎn)單易懂疮丛,占用內(nèi)存小以及容易擴(kuò)展和重用等幔嫂。由于 Clang 在設(shè)計(jì)上的優(yōu)異性,使得 Clang 非常適合用于設(shè)計(jì)源代碼級(jí)別的分析和轉(zhuǎn)化工具誊薄。Clang 也已經(jīng)被應(yīng)用到一些重要的開(kāi)發(fā)領(lǐng)域履恩,如 Static Analysis 是一個(gè)基于 Clang 的靜態(tài)代碼分析工具。


用 Clang 編譯 OC 程序

當(dāng)用 Xcode 創(chuàng)建了項(xiàng)目呢蔫,然后點(diǎn)擊 run 運(yùn)行的時(shí)候切心,可以在 Xcode 中看到編譯的信息:

看一下編譯 main.m 文件的信息,相當(dāng)于執(zhí)行了一長(zhǎng)串的命令片吊,其中命令的參數(shù)就是你在 Build Settings 里面設(shè)置的一些選項(xiàng)绽昏,拼接成了這一串命令,主要的就是調(diào)用 Clang 編譯的命令:

clang -x objective-c -fobjc-arc ... main.m -o main.o


clang 命令:

在命令行中 clang 相當(dāng)于一個(gè)黑盒的驅(qū)動(dòng)俏脊,里面封裝了編譯管線(xiàn)全谤、前段命令、LLVM 命令爷贫、Toolchain 命令等认然。


clang 編譯的過(guò)程

下面通過(guò)編譯 main.m 文件,來(lái)看一下編譯的過(guò)程漫萄。main.m 中是很簡(jiǎn)單的打印代碼:

#import <Foundation/Foundation.h>
int main() {
    @autoreleasepool {
        NSLog(@"Hello world!");
    }
    return 0;
}

命令行輸入命令:
clang -fobjc-arc -framework Foundation main.m -o main

-fobjc-arc 表示編譯器需要支持 ARC 特性
-framework Foundation 表示引用Foundation框架

上面的命令會(huì)生成可執(zhí)行文件 main卷员,然后命令行輸入執(zhí)行文件 main:
./main
看到運(yùn)行結(jié)果:
Hello world!

實(shí)質(zhì)上,上述編譯過(guò)程是分為四個(gè)階段進(jìn)行的卷胯,即預(yù)處理(Preprocess)子刮、編譯(Compilation)、匯編 (Assembly)和連接(Linking)窑睁。


1挺峡、預(yù)處理(Preprocess)

這個(gè)步驟會(huì)進(jìn)行,import 頭文件的處理担钮,宏定義的展開(kāi)橱赠,#開(kāi)頭的預(yù)處理指令等,的處理箫津。
預(yù)處理的命令:
clang -E main.m 或者 clang -E main.m -o test.i

查看文件可看到頭部狭姨,十幾萬(wàn)行的預(yù)處理


-fmodules 參數(shù)可以把那些庫(kù)打包導(dǎo)入,import Foundation苏遥,這樣每次編譯都不用展開(kāi)那么多東西
clang -E -fmodules main.m


2饼拍、詞法分析(Lexical Analysis)

詞法分析,將預(yù)處理過(guò)的代碼轉(zhuǎn)化成一個(gè)個(gè)的 Token田炭,對(duì)應(yīng)的命令為:
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m

處理后的結(jié)果為:


可看到代碼师抄,拆分成了一個(gè)個(gè)的 Token


3、語(yǔ)法分析(Semantic Analysis)

語(yǔ)法分析教硫,在 clang 中由 Parser 和 Sema 兩個(gè)模塊配合完成叨吮,來(lái)驗(yàn)證語(yǔ)法是否正確辆布。
提示代碼哪里寫(xiě)錯(cuò)了,少了冒號(hào)茶鉴、括號(hào)等一些提示锋玲。
根據(jù)當(dāng)前語(yǔ)言的語(yǔ)法,生成語(yǔ)義節(jié)點(diǎn)涵叮,并將所有節(jié)點(diǎn)組合成抽象語(yǔ)法樹(shù)(AST: Abstract Syntax Tree)惭蹂,對(duì)應(yīng)的命令為:
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m

處理后,生成的語(yǔ)法樹(shù):


FunctionDecl围肥,表示函數(shù)名 main
CompoundStmt剿干,表示大括號(hào) {}
ObjCAutoreleasePoolStmt,表示 @autoreleasepool

靜態(tài)分析:
編譯器把源碼生成了抽象語(yǔ)法樹(shù)穆刻,編譯器可以對(duì)這棵樹(shù)做分析處理,以找出代碼中的錯(cuò)誤杠步,比如類(lèi)型檢查:即檢查程序中是否有類(lèi)型錯(cuò)誤氢伟。例如:如果代碼中給某個(gè)對(duì)象發(fā)送了一個(gè)消息,編譯器會(huì)檢查這個(gè)對(duì)象是否實(shí)現(xiàn)了這個(gè)消息(函數(shù)幽歼、方法)朵锣。此外,clang 對(duì)整個(gè)程序還做了其它更高級(jí)的一些分析甸私,以確保程序沒(méi)有錯(cuò)誤诚些。

還有一種類(lèi)型檢查:動(dòng)態(tài)分析
動(dòng)態(tài)的在運(yùn)行時(shí)做檢查,靜態(tài)的在編譯時(shí)做檢查皇型。編寫(xiě)代碼時(shí)可以向任意對(duì)象發(fā)送任何消息诬烹,在運(yùn)行時(shí),才會(huì)檢查對(duì)象是否能夠響應(yīng)這些消息弃鸦。


4绞吁、IR 代碼生成 (CodeGen)

clang 完成代碼的標(biāo)記,解析和分析后唬格,接著就會(huì)生成 LLVM 代碼家破。將上一步的語(yǔ)法樹(shù)從頂至下遍歷,翻譯成 LLVM IR购岗。
LLVM IR 是編譯前端的輸出汰聋,也是 LLVM 后端的輸入,是前后端橋接語(yǔ)言喊积。

與 OC Runtime 橋接:

  • Class烹困、Meta Class、Protocol注服、Category 內(nèi)存結(jié)構(gòu)生成韭邓,并存放在指定 section 中(如 Class:_DATA, _objc_classrefs)

  • Method措近、Ivar、Property 內(nèi)存結(jié)構(gòu)生成

  • 組成 method_list女淑、ivar_list瞭郑、property_list 并填入 Class

  • Non-Fragile ABI:為每個(gè) Ivar 合成 OBJC_IVAR_$_ 偏移值常量

  • 存取 Ivar 的語(yǔ)句(_ivar = 123; int a = _ivar;)
    轉(zhuǎn)寫(xiě)成 base + OBJC_IVAR_$_的形式

  • 將語(yǔ)法樹(shù)中的 ObjCMessageExpr 翻譯成相應(yīng)版本的 obj_msgSend,對(duì) super 關(guān)鍵字的調(diào)用翻譯成 objc_msgSendSuper

  • 根據(jù)修飾符 strong鸭你、weak屈张、copy、atomic 合成 @property 自動(dòng)實(shí)現(xiàn)的 setter袱巨、getter

  • 處理 @synthesize

  • 生成 block_layout 的數(shù)據(jù)結(jié)構(gòu)

  • 變量的 capture(__block __weak)

  • 生成 _block_invoke 函數(shù)

  • ARC 分析對(duì)象引用關(guān)系阁谆,將 obj_storeStrong/objc_storeWeak 等 ARC 代碼插入

  • 將 ObjcCAutoreleasePoolStmt 轉(zhuǎn)譯成 objc_autoreleasePoolPush/Pop

  • 實(shí)現(xiàn)自動(dòng)調(diào)用 [super dealloc]

  • 為每個(gè)擁有 ivar 的 Class 合成 .cxx_destructor 方法來(lái)自動(dòng)釋放類(lèi)的成員變量,代替 MRC 時(shí)代的 self.xxx = nil

可以使用下面的命令愉老,查看生成 IR 代碼:
clang -S -fobjc-arc -emit-llvm main.m -o main.ll
-S 編譯到匯編層面
-fobjc-arc 開(kāi)啟ARC
-emit-llvm 生成中間的LLVM語(yǔ)言

執(zhí)行命令后可得到文件 main.ll:

define i32 @main() #0 {
 %1 = alloca i32, align 4
 store i32 0, i32* %1, align 4
 %2 = call i8* @llvm.objc.autoreleasePoolPush() #1
 notail call void (i8*, ...) @NSLog(i8* bitcast (%struct.__NSConstantString_tag* @_unnamed_cfstring_ to i8*))
 call void @llvm.objc.autoreleasePoolPop(i8* %2)
 ret i32 0
}

這里 LLVM 會(huì)去做些優(yōu)化工作场绿,在 Xcode 的編譯設(shè)置里也可以設(shè)置優(yōu)化級(jí)別 -O1,-O3嫉入,-Os :
clang -O3 -S -fobjc-arc -emit-llvm main.m -o main.ll

下面是優(yōu)化后的中間LLVM代碼

define i32 @main() local_unnamed_addr #0 {
  %1 = tail call i8* @llvm.objc.autoreleasePoolPush() #1
  notail call void (i8*, ...) @NSLog(i8* bitcast (%struct.__NSConstantString_tag* @_unnamed_cfstring_ to i8*)), !clang.arc.no_objc_arc_exceptions !9
  tail call void @llvm.objc.autoreleasePoolPop(i8* %1) #1
  ret i32 0
}

生成字節(jié)碼 LLVM Bitcode
clang -emit-llvm -c main.m -o main.bc
將上面生成的 IR 代碼生成二進(jìn)制碼格式


5焰盗、生成 Target 相關(guān)匯編(Assemble)

生成對(duì)應(yīng)機(jī)器相關(guān)的匯編語(yǔ)言:
clang -S -fobjc-arc main.m -o main.s

生成 Target 相關(guān) Mach-O 文件
clang -fmodules -c main.m -o main.o

Mach-O(Mach object)文件,是一種用于記錄可執(zhí)行文件咒林、對(duì)象代碼熬拒、共享庫(kù)、動(dòng)態(tài)加載代碼和內(nèi)存轉(zhuǎn)儲(chǔ)的文件格式垫竞,是macOS/iOS上程序以及庫(kù)的標(biāo)準(zhǔn)格式澎粟。


6、生成可執(zhí)行文件

鏈接生成可執(zhí)行文件 main欢瞪。
將程序的目標(biāo)文件與所需的所有附加的目標(biāo)文件(靜態(tài)連接庫(kù)和動(dòng)態(tài)連接庫(kù))連接起來(lái)活烙,最終生成可執(zhí)行文件。
clang main.o -o main
執(zhí)行就可以看到程序運(yùn)行
./main


7引有、編譯過(guò)程小結(jié)
  • 預(yù)處理
    • 符號(hào)化 (Tokenization)
    • 宏定義的展開(kāi)
    • #include 的展開(kāi)
  • 語(yǔ)法和語(yǔ)義分析
    • 將符號(hào)化后的內(nèi)容轉(zhuǎn)化為一棵解析樹(shù) (parse tree)
    • 解析樹(shù)做語(yǔ)義分析
    • 輸出一棵抽象語(yǔ)法樹(shù)(Abstract Syntax Tree* (AST))
  • 生成代碼和優(yōu)化
    • 將 AST 轉(zhuǎn)換為更低級(jí)的中間碼 (LLVM IR)
    • 對(duì)生成的中間碼做優(yōu)化
    • 生成特定目標(biāo)代碼
    • 輸出匯編代碼
  • 匯編器
    • 將匯編代碼轉(zhuǎn)換為目標(biāo)對(duì)象文件瓣颅。
  • 鏈接器
    • 將多個(gè)目標(biāo)對(duì)象文件合并為一個(gè)可執(zhí)行文件 (或者一個(gè)動(dòng)態(tài)庫(kù))


clang 的應(yīng)用

上面介紹了,用 clang 編譯的過(guò)程譬正,從預(yù)處理到詞法分析宫补、語(yǔ)法分析,再到生成匯編語(yǔ)言曾我。
那么我們能用 clang 做些什么粉怕?

libclang
libclang 是一個(gè) C 的類(lèi)庫(kù),提供了簡(jiǎn)潔的 API抒巢,可以對(duì) C 和 clang 做橋接贫贝,并可以用它對(duì)所有的源碼做分析處理,如獲取 Tokens、遍歷語(yǔ)法樹(shù)稚晚、代碼補(bǔ)全崇堵、獲取診斷信息等。
libclang api 穩(wěn)定客燕,不受 clang 源碼更新影響鸳劳,但是只能訪(fǎng)問(wèn)上層語(yǔ)法樹(shù),不能獲取全部信息也搓。
推薦使用 ClangKit 它是基于 clang 提供的功能赏廓,用 Objective-C 進(jìn)行封裝的一個(gè)庫(kù)。

clang 還提供了一個(gè)直接使用 LibTooling 的 C++ 類(lèi)庫(kù)傍妒。它能夠發(fā)揮 clang 的強(qiáng)大功能幔摸,可以對(duì)源碼做任意類(lèi)型的分析,甚至重寫(xiě)程序颤练,對(duì)語(yǔ)法樹(shù)有完全的控制權(quán)既忆。如果你想要給 clang 添加一些自定義的分析、創(chuàng)建自己的重構(gòu)器 (refactorer)嗦玖、或者需要基于現(xiàn)有代碼做出大量修改尿贫,甚至想要基于工程生成相關(guān)圖形或者文檔,那么可以使用 LibTooling踏揣。


將 OC 代碼轉(zhuǎn)換為 C/C++

當(dāng)需要查看 OC 代碼底層實(shí)現(xiàn)時(shí),可以利用 clang 將 OC 代碼轉(zhuǎn)換為 C/C++ 代碼匾乓。
下面命令可以進(jìn)行轉(zhuǎn)換:

clang -rewrite-objc main.m

//-fobjc-arc 表示ARC環(huán)境捞稿。-fobjc-runtime 表示當(dāng)前運(yùn)行時(shí)環(huán)境。
clang -rewrite-objc -fobjc-arc -fobjc-runtime=macosx-10.15 main.m

執(zhí)行后拼缝,可在當(dāng)前文件夾下生成 main.cpp 文件娱局。

當(dāng) .m 文件包含系統(tǒng)頭文件時(shí),會(huì)報(bào)錯(cuò)找不到頭文件咧七,可以指定SDK解決:

clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m

或者

xcrun -sdk iphonesimulator13.2 clang -rewrite-objc main.m
//其中的 sdk iphonesimulator13.2 可以通過(guò)命令 xcodebuild -showsdks 來(lái)查看

或者使用下面命令衰齐,指定了 SDK 和架構(gòu),轉(zhuǎn)換后的代碼會(huì)少一些:

xcrun -sdk iphoneos clang -arch arm64 -w -rewrite-objc main.m


References

https://llvm.org/
http://clang.llvm.org/docs/index.html
https://objccn.io/issue-6-2/
https://objccn.io/issue-6-3/
https://www.cnblogs.com/wfwenchao/p/5543595.html
https://www.ibm.com/developerworks/cn/opensource/os-cn-clang/index.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末继阻,一起剝皮案震驚了整個(gè)濱河市耻涛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瘟檩,老刑警劉巖抹缕,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異墨辛,居然都是意外死亡卓研,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)奏赘,“玉大人寥闪,你說(shuō)我怎么就攤上這事∧ヌ剩” “怎么了疲憋?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)伦糯。 經(jīng)常有香客問(wèn)我柜某,道長(zhǎng),這世上最難降的妖魔是什么敛纲? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任喂击,我火速辦了婚禮,結(jié)果婚禮上淤翔,老公的妹妹穿的比我還像新娘翰绊。我一直安慰自己,他們只是感情好旁壮,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布监嗜。 她就那樣靜靜地躺著,像睡著了一般抡谐。 火紅的嫁衣襯著肌膚如雪裁奇。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,475評(píng)論 1 312
  • 那天麦撵,我揣著相機(jī)與錄音刽肠,去河邊找鬼。 笑死免胃,一個(gè)胖子當(dāng)著我的面吹牛音五,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播羔沙,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼躺涝,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了扼雏?” 一聲冷哼從身側(cè)響起坚嗜,我...
    開(kāi)封第一講書(shū)人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎呢蛤,沒(méi)想到半個(gè)月后惶傻,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡其障,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年银室,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蜈敢,死狀恐怖辜荠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情抓狭,我是刑警寧澤伯病,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站否过,受9級(jí)特大地震影響午笛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜苗桂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一药磺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧煤伟,春花似錦癌佩、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至放案,卻和暖如春姚建,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背吱殉。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工桥胞, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人考婴。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像催烘,于是被迫代替她去往敵國(guó)和親沥阱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361