編寫的程式碼經過編譯連結後生成的可執行檔案就是 Mach-O 檔案。不過 Mach-O 不僅僅只是可執行檔案,它可以代表很多。
Mach-O 是 Mach object 的縮寫,是 Mac\iOS 上用於儲存程式、庫的標準格式。
Mach-O 檔案有哪些型別,檢視 xun 的原始碼可以看到:
如何證明一個檔案是 Mach-O 檔案:
假設我們編寫了一個 .c 檔案 test.c,在終端中對它進行編譯 $ clang -c test.c
,這時就可以看到目錄中多出了一個 test.o
檔案,然後執行$ file test.o
就可看見結果:test.o: Mach-O 64-bit object x86_64。
常見的 Mach-O 檔案型別:
- MH_OBJECT
- 目標檔案(.o):原始檔與可執行檔案的中間產物
.c -> .o -> 可執行檔案
。假設專案裡有三個 c 語言檔案,每個檔案分別編譯成一個目標檔案,三個目標檔案經過連線之後生成一個可執行檔案。 - 靜態庫檔案(.a),靜態庫其實就是 N 個 .o 合併在一起
- 目標檔案(.o):原始檔與可執行檔案的中間產物
- MH_EXECUTE: 可執行檔案
$ clang -o test2 test.c
執行這個命令,讓 test.c 生成可執行檔案 test2 。執行命令$ file test2
,即可驗證。
- MH_DYLIB: 動態庫檔案
- .dylib
- .framework/xx
- MH_DYLINKER: 動態連結編輯器
/usr/lib/dyld
。這是一個專門用來載入我們動態庫檔案的東西,它也屬於 Mach-O 檔案。
- MH_DSYM: 儲存二進位制檔案符號資訊的檔案
- iOS 專案沒次打包的時候就會生成一個 dsym 檔案。除錯崩潰的時候會用到。
Mach-O 的基本結構
Mach-O 檔案都有共同的基本結構。
它主要由三部分組成:Header、Load commands、Data
- Header
- 檔案型別、目標架構型別等
- Load commands
- 描述檔案在虛擬記憶體中的邏輯結構、佈局(平時我們說的記憶體都是虛擬記憶體,比如堆空間、棧空間等等)
- Raw segment data
- 在 Load commands 中定義的 Segment 的原始資料
Load commands 僅僅是描述了結構與佈局,Raw segment data 說明了具體的資料。
簡單認識dyld(/usr/lib/dyld)
- dyld 將可執行檔案載入進記憶體。程式碼編寫好後,編譯連結後生成了一個可以行檔案。要執行的話,就需要把它載入進記憶體,就是 dyld 將它載入進記憶體的。
- dyld 將動態庫載入進記憶體。dyld 會先判斷動態庫共享快取中是否有這個動態庫,如果沒有的話,就載入;如果有的話就直接連結就
dyld用於載入以下型別的 Mach-O 檔案
- MH_EXECUTE
- MH_DYLIB
- MH_BUNDLE
動態庫共享快取(dyld shared cache)
從 iOS3.1 開始,為了提高效能,絕大部分的系統動態庫都存放到了一個快取檔案中,路徑是:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_armX
比如:MapKit.frameworks、UIKit.frameworks、CoreGraphics.frameworks 等等
動態庫共享快取非常明顯的好處就是:節省記憶體。
- 假設現在有多個APP,APP1、APP2、APP3 它們都要用到 UIKit.frameworks。一種方式是把 UIKit.frameworks 分別載入到這三個 APP,另一種方式是把 UIKit.frameworks 放到 APP 記憶體之外的另一個地方,APP 們可以共享它,顯然這樣節省了記憶體。
- 還有一點如果有多個動態庫 UIKit、MapKit 等等,蘋果還會把他們合併成一個檔案 dyld_shared_cache_armX,這樣可以把多個動態庫中一些通用的東西只用寫一份就可以了。
點選執行按鈕後,程式經歷的過程
預處理(.i) -> 彙編(.s、.asm) -> 編譯(.o、.obj) -> 連結(準確講叫做靜態連結) -> 可執行檔案
接下來要執行程式,比如點選 APP 圖示冷啟動程式 或者 Xcode 自動執行:
- dyld 階段:dyld 會把可執行檔案載入到 APP 的記憶體中;還會載入動態庫,載入動態庫的時候會先判斷動態共享快取中有沒有這個動態庫,有的話就直接連結,沒有的話就先進行載入。它還會載入 MH_BUNDLE 檔案。簡單地概括就是它可以載入部分 Mach-O 檔案。
- runtime 階段
- main 階段