在 Clang 編譯 Objective-C 源文件時(shí),需要先將 Objective-C 代碼轉(zhuǎn)化為 C 語(yǔ)言代碼,然后編譯得到目標(biāo)文件(object),最后將目標(biāo)文件鏈接為二進(jìn)制文件(binary)。二進(jìn)制文件表現(xiàn)為應(yīng)用或框架、類庫(kù)等。操作系統(tǒng)通過dyld提供的 API 啟動(dòng)或加載二進(jìn)制文件。大致過程是:首先通過open(...)函數(shù)打開二進(jìn)制文件;然后通過mmap(...)內(nèi)存映射函數(shù),將二進(jìn)制文件中的目標(biāo)文件映射到內(nèi)存空間,因此目標(biāo)文件也可以稱為鏡像文件,鏡像文件所映射的內(nèi)存區(qū)域則為鏡像(image);最后進(jìn)行鏡像綁定、依賴初始化等其他操作。
本文的主要內(nèi)容是:探討 runtime 如何讀取鏡像中定義的 Objective-C 元數(shù)據(jù)。
注意:工程編譯生成的動(dòng)態(tài)鏈接庫(kù)(Overview of Dynamic Libraries、深入剖析iOS動(dòng)態(tài)鏈接庫(kù))、靜態(tài)鏈接庫(kù)、可執(zhí)行程序統(tǒng)稱為二進(jìn)制文件(binary)。
一、Objective-C 體系初始化
面向?qū)ο蟮?Objective-C 體系的總加載入口在objc-os.mm文件中的void _objc_init(void)。操作系統(tǒng)執(zhí)行了_objc_init()后,才能開始加載 Objective-C 編寫的應(yīng)用。
void _objc_init(void)用于初始化 runtime 環(huán)境,并注冊(cè)三個(gè)回調(diào) 以監(jiān)聽dyld加載鏡像的兩個(gè)狀態(tài)、以及卸載鏡像動(dòng)作。開頭的三行代碼說明該函數(shù)只被執(zhí)行一次。操作系統(tǒng)調(diào)用dyld的 API 加載(dyld開源代碼)二進(jìn)制文件并完成內(nèi)存映射后,一旦鏡像切換到指定狀態(tài) 或者 監(jiān)聽到鏡像卸載動(dòng)作,則會(huì)觸發(fā)_objc_init中所注冊(cè)的相應(yīng)的回調(diào)函數(shù):
鏡像切換到
dyld_image_state_bound(鏡像完成綁定)時(shí),觸發(fā)map_images(...)回調(diào)函數(shù),將鏡像中定義的 Objective-C 元數(shù)據(jù)(類、分類、協(xié)議等等)加載到 runtime;鏡像加載切換到
dyld_image_state_dependents_initialized(鏡像的依賴庫(kù)完成初始化)時(shí),觸發(fā)load_images (...)回調(diào)函數(shù),執(zhí)行鏡像中的 Objective-C 元素初始化操作,主要是執(zhí)行類和分類的load方法;監(jiān)測(cè)到卸載鏡像動(dòng)作時(shí),觸發(fā)
unmap_image (...)回調(diào)函數(shù),將屬于該鏡像的元素從 runtime 系統(tǒng)記錄的已加載 Objective-C 元素中移除。
// 運(yùn)行時(shí)環(huán)境的啟動(dòng)總?cè)肟?void _objc_init(void)
{
// 重要:_objc_init全局只調(diào)用一次
static bool initialized = false;
if (initialized) return;
initialized = true;
// 運(yùn)行環(huán)境初始化系列操作
environ_init();
tls_init();
static_init();
lock_init();
exception_init();
// 監(jiān)聽鏡像加載卸載,注冊(cè)回調(diào)
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
二、載入鏡像
載入鏡像是由系統(tǒng)或應(yīng)用調(diào)用dyld的 API 實(shí)現(xiàn)的,例如用戶打開 APP 則會(huì)觸發(fā)載入鏡像過程。鏡像載入過程需要完成目標(biāo)文件映射到內(nèi)存空間、依賴庫(kù)初始化等工作。而本章僅介紹內(nèi)存映射過程生成的鏡像的內(nèi)存布局。使用 Clang 編譯.m源文件,所生成的.o目標(biāo)文件包含以下三大塊信息,依次相鄰分布:
- 鏡像元數(shù)據(jù)區(qū):包含頭信息(header info)、架構(gòu)相關(guān)信息
fat_header(針對(duì)多架構(gòu)編譯的目標(biāo)文件才有); - 加載命令區(qū):加載命令(load commands),包含了鏡像中保存的數(shù)據(jù)的元數(shù)據(jù),加載命令用于訪問或操作目標(biāo)文件中的數(shù)據(jù)。例如
LC_SEGMENT_64用于讀取目標(biāo)數(shù)據(jù)段中保存的數(shù)據(jù); - 數(shù)據(jù)區(qū):包括代碼段(text segment)、數(shù)據(jù)段(data segment)、符號(hào)表(symbol table)等等。其中,代碼段顧名思義用于保存代碼,目標(biāo)文件中保存的代碼是機(jī)器碼。數(shù)據(jù)段用于保存 在代碼中初始化的全局靜態(tài)變量及全局變量,這些數(shù)據(jù)固化在鏡像文件中。
下圖是一個(gè).o目標(biāo)文件的結(jié)構(gòu)示例:
注意:關(guān)于目標(biāo)文件的結(jié)構(gòu)的詳細(xì)介紹,可以參考 認(rèn)識(shí)MachO。
2.1 鏡像的內(nèi)存結(jié)構(gòu)
目標(biāo)文件映射到內(nèi)存生成鏡像,由于操作系統(tǒng)的 CPU 架構(gòu)通常是單一的,因此鏡像基本只包含單一架構(gòu)的數(shù)據(jù)。以下是目標(biāo)文件映射到內(nèi)存生成的鏡像的內(nèi)存結(jié)構(gòu)示意圖。圖中左邊表示鏡像內(nèi)存的布局結(jié)構(gòu),從下至上為低地址到高地址。右邊的mach_header_64結(jié)構(gòu)體定義了鏡像頭信息的數(shù)據(jù)結(jié)構(gòu);load_command結(jié)構(gòu)體表示抽象的加載命令類型,通常鏡像中不會(huì)直接保存load_command,而是繼承了load_command的具體加載命令類型,例如:segment_command_64用于加載鏡像中 segment 的數(shù)據(jù);section_64結(jié)構(gòu)體用于記錄 segment 中具有特定含義的一塊數(shù)據(jù)(section)的元信息。
- 紅色區(qū)域表示保存
mach_header結(jié)構(gòu)體; - 綠色區(qū)域表示保存
load_command結(jié)構(gòu)體,加載數(shù)據(jù)段; - 黃色區(qū)域表示保存
section_64結(jié)構(gòu)體; - 紫色區(qū)域表示保存了一塊代碼段數(shù)據(jù)(section);
- 洋紅色區(qū)域表示保存了一塊常量數(shù)據(jù)段數(shù)據(jù)(section);
- 青色區(qū)域保存保存了一塊數(shù)據(jù)段數(shù)據(jù)(section);
2.1 讀取鏡像中的數(shù)據(jù)
上圖中segment_command_64中紅色標(biāo)記的成員,以及section_64中綠色標(biāo)記的成員,它們是讀取鏡像中數(shù)據(jù)的關(guān)鍵。
segment_command_64結(jié)構(gòu)體中:
-
cmdsize:具體的加載命令結(jié)構(gòu)體類型 所占用字節(jié)數(shù); -
fileoff:數(shù)據(jù)段(segment)的數(shù)據(jù)區(qū)在鏡像內(nèi)存空間數(shù)據(jù)區(qū)中的相對(duì)地址; -
filesize:數(shù)據(jù)段(segment)的數(shù)據(jù)區(qū)占用字節(jié)數(shù); -
nsect:表示數(shù)據(jù)段中包含的 data section 的數(shù)量;
section_64結(jié)構(gòu)體中:
-
size:數(shù)據(jù)段 data section 占用字節(jié)數(shù); -
offset:數(shù)據(jù)段 data section 在鏡像內(nèi)存空間數(shù)據(jù)區(qū)中的相對(duì)地址。
讀取鏡像的數(shù)據(jù)段(segment)的代碼如下。其中head參數(shù),指向鏡像的頭信息結(jié)構(gòu)體,即指向鏡像文件映射的內(nèi)存緩沖區(qū),segname為數(shù)據(jù)段名稱。其原理是:
- 首先通過
頭信息內(nèi)存地址 + sizeof(struct mach_header_64)獲取加載命令區(qū)的起始地址; - 然后遍歷加載命令區(qū)中所有的加載命令,當(dāng)遍歷到的加載命令
sgp為LC_SEGMENT_64,且加載命令的數(shù)據(jù)段名稱segname匹配時(shí),返回該數(shù)據(jù)段,反之則通過sgp = sgp->cmdsize跳過命令占用空間以遍歷到下一個(gè)加載命令。
static const segment_command_64 *
getsegbynamefromheader(const mach_header_64 *head, const char *segname)
{
const segment_command_64 *sgp;
unsigned long I;
sgp = (const segment_command_64 *) (head + 1);
for (i = 0; i < head->ncmds; i++){
if (sgp->cmd == LC_SEGMENT_64) {
if (strncmp(sgp->segname, segname, sizeof(sgp->segname)) == 0) {
return sgp;
}
}
sgp = (const segment_command_64 *)((char *)sgp + sgp->cmdsize);
}
return NULL;
}
讀取數(shù)據(jù)段中的 data section 數(shù)據(jù)的模擬實(shí)現(xiàn)代碼如下,該函數(shù)定義在<mach-o/getsect.h>頭文件中,但是沒有公布實(shí)現(xiàn)代碼,以下是根據(jù)目標(biāo)文件內(nèi)存布局模擬的實(shí)現(xiàn)邏輯。原理是:
- 首先通過
getsegbynamefromheader(...)獲取目標(biāo)數(shù)據(jù)段sgp; - 然后計(jì)算數(shù)據(jù)區(qū)起始地址
datastart = mhp + sizeofheader + sizeofcmds,表示數(shù)據(jù)區(qū)緊接在 頭信息 和 加載命令區(qū) 后面; - 然后遍歷
sgp中所有 data section 的元數(shù)據(jù)sectptr。當(dāng)sectname匹配時(shí)返回datastart + sectptr->offset,表示 data section 的數(shù)據(jù)區(qū)域的起始地址;反之則通過sectptr += sizeof(struct section_64)以遍歷到下一個(gè) data section 的元數(shù)據(jù);
extern uint8_t *getsectiondata(
const struct mach_header_64 *mhp,
const char *segname,
const char *sectname,
unsigned long *size){
// 1. 獲取目標(biāo)數(shù)據(jù)段
const segment_command_64 *sgp = getsegbynamefromheader(mhp, segname);
if (!sgp)
return NULL;
uint_32t sizeofcmds = mhp->sizeofcmds;
uint_32t sizeofheader = sizeof(struct mach_header_64);
uint8_t * datastart = mhp + sizeofheader + sizeofcmds; // 計(jì)算數(shù)據(jù)區(qū)起始地址
// 2. 獲取目標(biāo)數(shù)據(jù)段首個(gè) data section 元數(shù)據(jù)(section_64結(jié)構(gòu)體)的起始地址
uint8_t * sectptr = sgp + sizeof(struct segment_command_64);
// 3. 遍歷目標(biāo)數(shù)據(jù)段的所有 data section
for(int i = 0; i < sgp->nsects; i++) {
if (strncmp(sectptr->sectname, sectname, sizeof(sectptr->sectname)) == 0) {
if(size > sectptr->size)
return NULL; // 獲取字節(jié)數(shù)超出 section 數(shù)據(jù)范圍應(yīng)該給出錯(cuò)誤提示
uint8_t *sectdataptr = datastart + sectptr->offset;
return sectdataptr;
}
sectptr += sizeof(struct section_64); // 偏移到下一個(gè)
}
return NULL;
}
三、鏡像內(nèi)容映射到內(nèi)存 map_images
載入鏡像后進(jìn)入特定的狀態(tài)將觸發(fā)_objc_init(...)初始化 Objective-C 運(yùn)行環(huán)境時(shí)注冊(cè)的三個(gè)回調(diào)。完成鏡像綁定后將觸發(fā)map_images回調(diào),加載鏡像中定義的 Objective-C 元素。傳入回調(diào)的參數(shù)包括:
- 待處理鏡像頭信息的數(shù)組
const struct mach_header * const mhdrs[]; - 鏡像的路徑
const char * const paths[]; - 鏡像頭信息數(shù)組的長(zhǎng)度
unsigned count。
map_images(...)函數(shù)直接調(diào)用了map_images_nolock (...)函數(shù),該函數(shù)包含很多底層知識(shí)點(diǎn),非常晦澀,這里并沒有深入分析,總結(jié)其功能為:
- 預(yù)處理優(yōu)化初始相關(guān),該部分邏輯公開的細(xì)節(jié)很少可以忽略;
- 通過
addHeader(...)匯總鏡像中的 Objective-C 元素,生成head_info的數(shù)組,作為下一步的輸入; - 調(diào)用
_read_images(...)加載鏡像中的 Objective-C 元素,如類、分類、協(xié)議、消息等等。
第3點(diǎn)是其核心功能。以下是map_images(...)及map_images_nolock(...)的源代碼,代碼中標(biāo)注了“核心邏輯”的部分將在后文分別詳細(xì)介紹。
void
map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
mutex_locker_t lock(runtimeLock);
return map_images_nolock(count, paths, mhdrs);
}
void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
static bool firstTime = YES;
header_info *hList[mhCount];
uint32_t hCount;
size_t selrefCount = 0;
// 預(yù)優(yōu)化環(huán)境初始化
if (firstTime) {
preopt_init();
}
hCount = 0; // 所有 包含Objective-C元素的鏡像
int totalClasses = 0; // 類的總數(shù)
int unoptimizedTotalClasses = 0; // 未優(yōu)化的類的總數(shù)
{
// 遍歷鏡像中所有元素信息,逐一轉(zhuǎn)換成header_info,并添加到hList
uint32_t i = mhCount;
while (i--) {
const headerType *mhdr = (const headerType *)mhdrs[I];
// (核心邏輯1)addHeader內(nèi)部邏輯將headerType轉(zhuǎn)化為header_info類型并添加到一張全局的
// 鏈表中,返回header_info類型的轉(zhuǎn)化結(jié)果
auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);
if (!hi) continue;
if (mhdr->filetype == MH_EXECUTE) {
// 一些可執(zhí)行文件的初始化代碼
#if __OBJC2__
size_t count;
_getObjc2SelectorRefs(hi, &count);
selrefCount += count;
_getObjc2MessageRefs(hi, &count);
selrefCount += count;
#else
_getObjcSelectorRefs(hi, &selrefCount);
#endif
// 忽略GC兼容檢查代碼
...
}
hList[hCount++] = hi;
}
}
if (firstTime) {
//初始化一些最基本的選擇器,如alloc、dealloc、initialize、load等等
sel_init(selrefCount);
// 初始化AutoreleasePool和SideTable
arr_init();
// 忽略GC兼容檢查代碼
...
// 忽略針對(duì)MAC OS X平臺(tái)的initialize的fork安全檢查代碼(Fixme: 不懂)
...
}
if (hCount > 0) {
// (核心邏輯2)加載二進(jìn)制文件中的元素,包括類、分類、協(xié)議等等
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
firstTime = NO;
}
void sel_init(size_t selrefCount)
{
// 保存到SelrefCount靜態(tài)的int中,只是為了后面初始化namedSelectors哈希表時(shí)提供初始size
SelrefCount = selrefCount;
#if SUPPORT_PREOPT
// 預(yù)處理優(yōu)化Selector,忽略
builtins = preoptimizedSelectors();
if (PrintPreopt && builtins) {
uint32_t occupied = builtins->occupied;
uint32_t capacity = builtins->capacity;
}
#endif
// sel_registerNameNoLock用于將SEL添加到全局的namedSelectors哈希表中,方便系統(tǒng)
// 快速判斷某個(gè)SEL是否可識(shí)別
#define s(x) SEL_##x = sel_registerNameNoLock(#x, NO)
#define t(x,y) SEL_##y = sel_registerNameNoLock(#x, NO)
mutex_locker_t lock(selLock);
s(load);
s(initialize);
t(resolveInstanceMethod:, resolveInstanceMethod);
t(resolveClassMethod:, resolveClassMethod);
t(.cxx_construct, cxx_construct);
t(.cxx_destruct, cxx_destruct);
s(retain);
s(release);
s(autorelease);
s(retainCount);
s(alloc);
t(allocWithZone:, allocWithZone);
s(dealloc);
s(copy);
s(new);
t(forwardInvocation:, forwardInvocation);
t(_tryRetain, tryRetain);
t(_isDeallocating, isDeallocating);
s(retainWeakReference);
s(allowsWeakReference);
#undef s
#undef t
}
void arr_init(void)
{
AutoreleasePoolPage::init(); //自動(dòng)釋放池初始化,后續(xù)獨(dú)立文章介紹
SideTableInit(); //SideTable初始化,后續(xù)獨(dú)立文章介紹
}
3.1 核心邏輯 addHeader
addHeader的作用不僅僅在于返回headerType對(duì)應(yīng)的header_info結(jié)構(gòu)體,其內(nèi)部調(diào)用了appendHeader()將所有已加載的鏡像的元素所對(duì)應(yīng)的header_info串聯(lián)成鏈表結(jié)構(gòu),鏈表首節(jié)點(diǎn)為FirstHeader、末尾節(jié)點(diǎn)為LastHeader。該鏈表在后面卸載鏡像元素時(shí)需要用到。至此完成鏡像數(shù)據(jù)收集工作。
3.1.1 addHeader 所收集數(shù)據(jù) header_info
addHeader所收集的信息header_info結(jié)構(gòu)體的功能如下:
- 用于快速定位鏡像中的頭信息(
mach_header_64結(jié)構(gòu)體)、鏡像信息。鏡像信息用于標(biāo)記鏡像的屬性,以objc_image_info結(jié)構(gòu)體格式保存于 鏡像文件的__DATA數(shù)據(jù)段的__objc_imageinfodata section,鏡像信息在構(gòu)建鏡像文件時(shí)生成; - 包含
header_info_rw rw_data[]用于記錄鏡像以及鏡像中的 Objective-C 元素的狀態(tài),該成員的數(shù)據(jù)是可讀寫的。
鏡像具體包含的狀態(tài)見header_info_rw結(jié)構(gòu)體:
- 最低位
isLoaded標(biāo)記鏡像是否已加載; - 次低位
allClassesRealized標(biāo)記鏡像中所有的 Objective-C 類是否已完成 class realizing; - 其他位用于保存下一個(gè)
header_info節(jié)點(diǎn)的地址,由于地址的最低 3 位必為0因此實(shí)際上僅需保存最低 3 位之外的位即可,又因?yàn)?code>header_info_rw僅用了最低 2 位作為特殊標(biāo)記位,因此指定當(dāng)前header_info* curHeader指向下一個(gè)節(jié)點(diǎn)header_info* nextHeader時(shí),僅需設(shè)置((header_info_rw*)&curHeader->rw_data[0])->next = nextHeader >> 2。
typedef struct header_info {
private:
// 保存鏡像中的頭信息mach_header_64相對(duì)當(dāng)前header_info的內(nèi)存地址偏移
intptr_t mhdr_offset;
// 保存鏡像中的鏡像信息相對(duì)當(dāng)前header_info
// 的內(nèi)存地址偏移
intptr_t info_offset;
public:
// 公開的API
...
private:
// 雖然定義為數(shù)組,但是包含的元素基本為1個(gè),不會(huì)超過1個(gè)。
header_info_rw rw_data[];
} header_info;
typedef struct header_info_rw {
// 公開的API
...
private:
#ifdef __LP64__
uintptr_t isLoaded : 1;
uintptr_t allClassesRealized : 1;
uintptr_t next : 62;
#else
uintptr_t isLoaded : 1;
uintptr_t allClassesRealized : 1;
uintptr_t next : 30;
#endif
} header_info_rw;
typedef struct objc_image_info {
uint32_t version; // currently 0
uint32_t flags;
#if __cplusplus >= 201103L
private:
enum : uint32_t {
IsReplacement = 1<<0, // used for Fix&Continue, now ignored
SupportsGC = 1<<1, // image supports GC
RequiresGC = 1<<2, // image requires GC
OptimizedByDyld = 1<<3, // image is from an optimized shared cache
CorrectedSynthesize = 1<<4, // used for an old workaround, now ignored
IsSimulated = 1<<5, // image compiled for a simulator platform
HasCategoryClassProperties = 1<<6, // class properties in category_t
// not yet used = 1<<7
// Swift版本兼容相關(guān),忽略
...
};
public:
// Swift版本兼容相關(guān),忽略
...
public:
// 公開的API,忽略
...
#endif
} objc_image_info;
3.1.2 addHeader 具體處理流程
具體處理流程及細(xì)節(jié)注釋在下列源代碼中。
#define SEG_OBJC "__OBJC"
header_info *FirstHeader = 0;
header_info *LastHeader = 0;
int HeaderCount = 0;
static header_info * addHeader(const headerType *mhdr, const char *path, int &totalClasses, int &unoptimizedTotalClasses)
{
header_info *hi;
if (bad_magic(mhdr)) return NULL; // 校驗(yàn)magic,忽略
bool inSharedCache = false;
hi = preoptimizedHinfoForHeader(mhdr); // 預(yù)處理優(yōu)化相關(guān),忽略
if (hi) {
// 預(yù)處理優(yōu)化相關(guān),忽略
// 在 dyld shared cache 中查找到mhdr
// 不允許重復(fù)加載
if (hi->isLoaded()) return NULL;
inSharedCache = true;
hi->setLoaded(true);
}
else
{
// 在 dyld shared cache 中未查找到mhdr
// 1. 不允許重復(fù)添加header_info到鏈表。getNext()用于獲取下一個(gè)header_info節(jié)點(diǎn)
for (hi = FirstHeader; hi; hi = hi->getNext()) {
if (mhdr == hi->mhdr()) return NULL;
}
size_t info_size = 0;
unsigned long seg_size;
const objc_image_info *image_info = _getObjcImageInfo(mhdr,&info_size); // 獲取鏡像信息
const uint8_t *objc_segment = getsegmentdata(mhdr,SEG_OBJC,&seg_size); // 獲取__OBJC數(shù)據(jù)段
// 2. 若__OBJC數(shù)據(jù)段、鏡像信息為空,則直接返回NULL
if (!objc_segment && !image_info) return NULL;
// 3. 分配內(nèi)存header_info結(jié)構(gòu)體需要占用的內(nèi)存空間,為header_info結(jié)構(gòu)體占用字節(jié)數(shù)、header_info_rw
// 結(jié)構(gòu)體的占用字節(jié)數(shù)之和,因?yàn)閔eader_info的rw_data數(shù)組僅包含1個(gè)header_info_rw元素
hi = (header_info *)calloc(sizeof(header_info) + sizeof(header_info_rw), 1);
// 4. 設(shè)置 header_info 中用于定位鏡像頭信息的 mhdr_offset
hi->setmhdr(mhdr);
// 5. 設(shè)置 header_info 中用于定位鏡像信息的 info_offset
static const objc_image_info emptyInfo = {0, 0};
hi->setinfo(image_info ?: &emptyInfo);
// 6. 設(shè)置鏡像isLoaded為YES,表示鏡像已加載
hi->setLoaded(true);
// 7. 設(shè)置鏡像allClassesRealized為NO,表示鏡像中定義的類尚未開始class realizing
hi->setAllClassesRealized(NO);
}
{
// 8. 統(tǒng)計(jì)鏡像中包含的類的總數(shù)
size_t count = 0;
if (_getObjc2ClassList(hi, &count)) {
totalClasses += (int)count;
if (!inSharedCache) unoptimizedTotalClasses += count;
}
}
// 9. 將構(gòu)建的header_info添加到全局的已加載鏡像鏈表,添加到鏈表末尾
appendHeader(hi);
return hi;
}
void appendHeader(header_info *hi)
{
HeaderCount++;
hi->setNext(NULL);
if (!FirstHeader) {
// 鏈表為空
FirstHeader = LastHeader = hi;
} else {
if (!LastHeader) {
// 找到最后一個(gè)節(jié)點(diǎn)
LastHeader = FirstHeader;
while (LastHeader->getNext()) LastHeader = LastHeader->getNext();
}
// 添加到最后一個(gè)節(jié)點(diǎn)后面
LastHeader->setNext(hi);
LastHeader = hi;
}
}
objc_image_info *
_getObjcImageInfo(const headerType *mhdr, size_t *outBytes)
{
return getDataSection<objc_image_info>(mhdr, "__objc_imageinfo",
outBytes, nil);
}
3.2 核心邏輯 _read_images
_read_images根據(jù)前面生成的header_info結(jié)構(gòu)體的數(shù)組,加載鏡像中定義的 Objective-C 元素,如類、分類、協(xié)議。下面的代碼非常長(zhǎng),將其分為幾個(gè)部分詳細(xì)分析。從代碼注釋中可以清晰地知道其處理流程。
// 加載鏡像中的 Objective-C 元素
void _read_images(header_info **hList, uint32_t hCount)
{
header_info *hi;
uint32_t hIndex;
size_t count;
size_t I;
Class *resolvedFutureClasses = nil;
size_t resolvedFutureClassCount = 0;
static bool doneOnce;
TimeLogger ts(PrintImageTimes);
runtimeLock.assertLocked();
#define EACH_HEADER \
hIndex = 0; \
crashlog_header_name(nil) && hIndex < hCount && (hi = hList[hIndex]) && crashlog_header_name(hi); \
hIndex++
// 1. 首次運(yùn)行的初始化配置,注意該邏輯塊內(nèi)的代碼全局只執(zhí)行一次
if (!doneOnce) {
doneOnce = YES;
// 1.1 配置 isa 類型支持相關(guān),忽略
...
// 1.2 配置 tagged pointer 支持相關(guān),忽略
...
// 1.3 初始化gdb_objc_realized_classes
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotal : total) * 4 / 3;
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
// 1.4 初始化allocatedClasses哈希表用于保存已完成內(nèi)存分配的類及元類,完成內(nèi)存
// 分配是指完成類的class_rw_t內(nèi)存分配
allocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil);
}
// 2. 發(fā)現(xiàn)類,代碼見<3.2.1>
...
// 3. 類重映射,代碼見<3.2.2>
...
// 4. 獲取__objc_selrefs數(shù)據(jù)section中定義的選擇器,添加到namedSelectors哈希表,忽略
...
#if SUPPORT_FIXUP
// 5. 獲取__objc_msgrefs數(shù)據(jù)section中定義的消息,添加消息的選擇器到namedSelectors
// 哈希表,若其IMP指向預(yù)定義的IMP,則需要保證其指向新版本runtime定義的IMP,忽略
...
#endif
// 6. 發(fā)現(xiàn)協(xié)議,忽略
...
// 7. 協(xié)議排序,忽略
...
// 8. 認(rèn)識(shí)非懶加載類,代碼見<3.2.3>
...
// 9. 認(rèn)識(shí)懶加載類,代碼見<3.2.4>
...
// 10. 發(fā)現(xiàn)分類,代碼見<3.2.5>
...
#undef EACH_HEADER
}
3.2.1 發(fā)現(xiàn)類
_read_images(...)中以下代碼用于發(fā)現(xiàn)類。實(shí)際上是構(gòu)建 future class 的實(shí)體類(remapped future class/resolved future class)的過程,可以簡(jiǎn)稱為 future class 解析(future class resolving)。具體步驟是:
- 首先,通過
_getObjc2ClassList(...)讀取header_info指向的鏡像中的__objc_classlist數(shù)據(jù) section,以提取鏡像中定義的所有類; - 然后,調(diào)用
readClass(...)讀取鏡像中對(duì)應(yīng) future class(保存在全局的future_named_class_map哈希表中)的類的元數(shù)據(jù),構(gòu)建 future class 的實(shí)體類(remapped future class),并將實(shí)體類添加到remappedClasses全局哈希表中; - 最后,若
readClass(...)返回的類不等于傳入的類,則說明該類已被重映射,將返回的類保存到已解析 future class 局部變量resolvedFutureClasses中。
for (EACH_HEADER) {
classref_t *classlist = _getObjc2ClassList(hi, &count);
if (! mustReadClasses(hi)) {
// 若鏡像有經(jīng)過優(yōu)化,則不需要調(diào)用readClass()
continue;
}
bool headerIsBundle = hi->isBundle();
bool headerIsPreoptimized = hi->isPreoptimized();
for (i = 0; i < count; i++) {
Class cls = (Class)classlist[I];
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
// 若readClass返回的類非空且不等于直接從header_info讀出來的類則該類為future class
if (newCls != cls && newCls) {
resolvedFutureClasses = (Class *)
realloc(resolvedFutureClasses,
(resolvedFutureClassCount+1) * sizeof(Class));
resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
}
}
}
3.2.2 類重新映射
_read_images(...)中以下代碼用于類重映射。若發(fā)現(xiàn)類階段構(gòu)建了 future class 的實(shí)體類,則鏡像中 原指向 future class 的類引用 以及父類引用 需要指向?qū)嶓w類,這就是類重映射過程。具體過程如下:
- 調(diào)用
_getObjc2ClassRefs(...)函數(shù)獲取鏡像中__objc_classrefs數(shù)據(jù) section 中保存的所有類引用; - 遍歷所有類引用:調(diào)用
remapClassRef(Class *clsref)重映射類引用。內(nèi)部邏輯是當(dāng)檢測(cè)到remppedClass哈希表內(nèi)Key為*clsref的Value值newCls不等于*clsref時(shí),表示該類需要重新映射,將clsref指向newCls; - 調(diào)用
_getObjc2SuperRefs(...)函數(shù)獲取鏡像中__objc_superrefs數(shù)據(jù) section 中保存的所有父類引用; - 遍歷所有類引用:調(diào)用
remapClassRef(Class *clsref)重映射父類引用。
// 若發(fā)現(xiàn)類階段有處理 future class
if (!noClassesRemapped()) {
for (EACH_HEADER) {
Class *classrefs = _getObjc2ClassRefs(hi, &count);
for (i = 0; i < count; i++) {
// 重新映射鏡像中定義的類的引用
remapClassRef(&classrefs[I]);
}
classrefs = _getObjc2SuperRefs(hi, &count);
for (i = 0; i < count; i++) {
// 重新映射鏡像中包含的 super
remapClassRef(&classrefs[I]);
}
}
}
3.2.3 認(rèn)識(shí)非懶加載類
完成 future class 解析和類重映射后,需要加載的類的內(nèi)存地址即可確定。此時(shí)需要對(duì)需要加載的類進(jìn)行 class realizing 以確定其class_rw_t數(shù)據(jù)的內(nèi)存地址,以及確定類的對(duì)象內(nèi)存布局。非懶加載類的 class realizing 的代碼如下,通過調(diào)用realizeClassWithoutSwift(...)函數(shù)實(shí)現(xiàn):
for (EACH_HEADER) {
classref_t *classlist =
_getObjc2NonlazyClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (!cls) continue;
// 模擬器環(huán)境特殊配置,忽略
...
addClassTableEntry(cls);
// Swift環(huán)境兼容相關(guān),忽略
...
realizeClassWithoutSwift(cls);
}
}
3.2.4 認(rèn)識(shí) resolved future class
繼續(xù)進(jìn)行已解析的 future class 的 class realizing 過程,代碼如下:
if (resolvedFutureClasses) {
for (i = 0; i < resolvedFutureClassCount; i++) {
realizeClass(resolvedFutureClasses[I]);
resolvedFutureClasses[i]->setRequiresRawIsa(false/*inherited*/);
}
free(resolvedFutureClasses);
}
3.2.5 分類發(fā)現(xiàn)
分類中擴(kuò)展類的遵循協(xié)議列表、屬性列表、方法列表需要在運(yùn)行時(shí)添加到類的class_rw_t數(shù)據(jù)中,該過程必須在類的內(nèi)存地址、class_rw_t內(nèi)存地址、類的對(duì)象內(nèi)存布局確定后方能進(jìn)行。大致過程如下:
- 調(diào)用
_getObjc2CategoryList(...)函數(shù)獲取鏡像的__objc_catlist數(shù)據(jù) section 中保存的鏡像中定義的分類列表; - 遍歷分類列表;
- 若分類所擴(kuò)展的類為空則報(bào)錯(cuò),并直接處理下一個(gè)分類;
- 若分類擴(kuò)展元素包括協(xié)議列表或?qū)嵗龑傩曰驅(qū)嵗椒?,則將調(diào)用
addUnattachedCategoryForClass(...)將分類添加到擴(kuò)展類的待處理分類哈希表,然后調(diào)用remethodizeClass(...)將協(xié)議列表或?qū)嵗龑傩曰驅(qū)嵗椒ㄔ靥砑拥綌U(kuò)展類的class_rw_t數(shù)據(jù)中; - 若分類擴(kuò)展元素包括協(xié)議列表或類屬性或類方法,則將調(diào)用
addUnattachedCategoryForClass(...)將分類添加到擴(kuò)展類的元類的待處理分類哈希表,然后調(diào)用remethodizeClass(...)將協(xié)議列表或?qū)嵗龑傩曰驅(qū)嵗椒ㄔ靥砑拥綌U(kuò)展類的元類的class_rw_t數(shù)據(jù)中;
for (EACH_HEADER) {
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
for (i = 0; i < count; i++) {
category_t *cat = catlist[I];
Class cls = remapClass(cat->cls);
if (!cls) {
// 分類所擴(kuò)展的類為空則報(bào)錯(cuò)
catlist[i] = nil;
continue;
}
// 協(xié)議、實(shí)例方法、實(shí)例屬性添加到類的class_rw_t中
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
}
// 協(xié)議、類方法、類屬性添加到類的元類的class_rw_t中
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
}
}
}
四、初始化類庫(kù)load_images
第三章介紹的map_images用于匯總鏡像信息,并加載其中定義的 Objective-C 元素,load_images的邏輯基于map_images所加載的類、分類信息進(jìn)行。load_images主要調(diào)用了兩個(gè)函數(shù):
-
prepare_load_methods(...):執(zhí)行類、分類的load方法進(jìn)行類的初始化之前的準(zhǔn)備工作,主要是收集鏡像中實(shí)現(xiàn)了load方法的類和分類; -
void call_load_methods(void):調(diào)用類、分類中的load方法初始化類;
prepare_load_methods和call_load_methods是成對(duì)存在的兩個(gè)操作,前者記錄實(shí)現(xiàn)了load方法的類、分類,后者按正確的順序調(diào)用所記錄的類和分類的load方法。
void
load_images(const char *path __unused, const struct mach_header *mh)
{
// 若鏡像中的所有類、分類均未實(shí)現(xiàn)load方法,則直接返回
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// 收集鏡像中實(shí)現(xiàn)了load方法的類和分類
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// 執(zhí)行prepare_load_methods收集的類和分類的load方法
call_load_methods();
}
4.1 prepare_load_methods函數(shù)
分析prepare_load_methods函數(shù)邏輯前,首先看兩個(gè)數(shù)據(jù)結(jié)構(gòu)loadable_class、loadable_category分別表示包含load方法的類和分類。聲明兩個(gè)靜態(tài)數(shù)組容器loadable_class的數(shù)組loadable_classes,以及loadable_category的數(shù)組loadable_categories,分別用于記錄包含了load方法但尚未調(diào)用load的類和分類。prepare_load_methods的主邏輯就是操作上述兩個(gè)數(shù)組容器。
loadable_classes數(shù)組支持動(dòng)態(tài)擴(kuò)容,占用內(nèi)存存在冗余空間,因此不僅需要loadable_classes_used記錄loadable_classes實(shí)際保存的loadable_class結(jié)構(gòu)體數(shù)量,還需要loadable_classes_allocated記錄為數(shù)組分配的用于保存loadable_class結(jié)構(gòu)體的內(nèi)存單元數(shù)量。loadable_categories同理。
struct loadable_class {
Class cls; // may be nil
IMP method;
};
struct loadable_category {
Category cat; // may be nil
IMP method;
};
// 數(shù)組容器,記錄包含load方法的所有類的信息
static struct loadable_class *loadable_classes = nil;
//數(shù)組內(nèi)存中存在冗余空間,用loadable_classes_used實(shí)際保存的單元數(shù)量
static int loadable_classes_used = 0;
//數(shù)組內(nèi)存中存在冗余空間,因此用loadable_classes_allocated記錄分配的單元數(shù)量
static int loadable_classes_allocated = 0;
// 數(shù)組容器,記錄包含load方法的所有分類的信息
static struct loadable_category *loadable_categories = nil;
static int loadable_categories_used = 0;
static int loadable_categories_allocated = 0;
prepare_load_methods包含兩大步驟:
- 獲取鏡像中所有非懶加載類,依次調(diào)用
schedule_class_load(...)函數(shù)將包含load方法的類添加到loadable_class數(shù)組容器。schedule_class_load(...)函數(shù)內(nèi)部,在調(diào)用add_class_to_loadable_list(cls)將類loadable_class數(shù)組容器前,遞歸調(diào)用了schedule_class_load(cls->superclass),以保證父類的load方法先于子類load方法執(zhí)行; - 獲取鏡像中所有非懶加載分類,依次調(diào)用
add_category_to_loadable_list(...)函數(shù)將包含load方法的類添加到loadable_category數(shù)組容器。注意在這之前需要保證分類所擴(kuò)展的類完成 class realizing;
void prepare_load_methods(const headerType *mhdr)
{
size_t count, I;
runtimeLock.assertWriting();
// 獲取二進(jìn)制文件中,非懶加載的所有類的信息
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
// 獲取二進(jìn)制文件中,非懶加載的所有分類的信息
category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[I];
Class cls = remapClass(cat->cls);
if (!cls) continue;
realizeClass(cls);
assert(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}
// ------- 將類添加到loadable_classes數(shù)組 ------- //
static void schedule_class_load(Class cls)
{
if (!cls) return;
assert(cls->isRealized());
if (cls->data()->flags & RW_LOADED) return;
schedule_class_load(cls->superclass);
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
void add_class_to_loadable_list(Class cls)
{
IMP method;
loadMethodLock.assertLocked();
method = cls->getLoadMethod();
if (!method) return;
// loadable_classes數(shù)組擴(kuò)容,因此loadable_classes數(shù)組中是存在冗余空間的,這是loadable_classes_allocated存在原因
if (loadable_classes_used == loadable_classes_allocated) {
loadable_classes_allocated = loadable_classes_allocated*2 + 16;
loadable_classes = (struct loadable_class *)
realloc(loadable_classes,
loadable_classes_allocated *
sizeof(struct loadable_class));
}
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}
// ------- 將分類類添加到loadable_categories數(shù)組 ------- //
void add_category_to_loadable_list(Category cat)
{
IMP method;
loadMethodLock.assertLocked();
method = _category_getLoadMethod(cat);
if (!method) return;
// loadable_categories數(shù)組擴(kuò)容
if (loadable_categories_used == loadable_categories_allocated) {
loadable_categories_allocated = loadable_categories_allocated*2 + 16;
loadable_categories = (struct loadable_category *)
realloc(loadable_categories,
loadable_categories_allocated *
sizeof(struct loadable_category));
}
loadable_categories[loadable_categories_used].cat = cat;
loadable_categories[loadable_categories_used].method = method;
loadable_categories_used++;
}
4.2 call_load_methods函數(shù)
分析完prepare_load_methods的邏輯,其實(shí)call_load_methods的邏輯已經(jīng)比較明顯了。就是從loadable_classes容器及loadable_categories容器中推出類和分類,依次調(diào)用load方法。
call_load_methods(void)代碼的邏輯比較怪異,在do-while循環(huán)內(nèi)部的while循環(huán)明明已經(jīng)判斷loadable_classes_used <= 0,為什么在do-while還要判斷loadable_classes_used > 0進(jìn)入下一次迭代?這是因?yàn)轭?、分類?code>load方法中,均可能存在動(dòng)態(tài)加載鏡像文件的邏輯,從而引入新的類、分類的load方法。do-while循環(huán)內(nèi)部,執(zhí)行類的load方法使用了一個(gè)while循環(huán),而執(zhí)行分類的load方法則只調(diào)用了一次,這是因?yàn)榉诸?code>load方法必須等待其擴(kuò)展類的load方法執(zhí)行完畢才能執(zhí)行,因此需要立即進(jìn)入下一次迭代以執(zhí)行擴(kuò)展類的load方法。
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1. 遍歷并執(zhí)行類的所有可調(diào)用的load方法
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. 執(zhí)行分類的load方法
more_categories = call_category_loads();
// 3. 循環(huán)直到類及分類的所有l(wèi)oad方法均被執(zhí)行
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
4.2.1 執(zhí)行類的load方法
由于在prepare_load_methods已經(jīng)確定了類的load方法執(zhí)行順序,因此call_class_loads(void)僅需簡(jiǎn)單迭代執(zhí)行loadable_class中的load方法即可。處理過程大致如下:
- 將局部變量
classes指向loadable_classes,將loadable_classes指向nil。classes表示本次需要執(zhí)行的所有類的load方法,為舊容器。loadable_classes表示本次執(zhí)行的類的load方法中動(dòng)態(tài)載入的所有新類的load方法,為新容器; - 遍歷
classes中所有loadable_class結(jié)構(gòu)體,執(zhí)行其method所指向的load方法。遍歷classes時(shí),若load方法中載入了新的類的load方法,則又會(huì)被收集于loadable_classes所指向的新容器中; - 釋放
classes局部變量所指向的舊容器內(nèi)存空間;
static void call_class_loads(void)
{
int I;
struct loadable_class *classes = loadable_classes;
// loadable_classes指向nil
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
// 執(zhí)行l(wèi)oad方法。load方法可能包含動(dòng)態(tài)加載鏡像的邏輯,此時(shí)loadable_classes則會(huì)指向
// 新的容器來收集動(dòng)態(tài)加載鏡像中的load方法
(*load_method)(cls, SEL_load);
}
if (classes) free(classes); // 釋放舊容器
}
4.2.2 執(zhí)行分類的load方法
執(zhí)行分類的load方法的邏輯比類稍微復(fù)雜。處理過程如下:
- 局部變量
cats指向loadable_categories表示舊容器。loadable_categories指向nil表示新容器; - 遍歷舊容器中的所有
loadable_category結(jié)構(gòu)體,若loadable_category的cls成員非空且可加載,則執(zhí)行method成員指向的load方法,并把cat成員置nil; - 用
cats收集 舊容器中未執(zhí)行load方法的所有分類(判斷cat成員非空); - 用
cats收集 執(zhí)行舊容器的load方法過程中動(dòng)態(tài)載入的所有分類; - 若
cats保存的loadable_category結(jié)構(gòu)體數(shù)量大于0,則設(shè)置loadable_categories指向cats所指向的內(nèi)存空間;反之loadable_categories置nil。
static bool call_category_loads(void)
{
int i, shift;
bool new_categories_added = NO;
struct loadable_category *cats = loadable_categories;
int used = loadable_categories_used;
int allocated = loadable_categories_allocated;
loadable_categories = nil;
loadable_categories_allocated = 0;
loadable_categories_used = 0;
for (i = 0; i < used; i++) {
Category cat = cats[i].cat;
load_method_t load_method = (load_method_t)cats[i].method;
Class cls;
if (!cat) continue;
cls = _category_getClass(cat);
if (cls && cls->isLoadable()) { //cls->isLoadable()恒為YES
(*load_method)(cls, SEL_load);
cats[i].cat = nil;
}
}
// 收集上面for循環(huán)未執(zhí)行l(wèi)oad方法的所有分類,其中包含了load方法中可能存在動(dòng)態(tài)加載
// 鏡像時(shí)載入的分類的load方法,這些load方法不能立刻執(zhí)行,需要其擴(kuò)展類的load方法
// 執(zhí)行完畢后才能執(zhí)行。
shift = 0;
for (i = 0; i < used; i++) {
if (cats[i].cat) {
cats[i-shift] = cats[I];
} else {
shift++;
}
}
// loadable_categories舊容器中尚未執(zhí)行l(wèi)oad方法的loadable_category結(jié)構(gòu)體數(shù)量,這些
// loadable_category均保留在loadable_categories新容器
used -= shift;
// 若loadable_categories_used大于0,說明在執(zhí)行分類load方法時(shí)收集到新的分類load方法
new_categories_added = (loadable_categories_used > 0);
// 將新收集的分類load方法添加到loadable_categories新容器
for (i = 0; i < loadable_categories_used; i++) {
if (used == allocated) {
allocated = allocated*2 + 16;
cats = (struct loadable_category *)
realloc(cats, allocated *
sizeof(struct loadable_category));
}
cats[used++] = loadable_categories[I];
}
// 釋放舊loadable_categories容器
if (loadable_categories) free(loadable_categories);
// 賦值新loadable_categories容器
if (used) {
loadable_categories = cats;
loadable_categories_used = used;
loadable_categories_allocated = allocated;
} else {
if (cats) free(cats);
loadable_categories = nil;
loadable_categories_used = 0;
loadable_categories_allocated = 0;
}
return new_categories_added;
}
五、總結(jié)
應(yīng)用加載的本質(zhì)是加載應(yīng)用沙盒中的可執(zhí)行文件(也是目標(biāo)文件、鏡像文件),鏡像文件可以映射到內(nèi)存空間生成鏡像。鏡像中包含了頭信息、加載命令、數(shù)據(jù)區(qū),鏡像中定義的 Objective-C 元素的元信息主要保存在
__DATA數(shù)據(jù)段、__CONST_DATA數(shù)據(jù)段中。例如__objc_classlist保存鏡像中定義的所有類、__objc_catlist保存鏡像中定義的所有分類;生成鏡像后,觸發(fā)
map_images回調(diào)讀取鏡像中定義的 Objective-C 元素的元信息,主要包括發(fā)現(xiàn)類、發(fā)現(xiàn)協(xié)議、認(rèn)識(shí)類、將分類中的方法添加到類、元類的class_rw_t的方法列表中;完成鏡像綁定后,觸發(fā)
load_images收集需要調(diào)用的類及分類的load方法,其優(yōu)先級(jí)是靜態(tài)加載的父類>靜態(tài)加載的子類>靜態(tài)加載的分類>動(dòng)態(tài)加載的父類>動(dòng)態(tài)加載的子類>動(dòng)態(tài)加載的分類;下一篇介紹 runtime 實(shí)現(xiàn)基于引用計(jì)數(shù)的內(nèi)存管理機(jī)制。