先來看一下iOS/Mac OS系統內核架構
需要注意:dyld是運行在用戶態的進程(下面解釋)。也就是說:App啟動過程從系統內核XNU到內核把控制權交給dyld,這個過程完成了內核態到用戶態的切換。
一、dyld
初識
什么是dyld
?
dyld
是英文 the dynamic link editor 的簡寫,翻譯過來就是動態鏈接器,是蘋果操作系統的一個重要的組成部分。在iOS/Mac OSX系統中,僅有很少量的進程只需要內核就能完成加載,基本上所有的進程都是動態鏈接的,所以Mach-O
鏡像文件中會有很多對外部的庫和符號的引用,但是這些引用并不能直接用,在啟動時還必須要通過這些引用進行內容的填補,這個填補工作就是由動態鏈接器dyld來完成的,也就是符號綁定。
dyld啟動時機及位置
動態鏈接器dyld是內核執行內核命令
LC_LOAD_DYLINKER
加載命令時啟動的,默認使用/usr/lib/dyld
文件作為動態鏈接器。補充:
LC_MAIN
指的就是程序main函數加載地址,LC_LOAD_DYLIB
指向的都是程序依賴庫加載信息,舉個例子LC_LOAD_DYLIB(AFNetworking)
指的就是AFNetworking
依賴庫的加載地址。
dyld與系統內核關系
dyld是一個用戶態進程,不屬于內核的一部分,單獨由蘋果維護,并且代碼已經開源。也就是說
dyld
可以理解成一個可插入的組件,可以用第三方進行替換。
傳送門:
dyld開源代碼下載
二、從App啟動的角度進行dyld源碼流程分析
下載dyld最新版源碼dyld-733.6
。
新建測試工程,斷點設置在在main
函數之前,打印調用堆棧信息,發現App在啟動的時候會執行libdyld.dylib
的start
操作。
然后就進不去了,根據這個線索,我們在源碼的dyldStartup.s
文件中找到入口_dyld_start
,仔細分析_dyld_start
源碼,發現這個文件中按照不同架構分別做了邏輯處理,比如i386
、x86_64
、arm64
、arm
。
下面筆者摘出arm64
架構下的部分的匯編源碼:
#if __arm64__
.text
.align 2
.globl __dyld_start
__dyld_start:
mov x28, sp
and sp, x28, #~15 // force 16-byte alignment of stack
...
// call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
bl __ZN13dyldbootstrap5startEPKN5dyld311MachOLoadedEiPPKcS3_Pm
mov x16,x0 // save entry point address in x16
...
}
找到關鍵部分bl
跳轉指令,根據注釋信息,這里會跳轉調用dyld
的引導程序dyldbootstrap::start
1、dyld
的引導程序dyldbootstrap::start
// call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
bl __ZN13dyldbootstrap5startEPKN5dyld311MachOLoadedEiPPKcS3_Pm
搜索 dyldbootstrap
關鍵字,在dyldInitialization.cpp
文件中找到定義,并找到方法start
,代碼如下:
//
// 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.
//
uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],
const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue)
{
...
rebaseDyld(dyldsMachHeader);
const char** envp = &argv[argc+1];
const char** apple = envp;
while(*apple != NULL) { ++apple; }
++apple;
__guard_setup(apple);
uintptr_t appsSlide = appsMachHeader->getSlide();
return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}
根據源碼,總結dyldbootstrap::start
主要的操作如下:
- 先調用
rebaseDyld()
dyld重定位 - 然后
__guard_setup
棧溢出保護 - 最后調用
dyld::_main
進入dyld
的_main
函數
為什么要rebaseDyld()
重定位呢?
這里要提到兩種蘋果用來保證應用安全的技術:ASLR
和CodeSign
ASLR:是Address Space Layout Randomization(地址空間布局隨機化)的簡稱。App在被啟動的時候,程序會被映射到邏輯地址空間,這個邏輯地址空間有一個起始地址,ASLR技術讓這個起始地址是隨機的。這個地址如果是固定的,黑客很容易就用起始地址+函數偏移地址找到對應的函數地址。
Code Sign:就是蘋果代碼加密簽名機制,但是在Code Sign操作的時候,加密的哈希不是針對整個文件,而是針對每一個Page的。這個就保證了dyld在加載的時候,可以對每個page進行獨立的驗證。
正是因為ASLR使得地址隨機化,導致起始地址不固定,以及Code Sign,導致不能直接修改Image。所以需要rebase來處理符號引用問題,Rebase的時候只需要通過增加對應偏移量就行了。Rebase主要的作用就是修正內部(指向當前Mach-O文件)的指針指向,也就是基地址復位功能。
下面就是rebaseDyld()
的源碼:
//
// On disk, all pointers in dyld's DATA segment are chained together.
// They need to be fixed up to be real pointers to run.
static void rebaseDyld(const dyld3::MachOLoaded* dyldMH)
{
// walk all fixups chains and rebase dyld
遍歷所有固定的 chains 然后 rebase dyld
const dyld3::MachOAnalyzer* ma = (dyld3::MachOAnalyzer*)dyldMH;
assert(ma->hasChainedFixups());
uintptr_t slide = (long)ma; // all fixup chain based images have a base address of zero, so slide == load address
所有基于修正鏈的映像的基地址為零,因此slide == 加載地址
__block Diagnostics diag;
ma->withChainStarts(diag, 0, ^(const dyld_chained_starts_in_image* starts) {
ma->fixupAllChainedFixups(diag, starts, slide, dyld3::Array<const void*>(), nullptr);
});
diag.assertNoError();
// now that rebasing done, initialize mach/syscall layer
mach_init();
// <rdar://47805386> mark __DATA_CONST segment in dyld as read-only (once fixups are done)
ma->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& info, bool& stop) {
if ( info.readOnlyData ) {
::mprotect(((uint8_t*)(dyldMH))+info.vmAddr, (size_t)info.vmSize, VM_PROT_READ);
}
});
}
接下來進入dyld::_main()
2、dyld
的主程序dyld::_main
分析
在dyldInitialization.cpp
文件中找到dyld::_main()
的實現部分,大概六七百行,筆者把非關鍵的代碼省略掉,關鍵部分及翻譯部分貼出來如下:
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步:初始化程序運行環境++++++++++++++++++++++++++++++++
//初始化運行環境配置以及拿到Mach-O頭文件 (macho_header里面包含整個Mach-O文件信息其中包括所有鏈入的動態庫信息)
uint8_t mainExecutableCDHashBuffer[20];
const uint8_t* mainExecutableCDHash = nullptr;
if ( hexToBytes(_simple_getenv(apple, "executable_cdhash"), 40, mainExecutableCDHashBuffer) )
mainExecutableCDHash = mainExecutableCDHashBuffer;
notifyKernelAboutImage(mainExecutableMH, _simple_getenv(apple, "executable_file"));
uintptr_t result = 0;
//獲取主程序的macho_header結構以及主程序的slide偏移值
sMainExecutableMachHeader = mainExecutableMH;
sMainExecutableSlide = mainExecutableSlide;
......
CRSetCrashLogMessage("dyld: launch started");
//設置上下文信息
setContext(mainExecutableMH, argc, argv, envp, apple);
//獲取主程序路徑
// Pickup the pointer to the exec path.
sExecPath = _simple_getenv(apple, "executable_path");
if (!sExecPath) sExecPath = apple[0];
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
sExecShortName = ::strrchr(sExecPath, '/');
if ( sExecShortName != NULL )
++sExecShortName;
else
sExecShortName = sExecPath;
//配置進程受限模式
configureProcessRestrictions(mainExecutableMH, envp);
//檢測環境變量
checkEnvironmentVariables(envp);
defaultUninitializedFallbackPaths(envp);
//判斷是否設置了sEnv.DYLD_PRINT_OPTS以及sEnv.DYLD_PRINT_ENV,分別打印argv參數和envp環境變量
if ( sEnv.DYLD_PRINT_OPTS )
printOptions(argv);
if ( sEnv.DYLD_PRINT_ENV )
printEnvironmentVariables(envp);
//獲取當前程序架構
getHostInfo(mainExecutableMH, mainExecutableSlide);
// load shared cache
//第2步、加載共享緩存 shared cache
++++++++++++++++++++++++++++++++
//檢查共享緩存是否開啟,iOS必須開啟!!!!!!
checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
#if TARGET_OS_SIMULATOR
if ( sSharedCacheOverrideDir)
mapSharedCache();
#else
mapSharedCache();
#endif
}
......
try {
// add dyld itself to UUID list
addDyldImageToUUIDList();
// 第3步:實例化主程序,并賦值給ImageLoader::LinkContext
+++++++++++++++++++++
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
gLinkContext.mainExecutable = sMainExecutable;
gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);
......
#if SUPPORT_VERSIONED_PATHS
checkVersionedPaths();
#endif
// dyld_all_image_infos image list does not contain dyld
// add it as dyldPath field in dyld_all_image_infos
// for simulator, dyld_sim is in image list, need host dyld added
#if TARGET_OS_SIMULATOR
// get path of host dyld from table of syscall vectors in host dyld
void* addressInDyld = gSyscallHelpers;
#else
// get path of dyld itself
void* addressInDyld = (void*)&__dso_handle;
#endif
char dyldPathBuffer[MAXPATHLEN+1];
int len = proc_regionfilename(getpid(), (uint64_t)(long)addressInDyld, dyldPathBuffer, MAXPATHLEN);
if ( len > 0 ) {
dyldPathBuffer[len] = '\0'; // proc_regionfilename() does not zero terminate returned string
if ( strcmp(dyldPathBuffer, gProcessInfo->dyldPath) != 0 )
gProcessInfo->dyldPath = strdup(dyldPathBuffer);
}
//第4步 加載插入的動態庫++++++++++++++++++++
// load any inserted libraries
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步:鏈接主程序++++++++++++++
gLinkContext.linkingMainExecutable = true;
#if SUPPORT_ACCELERATE_TABLES
if ( mainExcutableAlreadyRebased ) {
// previous link() on main executable has already adjusted its internal pointers for ASLR
// work around that by rebasing by inverse amount
sMainExecutable->rebase(gLinkContext, -mainExecutableSlide);
}
#endif
link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
sMainExecutable->setNeverUnloadRecursive();
if ( sMainExecutable->forceFlat() ) {
gLinkContext.bindFlat = true;
gLinkContext.prebindUsage = ImageLoader::kUseNoPrebinding;
}
//第6步、鏈接插入的動態庫++++++++
// 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), -1);
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];
image->registerInterposing(gLinkContext);
}
}
// <rdar://problem/19315404> dyld should support interposition even without DYLD_INSERT_LIBRARIES
for (long i=sInsertedDylibCount+1; i < sAllImages.size(); ++i) {
ImageLoader* image = sAllImages[i];
if ( image->inSharedCache() )
continue;
image->registerInterposing(gLinkContext);
}
......
// apply interposing to initial set of images
for(int i=0; i < sImageRoots.size(); ++i) {
sImageRoots[i]->applyInterposing(gLinkContext);
}
ImageLoader::applyInterposingToDyldCache(gLinkContext);
// Bind and notify for the main executable now that interposing has been registered
uint64_t bindMainExecutableStartTime = mach_absolute_time();
sMainExecutable->recursiveBindWithAccounting(gLinkContext, sEnv.DYLD_BIND_AT_LAUNCH, true);
uint64_t bindMainExecutableEndTime = mach_absolute_time();
ImageLoaderMachO::fgTotalBindTime += bindMainExecutableEndTime - bindMainExecutableStartTime;
gLinkContext.notifyBatch(dyld_image_state_bound, false);
// Bind and notify for the inserted images now interposing has been registered
if ( sInsertedDylibCount > 0 ) {
for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
ImageLoader* image = sAllImages[i+1];
image->recursiveBind(gLinkContext, sEnv.DYLD_BIND_AT_LAUNCH, true);
}
}
//第7步、在鏈接所有插入的image后,執行弱綁定++++++++++++++++++++++++++++++
// <rdar://problem/12186933> do weak binding only after all inserted images linked
sMainExecutable->weakBind(gLinkContext);
gLinkContext.linkingMainExecutable = false;
sMainExecutable->recursiveMakeDataReadOnly(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步:執行所有的初始化方法+++++++++++++++++++++
// run all initializers
initializeMainExecutable();
#endif
// notify any montoring proccesses that this process is about to enter main()
notifyMonitoringDyldMain();
if (dyld3::kdebug_trace_dyld_enabled(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE)) {
dyld3::kdebug_trace_dyld_duration_end(launchTraceID, DBG_DYLD_TIMING_LAUNCH_EXECUTABLE, 0, 0, 2);
}
ARIADNEDBG_CODE(220, 1);
#if __MAC_OS_X_VERSION_MIN_REQUIRED
if ( gLinkContext.driverKit ) {
result = (uintptr_t)sEntryOveride;
if ( result == 0 )
halt("no entry point registered");
*startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit;
}
else
#endif
{
//第9步:查找主程序的入口點并返回
// find entry point for main executable
result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();
if ( result != 0 ) {
// main executable uses LC_MAIN, we need to use helper in libdyld to call into main()
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->getEntryFromLC_UNIXTHREAD();
*startGlue = 0;
}
}
#if __has_feature(ptrauth_calls)
// start() calls the result pointer as a function pointer so we need to sign it.
result = (uintptr_t)__builtin_ptrauth_sign_unauthenticated((void*)result, 0, 0);
#endif
}
catch(const char* message) {
syncAllImages();
halt(message);
}
catch(...) {
dyld::log("dyld: launch failed\n");
}
......
return result;
}
總結dyld::_main
主要做了以下操作(就不一一分析了):
- 主程序運行環境初始化及配置,拿到Mach-O頭文件 (macho_header里面包含整個Mach-O文件信息其中包括所有鏈入的動態庫信息)
- 加載共享緩存 shared cache
- 實例化主程序,并賦值給ImageLoader::LinkContext
- 加載所有插入的動態庫,將可執行文件以及相應的依賴庫與插入庫加載進內存生成對應的ImageLoader類的image(鏡像文件)對象
- 鏈接主程序(必須先鏈接主程序后才能插入)
- 鏈接所有的動態庫ImageLoader的image(鏡像文件)對象,并注冊插入的信息,方便后續進行綁定
- 在鏈接完所有插入的動態庫鏡像文件之后執行弱綁定
- 執行所有動態庫image的初始化方法initializeMainExecutable
- 查找主程序的入口點
LC_MAIN
并返回result結果,結束整個_dyld_start
流程,進入我們App的main()函數!
這里解釋一下共享緩存機制:
dyld加載時,為了優化程序啟動,在
dyld::_main
中啟用了共享緩存(shared cache)技術。共享緩存會在進程啟動時被dyld映射到內存中,之后,當任何Mach-O映像加載時,dyld首先會檢查該Mach-O映像與所需的動態庫是否在共享緩存中,如果存在,則直接將它在共享內存中的內存地址映射到進程的內存地址空間。在程序依賴的系統動態庫很多的情況下,這種做法對程序啟動性能會有明顯提升。
接下來分析一下_main
的第8步,initializeMainExecutable()
在源碼中搜索initializeMainExecutable
,然后在dyld2.cpp
文件中找到實現部分:
void initializeMainExecutable()
{
......
對每一個插入進來的dylib調用runInitializers方法進行初始化
ImageLoader::InitializerTimingList initializerTimes[allImagesCount()];
initializerTimes[0].count = 0;
const size_t rootCount = sImageRoots.size();
if ( rootCount > 1 ) {
for(size_t i=1; i < rootCount; ++i) {
sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);
}
}
對主程序調用runInitializers方法初始化
sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
// register cxa_atexit() handler to run static terminators in all loaded images when this process exits
注冊cxa_atexit()回調以在此進程退出時在所有加載的圖像中運行靜態終止符
......
}
總結initializeMainExecutable
函數中間做了什么:
- 對每一個插入進來的dylib調用runInitializers方法進行初始化
- 對主程序調用runInitializers方法初始化
注意!這兩步都涉及到了關鍵的函數 runInitializers()
,我們進入它的源碼,發現內部調用了processInitializers
,繼續進入,發現processInitializers
內部又調用了recursiveInitialization
下面是recursiveInitialization
的實現:
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
recursive_lock lock_info(this_thread);
recursiveSpinLock(lock_info);
if ( fState < dyld_image_state_dependents_initialized-1 ) {
uint8_t oldState = fState;
// break cycles
fState = dyld_image_state_dependents_initialized-1;
try {
// 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.imagesAndPaths[uninitUps.count] = { dependentImage, libPath(i) };
uninitUps.count++;
}
else if ( dependentImage->fDepth >= fDepth ) {
dependentImage->recursiveInitialization(context, this_thread, libPath(i), timingInfo, uninitUps);
}
}
}
// record termination order
if ( this->needsTermination() )
context.terminationRecorder(this);
// let objc know we are about to initialize this image
uint64_t t1 = mach_absolute_time();
fState = dyld_image_state_dependents_initialized;
oldState = fState;
context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
// initialize this image
bool hasInitializers = this->doInitialization(context);
// let anyone know we finished initializing this image
fState = dyld_image_state_initialized;
oldState = fState;
context.notifySingle(dyld_image_state_initialized, this, NULL);
if ( hasInitializers ) {
uint64_t t2 = mach_absolute_time();
timingInfo.addTime(this->getShortName(), t2-t1);
}
}
catch (const char* msg) {
// this image is not initialized
fState = oldState;
recursiveSpinUnLock();
throw;
}
}
recursiveSpinUnLock();
}
在recursiveInitialization
的實現中發現關鍵代碼notifySingle
,
context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
繼續深入,在dyld2.cpp
文件中找到實現
static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
{
...
if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && image->notifyObjC() ) {
uint64_t t0 = mach_absolute_time();
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
uint64_t t1 = mach_absolute_time();
uint64_t t2 = mach_absolute_time();
uint64_t timeInObjC = t1-t0;
uint64_t emptyTime = (t2-t1)*100;
if ( (timeInObjC > emptyTime) && (timingInfo != NULL) ) {
timingInfo->addTime(image->getShortName(), timeInObjC);
}
}
...
找到一個關鍵的函數指針* sNotifyObjCInit
, 我們來看看這個指針是用來干嘛的, 在當前文件下,搜索,找到sNotifyObjCInit
賦值的地方
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
// record functions to call
sNotifyObjCMapped = mapped;
sNotifyObjCInit = init;
sNotifyObjCUnmapped = unmapped;
...
}
全局搜索,看看registerObjCNotifiers
這個方法會被誰調用,找到調用的地方_dyld_objc_notify_register
函數
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
_dyld_objc_notify_init init,
_dyld_objc_notify_unmapped unmapped)
{
dyld::registerObjCNotifiers(mapped, init, unmapped);
}
繼續搜索,發現找不到_dyld_objc_notify_register
方法的調用者,那么問題來了:
_dyld_objc_notify_register
在啥時候調用了呢?
接下來我們回到測試工程,打符號斷點如下:
運行發現:
首先根據調用堆棧信息,我們能看出來_dyld_objc_notify_register
是_objc_init
進行調用的。而_objc_init
函數則是Runtime的入口函數!
打開Objc源碼
,搜索_objc_init
,列出_objc_init()
實現的源碼部分:
/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/
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_objc_notify_register(&map_images, load_images, unmap_image);
}
在最底部發現了我們要找的_dyld_objc_notify_register()
!接下來重點看一下這個函數的注釋部分
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* //引導程序初始化。 用dyld注冊我們的image通知程序。
* Called by libSystem BEFORE library initialization time
* //在庫初始化之前由libSystem調用!!!!!
*
注釋的意思就是說這個函數_objc_init
的調用時機是在其他動態庫加載之前由libSystem
系統庫先調用的。
那么到現在就很明確了,其實在dyld::_main
主程序的第8步,初始化所有動態庫及主程序的時候之前,就先注冊了load_images
的回調,之后在Runtime
調用load_images
加載完所有load
方法之后,就會回調到dyld::_main
的initializeMainExecutable()
內部執行回調。
我們來通過斷點驗證一下:
給我們的測試工程加個自定義load方法,斷點截圖如下:
三、App啟動之dyld流程圖總結如下:
喜歡的可以給個贊哦~