前言:
關(guān)于Mach-O
文件财边,在iOS App 加載流程知識中已經(jīng)提到過肌括。看到優(yōu)秀簡書進(jìn)行一波兒轉(zhuǎn)載酣难。本文轉(zhuǎn)載http://www.reibang.com/p/d641e8270108
整體結(jié)構(gòu)大致如下:
Mach-O定義
Mach-O
(Mach Object)是macOS谍夭、iOS、iPadOS存儲程序和庫的文件格式
憨募。對應(yīng)系統(tǒng)通過應(yīng)用二進(jìn)制接口
(application binary interface紧索,縮寫為ABI
) 來運(yùn)行該格式的文件。
Mach-O格式
用來代替BSD
系統(tǒng)的a.out格式
菜谣。Mach-O文件格式
保存了在編譯過程
和鏈接過程
中產(chǎn)生的機(jī)器代碼和數(shù)據(jù)珠漂,從而為靜態(tài)鏈接
和動(dòng)態(tài)鏈接
的代碼提供了單一文件格式。
Mach-O
=文件配置
+二進(jìn)制文件
除了可執(zhí)行文件
之外尾膊,還有一些文件也是Mach-O
格式媳危,比如:
- 目標(biāo)文件
.o
- 庫文件
.a
.dylib
Framework
dyld
(動(dòng)態(tài)鏈接器).dsym
(符號表)
由此我們知道,可執(zhí)行文件
只是Mach-O
的一種冈敛,因此我們將Mach-O
文件分為以下幾種:
名稱 | 注釋 |
---|---|
Mach-O Object |
目標(biāo)文件 |
Mach-O ececutable |
可執(zhí)行文件 |
Mach-O dynamically |
動(dòng)態(tài)庫文件 |
Mach-O dynamic linker |
動(dòng)態(tài)鏈接器文件 |
Mach-O DSYM companion |
符號表文件 |
通用二進(jìn)制文件(Universal binary)
支持多架構(gòu)的Mach-O ececutable
(可執(zhí)行文件)被稱為:通用二進(jìn)制文件待笑,即多種架構(gòu)都可讀取運(yùn)行。
通用二進(jìn)制文件
具有以下特性:
1抓谴、Apple 提出的一種程序代碼暮蹂,能夠同時(shí)適配多種架構(gòu)的二進(jìn)制文件寞缝。
2、同一個(gè)程序包中仰泻,同時(shí)為多種架構(gòu)提供最理想的性能荆陆。
3、通用二進(jìn)制應(yīng)用程序通常比單一平臺二進(jìn)制程序大集侯,因?yàn)樾枰鎯Χ喾N代碼被啼。
4、由于多種架構(gòu)之間有共通的非執(zhí)行資源浅悉,所以并不會比單一架構(gòu)的兩倍大趟据。
5、程序在執(zhí)行的時(shí)候只調(diào)用一部分代碼术健,運(yùn)行起來不需要額外的內(nèi)存汹碱。-
那么多種架構(gòu)是什么意思呢?下面我們通過
file
指令來看一下我們的可執(zhí)行文件:通過上圖荞估,我們可以看到
test
可執(zhí)行文件的類型是Mach-O
咳促;架構(gòu)是x86_64
,這是我們用模擬器運(yùn)行的可執(zhí)行文件勘伺。
我們再實(shí)際開發(fā)中遇到的設(shè)置arm64
&armv7
這些都是對應(yīng)的架構(gòu):名字 注釋 arm64
真機(jī)64位處理器需要arm64架構(gòu)(iphone6,iphone6p以上的真機(jī)) armv7s 真機(jī)32位處理器 ( ipnone5,iphone5s真機(jī)/armv7s) armv7 真機(jī)32位處理器 (iphone4真機(jī)/armv7) x86_64 模擬器64位處理器 (iphone6以上的模擬器) i386 模擬器32位處理器 (iphone5,iphone5s以下的模擬器) -
Tips:
在Xcode中設(shè)置Arhitectures
跪腹,Debug
屬性設(shè)置為NO
的時(shí)候,會編譯支持所有架構(gòu)的版本飞醉,編譯的速度會變慢冲茸,設(shè)置為yes
的時(shí)候,只編譯當(dāng)前的Arhitectures
版本缅帘,編譯速度快轴术。
通用二進(jìn)制文件的拆分 與 組合
在
MachOView
中,通用二進(jìn)制文件
也被叫做Fat binary
钦无。
這種二進(jìn)制文件是可以拆分逗栽、或者重新組合的
?? 注意這里我采用的是真機(jī)測試,Scheme
對應(yīng)的Build Configuration
選用Release
模式失暂。(關(guān)于Xcode環(huán)境的配置彼宠,有不清楚的同學(xué)可以看這里:Xcode 多環(huán)境的配置)
?? 這里還有一點(diǎn)要注意:測試的時(shí)候,如果工程只包含一種架構(gòu)弟塞,此時(shí)要手動(dòng)添加其他架構(gòu)凭峡。-
我們可以通過
file
指令,也可以通過lipo -info
指令查看二進(jìn)制文件支持的架構(gòu):
可以看到决记,目前test
可執(zhí)行程序支持arm64
和 arm_v7
兩種架構(gòu)摧冀。
那么下面我們先進(jìn)行文件拆分:
拆分Fat binary
linpo mach-o文件名 -thin 要拆分的架構(gòu)名 -output 拆分出來的文件名
拆分前的ipa
包內(nèi)容:
拆分后的ipa
包內(nèi)容:
?? 拆分后源文件并不會發(fā)生改變,類似于從源文件中copy
出來一個(gè)架構(gòu)單一的二進(jìn)制文件,注意這里不是單獨(dú)的分離架構(gòu)按价。
合并 Fat binary
lipo -create macho_arm64 macho_armv7 -output newTest
合并之后的文件與原文件并無差異,我們可以通過哈希值也看一下:
Mach-O 文件結(jié)構(gòu)
-
Mach-O
文件主要由 3 部分組成:
名字 | 注釋 |
---|---|
Mach Header (Mach-O 頭) |
描述了Mach-O 的CPU 架構(gòu)笙瑟、文件類型以及加載命令等信息 |
Load Commands (加載命令) |
描述了文件中數(shù)據(jù)的具體組織結(jié)構(gòu)楼镐,不同的數(shù)據(jù)類型用不同的加載命令表示 |
Data (數(shù)據(jù)區(qū)) |
Data 中每一個(gè)段 (Segment ) 的數(shù)據(jù)都保存在這里,都擁有一個(gè)或多個(gè)Section 往枷,用來存儲數(shù)據(jù)和代碼 |
?? 既然Mach-O
是二進(jìn)制文件框产,那么它又是怎么知道哪一塊內(nèi)容是Load commands
,哪一塊又是Header
的呢错洁?
其實(shí)這里涉及到一個(gè)概念叫做結(jié)構(gòu)體對齊秉宿,簡單的講就是:按照一定的規(guī)則組合到一起,再按照既定的規(guī)則拆分就可以了屯碴。
Mach Header
可以看到Mach Header
里面有很多的Description(描述)
那么對應(yīng)的都是什么意思呢描睦?
我們可以在工程中搜索一下,使用快捷鍵(command + shift + o
) 搜索load.h
文件导而,打開該文件忱叭,由于是當(dāng)前是64位的,所以找到:
/*
* The 64-bit mach header appears at the very beginning of object files for
* 64-bit architectures.
*/
struct mach_header_64 {
uint32_t magic; /* mach magic number identifier */
cpu_type_t cputype; /* cpu specifier */
cpu_subtype_t cpusubtype; /* machine specifier */
uint32_t filetype; /* type of file */
uint32_t ncmds; /* number of load commands */
uint32_t sizeofcmds; /* the size of all the load commands */
uint32_t flags; /* flags */
uint32_t reserved; /* reserved */
};
在load.h
文件中mach_header_64
比mach_header(32位的頭文件)
多了一個(gè)保留字段
uint32_t reserved; /* reserved */
mach_header
是鏈接器加載的時(shí)候最先讀取的內(nèi)容今艺,它決定了一些基礎(chǔ)架構(gòu)韵丑,系統(tǒng)類型,指令條數(shù)等信息虚缎。
Load Commands
Load Commands
詳細(xì)保存著加載指令的內(nèi)容撵彻,告訴鏈接器如何去加載當(dāng)前的Mach-O
文件。
那么每一條Load Command
對應(yīng)的又是什么意思呢实牡?
同樣的我們也可以在load.h
搜索的到陌僵,我們以LC_SEGMENT_64
為例:
下面我們列舉一些常見的:
名字 | 注釋 |
---|---|
LC_SEGMENT_64 |
將文件中的段映射到進(jìn)程地址空間中 |
LC_DYLD_INFO_ONLY |
加載動(dòng)態(tài)鏈接庫信息(重定向地址、弱引用綁定铲掐、懶加載綁定拾弃、開放函數(shù)等的偏移值信息) |
LC_SYMTAB |
載入符號表地址 |
LC_DYSYMTAB |
載入動(dòng)態(tài)符號表地址 |
LC_LOAD_DYLINKER |
加載動(dòng)態(tài)鏈接器 |
LC_UUID |
唯一標(biāo)識,crash 解析中也會用到摆霉,檢查dysm 文件和crash 文件是否匹配 |
LC_VERSION_MIN_MACOSX / LC_VERSION_MIN_IPHONEOS |
二進(jìn)制文件支持的最底操作系統(tǒng)版本 |
LC_SOURCE_VERSION |
構(gòu)建二進(jìn)制文件使用的源代碼版本 |
LC_MAIN |
設(shè)置程序主線程的入口地址和棧大小(這也就是為什么我們的程序每次運(yùn)行都是從main() 進(jìn)來的原因) |
LC_ENCRYPTION_INFO_64 |
獲取加密信息 |
LC_LOAD_DYLIB |
加載額外的動(dòng)態(tài)庫 |
LC_FUNCTION_STARTS |
函數(shù)起始地址表 |
LC_DATA_IN_CODE |
定義在代碼段(__text )內(nèi)的非指令表 |
LC_CODE_SIGNATURE |
應(yīng)用的簽名信息 |
Data
Data
段又分為:__TEXT
段 和 __DATA
段
-
__TEXT
段
代碼的讀取是從__TEXT
段開始讀取的豪椿,其中不同的__TEXT
代表的意思如下:
名字 | 注釋 |
---|---|
__text |
主程序代碼 |
__cstring |
C 語言字符串 |
__const |
const 關(guān)鍵字修飾的常量 |
__stubs |
用于Stub 的占位代碼,很多地方稱之為樁代碼 |
__stubs_helper |
當(dāng)Stub 無法找到真正的符號地址后的最終指向 |
__objc_methname |
OC 方法名 |
__objc_methtype |
OC 方法類型 |
__objc_classname |
OC 類名 |
-
__DATA
段
__DATA
段在內(nèi)存中緊跟在__TEXT
段之后
名字 | 注釋 |
---|---|
__got |
全局偏移表 |
__la_symbol_ptr |
lazy binding 的指針表携栋,表中的指針一開始都指向__stub_helper
|
__cfstring |
工程中使用的Core Foundation 字符串(CFStringRefs ) |
__objc_classlist |
OC 類列表 |
__objc_protolist |
OC protocol 列表 |
__objc_imginfo |
OC 鏡像信息 |
__const |
沒有初始化過的常量 |
__objc_selfrefs |
OC 引用的SEL 列表 |
__objc_protorefs |
OC 引用的protocol 列表 |
__objc_superrefs |
OC 引用的父類列表 |
__objc_ivar |
OC ivar 信息 |
__objc_data |
class 信息 |
__bss |
BSS搭盾,存放 未初始化的全局變量,就是常說的靜態(tài)內(nèi)存分配 |
__data |
初始化的可變數(shù)據(jù) |
?? 這里有一點(diǎn)大家需要注意婉支,系統(tǒng)庫的方法在我們自己的Mach-O
文件里面是找不到的鸯隅,它存放在共享緩存區(qū)。那么我們自己的Mach-O
文件又怎么去調(diào)用這些系統(tǒng)方法實(shí)現(xiàn)呢?