理解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常用命令學習