引子
準(zhǔn)備工作, 本文涉及到的Apple 開源源碼如下, 這些開源庫在 https://opensource.apple.com 都能下載:
- dyld
- objc4-750
- libdispatch
- libSystem
我們在一個(gè)Demo APP的AppDelegate.m中增加+load方法并且打上斷點(diǎn), 并且在main()函數(shù)第一語句也打上斷點(diǎn).
在APP運(yùn)行以后, 我們可以看到在xcode調(diào)用棧中有以下內(nèi)容:
0 +[AppDelegate load]
1 call_load_methods
2 load_images
3 dyld::notifySingle(...)
4 ImageLoader::recursiveInitialization(...)
5 ImageLoader::processInitializers(...)
6 ImageLoader::runInitializers(...)
7 dyld::_main(...)
8 dyldbootstrap::start(...)
9 _dyld_start
我們發(fā)現(xiàn), APP暫停在了+[AppDelegate load]而不是main()函數(shù)中, 也就是說+load方法會(huì)在main()函數(shù)之前執(zhí)行. 那么為什么會(huì)出現(xiàn)這種情況呢.
注意使用真機(jī)調(diào)試和Simulator調(diào)試可能調(diào)用棧不太一樣, 這里建議使用真機(jī)調(diào)試.
dyld的_main方法
dyld被稱為動(dòng)態(tài)鏈接器, 在dyldStartup.s文件中, 有一個(gè)_dyld_start方法, 這個(gè)方法是一個(gè)匯編方法
__dyld_start:
popq %rdi # param1 = mh of app
pushq $0 # push a zero for debugger end of frames marker
movq %rsp,%rbp # pointer to base of kernel frame
andq $-16,%rsp # force SSE alignment
subq $16,%rsp # room for local variables
# call dyldbootstrap::start(app_mh, argc, argv, slide, dyld_mh, &startGlue)
movl 8(%rbp),%esi # param2 = argc into %esi
leaq 16(%rbp),%rdx # param3 = &argv[0] into %rdx
movq __dyld_start_static(%rip), %r8
leaq __dyld_start(%rip), %rcx
subq %r8, %rcx # param4 = slide into %rcx
leaq ___dso_handle(%rip),%r8 # param5 = dyldsMachHeader
leaq -8(%rbp),%r9
call __ZN13dyldbootstrap5startEPK12macho_headeriPPKclS2_Pm
movq -8(%rbp),%rdi
cmpq $0,%rdi
jne Lnew
會(huì)調(diào)用dyldbootstrap::start(...)方法, 這個(gè)方法:
// This is code to bootstrap dyld. This work in normally done for a program by dyld and crt.
// In dyld we have to do this manually.
//
// 核心方法: dyldbootstrap::start(...), 首先bootstrapping dyld, 然后調(diào)用dyld::_main核心方法
uintptr_t start(const struct macho_header* appsMachHeader, int argc, const char* argv[],
intptr_t slide, const struct macho_header* dyldsMachHeader,
uintptr_t* startGlue)
{
// if kernel had to slide dyld, we need to fix up load sensitive locations
// we have to do this before using any global variables
// 如果slide dyld, 我們必須 fixeup 一下dyly中的內(nèi)容.
if ( slide != 0 ) {
rebaseDyld(dyldsMachHeader, slide);
}
// allow dyld to use mach messaging
mach_init();
// 內(nèi)核設(shè)置的env pointers, 也就是環(huán)境參數(shù)
// kernel sets up env pointer to be just past end of agv array
const char** envp = &argv[argc+1];
// kernel sets up apple pointer to be just past end of envp array
const char** apple = envp;
while(*apple != NULL) { ++apple; }
++apple;
// set up random value for stack canary
__guard_setup(apple);
#if DYLD_INITIALIZER_SUPPORT
// run all C++ initializers inside dyld
runDyldInitializers(dyldsMachHeader, slide, argc, argv, envp, apple);
#endif
// 當(dāng)前這里 已經(jīng)完成了 bootstrapping dyld過程, 后面需要調(diào)用 dyld 的_main函數(shù)
// now that we are done bootstrapping dyld, call dyld's main
uintptr_t appsSlide = slideOfMainExecutable(appsMachHeader);
// 前面經(jīng)過很多設(shè)置最后調(diào)用 dyld::_main 方法
return dyld::_main(appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}
代碼邏輯比較清晰, 經(jīng)過很多配置的參數(shù)然后調(diào)用dyld::_main()方法:
注意
dyld::_main(...)App中的main()是兩個(gè)不同的方法.
/*
//
// Entry point for dyld. The kernel loads dyld and jumps to __dyld_start which
// sets up some registers and call this function.
//
// Returns address of main() in target program which __dyld_start jumps to
//
*/
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide,
int argc, const char* argv[], const char* envp[], const char* apple[],
uintptr_t* startGlue)
{
//1 設(shè)置運(yùn)行環(huán)境,處理環(huán)境變量
uintptr_t result = 0;
sMainExecutableMachHeader = mainExecutableMH;
CRSetCrashLogMessage("dyld: launch started");
// 設(shè)置上下文
setContext(mainExecutableMH, argc, argv, envp, apple);
// Pickup the pointer to the exec path.
// 獲取可執(zhí)行文件的路徑
sExecPath = _simple_getenv(apple, "executable_path");
// <rdar://problem/13868260> Remove interim apple[0] transition code from dyld
if (!sExecPath) sExecPath = apple[0];
// 將可執(zhí)行文件的路徑由相對路徑轉(zhuǎn)化成絕對路徑
bool ignoreEnvironmentVariables = false;
if ( sExecPath[0] != '/' ) {
// have relative path, use cwd to make absolute
char cwdbuff[MAXPATHLEN];
if ( getcwd(cwdbuff, MAXPATHLEN) != NULL ) {
// maybe use static buffer to avoid calling malloc so early...
char* s = new char[strlen(cwdbuff) + strlen(sExecPath) + 2];
strcpy(s, cwdbuff);
strcat(s, "/");
strcat(s, sExecPath);
sExecPath = s;
}
}
// Remember short name of process for later logging
// 獲取可執(zhí)行文件去除前面的路徑, 獲取它的name,
sExecShortName = ::strrchr(sExecPath, '/');
if ( sExecShortName != NULL )
++sExecShortName;
else
sExecShortName = sExecPath;
// 配置進(jìn)程是否受到限制
sProcessIsRestricted = processRestricted(mainExecutableMH, &ignoreEnvironmentVariables, &sProcessRequiresLibraryValidation);
if ( sProcessIsRestricted ) {
#if SUPPORT_LC_DYLD_ENVIRONMENT
checkLoadCommandEnvironmentVariables();
#endif
pruneEnvironmentVariables(envp, &apple);
// set again because envp and apple may have changed or moved
setContext(mainExecutableMH, argc, argv, envp, apple);
} else {
if ( !ignoreEnvironmentVariables )
checkEnvironmentVariables(envp);// 檢查環(huán)境變量
defaultUninitializedFallbackPaths(envp);
}
if ( sEnv.DYLD_PRINT_OPTS )
printOptions(argv);
if ( sEnv.DYLD_PRINT_ENV )
printEnvironmentVariables(envp);
// 獲取當(dāng)前設(shè)備的CPU架構(gòu)信息
getHostInfo(mainExecutableMH, mainExecutableSlide);
// install gdb notifier
// 注冊gdb的監(jiān)聽者, 用于調(diào)試
stateToHandlers(dyld_image_state_dependents_mapped, sBatchHandlers)->push_back(notifyGDB);
stateToHandlers(dyld_image_state_mapped, sSingleHandlers)->push_back(updateAllImages);
// make initial allocations large enough that it is unlikely to need to be re-alloced
sAllImages.reserve(INITIAL_IMAGE_COUNT);
sImageRoots.reserve(16);
sAddImageCallbacks.reserve(4);
sRemoveImageCallbacks.reserve(4);
sImageFilesNeedingTermination.reserve(16);
sImageFilesNeedingDOFUnregistration.reserve(8);
#ifdef WAIT_FOR_SYSTEM_ORDER_HANDSHAKE
// <rdar://problem/6849505> Add gating mechanism to dyld support system order file generation process
WAIT_FOR_SYSTEM_ORDER_HANDSHAKE(dyld::gProcessInfo->systemOrderFlag);
#endif
//2 初始化主程序
try {
// add dyld itself to UUID list
addDyldImageToUUIDList();
CRSetCrashLogMessage(sLoadingCrashMessage);
// instantiate ImageLoader for main executable
// 加載sExecPath路徑下的可執(zhí)行文件, 實(shí)例化一個(gè)ImageLoader對象.
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
// 設(shè)置上下文, 將MainExecutable 這個(gè) ImageLoader設(shè)置給鏈接上下文, 配置鏈接上下文其他變量
gLinkContext.mainExecutable = sMainExecutable;
gLinkContext.processIsRestricted = sProcessIsRestricted;
gLinkContext.processRequiresLibraryValidation = sProcessRequiresLibraryValidation;
gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);
// load shared cache
//3 加載共享緩存
checkSharedRegionDisable();
#if DYLD_SHARED_CACHE_SUPPORT
if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion )
mapSharedCache();
#endif
// Now that shared cache is loaded, setup an versioned dylib overrides
#if SUPPORT_VERSIONED_PATHS
checkVersionedPaths();
#endif
// load any inserted libraries
//4 加載插入的動(dòng)態(tài)庫
//變量 `DYLD_INSERT_LIBRARIES` 環(huán)境變量, 調(diào)用`loadInsertedDylib`方法加載所有要插入的庫, 這些庫都被加入到`sAllImages`數(shù)組中
if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib)
loadInsertedDylib(*lib);
}
// record count of inserted libraries so that a flat search will look at
// inserted libraries, then main, then others.
sInsertedDylibCount = sAllImages.size()-1;
// link main executable
//5 鏈接主程序
// 開始鏈接主程序, 此時(shí)主程序已經(jīng)被加載到gLinkContext.mainExecutable中, 調(diào)用 link 鏈接主程序。內(nèi)核調(diào)用的是ImageLoader::link 函數(shù)。
gLinkContext.linkingMainExecutable = true;
link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL));
sMainExecutable->setNeverUnloadRecursive();
if ( sMainExecutable->forceFlat() ) {
gLinkContext.bindFlat = true;
gLinkContext.prebindUsage = ImageLoader::kUseNoPrebinding;
}
//6 鏈接插入的動(dòng)態(tài)庫
//對 sAllimages (除了主程序的Image外)中的庫調(diào)用link進(jìn)行鏈接,然后調(diào)用 registerInterposing 注冊符號插入, 例如是libSystem就是此時(shí)加入的
// link any inserted libraries
// do this after linking main executable so that any dylibs pulled in by inserted
// dylibs (e.g. libSystem) will not be in front of dylibs the program uses
if ( sInsertedDylibCount > 0 ) {
for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
ImageLoader* image = sAllImages[i+1];
link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL));
image->setNeverUnloadRecursive();
}
// only INSERTED libraries can interpose
// register interposing info after all inserted libraries are bound so chaining works
for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
ImageLoader* image = sAllImages[i+1];
// 注冊符號插入,Interposition, 是通過編寫與函數(shù)庫同名的函數(shù)來取代函數(shù)庫的行為.
image->registerInterposing();
}
}
// <rdar://problem/19315404> dyld should support interposition even without DYLD_INSERT_LIBRARIES
for (int i=sInsertedDylibCount+1; i < sAllImages.size(); ++i) {
ImageLoader* image = sAllImages[i];
if ( image->inSharedCache() )
continue;
image->registerInterposing();
}
// apply interposing to initial set of images
for(int i=0; i < sImageRoots.size(); ++i) {
sImageRoots[i]->applyInterposing(gLinkContext);
}
gLinkContext.linkingMainExecutable = false;
//7 執(zhí)行弱符號綁定
// <rdar://problem/12186933> do weak binding only after all inserted images linked
sMainExecutable->weakBind(gLinkContext);
CRSetCrashLogMessage("dyld: launch, running initializers");
#if SUPPORT_OLD_CRT_INITIALIZATION
// Old way is to run initializers via a callback from crt1.o
if ( ! gRunInitializersOldWay )
initializeMainExecutable();
#else
//8 執(zhí)行初始化方法
// 執(zhí)行初始化方法, 其中`+load` 和constructor方法就是在這里執(zhí)行, `initializeMainExecutable`方法先是內(nèi)部調(diào)用動(dòng)態(tài)庫的初始化方法, 然后調(diào)用主程序的初始化方法
// run all initializers
initializeMainExecutable();
#endif
//9 查找APP入口點(diǎn)并返回
// find entry point for main executable
result = (uintptr_t)sMainExecutable->getThreadPC();
if ( result != 0 ) {
// main executable uses LC_MAIN, needs to return to glue in libdyld.dylib
if ( (gLibSystemHelpers != NULL) && (gLibSystemHelpers->version >= 9) )
*startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit;
else
halt("libdyld.dylib support not present for LC_MAIN");
}
else {
// main executable uses LC_UNIXTHREAD, dyld needs to let "start" in program set up for main()
result = (uintptr_t)sMainExecutable->getMain();
*startGlue = 0;
}
}
catch(const char* message) {
syncAllImages();
halt(message);
}
catch(...) {
dyld::log("dyld: launch failed\n");
}
CRSetCrashLogMessage(NULL);
return result;
}
這里dyld::_main(...方法比較長, 這里簡單歸納一下:
1. 設(shè)置運(yùn)行環(huán)境,處理環(huán)境變量
代碼在開始時(shí)候, 將傳入的變量mainExecutableMH賦值給了sMainExecutableMachHeader, 這是一個(gè)macho_header類型的變量, 其結(jié)構(gòu)體內(nèi)容就是本章前面介紹的mach_header結(jié)構(gòu)體, 表示的是當(dāng)前主程序的Mach-O頭部信息, 有了頭部信息, 加載器就可以從頭開始, 遍歷整個(gè)Mach-O文件的信息.
接著執(zhí)行了setContext(), 此方法設(shè)置了全局一個(gè)鏈接上下文, 包括一些回調(diào)函數(shù), 參數(shù)與標(biāo)志設(shè)置信息, 設(shè)置的回調(diào)函數(shù)都是dyld本模塊實(shí)現(xiàn)的, 如loadLibrary方法就是本模塊的libraryLocator()方法, 負(fù)責(zé)加載動(dòng)態(tài)庫。在設(shè)置完這些信息后, 執(zhí)行processRestricted()方法判斷進(jìn)程是否受限, 如果進(jìn)程受限后,執(zhí)行了以下三個(gè)方法:
-
checkLoadCommandEnvironmentVariables():遍歷Mach-O中所有的LC_DYLD_ENVIRONMENT加載命令, 然后調(diào)用processDyldEnvironmentVariable()對不同的環(huán)境變量做相應(yīng)的處理。 -
pruneEnvironmentVariables():刪除進(jìn)程的LD_LIBRARY_PATH與所有以DYLD_開頭的環(huán)境變量, 這樣以后創(chuàng)建的子進(jìn)程就不包含這些環(huán)境變量了. - setContext(): 重新設(shè)置鏈接上下文。這一步執(zhí)行的主要目的是由于環(huán)境變量發(fā)生變化了, 需要更新進(jìn)程的envp與apple參數(shù)。
2. 初始化主程序
這一步主要執(zhí)行instantiateFromLoadedImage(), 它的代碼如下:
isCompatibleMachO()判斷程序與當(dāng)前的系統(tǒng)是否兼容, 如果兼容接下來就調(diào)用instantiateMainExecutable()實(shí)例化主程序, 接著調(diào)用addImage(), 會(huì)將主程序添加到全局主列表sAllImages中, 最后調(diào)用addMappedRange()申請內(nèi)存, 更新主程序映像映射的內(nèi)存區(qū),做完這些工作,第二步初始化主程序就算完成了.
3. 加載共享緩存
這一步主要執(zhí)行mapSharedCache()來映射共享緩存, 該函數(shù)先通過_shared_region_check_np()來檢查緩存是否已經(jīng)映射到了共享區(qū)域了, 如果已經(jīng)映射了, 就更新緩存的slide與UUID, 然后返回.
反之, 判斷系統(tǒng)是否處于安全啟動(dòng)模式(safe-boot mode)下,如果是就刪除緩存文件并返回, 正常啟動(dòng)的情況下, 接下來調(diào)用openSharedCacheFile()打開緩存文件, 該函數(shù)在sSharedCacheDir路徑下, 打開與系統(tǒng)當(dāng)前cpu架構(gòu)匹配的緩存文件,也就是/var/db/dyld/dyld_shared_cache_x86_64h, 接著讀取緩存文件的前8192字節(jié), 解析緩存頭dyld_cache_header的信息, 將解析好的緩存信息存入mappings變量, 最后調(diào)用_shared_region_map_and_slide_np()完成真正的映射工作.
共享緩存加載完畢后, 接著進(jìn)行動(dòng)態(tài)庫的版本化重載,這主要通過函數(shù)checkVersionedPaths()完成, 該函數(shù)讀取DYLD_VERSIONED_LIBRARY_PATH與DYLD_VERSIONED_FRAMEWORK_PATH環(huán)境變量, 將指定版本的庫比當(dāng)前加載的庫的版本做比較, 如果當(dāng)前的庫版本更高的話, 就使用新版本的庫來替換掉舊版本的。
4 加載插入的動(dòng)態(tài)庫
這一步循環(huán)遍歷DYLD_INSERT_LIBRARIES環(huán)境變量中指定的動(dòng)態(tài)庫列表, 并調(diào)用loadInsertedDylib()將其加載, 該函數(shù)調(diào)用load()完成加載工作. load()會(huì)調(diào)用loadPhase0()嘗試從文件加載,loadPhase0()會(huì)向下調(diào)用下一層phase來查找動(dòng)態(tài)庫的路徑, 直到loadPhase6(),查找的順序?yàn)?code>DYLD_ROOT_PATH->LD_LIBRARY_PATH->DYLD_FRAMEWORK_PATH->原始路徑->DYLD_FALLBACK_LIBRARY_PATH,找到后調(diào)用ImageLoaderMachO::instantiateFromFile()來實(shí)例化一個(gè)ImageLoader, 之后調(diào)用checkandAddImage()驗(yàn)證映像并將其加入到全局映像列表中. 如果loadPhase0()返回為空, 表示在路徑中沒有找到動(dòng)態(tài)庫, 就嘗試從共享緩存中查找, 找到就調(diào)用ImageLoaderMachO::instantiateFromCache()從緩存中加載, 否則就拋出沒找到映像的異常.
5 鏈接主程序
這一步執(zhí)行link()完成主程序的鏈接操作, 該函數(shù)調(diào)用了ImageLoader自身的link()函數(shù), 主要的目的是將實(shí)例化的主程序的動(dòng)態(tài)數(shù)據(jù)進(jìn)行修正, 達(dá)到讓進(jìn)程可用的目的, 典型的就是主程序中的符號表修正操作.
6 鏈接插入的動(dòng)態(tài)庫
鏈接插入的動(dòng)態(tài)庫與鏈接主程序一樣, 都是使用的link(), 插入的動(dòng)態(tài)庫列表是前面調(diào)用addImage()保存到sAllImages中的, 之后, 循環(huán)獲取每一個(gè)動(dòng)態(tài)庫的ImageLoader, 調(diào)用link()對其進(jìn)行鏈接, 注意: sAllImages中保存的第一項(xiàng)是主程序的映像. 接下來調(diào)用每個(gè)映像的registerInterposing()方法來注冊動(dòng)態(tài)庫插入與調(diào)用applyInterposing()應(yīng)用插入操作.
registerInterposing()查找__DATA段的__interpose節(jié)區(qū), 找到需要應(yīng)用插入操作(也可以叫作符號地址替換)的數(shù)據(jù), 然后做一些檢查后, 將要替換的符號與被替換的符號信息存入fgInterposingTuples列表中, 供以后具體符號替換時(shí)查詢. applyInterposing()調(diào)用了虛方法doInterpose()來做符號替換操作, 在ImageLoaderMachOCompressed中實(shí)際是調(diào)用了eachBind()與eachLazyBind()分別對常規(guī)的符號與延遲加載的符號進(jìn)行應(yīng)用插入操作, 具體使用的是interposeAt(), 該方法調(diào)用interposedAddress()在fgInterposingTuples中查找要替換的符號地址, 找到后然后進(jìn)行最終的符號地址替換.
注意是
libSystem就是此時(shí)加入的,runtime的初始化函數(shù)_objc_init()就是libSystem調(diào)用的
7 執(zhí)行弱符號綁定
weakBind()函數(shù)執(zhí)行弱符號綁定. 首先通過調(diào)用context的getCoalescedImages()將sAllImages中所有含有弱符號的映像合并成一個(gè)列表,合并完后調(diào)用initializeCoalIterator()對映像進(jìn)行排序,排序完成后調(diào)用incrementCoalIterator()收集需要進(jìn)行綁定的弱符號,后者是一個(gè)虛函數(shù),在ImageLoaderMachOCompressed中,該函數(shù)讀取映像動(dòng)態(tài)鏈接信息的weak_bind_off與weak_bind_size來確定弱符號的數(shù)據(jù)偏移與大小,然后挨個(gè)計(jì)算它們的地址信息. 之后調(diào)用getAddressCoalIterator(),按照映像的加載順序在導(dǎo)出表中查找符號的地址,找到后調(diào)用updateUsesCoalIterator()執(zhí)行最終的綁定操作,執(zhí)行綁定的是bindLocation(),前面有講過,此處不再贅述.
8 執(zhí)行初始化方法
執(zhí)行初始化的方法是:
initializeMainExecutable()ImageLoader::runInitializers()ImageLoader::processInitializers()ImageLoader::recursiveInitialization()- 先調(diào)用
image自己的初始化函數(shù)initializer, 然后發(fā)出通知, 告知觀察者當(dāng)前image已經(jīng)完成加載, 狀態(tài)是dyld_image_state_initialized-
doInitialization-
doImageInit, 獲取mach-o的init方法的地址并調(diào)用 -
doModInitFunctions, 獲取mach-o的static initializer的地址并調(diào)用
-
-
context.notifySingle(dyld_image_state_initialized, this), 對外發(fā)出通知,當(dāng)前image已經(jīng)初始化完畢.
-
9 APP的main函數(shù)地址入口點(diǎn)并返回
這一步調(diào)用主程序映像的getThreadPC()函數(shù)來查找主程序的LC_MAIN加載命令獲取程序的入口點(diǎn), 沒找到就調(diào)用getMain()到LC_UNIXTHREAD加載命令中去找, 找到后就跳到入口點(diǎn)指定的地址, dyld::main函數(shù)會(huì)返回程序的main函數(shù)地址, main函數(shù)被調(diào)用, 從而代碼來到了我們熟悉的程序入口。
到這里, dyld整個(gè)加載動(dòng)態(tài)庫的過程就算完成了。
關(guān)于initializeMainExecutable方法中, image的初始化詳解
前面一節(jié)中第8步的內(nèi)容, 是針對所有image執(zhí)行initializeMainExecutable方法, 具體的代碼如下:
/**
initializeMainExecutable 執(zhí)行初始化方法,其中 +load 和 constructor 方法就是在這里執(zhí)行。
initializeMainExecutable 內(nèi)部先調(diào)用了動(dòng)態(tài)庫的初始化方法,后調(diào)用主程序的初始化方法。
*/
void initializeMainExecutable() {
// record that we've reached this step
// 進(jìn)行標(biāo)志 gLinkContext, 已經(jīng)開始 initialize MainExecutable這個(gè)image.
gLinkContext.startedInitializingMainExecutable = true;
// run initialzers for any inserted dylibs
// 給被插入的所有的 dylibs 進(jìn)行初始化 -- 調(diào)用 initialzers
ImageLoader::InitializerTimingList initializerTimes[sAllImages.size()];
initializerTimes[0].count = 0;
const size_t rootCount = sImageRoots.size();
if ( rootCount > 1 ) {
// 從 sImageRoots 中的第一個(gè)變量是 MainExcutable image, 因此這里初始化的時(shí)候需要跳過第一個(gè)數(shù)據(jù), 對其他后面插入的dylib進(jìn)行調(diào)用ImageLoader::runInitializers進(jìn)行初始化
for(size_t i=1; i < rootCount; ++i) {
sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);
}
}
// 單獨(dú)對 main executable調(diào)用ImageLoader::runInitializers進(jìn)行初始化
// run initializers for main executable and everything it brings up
sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
// register cxa_atexit() handler to run static terminators in all loaded images when this process exits
if ( gLibSystemHelpers != NULL )
(*gLibSystemHelpers->cxa_atexit)(&runAllStaticTerminators, NULL, NULL);
// dump info if requested
if ( sEnv.DYLD_PRINT_STATISTICS )
ImageLoaderMachO::printStatistics((unsigned int)sAllImages.size(), initializerTimes[0]);
}
其中, 主要是調(diào)用所有image的ImageLoader::runInitializers()方法, 代碼如下:
/**
初始化過程如下, 實(shí)際調(diào)用ImageLoader::processInitializers
@param context
@param timingInfo
*/
void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo) {
// 當(dāng)前系統(tǒng)時(shí)間
uint64_t t1 = mach_absolute_time();
// 當(dāng)前調(diào)用線程
mach_port_t thisThread = mach_thread_self();
ImageLoader::UninitedUpwards up;
up.count = 1;
up.images[0] = this;
// 初始化當(dāng)前 imageLoader 中的 image鏡像的實(shí)際調(diào)用方法 ImageLoader::processInitializers
processInitializers(context, thisThread, timingInfo, up);
context.notifyBatch(dyld_image_state_initialized);
mach_port_deallocate(mach_task_self(), thisThread);
uint64_t t2 = mach_absolute_time();
fgTotalInitTime += (t2 - t1);
}
其中最核心的方法是ImageLoader::processInitializers:
// <rdar://problem/14412057> upward dylib initializers can be run too soon
// To handle dangling dylibs which are upward linked but not downward, all upward linked dylibs
// have their initialization postponed until after the recursion through downward dylibs
// has completed.
// 調(diào)用 recursiveInitialization 方法!!!!
void ImageLoader::processInitializers(const LinkContext& context, mach_port_t thisThread, InitializerTimingList& timingInfo, ImageLoader::UninitedUpwards& images) {
uint32_t maxImageCount = context.imageCount();
ImageLoader::UninitedUpwards upsBuffer[maxImageCount];
ImageLoader::UninitedUpwards& ups = upsBuffer[0];
ups.count = 0;
// Calling recursive init on all images in images list, building a new list of
// uninitialized upward dependencies.
// 處理當(dāng)前image依賴 dylib動(dòng)態(tài)庫, 調(diào)用 recursiveInitialization 方法!!!
for (uintptr_t i=0; i < images.count; ++i) {
images.images[i]->recursiveInitialization(context, thisThread, timingInfo, ups);
}
// If any upward dependencies remain, init them.
if ( ups.count > 0 )
processInitializers(context, thisThread, timingInfo, ups);
}
ImageLoader::recursiveInitialization方法中, 會(huì)遞歸的調(diào)用當(dāng)前image的依賴的dylib動(dòng)態(tài)庫的初始化函數(shù)進(jìn)行初始化, 然后才調(diào)用doInitialization來調(diào)用自己的初始化函數(shù), 在中間image的state狀態(tài)切換時(shí),對外通過notifySingle方法給外部環(huán)境contenxt發(fā)出狀態(tài)變化的通知(外部如果有內(nèi)容監(jiān)聽了相關(guān)通知, 那么會(huì)執(zhí)行相應(yīng)回調(diào), runtime就是如此):
/**
遞歸調(diào)用 image 進(jìn)行初始化, 先調(diào)用image依賴的image進(jìn)行初始化. 直到自己
*/
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
recursive_lock lock_info(this_thread);
recursiveSpinLock(lock_info);
// 當(dāng)前ImageLoader依賴的Image還沒有初始化完, 進(jìn)入if中, 如果執(zhí)行完成, 直接返回
if ( fState < dyld_image_state_dependents_initialized-1 ) {
uint8_t oldState = fState;
// break cycles -> 這是設(shè)置當(dāng)前imageLoader的state接近依賴初始化.
fState = dyld_image_state_dependents_initialized-1;
try {
// 首先初始化image底層的依賴庫
// initialize lower level libraries first
for(unsigned int i=0; i < libraryCount(); ++i) {
ImageLoader* dependentImage = libImage(i);
if ( dependentImage != NULL ) {
// don't try to initialize stuff "above" me yet
if ( libIsUpward(i) ) {
uninitUps.images[uninitUps.count] = dependentImage;
uninitUps.count++;
}
else if ( dependentImage->fDepth >= fDepth ) {
dependentImage->recursiveInitialization(context, this_thread, timingInfo, uninitUps);
}
}
}
// record termination order
if ( this->needsTermination() )
context.terminationRecorder(this);
// 到這里image底層的依賴庫都遞歸調(diào)用, 初始化完成.
// let objc know we are about to initialize this image
uint64_t t1 = mach_absolute_time();
fState = dyld_image_state_dependents_initialized;
oldState = fState;
// 通知 runtime, 當(dāng)前狀態(tài)發(fā)生變化 -- image的依賴已經(jīng)完全加載. 注意這里可能在runtime中注冊了狀態(tài)監(jiān)聽, 注冊了callback函數(shù), 當(dāng)狀態(tài)發(fā)送變化時(shí), 會(huì)觸發(fā)回調(diào)函數(shù).
context.notifySingle(dyld_image_state_dependents_initialized, this);
// 初始化當(dāng)前image, `ImageLoaderMachO::doInitialization`方法內(nèi)部會(huì)調(diào)用image的"Initializer", 這是一個(gè)函數(shù)指針, 實(shí)際是image的初始化方法. 例如 `libSystem.dylib`, 它的初始化方法就比較特殊, 我們可以參考libSystem的init.c源碼, 內(nèi)部的`libsystem_initializer`函數(shù)就是初始化真正調(diào)用的函數(shù)
// typedef void (*Initializer)(int argc, const char* argv[], const char* envp[], const char* apple[], const ProgramVars* vars);
// _init_objc方法!!!!
// initialize this image
bool hasInitializers = this->doInitialization(context);
// let anyone know we finished initializing this image
fState = dyld_image_state_initialized;
oldState = fState;
// 通知runtime, 檔期那狀態(tài)發(fā)送變化 -- image自己已經(jīng)完成初始化!!!!
context.notifySingle(dyld_image_state_initialized, this);
if ( hasInitializers ) {
uint64_t t2 = mach_absolute_time();
timingInfo.images[timingInfo.count].image = this;
timingInfo.images[timingInfo.count].initTime = (t2-t1);
timingInfo.count++;
}
}
catch (const char* msg) {
// this image is not initialized
fState = oldState;
recursiveSpinUnLock();
throw;
}
}
recursiveSpinUnLock();
}
我們先來看,doInitialization方法, 它會(huì)調(diào)用doImageInit和doModInitFunctions方法, 這兩個(gè)方法做的工作就是從image鏡像中獲取這個(gè)鏡像的真正的入口初始化方法initializer并調(diào)用.
注意,
initalizer并非名為initalizer的方法, 而是C++靜態(tài)對象初始化構(gòu)造器,atribute((constructor)進(jìn)行修飾的方法, 在ImageLoader類中initializer函數(shù)指針?biāo)赶蛟摮跏蓟椒ǖ牡刂?/p>
/**
image 真正的初始化調(diào)用方法, 內(nèi)部會(huì)調(diào)用`doImageInit`, 這個(gè)方法會(huì)調(diào)用 mach-o 的initiaizer方法. 以及 static initializers 方法.
*/
bool ImageLoaderMachO::doInitialization(const LinkContext& context) {
CRSetCrashLogMessage2(this->getPath());
// mach-o has -init and static initializers
doImageInit(context); // mach-o 的init方法
doModInitFunctions(context); // mach-o的 static initializer方法
CRSetCrashLogMessage2(NULL);
return (fHasDashInit || fHasInitializers);
}
簡單看一下Initializer函數(shù)指針的的定義和調(diào)用的過程, 首先Initializer是一個(gè)函數(shù)指針, 在doModInitFunctions方法中是獲取image中Initializer方法地址, 然后直接執(zhí)行.
typedef void (*Initializer)(int argc, const char* argv[], const char* envp[], const char* apple[], const ProgramVars* vars);
void ImageLoaderMachO::doModInitFunctions(const LinkContext& context){
...
Initializer* inits = (Initializer*)(sect->addr + fSlide);
...
Initializer func = inits[i];
...
func(context.argc, context.argv, context.envp, context.apple, &context.programVars);
...
}
在最后會(huì)調(diào)用notifySingle通知方法, 會(huì)觸發(fā)外部監(jiān)聽dyld_image_state_initialized狀態(tài)的callback回調(diào)方法.
libdiaptch的初始化和runtime的初始化
上一節(jié)中, 我們知道一個(gè)image動(dòng)態(tài)庫要被初始化并且加載完成, 需要先遞歸加載它依賴的image動(dòng)態(tài)庫.
那么一個(gè)最簡單的iOS程序依賴哪些動(dòng)態(tài)庫呢?
我們可以在一個(gè)iOS APP中設(shè)置環(huán)境變量DYLD_PRINT_INITIALIZERS為1, 然后編譯運(yùn)行, 控制臺(tái)就會(huì)打印出當(dāng)前APP依賴的所有的動(dòng)態(tài)庫.
具體步驟是: Edit-Scheme->Arguments->Enviroments Vairables增加一個(gè)參數(shù) --- Name: DYLD_PRINT_INITIALIZERS, Value: 1, 然后編譯, 控制臺(tái)會(huì)打印如下內(nèi)容:
dyld: calling initializer function 0x1805efa7c in /usr/lib/libSystem.B.dylib
dyld: calling -init function 0x1022b8bbc in /Developer/usr/lib/libBacktraceRecording.dylib
dyld: calling initializer function 0x180608098 in /usr/lib/libc++.1.dylib
dyld: calling -init function 0x181427b48 in /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation
dyld: calling initializer function 0x18148737c in /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation
...
我們看到第一個(gè)initializer function是libSystem.B.dylib.
我們在Apple官網(wǎng)找到libSystem.dylib開源庫(可能和實(shí)際有所不同), 我們看一下它的初始化方法具體做了什么工作, 我們找到libSystem源碼中的init.c文件:
// libsyscall_initializer() initializes all of libSystem.dylib
// libsyscall_initializer() 就是 libSystem.dylib的initializer初始化方法
// <rdar://problem/4892197>
__attribute__((constructor))
static void
libSystem_initializer(int argc,
const char* argv[],
const char* envp[],
const char* apple[],
const struct ProgramVars* vars)
{
...
// 內(nèi)核初始化
__libkernel_init(&libkernel_funcs, envp, apple, vars);
// 平臺(tái)信息初始化
__libplatform_init(NULL, envp, apple, vars);
// 線程初始化
__pthread_init(&libpthread_funcs, envp, apple, vars);
_libc_initializer(&libc_funcs, envp, apple, vars);
// TODO: Move __malloc_init before __libc_init after breaking malloc's upward link to Libc
__malloc_init(apple);
#if !TARGET_OS_SIMULATOR && !TARGET_OS_TV && !TARGET_OS_WATCH
/* <rdar://problem/9664631> */
__keymgr_initializer();
#endif
_dyld_initializer();
// !!!!!! GCD的初始化方法!!!!!
//libdispatch_init里調(diào)用了到了runtime初始化方法_objc_init.我們可以、在程序中打個(gè)符號斷點(diǎn)來驗(yàn)證:
libdispatch_init();
_libxpc_initializer();
// must be initialized after dispatch
_libtrace_init();
...
}
libdispatch_init里調(diào)用了到了runtime初始化方法_objc_init.
我們可以、在程序中打個(gè)符號斷點(diǎn)來驗(yàn)證, 在Xcode設(shè)置Symbolic Breakpoint, 然后設(shè)置Symblic = _objc_init:
Apple官網(wǎng)開源了
libsystem的源碼
https://opensource.apple.com/tarballs/Libsystem/

從圖中我們看到, 先libSystem_initializer調(diào)用libdispatch_init再到_objc_init初始化runtime
runtime初始化后不會(huì)閑著, 在_objc_init中注冊了幾個(gè)通知, 從dyld這里接手了幾個(gè)活, 其中包括負(fù)責(zé)初始化相應(yīng)依賴庫里的類結(jié)構(gòu), 調(diào)用依賴庫里所有的+load方法:
// Initializer called by libSystem
OBJC_EXPORT void _objc_init(void);
/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
Q:load 方法是如何被調(diào)用的?
A:_objc_init(OBJC runtime)是 libSystem 在library初始化之前調(diào)用, 進(jìn)行runtime的初始化!!!! 當(dāng) Objective-C runtime初始化的時(shí)候, 會(huì)通過 dyld_register_image_state_change_handler注冊針對一些事件的監(jiān)聽, 在每次有新的鏡像加入運(yùn)行時(shí)的時(shí)候,進(jìn)行回調(diào)調(diào)用`load_images`(也有其他事件觸發(fā) map_images和 unmap_image方法).
執(zhí)行 load_images 將所有包含 load 方法的文件加入列表 loadable_classes ,然后從這個(gè)列表中找到對應(yīng)的 load 方法的實(shí)現(xiàn),調(diào)用 load 方法。
**********************************************************************/
void _objc_init(void) {
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
lock_init();
exception_init();
// 這里是在dyld中加入一個(gè)狀態(tài)監(jiān)聽器, 一旦dyld監(jiān)聽到有新的鏡像image加載完成, 就調(diào)用 load_images 方法, 并傳入最新鏡像的信息類別 infoList
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
前面說到image在被dyld加載的時(shí)候, notifySingle 會(huì)通知image的狀態(tài)變化, 這樣_objc_init中最后通過_dyld_objc_notify_register方法注冊的監(jiān)聽器, 后面一旦有新的image被加載到dyld時(shí), 就會(huì)調(diào)用objc::load_images方法!!!
關(guān)于
runtime中的load_images, 其他文章會(huì)進(jìn)行詳解解答后面的內(nèi)容.
啟動(dòng)流程總結(jié)
一個(gè) iOS 程序會(huì)依賴于某些動(dòng)態(tài)庫, 可以通過otool -v -L debug-objc命令查看, 在啟動(dòng)的時(shí)候需要由動(dòng)態(tài)鏈接器進(jìn)行動(dòng)態(tài)鏈接,而動(dòng)態(tài)鏈接器自身也是動(dòng)態(tài)庫,因此動(dòng)態(tài)鏈接器會(huì)首先自舉自身,然后再將程序依賴的動(dòng)態(tài)庫依次加載進(jìn)內(nèi)存進(jìn)行鏈接,動(dòng)態(tài)鏈接做完之后,把控制權(quán)交給程序的main函數(shù)。
通過 otool 命令可以查看到依賴列表中有 /usr/lib/libSystem.B.dylib 和 /usr/lib/libobjc.A.dylib 這樣兩條記錄.libSystem.B.dylib 在編譯的時(shí)候依賴于很多系統(tǒng)級別的 lib, 如 libdispatch,libsystem_c, libcommonCrypto等等.而這個(gè)libobjc.A.dylib就是objc-runtime, 其初始化函數(shù)是 _objc_init, 前面說過, 可以根據(jù)_objc_init設(shè)置symbol breakpoint進(jìn)行后續(xù)的調(diào)試.
- 首先系統(tǒng)內(nèi)核建立進(jìn)程, 創(chuàng)建虛擬空間, 讀取
Mach-O文件頭,并建立虛擬空間與Mach-O文件的映射關(guān)系, 然后找到 dyld 動(dòng)態(tài)鏈接器入口_dyld_start之后, 控制權(quán)交給動(dòng)態(tài)鏈接器, 接下來就是 dyld 的自舉, 即dyldbootstrap::start(). - 自舉過程完成, 即在
dyldbootstrap::start()函數(shù)的最末尾會(huì)調(diào)用dyld::_main函數(shù), 并把此函數(shù)的返回值返還給_dyld_start, 這個(gè)返回值是 iOS 可執(zhí)行程序main函數(shù)的地址,_dyld_start會(huì)跳到該地址繼續(xù)執(zhí)行,進(jìn)入程序的主邏輯. - 不過在這個(gè)地址返回之前, 也就是在
dyld::_main函數(shù)中需要做兩個(gè)特別重要的工作:- 重定位和初始化, 也就是動(dòng)態(tài)鏈接的過程. 在完成了自舉之后, 鏈接器需要裝載動(dòng)態(tài)鏈接庫, 然后完成符號的重定位.
- 如果動(dòng)態(tài)鏈接庫文件中還有相應(yīng)的初始化信息,即含有
__mod_init_func section,就再需要調(diào)用其相應(yīng)的初始化函數(shù).
- 在
dyld::initializeMainExecutable函數(shù)中, 程序所依賴的動(dòng)態(tài)庫進(jìn)行各自的初始化, 因此ImageLoader::runInitializers函數(shù)被調(diào)用, 這個(gè)函數(shù)調(diào)用ImageLoader::processInitializers來處理初始化, 而某個(gè)動(dòng)態(tài)庫可能依賴于其它的動(dòng)態(tài)庫, 那么它所依賴的動(dòng)態(tài)庫就需要先初始化, 這里形成一個(gè)遞歸, 也就是ImageLoader::recursiveInitialization - 動(dòng)態(tài)庫初始化函數(shù)的真正調(diào)用是在
ImageLoaderMachO::doModInitFunctions函數(shù)中, 對于libSystem.B.dylib來說其初始化函數(shù)是libSystem_initializer, 在這個(gè)函數(shù)中libdispatch_init被調(diào)用,libSystem以及libdispatch也是開源的, 可以查看相關(guān)源碼. - 在
libdispatch_init中, 對Runtime的_objc_init進(jìn)行了調(diào)用, 而在Runtime的初始化過程中, 查看源碼可以看到Runtime向dyld綁定了回調(diào), 當(dāng)image加載到內(nèi)存后,dyld會(huì)通知Runtime進(jìn)行處理,Runtime接手后調(diào)用map_images做解析和處理, 把Category的實(shí)例方法, 協(xié)議以及屬性添加到類上, 把Category的類方法和協(xié)議添加到類的metaclass上; 接下來load_images中調(diào)用call_load_methods方法, 遍歷所有加載進(jìn)來的Class, 按繼承層級依次調(diào)用Class的load方法和其Category的load方法(這部分內(nèi)容,會(huì)在后面針對runtime的文章進(jìn)行分析) - 在所有的動(dòng)態(tài)庫做好符號重定位和初始化工作之后, 也就是
dyld::_main臨近末尾的時(shí)候,dyld會(huì)獲取main函數(shù)的地址返回給dyld,dyld緊接著調(diào)用main函數(shù), 將控制權(quán)交換給主程序, 程序開始真正的執(zhí)行.
結(jié)語
最后一句話概括APP的啟動(dòng):
內(nèi)核exec做好準(zhǔn)備工作,轉(zhuǎn)移控制權(quán)給dyld->dyld加載依賴庫->libdispatch初始化->runtime初始化->main開始執(zhí)行
APP的啟動(dòng)優(yōu)化
可以參考: http://www.itdecent.cn/p/7096478ccbe7
參考文章
http://www.itdecent.cn/p/7096478ccbe7
http://www.itdecent.cn/p/43db6b0aab8e
https://blog.csdn.net/fishmai/article/details/51419824
https://blog.csdn.net/guojin08/article/details/70308576
https://blog.csdn.net/nathan1987_/article/details/78591468
http://www.itdecent.cn/p/885c8077b27d
https://www.cnblogs.com/maizi008/p/5086103.html
https://blog.csdn.net/TuGeLe/article/details/81609604
http://www.itdecent.cn/p/5f337da8fbef
http://www.itdecent.cn/p/231b1cebf477
https://www.cnblogs.com/xs514521/p/7010458.html
http://www.itdecent.cn/p/73a99303cd91