本文將簡(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