25:LLVM 簡介和編譯流程詳解

目錄

image.png

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

image.png
  • 輸入源代碼(Obj-C, Swift, ...) → 編譯器處理 → 輸出機(jī)器碼(010101)

  • 編譯器處理分為以下步驟

前端 (Frontend)

負(fù)責(zé)解析源代碼铭段,進(jìn)行:

  • 詞法分析

  • 語法分析骤宣,語義分析,檢查源代碼是否有錯誤序愚,構(gòu)建 抽象語法樹 (Abstract Syntax Tree, AST)

優(yōu)化器 (Optimizer)

負(fù)責(zé)進(jìn)行各種優(yōu)化憔披。例如消除冗余計(jì)算 (甚至直接將方法優(yōu)化成一個固定值,而不去調(diào)用方法)等爸吮。

后端 (Backend)

將代碼映射到目標(biāo)指令集芬膝。生成機(jī)器語言,此過程會再次優(yōu)化 (機(jī)器語言層面)形娇。

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

  • 從圖里看出锰霜,編譯器前端輸入源代碼,后端輸出機(jī)器碼桐早。因?yàn)閭鹘y(tǒng)編譯器是按照整體程序設(shè)計(jì)的锈遥,所以總共需要做 n×m 個編譯器。

  • LLVM使用通用的代碼表現(xiàn)形式 (IR勘畔,可以理解為中間碼)所灸,優(yōu)化器的出入口都是IR,所以LLVM可以為任何編程語言獨(dú)立編寫前端炫七,為任何硬件架構(gòu)獨(dú)立編寫后端爬立,工作量縮減為 n+m,且能集中力量不斷提升優(yōu)化器性能万哪。

    image.png

Clang 編譯流程

ClangLLVM的一個子項(xiàng)目侠驯。它屬于整個LLVM架構(gòu)的編譯器 前端抡秆,負(fù)責(zé)編譯 CC++吟策、Objective-C儒士。

運(yùn)行命令,打印源碼編譯階段

運(yùn)行命令clang -ccc-print-phases main.m

0: input, "main.m", objective-c
1: preprocessor, {0}, objective-c-cpp-output
2: compiler, {1}, ir
3: backend, {2}, assembler
4: assembler, {3}, object
5: linker, {4}, image
6: bind-arch, "x86_64", {5}, image
  • 0:輸入文件:找到源文件
  • 1:預(yù)處理:替換宏檩坚,但不會替換別名typedef着撩;頭文件導(dǎo)入并展開,包括頭文件的頭文件匾委,代碼行數(shù)激增
  • 2:編譯:詞法分析 (切割成一個個詞拖叙,不檢查語法錯誤)、語法分析 (組裝詞赂乐,檢查語法錯誤)薯鳍、最終生成IR
  • 3:后端:LLVM通過一個個Pass (類似節(jié)點(diǎn)) 去優(yōu)化,每個Pass有自己的優(yōu)化方式挨措,最終生成匯編代碼
  • 4:把匯編文件變成.o文件
  • 5:各個.o文件有聯(lián)系挖滤,需要進(jìn)行鏈接,生成Mach-O文件
  • 6:對應(yīng)不同架構(gòu)浅役,生成對應(yīng)的Mach-O文件

1: 預(yù)處理

  • main.m文件

    #import <stdio.h>
    
    #define a 10
    
    typedef int MD_INT_64;
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            MD_INT_64 b = 20;
            printf("sum = %d", a + b + 50);
        }
        return 0;
    }
    
  • 運(yùn)行命令clang -E main.m >> main1.cpp壶辜,如果不輸入>> main1.cpp,則不會新生成文件担租,而直接在命令行工具打印砸民。以下省略前面549行代碼 ↓

    typedef int MD_INT_64;
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
    
            MD_INT_64 b = 20;
            printf("sum = %d", 10 + b + 50);
        }
        return 0;
    }
    

2.1: 編譯-詞法分析 (切割詞)

  • 運(yùn)行命令clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m

  • 第幾行,第幾個字符開始奋救,第幾個字符結(jié)束岭参,一目了然。只截取了一些 ↓

            // insert'      Loc=<main.m:9:1>
    typedef 'typedef'    [StartOfLine]  Loc=<main.m:13:1>
    int 'int'    [LeadingSpace] Loc=<main.m:13:9>
    identifier 'MD_INT_64'   [LeadingSpace] Loc=<main.m:13:13>
    semi ';'        Loc=<main.m:13:22>
    int 'int'    [StartOfLine]  Loc=<main.m:15:1>
    identifier 'main'    [LeadingSpace] Loc=<main.m:15:5>
    l_paren '('     Loc=<main.m:15:9>
    int 'int'       Loc=<main.m:15:10>
    identifier 'argc'    [LeadingSpace] Loc=<main.m:15:14>
    

2.2: 編譯-語法分析 (重新組合尝艘,生成抽象語法樹)

  • 運(yùn)行命令clang -fmodules -fsyntax-only -Xclang -ast-dump main.m

  • 如果導(dǎo)入了iOS特有的頭文件演侯,需要修改一下指令 (僅供參考,每個人電腦路徑和模擬器版本不一樣) clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator12.2.sdk??????SDK?????? -fmodules -fsyntax-only -Xclang -ast-dump main.m

  • 經(jīng)過重新組合背亥,語法分析出來的代碼行數(shù)通常會比詞法分析短一些秒际,譬如詞法分析里的intargc狡汉,在語法分析里變成一行這是一個名叫argc的int類型參數(shù)娄徊。最好帶著棧思維去讀抽象語法樹。只截取了一些 ↓

    |-TypedefDecl 0x7fd405845368 <line:13:1, col:13> col:13 referenced MD_INT_64 'int'
    | `-BuiltinType 0x7fd405036700 'int'
    `-FunctionDecl 0x7fd405845640 <line:15:1, line:22:1> line:15:5 main 'int (int, const char **)'
      |-ParmVarDecl 0x7fd4058453d8 <col:10, col:14> col:14 argc 'int'
      |-ParmVarDecl 0x7fd4058454f0 <col:20, col:38> col:33 argv 'const char **':'const char **'
      `-CompoundStmt 0x7fd4050f1ad8 <col:41, line:22:1>
        |-ObjCAutoreleasePoolStmt 0x7fd4050f1a90 <line:16:5, line:20:5>
        | `-CompoundStmt 0x7fd4050f1a70 <line:16:22, line:20:5>
        |   |-DeclStmt 0x7fd4050f1868 <line:18:9, col:25>
        |   | `-VarDecl 0x7fd4050f1400 <col:9, col:23> col:19 used b 'MD_INT_64':'int' cinit
        |   |   `-IntegerLiteral 0x7fd4050f1468 <col:23> 'int' 20
        |   `-CallExpr 0x7fd4050f1a10 <line:19:9, col:38> 'int'
        |     |-ImplicitCastExpr 0x7fd4050f19f8 <col:9> 'int (*)(const char *, ...)' <FunctionToPointerDecay>
        |     | `-DeclRefExpr 0x7fd4050f1880 <col:9> 'int (const char *, ...)' Function 0x7fd4050f1490 'printf' 'int (const char *, ...)'
    

2.3 / 3.0: 生成中間碼 IR (Intermediate Representation) / Pass 優(yōu)化

  • 代碼生成器 (Code Generation) 會將語法樹自頂向下遍歷盾戴,翻譯成LLVM IR

  • 運(yùn)行命令clang -S -fobjc-arc -emit-llvm main.m寄锐,獲得main.ll文件。和匯編有點(diǎn)像。只截取了main函數(shù) ↓

  • IR基本語法

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

    define i32 @main(i32, i8**) #0 {
      %3 = alloca i32, align 4
      %4 = alloca i32, align 4
      %5 = alloca i8**, align 8
      %6 = alloca i32, align 4
      store i32 0, i32* %3, align 4
      store i32 %0, i32* %4, align 4
      store i8** %1, i8*** %5, align 8
      %7 = call i8* @llvm.objc.autoreleasePoolPush() #1
      store i32 20, i32* %6, align 4
      %8 = load i32, i32* %6, align 4
      %9 = add nsw i32 10, %8
      %10 = add nsw i32 %9, 50
      %11 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([9 x i8], [9 x i8]* @.str, i64 0, i64 0), i32 %10)
      call void @llvm.objc.autoreleasePoolPop(i8* %7)
      ret i32 0
    }
    
  • 剛才是沒有優(yōu)化的剩膘,看看優(yōu)化的,LLVM的優(yōu)化級別分別為-O0 -O1 -O2 -03 -Os盆顾,我們試試-Os怠褐,運(yùn)行命令clang -Os -S -fobjc-arc -emit-llvm main.m,獲得main.ll文件您宪。print函數(shù)的參數(shù)奈懒,直接用絕對值80,而不像剛才用局部變量算來算去蚕涤。只截取了main函數(shù) ↓

    define i32 @main(i32, i8** nocapture readnone) local_unnamed_addr #0 {
      %3 = tail call i8* @llvm.objc.autoreleasePoolPush() #1
      %4 = tail call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([9 x i8], [9 x i8]* @.str, i64 0, i64 0), i32 80) #3, !clang.arc.no_objc_arc_exceptions !9
      tail call void @llvm.objc.autoreleasePoolPop(i8* %3) #1
      ret i32 0
    }
    
  • 這個優(yōu)化級別在Xcode可以調(diào):Build SettingsCode GenerationDebug模式下為了編譯快點(diǎn)一般不優(yōu)化铣猩,選None [-O0]

    image.png

LLVM的優(yōu)化使用了叫Pass的東西揖铜,可以理解為優(yōu)化節(jié)點(diǎn),每個節(jié)點(diǎn)負(fù)責(zé)不同的優(yōu)化事項(xiàng) (跳轉(zhuǎn)达皿、運(yùn)算等)天吓,一個個Pass搞下來,邏輯處理發(fā)生變化峦椰,就完成了優(yōu)化龄寞。如果想玩LLVM優(yōu)化可以試試寫Pass

Pass能使FuncA→FuncB→FuncC變成FuncA→FuncC甚至FuncA(算好的值)汤功;也能使FuncA→FuncB變成FuncA→FuncX→FuncY→FuncB物邑,變得復(fù)雜,做到混淆效果滔金。不光是邏輯色解,其中的局部標(biāo)識也能增加。直接混淆還能看懂些餐茵,優(yōu)化完以后再混淆就真的難看懂科阎。

2.4: Bitcode

Xcode7以后,Enable Bitcode蘋果會在IR的基礎(chǔ)上做進(jìn)一步的優(yōu)化忿族,生成.bc代碼锣笨。

iOS端:Bitcode可選
watchOS端:Bitcode必選
macOS端:Bitcode不可選

  • 運(yùn)行命令clang -emit-llvm -c main.ll -o main.bc.bc文件暫時不知道怎么打開道批,沒有截圖错英。

3.1: 生成匯編代碼 (屬于 后端Backend / 代碼生成器CodeGenerator)

匯編代碼可以由.ll.bc代碼生成。

  • 運(yùn)行命令clang -S -fobjc-arc main.bc -o main.s

  • 或運(yùn)行命令clang -S -fobjc-arc main.ll -o main.s

  • 這里也能優(yōu)化 (機(jī)器語言層面) clang -Os -S -fobjc-arc main.m -o main.s

  • 只截取部分代碼 ↓

    subq    $48, %rsp
    movl    $0, -4(%rbp)
    movl    %edi, -8(%rbp)
    movq    %rsi, -16(%rbp)
    callq   _objc_autoreleasePoolPush
    movl    $20, -20(%rbp)
    

4: 生成目標(biāo)文件 .o

匯編器將匯編代碼轉(zhuǎn)換為機(jī)器代碼隆豹,這就是.o文件 (object file)走趋。

  • 運(yùn)行命令clang -fmodules -c main.s -o main.o

  • 運(yùn)行命令xcrun nm -nm main.o,查看main.o中的符號

    • undefined,當(dāng)前文件暫時找不到
    • external簿煌,這個符號在外部找 (我們自己內(nèi)部沒有)
                     (undefined) external _objc_autoreleasePoolPop
                     (undefined) external _objc_autoreleasePoolPush
                     (undefined) external _printf
    0000000000000000 (__TEXT,__text) external _main
    

5. 生成可執(zhí)行文件 Mach-O

鏈接器 (Linker) 把.o文件和.dylib .a文件 生成一個Mach-O文件氮唯。

現(xiàn)在是編譯階段,這個Linker不是dyld姨伟,dyld是運(yùn)行時的事情惩琉。

  • 運(yùn)行命令clang main.o -o main

    友情提示:如果是上面一路跟下來的,這里會因?yàn)檎也坏?code>@autoreleasepool報(bào)錯夺荒,請去掉源碼里的@autoreleasepool再跟一下)

  • 文件變大了瞒渠,main.s1KB,main13KB

  • 運(yùn)行命令xcrun nm -nm main技扼,查看main中的符號伍玖。

                     (undefined) external _printf (from libSystem)
                     (undefined) external dyld_stub_binder (from libSystem)
    0000000100000000 (__TEXT,__text) [referenced dynamically] external __mh_execute_header
    0000000100000f73 (__TEXT,__text) external _main
    0000000100002008 (__DATA,__data) non-external __dyld_private
    
  • 上面是編譯階段,下面要講的是運(yùn)行階段(dyld相關(guān))的事情剿吻。雖然printf仍然是undefined窍箍,但這只是一個標(biāo)示,后面寫了(from libSystem)丽旅,意味著當(dāng)程序跑起來的時候椰棘,自己沒有printf,它是個external外部函數(shù)榄笙,找libSystem邪狞,剛好iOS操作系統(tǒng)有libSystem,在那里找到printf的地址以后茅撞,進(jìn)行符號綁定就OK了帆卓。

  • 運(yùn)行命令./main,執(zhí)行程序

    sum = 80%
    
  • 運(yùn)行命令米丘,file main鳞疲,看文件類型和架構(gòu)

    main: Mach-O 64-bit executable x86_64
    
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蠕蚜,隨后出現(xiàn)的幾起案子尚洽,更是在濱河造成了極大的恐慌,老刑警劉巖靶累,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件腺毫,死亡現(xiàn)場離奇詭異,居然都是意外死亡挣柬,警方通過查閱死者的電腦和手機(jī)潮酒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來邪蛔,“玉大人急黎,你說我怎么就攤上這事。” “怎么了勃教?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵淤击,是天一觀的道長。 經(jīng)常有香客問我故源,道長污抬,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任绳军,我火速辦了婚禮印机,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘门驾。我一直安慰自己射赛,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布奶是。 她就那樣靜靜地躺著楣责,像睡著了一般。 火紅的嫁衣襯著肌膚如雪诫隅。 梳的紋絲不亂的頭發(fā)上腐魂,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天帐偎,我揣著相機(jī)與錄音逐纬,去河邊找鬼。 笑死削樊,一個胖子當(dāng)著我的面吹牛豁生,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播漫贞,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼甸箱,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了迅脐?” 一聲冷哼從身側(cè)響起芍殖,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎谴蔑,沒想到半個月后豌骏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡隐锭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年窃躲,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钦睡。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡蒂窒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情洒琢,我是刑警寧澤秧秉,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站纬凤,受9級特大地震影響福贞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜停士,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一挖帘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧恋技,春花似錦拇舀、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至薄辅,卻和暖如春要拂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背站楚。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工脱惰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人窿春。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓拉一,卻偏偏與公主長得像,于是被迫代替她去往敵國和親旧乞。 傳聞我的和親對象是個殘疾皇子蔚润,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345