iOS之武功秘籍?: LLVM編譯流程

iOS之武功秘籍 文章匯總

寫在前面

本文主要是理解LLVM的編譯流程

本節(jié)可能用到的秘籍Demo

一、什么是編譯器?

① Python案例

  • 創(chuàng)建Python文件夾衙吩,新建helloDemo.py文件笔刹,內(nèi)容 print("hello\n")
  • 調(diào)用python helloDemo.py執(zhí)行文件芥备,打印出hello

② C 案例

  • vim創(chuàng)建helloDemo.c文件
  • 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í)行:

③ 相關(guān)疑問

③.1 解釋型語言與編譯型語言

  • 編譯型語言編譯后輸出的是指令0、1組合)日月,cpu可直接執(zhí)行指令
    • C語言編譯型語言袱瓮,不能直接執(zhí)行,需要編譯器將其轉(zhuǎn)換成機器識別語言
  • 解釋性語言:生成的是數(shù)據(jù)爱咬,不是0尺借、1組合,機器也能直接識別
    • python解釋型語言精拟,一邊翻譯一邊執(zhí)行.和js一樣燎斩,機器可直接執(zhí)行.

編譯器的作用,就是將高級語言轉(zhuǎn)化為機器能夠識別的語言(可執(zhí)行文件)

③.2 匯編有指令嗎蜂绎?

  • 早期科學(xué)家栅表,使用0、1編碼. 比如 00001111 對應(yīng) call师枣, 00000111 對應(yīng)bl.有了對應(yīng)關(guān)系后. 再手敲0和1就有點難受了.于是寫個中間解釋器怪瓶,我們只用輸入callbl這樣的標(biāo)記指令践美,經(jīng)過解釋器洗贰,變成0和1的組合,再交給機器去執(zhí)行.這就是匯編的由來.
  • 而基于匯編往上陨倡,再映射封裝相關(guān)對應(yīng)關(guān)系.就跨時代性c語言敛滋,再往上層封裝,就出現(xiàn)了高級語言oc玫膀、swift等語言.所以匯編執(zhí)行快矛缨,因為它是直接轉(zhuǎn)換機器語言.
  • 匯編指令集爹脾,是針對同一操作系統(tǒng)而言帖旨,它不支持跨平臺.機器指令cpu的在識別.早期的計算機廠家非常多,雖然都用01的組合灵妨,但相同組合背后卻是相應(yīng)不同的指令.所以匯編無法跨平臺解阅,不同操作系統(tǒng)下,匯編指令不同的.

二泌霍、LLVM概述

LLVM架構(gòu)編譯器(compiler)的框架系統(tǒng)货抄,以C++編寫而成述召,用于優(yōu)化以任意程序語言編寫的程序的編譯時間(compile-time)、鏈接時間(link-time)蟹地、運行時間(run-time)以及空閑時間(idle-time)积暖,對開發(fā)者保持開放,并兼任已有腳本.
LLVM計劃啟動于2000年,最初由美國UIUC大學(xué)的Chris Lattner博士主持開展.
2006年Chris Lattner加盟Apple Inc.并致力于LLVMApple開發(fā)體系中的應(yīng)用.Apple也是LLVM計劃的主要資助者.
目前LLVM已經(jīng)被蘋果iOS開發(fā)工具怪与、Xilinx Vivado夺刑、FacebookGoogle等各大公司采用.

三分别、傳統(tǒng)編譯器的設(shè)計

源碼 Source Code + 前端 Frontend + 優(yōu)化器 Optimizer + 后端 Backend(代碼生成器 CodeGenerator)+ 機器碼 Machine Code遍愿,如下圖所示

編譯器前端(Frontend)
編譯器前端的任務(wù)是解析源代碼(編譯階段),它會進行 詞法分析耘斩、語法分析沼填、語義分析檢查源代碼是否存在錯誤括授,然后構(gòu)建抽象語法樹(Abstract Syntax Tree AST)坞笙,LLVM的前端還會生成中間代碼(intermediate representation,簡稱IR)刽脖,可以理解為LLVM編譯器 + 優(yōu)化器羞海, 接收的是IR中間代碼,輸出的還是IR曲管,給后端却邓,經(jīng)過后端翻譯成目標(biāo)指令集

優(yōu)化器(Optimizer)
優(yōu)化器負責(zé)進行各種優(yōu)化,改善代碼的運行時間院水,例如消除冗余計算等

后端(Backend)/(代碼生成器 Code Generator)
將代碼映射到目標(biāo)指令集腊徙,生成機器代語言,并且進行機器代碼相關(guān)的代碼優(yōu)化

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

Objective C/C/C++ 使用的編譯器前端是Clang檬某,Swift是swift撬腾,后端都是LLVM.

LLVM的設(shè)計

當(dāng)編譯器決定支持多種源語言或多種硬件架構(gòu)時,LLVM最重要的地方就來了.其他的編譯器如GCC,它方法非常成功,但由于它是作為整體應(yīng)用程序設(shè)計的,因此它們的用途受到了很大的限制.
LLVM設(shè)計的最重要方面是,使用通用的代碼表示形式(IR)恢恼,它是用來在編譯器中表示代碼的形式民傻,所以LLVM可以為任何編程語言獨立編寫前端,并且可以為任意硬件架構(gòu)獨立編寫后端场斑,如下所示

通俗的一句話理解就是:LLVM的設(shè)計是前后端分離的漓踢,無論前端還是后端發(fā)生變化,都不會影響另一個

Clang簡介

ClangLLVM項目中的一個子項目漏隐,它是基于LLVM架構(gòu)圖的輕量級編譯器喧半,誕生之初是為了替代GCC,提供更快的編譯速度青责,它是負責(zé)C挺据、C++取具、OC語言的編譯器,屬于整個LLVM架構(gòu)中的 編譯器前端扁耐,對于開發(fā)者來說暇检,研究Clang可以給我們帶來很多好處

四、LLVM編譯流程

  • 新建一個Mac OS命令行工程:
  • 沒有改動代碼

① 打印源碼的編譯階段

  • cdmain.m的文件夾.使用clang -ccc-print-phases main.m命令查看main.m的編譯步驟:

編譯流程分為以下7步

  • 0: input, "main.m", objective-c
    • 輸入文件:找到源文件
  • 1: preprocessor, {0}, objective-c-cpp-output
    • 預(yù)處理:宏的展開婉称,頭文件的導(dǎo)入
  • 2: compiler, {1}, ir
    • 編譯:詞法占哟、語法、語義分析酿矢,最終生成IR
  • 3: backend, {2}, assembler ()
    • 匯編: LLVM通過一個個的Pass去優(yōu)化榨乎,每個Pass做一些事,最后生成匯編代碼
  • 4: assembler, {3}, object
    • 目標(biāo)文件
  • 5: linker, {4}, image
    • 鏈接: 鏈接需要的動態(tài)庫和靜態(tài)庫瘫筐,生成可執(zhí)行文件
  • 6: bind-arch, "x86_64", {5}, image
    • 架構(gòu)可執(zhí)行文件:通過不同架構(gòu)蜜暑,生成對應(yīng)的可執(zhí)行文件

optimizer優(yōu)化并沒有作為一個獨立階段,在編譯階段內(nèi)部完成的

② 預(yù)處理階段

這個階段主要是處理包括宏的替換策肝,頭文件的導(dǎo)入肛捍,可以執(zhí)行如下命令,執(zhí)行完畢可以看到頭文件的導(dǎo)入和宏的替換

  • main.m文件中準(zhǔn)備測試代碼:
  • clang預(yù)編譯輸出main2.m文件:通過指令clang -E main.m >> main2.m
  • 打開main2.m文件其中大部分是stdio庫的代碼:
  • 我們發(fā)現(xiàn)測試代碼中的宏C之众,在預(yù)編譯階段完成了替換拙毫,變成了30

  • 修改測試代碼,給int類型取個別名CJ_INT_64棺禾,再次預(yù)編譯處理

  • 發(fā)現(xiàn)typedef不會被替換

小結(jié):

  • typedef在給數(shù)據(jù)類型取別名時缀蹄,在預(yù)處理階段不會被替換掉
  • define則在預(yù)處理階段會被替換,所以經(jīng)常被用來進行代碼混淆膘婶,目的是為了app安全缺前,實現(xiàn)邏輯是:將app中核心類、核心方法等用系統(tǒng)相似的名稱進行取別名悬襟,然后在預(yù)處理階段就被替換了衅码,來達到代碼混淆的目的

③ 編譯階段

編譯階段主要是進行詞法、語法等的分析和檢查脊岳,然后生成中間代碼IR

③.1 詞法分析

預(yù)處理完成后就會進行詞法分析逝段,這里會把代碼切成一個個Token,比如大小括號割捅、等于號還有字符串等,而且還標(biāo)注了位置第幾行的第幾個字符開始的.

  • 可以通過clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m命令查看

③.2 語法分析

詞法分析完成后就是語法分析奶躯,它的任務(wù)是驗證語法是否正確,在詞法分析的基礎(chǔ)上將單詞序列組合成各類此法短語棺牧,如程序巫糙、語句朗儒、表達式 等等颊乘,然后將所有節(jié)點組成抽象語法樹(Abstract Syntax Tree, AST)参淹,語法分析程序判斷源程序結(jié)構(gòu)上是否正確.

  • 可以通過clang -fmodules -fsyntax-only -Xclang -ast-dump main.m命令查看語法分析的結(jié)果

其中,主要說明幾個關(guān)鍵字的含義

  • -FunctionDecl 函數(shù)
  • -ParmVarDecl 參數(shù)
  • -CallExpr 調(diào)用一個函數(shù)
  • -BinaryOperator 運算符

④ 生成中間代碼IR

完成以上步驟后乏悄,就開始生成中間代碼IR了檩小,代碼生成器(Code Generation)會將語法樹自頂向下遍歷逐步翻譯成LLVM IR.

便于理解开呐,我們簡化代碼:
  • 通過clang -S -fobjc-arc -emit-llvm main.m命令可以生成.ll的文本文件,查看IR代碼.OC代碼在這一步會進行runtime橋接:property合成规求、ARC處理等

IR基本語法

  • @ 全局標(biāo)識
  • % 局部標(biāo)識
  • alloca 開辟空間
  • align 內(nèi)存對齊
  • i32 32bit筐付,4個字節(jié)
  • store 寫入內(nèi)存
  • load 讀取數(shù)據(jù)
  • call 調(diào)用函數(shù)
  • ret 返回

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

其中,test函數(shù)的參數(shù)解釋為

圖中為何多創(chuàng)建那么多局部變量阻肿?(如test函數(shù)內(nèi)的a5瓦戚、a6)
因為在上一階段(編譯階段),我們將代碼編譯成了語法樹結(jié)構(gòu).而此時丛塌,我們只是沿著語法樹進行讀取.語法樹每一個層級较解,都需要一個臨時變量來承接.再返回上一層級處理.所以會產(chǎn)生那么多局部變量

當(dāng)然,IR文件在OC中是可以進行優(yōu)化的赴邻,一般Xcode中設(shè)置是在target - Build Setting - Optimization Level(優(yōu)化器等級)中設(shè)置.(Debug模式默認None [O0]無優(yōu)化印衔,Release模式默認Fastest,Smallest [Os]最快最小)

LLVM的優(yōu)化級別分別是-O0 -O1 -O2 -O3 -Os(第一個是大寫英文字母O),下面是帶優(yōu)化的生成中間代碼IR的命令

clang -Os -S -fobjc-arc -emit-llvm main.m -o main.ll

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

優(yōu)化后的代碼姥敛,舒服多了.之前那些冗余的臨時局部變量奸焙,也都被優(yōu)化,代碼量減少很多.

  • xcode7以后開啟bitcode彤敛,蘋果會做進一步優(yōu)化忿偷,生成.bc的中間代碼,我們通過優(yōu)化后的IR代碼生成.bc代碼.
    • 優(yōu)化指令clang -emit-llvm -c main.ll -o main.bc

⑤ 生成匯編代碼

LLVM在后端主要是會通過一個個的Pass去優(yōu)化臊泌,每個Pass做一些事情鲤桥,最終生成匯編代碼

  • 完成中間代碼的生成后,可以將代碼轉(zhuǎn)變?yōu)?code>匯編代碼了
  • 此刻我們有4種不同程度的代碼(源代碼->無優(yōu)化IR代碼->Os優(yōu)化IR代碼 -> bitcode優(yōu)化代碼
  • 分別對4種程度的代碼輸出匯編文件:
clang -S -fobjc-arc main.m -o main.s
clang -S -fobjc-arc main.ll -o mainO0.s
clang -S -fobjc-arc mainOs.ll -o mainOs.s
clang -S -fobjc-arc main.bc -o mainbc.s

可以看到在生成匯編代碼時渠概,只有選擇了優(yōu)化等級茶凳,才能減少匯編代碼量.

  • 生成匯編代碼也可以進行優(yōu)化---即在生成中間代碼的前后,都可以進行優(yōu)化
    • ① 將main.m直接選擇Os級別優(yōu)化生成.s匯編文件--clang -Os -S -fobjc-arc main.m -o mainOs.s
    • ② 將main.m生成無優(yōu)化的mainO0.ll播揪,再mainO0.ll選擇Os級別優(yōu)化生成.s匯編文件 -- clang -S -fobjc-arc -emit-llvm main.m -o mainO0.ll,clang -Os -S -fobjc-arc mainO0.ll -o mainO0Os.s
    • ③ 將main.m選擇Os級別優(yōu)化生成mainOs.ll贮喧,再mainOs.ll選擇無優(yōu)化級別生成.s匯編文件 -- clang -Os -S -fobjc-arc -emit-llvm main.m -o mainOs.ll,clang -S -fobjc-arc mainOs.ll -o mainOsO0.s
    • ④ 將main.m選擇Os級別優(yōu)化生成mainOs.ll,再mainOs.ll選擇Os級別優(yōu)化生成.s匯編文件 -- clang -Os -S -fobjc-arc -emit-llvm main.m -o mainOs.ll,clang -Os -S -fobjc-arc mainOs.ll -o mainOsOs.s

⑥ 生成目標(biāo)文件(機器代碼)

目標(biāo)文件的生成猪狈,是匯編器匯編代碼作為插入箱沦,將匯編代碼轉(zhuǎn)換為機器代碼,最后輸出目標(biāo)文件(object file)-- clang -fmodules -c main.s -o main.o

  • 此時我們file對比一下main.s匯編代碼和main.o機器代碼.
  • 可以通過nm命令雇庙,查看下main.o中的符號 -- xcrun nm -nm main.o

    • _printf函數(shù)是一個是undefined 谓形、external
    • undefined表示在當(dāng)前文件暫時找不到符號_printf
    • external表示這個符號外部可以訪問的

所以當(dāng)前雖轉(zhuǎn)換成了機器代碼.但是只是目標(biāo)文件灶伊,并不能直接執(zhí)行,需要將所有資源鏈接起來寒跳,才可以執(zhí)行.

⑦ 生成可執(zhí)行文件(鏈接)

鏈接主要是鏈接需要的動態(tài)庫靜態(tài)庫聘萨,生成可執(zhí)行文件,其中

  • 靜態(tài)庫會和可執(zhí)行文件合并
  • 動態(tài)庫是獨立的

連接器把編譯生成的.o文件和 .dyld 童太、.a文件鏈接米辐,生成一個mach-o文件,接著輸入以下指令

clang main.o -o main // 將目標(biāo)文件轉(zhuǎn)成可執(zhí)行文件
file main            // 查看文件
xcrun nm -nm main    // 查看main的符號

結(jié)果如下所示,其中的undefined表示會在運行時進行動態(tài)綁定

對比main.o目標(biāo)文件书释,此時生成的main文件:

  • object文件變成了executable可執(zhí)行文件
  • 雖然都有undefined翘贮,但是可執(zhí)行文件中指定了該符號的來源庫.機器在運行時,會從相應(yīng)的庫中取讀取該符號(printf)

⑧ 綁定

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

至此择膝,我們已完整分析了:從源代碼可執(zhí)行文件整個流程.

寫在后面

和諧學(xué)習(xí),不急不躁.我還是我,顏色不一樣的煙火.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市检激,隨后出現(xiàn)的幾起案子肴捉,更是在濱河造成了極大的恐慌,老刑警劉巖叔收,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件齿穗,死亡現(xiàn)場離奇詭異,居然都是意外死亡饺律,警方通過查閱死者的電腦和手機窃页,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來复濒,“玉大人脖卖,你說我怎么就攤上這事∏删保” “怎么了畦木?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長砸泛。 經(jīng)常有香客問我十籍,道長,這世上最難降的妖魔是什么唇礁? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任勾栗,我火速辦了婚禮,結(jié)果婚禮上盏筐,老公的妹妹穿的比我還像新娘围俘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布界牡。 她就那樣靜靜地躺著簿寂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪欢揖。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天奋蔚,我揣著相機與錄音她混,去河邊找鬼。 笑死泊碑,一個胖子當(dāng)著我的面吹牛坤按,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播馒过,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼臭脓,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了腹忽?” 一聲冷哼從身側(cè)響起来累,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎窘奏,沒想到半個月后嘹锁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡着裹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年领猾,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片骇扇。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡摔竿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出少孝,到底是詐尸還是另有隱情继低,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布稍走,位于F島的核電站郁季,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏钱磅。R本人自食惡果不足惜梦裂,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一席里、第九天 我趴在偏房一處隱蔽的房頂上張望六剥。 院中可真熱鬧劫窒,春花似錦铸抑、人聲如沸安寺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽喳挑。三九已至,卻和暖如春掀抹,著一層夾襖步出監(jiān)牢的瞬間虐拓,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工傲武, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蓉驹,地道東北人。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓揪利,卻偏偏與公主長得像态兴,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子疟位,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容

  • LLVM 簡介 LLVM 是架構(gòu)編譯器(compiler)的框架系統(tǒng)瞻润,以 C++ 編寫而成,用于優(yōu)化以任意程序語言...
    遠方竹葉閱讀 965評論 0 2
  • 目錄 傳統(tǒng)編譯器設(shè)計 輸入源代碼(Obj-C, Swift, ...) → 編譯器處理 → 輸出機器碼(01010...
    小瞎_MarkDash閱讀 1,246評論 0 2
  • 前言 作為一個合格的iOS開發(fā)者甜刻,我們必須清楚绍撞,我們平時寫的代碼,是如何一步步轉(zhuǎn)變生成App包的得院,這個過程就是我們...
    深圳_你要的昵稱閱讀 1,759評論 0 6
  • 前言 語言類型 我們有很多維度可以將計算機語言進行分類楚午,其中以編譯/執(zhí)行方式為維度,可以將計算機語言分為: 編譯型...
    AiLearn閱讀 2,396評論 1 6
  • LLVM的編譯流程 在介紹編譯流程之前尿招,首先回顧一下LLVM:LLVM是一個模塊化的矾柜、可重用的編譯器和工具鏈技術(shù)的...
    愛笑的云里看夢閱讀 6,507評論 1 8