編譯 想必都知道,那么LLVM是什么邢滑?
LLVM是一種編譯器!LLVM編譯流程是怎么樣的困后?
本篇就LLVM進(jìn)行初探
首先讓我們來了解編譯器是神馬~
一、編譯器是什么摇予?
1汽绢、以python
程序?yàn)槔?br>
新建一個(gè)python文件
:helloDemo.py(內(nèi)部一行代碼打印hello
)
print("hello\n")
進(jìn)入python文件
侧戴,執(zhí)行python helloDemo.py
2、
c
語言程序?yàn)槔?br>
新建helloDemo.c
文件:
#include <stdio.h>
int main(int a,char * argv[]){
printf("hello \n");
return 0;
}
執(zhí)行clang helloDemo.c
編譯积仗,生成a.out
文件蜕猫,file a.out
查看文件
發(fā)現(xiàn)最終生成文件是:.out文件:64位的Mach-O可執(zhí)行文件,當(dāng)前clang出來的是x86_64架構(gòu)隆圆, 說明mac電腦可讀。 所以可以./a.out直接執(zhí)行:
以上可以看到,python和C輸出流程是完全不同的租漂,pthyon直接用python命令就可以輸出結(jié)果哩治,而C需要先生成.out文件。這是由于兩種編譯過程不一樣
- python是
解釋型
語言业筏,一邊翻譯一邊執(zhí)行蒜胖,機(jī)器可直接執(zhí)行。 - C語言是
編譯型
語言寻狂,不可以直接執(zhí)行朋沮,需要編譯器將其轉(zhuǎn)換成機(jī)器識(shí)別語言。
注:
編譯型語言:編譯后輸出的是指令(0纠亚、1組合)筋夏,cpu可直接執(zhí)行指令
解釋性語言:生成的是數(shù)據(jù),不是0条篷、1組合拥娄,機(jī)器也能直接識(shí)別
-
編譯器就是將高級(jí)語言轉(zhuǎn)換成機(jī)器可識(shí)別的語言(
可執(zhí)行文件
)
補(bǔ):
匯編語言是否需要編譯?
直接解釋
1牡昆、早期科學(xué)家,就是使用0丢烘、1編程播瞳。 為了摒棄手敲0和1的繁瑣。開發(fā)者們創(chuàng)造了中間解釋器赢乓,再創(chuàng)建出call牌芋、bl等這種容易記的指令來代表0、1組合(例如call對(duì)應(yīng)00001111)躺屁。有了對(duì)應(yīng)關(guān)系后犀暑,就好辦了。程序員只用輸入call耐亏、bl這樣的標(biāo)記指令苹熏,經(jīng)過解釋器,變成0和1的組合袱耽,就可以直接交給機(jī)器去執(zhí)行了干发。 這就是匯編的由來。
2枉长、基于匯編以上必峰,再映射和封裝相關(guān)對(duì)應(yīng)關(guān)系。于是生成了跨時(shí)代性的c語言吼蚁,再往上層封裝,就出現(xiàn)了高級(jí)語言oc粒蜈、swift、JAVA等語言注整。之所以匯編執(zhí)行快度硝,是因?yàn)樗苯愚D(zhuǎn)換為機(jī)器語塘淑。
3蚂斤、既然匯編速度這么快,為什么不都用匯編開發(fā)捌治?
因?yàn)閰R編的指令集纽窟,是針對(duì)同一操作系統(tǒng)而言,不支持跨平臺(tái)森枪。機(jī)器指令是cpu的在識(shí)別审孽。早期的計(jì)算機(jī)廠家非常多,雖然都用0和1的組合式散,但相同組合背后卻是相應(yīng)不同的指令打颤。所以匯編無法跨平臺(tái),不同操作系統(tǒng)下编饺,匯編指令是不同的透且,所以需要可以跨平臺(tái)的高級(jí)語言來開發(fā)~
二、LLVM概述
1、上面我們知道了編譯器是什么畅形。那么LLVM
是什么呢诉探?
llVM
就是編譯器的一種:
-
架構(gòu)編譯器(compiler
)的框架系統(tǒng)肾胯,以c++
編寫而成,用于優(yōu)化
以任意程序語言編寫的程序的編譯時(shí)間(compile-time)
毕荐、鏈接時(shí)間(link-time)
艳馒、運(yùn)行時(shí)間(run-time)
以及空閑時(shí)間(idle-time)
,對(duì)開發(fā)者保持開放
第美,并兼任已有腳本 - 2006年Chris Lattner加盟Apple Inc.并致力于LLVM在Apple開發(fā)體系中的應(yīng)用陆爽。
Apple
也是LLVM計(jì)劃
的主要資助者
慌闭。
目前LLVM
已經(jīng)被蘋果iOS開發(fā)工具
、Xilinx Vivado兔港、Facebook仔拟、Google等各大公司采用
2、傳統(tǒng)編譯器
- 編譯器前端(Frontend):
編譯器的前端任務(wù)是解析源代碼科侈。 會(huì)進(jìn)行詞法分析
臀栈、語法分析
挠乳、語義分析
姑躲。檢查
源代碼是否存在錯(cuò)誤
黍析,然后構(gòu)建抽象語法樹
(Abstract Syntax Tree AST)屎开,LLVM前端
還會(huì)生成中間代碼(intermediate representation, IR
)
- 優(yōu)化器(Optimizer)
優(yōu)化器負(fù)責(zé)各種優(yōu)化
。改善
代碼的運(yùn)行時(shí)間
蔼两,如消除冗余計(jì)算
等
源代碼中有很多函數(shù)調(diào)用额划,就會(huì)需要需要申請(qǐng)很多函數(shù)調(diào)用椀翟螅空間,調(diào)用函數(shù)棧時(shí)品抽,需要壓棧輸入數(shù)據(jù)甜熔,調(diào)用完畢后腔稀,出棧羽历。其中過程邏輯可能非常復(fù)雜,那么就會(huì)無形中就會(huì)占用很多內(nèi)存诵闭,優(yōu)化器就是用來優(yōu)化一些邏輯澎嚣,以節(jié)省時(shí)間和空間易桃。
- 后端(Backkend)/ 代碼生成器(CodeGenerator)
將代碼映射
到目標(biāo)指令集
,生成機(jī)器語言
敌呈,并進(jìn)行機(jī)器相關(guān)的代碼優(yōu)化
(目標(biāo)指不同操作系統(tǒng))
3、iOS
的編譯器架構(gòu)
Objective-C吭练、C褐鸥、C++
編譯器的前端用的都是Clang
,Swift
使用的是swift
叫榕,后端使用的是llvm
三、LLVM的設(shè)計(jì)
1寓落、llvm:支持多種語言
或多種硬件架構(gòu)
荞下。使用通用代碼表示形式:IR
(用來在編譯器中表示代碼的形式)
??GCC
也是一個(gè)非常成功的編譯器
尖昏,但由于它作為整體
應(yīng)用程序設(shè)計(jì)的,用途
受到了限制
因?yàn)?code>llvm使用IR
作為中間文件
,所以
LLVM
可以為任何
編程語言
獨(dú)立編寫前端
陨簇,也可以為任何
硬件架構(gòu)
獨(dú)立編寫后端
.-
LLVM
不
是一個(gè)簡(jiǎn)單
的編譯器
迹淌,而是架構(gòu)編譯器
唉窃,可以兼容
所有前端
和后端
。
2苟跪、Clang
Clang
是LLVM
項(xiàng)目的一個(gè)子項(xiàng)目
件已〈浪瘢基于LLVM架構(gòu)
的輕量級(jí)編輯器
,誕生之初就是為了替代GCC
瞻惋,提供更快
的編譯速度
。 他是負(fù)責(zé)編譯C掏导、C++羽峰、Objecte-C語言
的編譯器梅屉,它屬于整個(gè)LLVM
架構(gòu)中的編譯器前端
對(duì)于開發(fā)者而言,研究Clang
可以給我們帶來很出益處
下面就Clang
分析一下OC編譯
流程
3虐唠、編譯流程
-
新建一個(gè).m文件
-
進(jìn)入文件
Test2
執(zhí)行命令clang -ccc-print-phases main.m
此步驟為7步驟
0: 輸入文件:找到源文件
1: 預(yù)處理:宏的展開疆偿,頭文件的導(dǎo)入
2: 編譯:詞法杆故、語法溉愁、語義分析,最終生成IR
3: 匯編: LLVM通過一個(gè)個(gè)的Pass去優(yōu)化罢缸,每個(gè)Pass做一些事投队,最后生成匯編代碼
4: 生成 目標(biāo)文件
5: 鏈接: 鏈接需要的動(dòng)態(tài)庫和靜態(tài)庫敷鸦,生成可執(zhí)行文件
6:架構(gòu)可執(zhí)行文件:通過不同架構(gòu)寝贡,生成對(duì)應(yīng)的可執(zhí)行文件
這里并沒有出現(xiàn)optimizer優(yōu)化器,因?yàn)樗仟?dú)立觸發(fā)碟案,與流程階段無關(guān)
-
預(yù)處理
在.文件里定義一個(gè)宏:C
使用命令:clang -E main.m >> main2.m
,生成main2.m
文件
查看main2.m:大部分是stdio庫的代碼价说,定位到main函數(shù)里,發(fā)現(xiàn)
C變成了50了
得知扮叨,
預(yù)處理:????????????????????????????????????????完成2個(gè)步驟彻磁,
- 導(dǎo)入頭文件 2.替換宏
還有一個(gè)定義類型的叫做typedef
,這里我們把 int
替換成wl_INT64
,試試是否
會(huì)還原
預(yù)處理結(jié)束:
發(fā)現(xiàn)tydef并沒有被還原
由此我們可以根據(jù)預(yù)處理結(jié)果屡限,做一些安全管理方面的混淆措施
使用define,將重要方法名稱或者類進(jìn)行替換瞧省。比如用#define BUY XXXDemo
代碼中使用宏BUY鞍匾,被hank時(shí),實(shí)際代碼是XXXDemo构拳,不易被發(fā)現(xiàn)梁棠。
且#define的真實(shí)內(nèi)容,不應(yīng)該寫成亂碼凫海,會(huì)讓人有此地?zé)o銀三百兩的感覺男娄,最好弄成系統(tǒng)類似名稱或其他不經(jīng)意的名稱模闲。才會(huì)被忽視,安全級(jí)別會(huì)更高 啰脚。
- 編譯
【1】詞法分析
使用clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
這些將代碼拆分成一個(gè)個(gè)Token橄浓。標(biāo)注了位置是第幾行的第幾個(gè)字符開始的。
【2】語法分析
是驗(yàn)證語法是否正確:
- 在詞法分析的基礎(chǔ)上谍倦,將單詞序列組合成各類語法短語泪勒,如“程序”,“語句”叼旋,“表達(dá)式”等沦辙,然后將所有節(jié)點(diǎn)組成抽象語法樹(Abstract Syntax Tree油讯,AST)。 語法分析程序判斷源程序在結(jié)構(gòu)上是否正確沈跨。
使用clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
- 可以看到語法樹饿凛。每一個(gè)操作都生成一個(gè)變量软驰,作用域、數(shù)據(jù)類型纠吴、運(yùn)算方式都在語法樹體現(xiàn)出來贰镣。
- 語法樹一次只能處理一次計(jì)算兩次運(yùn)算碑隆,就得多分一層蹬音。
- 語法分析著淆,就是在生成語法樹時(shí)完成檢測(cè)的
?????????????????????????????? 如果頭文件找不到拴疤,可以指定SDK
clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator12.2.sdk(自己SDK路徑) -fmodules -fsyntax-only -Xclang -ast-dump main.m
-
生成中間代碼IR(Intermediate representation)
完成以上步驟后独泞,就開始生成中間代碼IR懦砂,代碼生成器(Code Generation)會(huì)將語法樹自頂向下遍歷逐步翻譯成LLVM的IR。通過下面命令生成.ll文本文件罚随,查看IR代碼:
clang -S -fobjc-arc -emit-llvm main.m
代碼在這一階段進(jìn)行runtime的橋接:property的合成淘菩,ARC處理等
IR的基本語法
@ 全局標(biāo)識(shí)
% 局部標(biāo)識(shí)
alloca 開辟空間
align 內(nèi)存對(duì)齊
i32 32個(gè)bit屠升,4個(gè)字節(jié)
store 寫入內(nèi)存
load 讀取數(shù)據(jù)
call 調(diào)用數(shù)據(jù)
ret 返回
-
IR的優(yōu)化
llvm的優(yōu)化級(jí)別分別是 ???????????????? -O0 -O1 -O2 -O3 -Os(????????????????????O)
使用命令clang -Os -S -fobjc-arc -emit-llvm main.m -o main.ll
bitCode
Xcode7之后腹暖,開啟bitCode蘋果會(huì)做進(jìn)一步的優(yōu)化,生成.bc的中間代碼趾疚,我們通過優(yōu)化后的IR代碼生成.bc文件
clang -emit-llvm -c main.ll -o main.bc
補(bǔ):日常開發(fā)中以蕴,我們引入第三方庫,有時(shí)會(huì)提示不支持bitcode赡磅,可以通過一焚廊、將源碼編譯一下习劫,二、工程設(shè)置不支持.bitcode即可
-
生成匯編代碼
我們通過最終生成的.bc或者.ll代生成匯編代碼
clang -S -fobjc-arc main.bc -o main.s
clang -S -fobjc-arc main.ll -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
通過nm命令霉囚,查看下main.o中的符號(hào):nm -nm main.o
_printf 是一個(gè)是undefined external的
undefined表示在當(dāng)前文件暫時(shí)找不到符號(hào)_printf
external表示這個(gè)符號(hào)是外部可以訪問的佛嬉。
以上打印結(jié)果表示是因?yàn)閜rintf闸天、PoolPop、PoolPush函數(shù)是來自于其他庫湾揽,所以找不到笼吟,需要鏈接其他目標(biāo)文件
-
生成可執(zhí)行文件(鏈接)??????????????????????
連接器把編譯產(chǎn)生的.o文件和(.dylib.a)文件贷帮,生成一個(gè)mach-o文件
clang main.o -o main
查看鏈接后的符號(hào)
nm -nm main
四、重新編輯Clang插件
由于國(guó)內(nèi)網(wǎng)絡(luò)限制民晒,我們需要借助鏡像下載LLVM源碼
https://mirror.tuna.tsinghua.edu.cn/help/llvm/
- 在llvm的tools目錄下下載Clang
cd llvm/tools
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/clang.git
- 在LLVM的projects目錄下下載compiler-rt??libcxx锄禽、??libcxxabi
- 在clang的tools下安裝extra工具
- llvm編譯沃但,最新的只支持cmake來編譯,所以需要安裝cmake
- 通過brew安裝cmake
brew install cmake
通過Xcode編譯llvm
- cmake編譯成Xcode項(xiàng)目
一頓猛如虎的操作后垂攘,電腦沒燒壞的情況下淤刃,就編譯成功了,然后創(chuàng)建插件仪芒,請(qǐng)聽下回分解~??