理解iOS app的編譯,執行過程

語言: CN / TW / HK

theme: channing-cyan highlight: arduino-light


前言

一年都沒怎麼寫iOS的文章。最近有一些知識學習了完以後總覺得零零散散的,打算以這一篇文章作為開篇,系統地記錄一些所學習的iOS知識。

本篇文章的基礎框架已經初步確定了,不會提及到特別深入的知識點。本篇文章以APP是一個app的編譯和執行過程的一個概述,為後續的文章提供一個基本的分析思路。之後時不時會隨著研究內容的時候補充一些細節。

一、 系統架構

在談iOS的執行過程前,我們首先需要了解iOS的系統。其實iOS和MacOS一脈相承,總體的結構設計都如下所示

底層都是Darwin,是一個作業系統。Darwin結構如下

其中Darwin的核心是XNU。然後XNU裡面使用了BSD和Mach。

到這裡可以基本理解為MacOS和iOS是一個類Unix系統,BSD也相容Posix這個標準,所以我們能在iOS開發中使用pthread,在Mac上終端處理unix命令的原因。 這裡關於iOS和MAC OS架構的描述就先說這麼多,如果有興趣瞭解更深一些的話,可以檢視《深入瞭解MACOS&iOS作業系統》一書。

先介紹這個系統架構,主要是為了讓大家瞭解,在後續的文章中用到的方法有些是Mach這一層的提供的。

二、 Mach-O

Mach-O是什麼?

我們在打開了一個App,可以理解在手機的作業系統上運行了一個App程序。而程序是特殊檔案在記憶體中載入得到的結果,這種特殊檔案必須是作業系統可理解的。

在unix中,可執行的程式會編譯成一個二進位制檔案,unix對於二進位制檔案,有一個標準的格式叫ElF。蘋果爸爸偏偏不依,定製了一個屬於蘋果的格式,這個就是Mach-O格式,全稱Mach-Object。

但是Mach-O不僅僅是隻是可執行檔案的格式標準,還是macOS和iOS中一些其他檔案的標準格式。常見的Mach-O格式有下面幾種 * 可執行檔案 * 庫檔案(.a .dylib .framework等) * dyld(動態聯結器) * dsym檔案(符號表) * 目標檔案(編譯輸出的.0檔案)

Mach-O總的來說可以說是蘋果的一種檔案標準的格式。

以下以可執行檔案為重點去講述Mach-O

可執行檔案

我們開發的app,編譯打包後,就會生成一個可執行的,Mach-O格式的二進位制檔案。

對於macOS和iOS中的App,其生成可執行二進位制檔案從支援的架構分為兩種 1. 單架構的二進位制檔案 2. 胖二進位制檔案

單架構的二進位制檔案就是運行於單個架構(如x86,ARM64等)的二進位制檔案。而胖二進位制就是一個二進位制檔案裡面包含多個單架構的二進位制檔案。

Mach-O內容

以mac中的計算器App為例,用MachOView檢視如下

截圖2022-01-03 上午12.09.33.png

可以看到這是一個Fat Binary型別的可執行檔案,也就是我們平常說的胖二進位制檔案。裡面包含了兩個Executable二進位制檔案,每一個Excutable裡面的格式可以參照單個架構的Mach-O的格式。當我們點選app的時候,就會從中選合適的Excutable載入到記憶體中。

單個Excutable的格式總體如下

對於上面的圖片,單架構的可執行檔案Mach-O檔案格式的內部結構總體如下

從圖中可知,Mach-O大概分為三個部分 * mach-header * load commands * Data

下面簡單的說下各個部分的作用是什麼。

1. mach_header

mach_header 位於 Mach-O 檔案的頭部,有以下作用 * 標明Mach-O 的格式 * 標明檔案型別,CPU 架構等資訊 * 標明load commands數量等 * 其他

對於計算器App,在MachOView中整體如下

截圖2022-01-03 上午12.01.28.png

2. load commands

這一部分儲存的是載入指令,直到作業系統該如何把可執行檔案載入到記憶體中。有以下作用 * 指導data部分的資料應該怎麼載入 * 指導dyld載入哪些庫 * 指明符號表地址 * 其他

對於計算器App,在MachOView中整體如下

3. Data

這一部分儲存的資料區域,包含程式執行的一切資料,表明資料載入到什麼位置。部分資料如 * 程式碼資料 * 執行時的類資料 * 符號表,動態符號表 * 其他資料

對於計算器App,在MachOView中整體如下

對於Data區域的文件,可以按照Segment和section劃分。Segment和section的關係有點像作業系統中的段和頁的概念。 這裡section可以簡單為Segment的子節點,而Data中的資料是以Segment為單位劃分的。常見的Segment有 1. __PAGEZERO。一段隨機大小,不可訪問的空間 2. __TEXT。程式碼區 3. __DATA。資料區域 4. __LINKEDIT;包含了方法和變數的元資料,程式碼簽名等資訊

三、App編譯

當我們在Xcode上編寫程式碼,如何生成一個可執行的檔案呢?對於一個通用編譯流程,如下

編譯流程.jpg

在Xcode中使用LLVM架構流程化處理上面的步驟,我們無需操心。但是作為開發者,我們可以理解一下LLVM的基本的流程。

LLVM的總體架構如下所示,分為前端,中間層,後端等

3008243-b517c768f5a97607.png

在iOS中,結合編譯流程與LLVM,各個步驟如下。

1. 預處理

在預處理的階段中,根據語言選擇編譯器前端。如對於OC,會呼叫編譯器前端Clang首先預處理我們程式碼。這一步會進行將巨集替換到程式碼中、刪除註釋、處理預編譯命令等工作

2. 編譯、優化

在這個階段,在編譯器前前端處理以下步驟 1. 詞法分析 2. 語法分析 3. 靜態分析 4. 為每一個檔案生成中間程式碼IR

3. 彙編

拿到第二個步驟的IR檔案後,LLVM中間層LLVM Optimizer就會對每一個IR檔案進行一些優化,如尾遞迴優化、迴圈優化、全域性變數優化。 優化完成後,LLVM會調用匯編生成器將每一個IR檔案轉化成彙編程式碼。再根據部署的平臺選擇對應的編譯器後端,將彙編轉變生成產物,也就是一個個.o檔案了(二進位制檔案)。

4. 連結程式

在上面步驟中,生成了一個個.o檔案。在我們正常開發中,一個檔案通常會引用別的檔案或是庫,連結過程會將多個目標文以及所需的庫檔案連結成最終的可執行檔案,也就是Mach-O的檔案。對於動態庫,連結構成中不會把庫載入進來,而是會把動態庫的相關資訊寫入到可執行檔案中。

經過上述步驟,我們開發的程式碼會生成一個Mach-O格式的可執行檔案。

四、 App執行過程

1. 載入到記憶體

當我們點選App的時候,作業系統就會在手機上開闢一段虛擬記憶體空間,然後載入Mach-O格式的二進位制檔案。如下

截圖2022-01-03 上午12.58.47.png

其中圖中類似於管道的部分可以理解為系統為程式分配的記憶體空間。然後把我們的可執行檔案載入到記憶體中。

注意圖中有一個PAGEZERO的區域,這個是蘋果的ASLR技術,也就是地址空間佈局隨機化。從【App編譯】小節我們可以知道,當我們編譯出一個App的時候,生成的Mach-O檔案的內容地址是固定的。

ASLR技術在每次執行App的時候,都會加上一個PAGEZERO,PAGEZERO的大小都是隨機的。加上PAGEZERO以後,Mach-O裡面的地址都會需要加上相對的偏移,這樣可以增加App的攻擊難度。

2. 呼叫dyld程序載入dylib

把Mach-O當作一個image載入到虛擬記憶體後,會啟動dyld程序,根據load commonds裡面的載入命令,載入需要的動態庫。以Mac上的計算器app為例,載入命令在Mach-O如下

截圖2022-01-03 下午9.15.04.png

dyld根據這些載入命令把這些第三方庫載入到虛擬記憶體中,載入後如下

截圖2022-01-03 下午9.13.53.png

3. 修復處理

Rebase

在編譯過程中,Mach-O檔案的檔案引用。由於簽名和ASLR技術載入了PAGEZERO區域,所以檔案裡面的內容都會有偏移。這時候就需要修正檔案裡面的指標引用。Rebase過程就是對於檔案裡面的指標一個修正過程。

Bind

在編譯過程,可執行檔案對動態庫的方法呼叫是隻聲明瞭符號。在呼叫dyld把這些動態庫在載入到記憶體後,需要去相應的動態庫中連結對應的方法,找到其指標,然後對可執行檔案中的指標執行修復。Bind過程中就是把這些指向動態庫的指標進行修復。

Objc

大部分修復操作都已經在Rebase和Bind中做了,這一部分的工作主要是做Objc執行時的處理。如載入category裡面的方法到類中,這一部分在蘋果開源的objc庫中可以找到相關內容,在筆者前面的文章也提及到過(ps:早期文章寫的比較隨意,大家可以結合文章裡面的註釋看原始碼)。

4.Initalizers

這一個階段基本就是執行c++的初始化,OC load方法。執行完以後。由dyld呼叫程式中的main函式

5.執行main函式

執行main函式,開始一個RunLoop,保持程式的不斷執行,然後開始執行我們程式中AppDelegate中的程式碼。

按照蘋果官方的PPT,載入可程式後執行的總體步驟如下

截圖2022-01-03 下午10.05.44.png

小結

筆者才疏學淺,這一篇文章是參考了其他部落格,結合自身的理解,得出的一篇關於iOS的編譯和執行過程的一個概述,如果有紕漏的地方,望各位讀者指正。

參考連結

Mach-O檔案結構

iOS App - 從編譯到執行

深入淺出讓你理解什麼是LLVM

優化啟動時間

clang常用命令學習