編譯過程
傳統編譯過程一般分為以下步驟。
源代碼(source code)→ 預處理器(preprocessor)→ 編譯器(compiler)→ 匯編程序(assembler)→ 目標代碼(object code)→ 鏈接器(linker)→ 可執行文件(executables)
其中預處理主要工作是宏定義的替換和頭文件的引入
編譯器
簡單而言,編譯器的設計一般分為三部分
傳統編譯器(compiler)的設計一般如下圖所示
LLVM編譯器設計如圖所示
- 前端 Frontend:前端的主要工作是解析源代碼, 詞法分析、語法分析、語義分析、檢查源代碼是否存在錯誤,然后構建抽象語法樹(Abstract Syntax Tree AST)。
- 優化器 Optimizer:負責優化代碼,例如消除冗余。
- 后端 Backend:將代碼映射到目標指令集,生成機器代碼。
LLVM
底層虛擬機(Low Level Virtual Machine),LLVM是一套編譯器基礎設施項目,以C++寫成,包含一系列模塊化的編譯器組件和工具鏈,用來開發編譯器前端和后端。優化各種語言寫的程序在編譯過程中環節,如編譯時期、鏈接時期、運行時期以及閑置時期。
LLVM前端
Clang是LLVM編譯器工具集的前端(front-end),屬于LLVM項目中的一個子項目,它是基于LLVM架構圖的輕量級編譯器,Clang的現在已經取代GCC,負責C、C++、OC語言的編譯。Clang主要工作是輸出代碼對應的抽象語法樹(Abstract Syntax Tree, AST),并將代碼編譯成LLVM Bitcode。接著在后端(back-end)使用LLVM編譯成平臺相關的機器語言。
LLVM中間端
LLVM的核心是中間端表達式(Intermediate Representation,IR),一種類似匯編的底層語言。
LLVM后端
LLVM后端的主要工作是將LLVM中間端表達式(IR)轉換成特定目標機器代碼(object code),機器代碼一般由機器代碼或接近于機器語言的代碼組成。即存放目標代碼的計算機文件,它常被稱作二進制文件(binaries)。目前LLVM支持輸出多種后端指令集,包括X86、PowerPC、ARM以及SPARC等。目標文件包含著機器代碼(可直接被CPU執行)以及代碼在運行時使用的數據,如重定位信息,如用于鏈接或調試的程序符號(變量和函數的名字),此外還包括其他調試信息。目標文件是從源代碼文件產生程序文件這一過程的中間產物。
LLVM鏈接器
LLD是LLVM項目中的鏈接器,替換GNU系統鏈接器。LLD把目標文件(.o文件和 .dyld .a)鏈接在一起來生成可執行文件或庫文件(mach-o文件)。
Mach-O文件格式
Mach-O 是 iOS 可執行文件的格式,典型的 Mach-O 是主二進制和動態庫。Mach-O 可以分為三部分:
- Header
- Load Commands
- Data
Header 的最開始是 Magic Number,表示這是一個 Mach-O 文件,除此之外還包含一些 Flags,這些 flags 會影響 Mach-O 的解析。
Load Commands 存儲 Mach-O 的布局信息,比如 Segment command 和 Data 中的 Segment/Section 是一一對應的。除了布局信息之外,還包含了依賴的動態庫等啟動 App 需要的信息。
Data 部分包含了實際的代碼和數據,Data 被分割成很多個 Segment,每個 Segment 又被劃分成很多個 Section,分別存放不同類型的數據。
標準的三個 Segment 是 TEXT,DATA,LINKEDIT,也支持自定義:
- TEXT,代碼段,只讀可執行,存儲函數的二進制代碼(__text),常量字符串(__cstring),Objective C 的類/方法名等信息
- DATA,數據段,讀寫,存儲 Objective C 的字符串(__cfstring),以及運行時的元數據:class/protocol/method…
- LINKEDIT,啟動 App 需要的信息,如 bind & rebase 的地址,代碼簽名,符號表…
iTunes Connect 會對上傳 Mach-O 的 TEXT 段進行加密,防止 IPA 下載下來就直接可以看到代碼。這也就是為什么逆向里會有個概念叫做“砸殼”,砸的就是這一層 TEXT 段加密。iOS 13 對這個過程進行了優化,在博文中的page fault過程中,需要讀取Page In 的時候不需要解密了。
參考文章
http://www.aosabook.org/en/llvm.html
抖音品質建設 - iOS啟動優化《原理篇》