010:dyld載入流程
問題
目錄
預備
正文
1:main、load、C++ 的執行順序
__attribute__((constructor)) void htFunc() { printf("%s \n",__func__); } @interface HTPerson : NSObject @end @implementation HTPerson + (void)load { NSLog(@"%s", __func__); } @end int main(int :, const char * argv[]) { @autoreleasepool { NSLog(@"%s",__func__); } return 0; }
列印順序:load
->c++(constructor)
->main
2:編譯過程及庫
2.1:編譯
原始檔
:載入.h、.m、.cpp等檔案預處理
:替換巨集,刪除註釋,展開標頭檔案,產生.i檔案編譯
:將.i檔案轉換為組合語言,產生.s檔案彙編
:將彙編檔案轉換為機器碼檔案,產生.o檔案連結
:對.o檔案中引用其他庫的地方進行引用,生成最後的可執行檔案
2.2:靜態庫 和 動態庫
程式碼庫有靜態庫
和動態庫
兩種,在開始探索app啟動流程前,我們先了解兩者的區別。
2.1 靜態庫:
靜態編譯
的庫,在編譯時
整個函式庫
的所有資料都整合
進目標程式碼
中。尾綴有.a
、.lib
、.framework
等。
- 優點:
模組化
,分工合作,提高
了程式碼的複用
和核心技術的保密
程度 - 缺點: 會
加大
包的體積
。如果靜態函式庫被改變
,程式必須重新編譯
。
2.2 動態庫:
編譯時
不會將函式庫
編譯進目的碼
中,只有程式執行
到相關函式
時,才呼叫函式庫的相應函式
。尾綴有.tbd
、.so
、.framework
等
- 優點: 可執行檔案
體積小
,多個應用程式共享記憶體
中同一份庫檔案,節省記憶體資源
,支援實時模組
升級。 - 蘋果的
動態庫
支援所有APP共享記憶體
(如UIKit),但APP
的動態庫
是寫入app main bundle
根目錄中,執行在沙盒
當前APP內共享記憶體
。(iOS8後App Extension功能支援主app和外掛之間共享動態庫)
3:dyld載入流程
1. 什麼是dyld?
dyld
是英文the dynamic link editor
的簡寫,翻譯過來就是動態連結器,是蘋果作業系統的一個重要的組成部分。在iOS/Mac OSX
系統中,僅有很少量的程序只需要核心就能完成載入,基本上所有的程序都是動態連結的,所以Mach-O
映象檔案中會有很多對外部的庫和符號的引用,但是這些引用並不能直接用,在啟動時還必須要通過這些引用進行內容的填補,這個填補工作就是由動態連結器dyld
來完成的,也就是符號繫結。動態連結器dyld
在系統中以一個使用者態的可執行檔案形式存在,一般應用程式會在Mach-O
檔案部分指定一個LC_LOAD_DYLINKER
的載入命令,此載入命令指定了dyld
的路徑,通常它的預設值是/usr/lib/dyld
。系統核心在載入Mach-O
檔案時,都需要用dyld
(位於/usr/lib/dyld
)程式進行連結。其實dyld
就是把應用的MachO檔案載入到記憶體中。
2:App載入過程是:
原始檔
(.h .m .cpp)->預編譯
(詞法語法分析) ->編譯
(載入靜態庫) ->彙編
->連結
(關聯動態庫) ->生成可執行檔案
(mach-o)
3:dyld
動態連結器載入流程
配置應用環境
->初始化主程式
->載入共享快取
->載入動態庫
->連結主程式
->連結動態庫
->弱符號繫結
->執行初始化
->呼叫main函式
。
4:dyld
載入流程分析
動態連結器dyld
是核心執行核心命令LC_LOAD_DYLINKER
載入命令時啟動的,預設使用/usr/lib/dyld
檔案作為動態連結器。
執行程式,打上斷點之後,然後檢視函式呼叫棧。
4.1:start
函式分析
在上圖的第11
行處,彙編指令callq
就是呼叫函式的指令,這個函式也是我們APP
開始的地方。
4.2:_dyld_start
分析
根據這個線索,我們在dyld
的原始碼搜尋_dyld_start
函式,可以在dyldStartup.s
檔案中找到入口,分析之後可以發現這個檔案中按照不同架構分別作了邏輯處理,比如i386
、x86_64
、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
4.3:dyldbootstrap::start
dyldbootstrap::start
就是指dyldbootstrap
這個名稱空間作用域裡的start
函式
我們在dyld
的原始碼裡搜尋dyldbootstrap
,然後找到start
函式。
uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[], const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue) { // Emit kdebug tracepoint to indicate dyld bootstrap has started <rdar://46878536> dyld3::kdebug_trace_dyld_marker(DBG_DYLD_TIMING_BOOTSTRAP_START, 0, 0, 0, 0); // if kernel had to slide dyld, we need to fix up load sensitive locations // we have to do this before using any global variables rebaseDyld(dyldsMachHeader); // 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(argc, argv, envp, apple); #endif // now that we are done bootstrapping dyld, call dyld's main uintptr_t appsSlide = appsMachHeader->getSlide(); return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue); }
- 首先呼叫
rebaseDyld()
dyld重定位; - 然後呼叫
__guard_setup
棧溢位保護; - 最後呼叫
dyld::_main
進入dyld
的_main
函式
為什麼要rebaseDyld()
重定位
這裡要提到兩種蘋果用來保證應用安全的技術:ASLR
和Code Sign
。
ASLR: 是Address Space Layout Randomization(地址空間佈局隨機化)的簡稱。App在被啟動的時候,程式會被對映到邏輯地址空間,這個邏輯地址空間有一個起始地址,ASLR
技術讓這個起始地址是隨機的。這個地址如果是固定的,攻擊者很容易就用起始地址+函式偏移地址找到對應的函式地址。
Code Sign: 是蘋果程式碼加密簽名機制,但是在Code Sign
操作的時候,加密的雜湊不是針對整個檔案,而是針對每一個Page
的。這個就保證了dyld
在載入的時候,可以對每個page
進行獨立的驗證。
正是因為ASLR
使得地址隨機化,導致起始地址不固定,以及Code Sign
,導致不能直接修改Image
。所以需要rebase
來處理符號引用問題,Rebase
的時候只需要通過增加對應偏移量就行了。Rebase
主要的作用就是修正內部(指向當前Mach-O
檔案)的指標指向,也就是基地址復位功能。
4.4rebaseDyld()
分析
// // 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); } }); }
4.5dyld::_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
主要做了什麼
- 1.主程式執行環境初始化及配置,拿到
Mach-O
標頭檔案 (macho_header
裡面包含整個Mach-O
檔案資訊其中包括所有鏈入的動態庫資訊); - 2.載入共享快取
shared cache
; - 3.例項化主程式,並賦值給
ImageLoader::LinkContext
; - 4.載入所有插入的動態庫,將可執行檔案以及相應的依賴庫與插入庫載入進記憶體生成對應的
ImageLoader
類的image
(映象檔案)物件; - 5.連結主程式(必須先連結主程式後才能插入);
- 6.連結所有的動態庫
ImageLoader
的image
(映象檔案)物件,並註冊插入的資訊,方便後續進行繫結; - 7.在連結完所有插入的動態庫映象檔案之後執行弱繫結;
- 8.執行所有動態庫
image
的初始化方法initializeMainExecutable
; - 9.查詢主程式的入口點
LC_MAIN
並返回result
結果,結束整個_dyld_start
流程,進入我們App
的main()
函式。
接下來我們分析第8
步,initializeMainExecutable()
。
4.6initializeMainExecutable
分析
void initializeMainExecutable() { // record that we've reached this step gLinkContext.startedInitializingMainExecutable = true; // run initialzers for any inserted dylibs // 對每一個插入進來的 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]); } } // run initializers for main executable and everything it brings up // 對主程式呼叫 runInitializers 方法初始化 sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]); // register cxa_atexit() handler to run static terminators in all loaded images when this process exits // 註冊 cxa_atexit() 回撥以在此程序退出時在所有載入的影象中執行靜態終止符 if ( gLibSystemHelpers != NULL ) (*gLibSystemHelpers->cxa_atexit)(&runAllStaticTerminators, NULL, NULL); // dump info if requested if ( sEnv.DYLD_PRINT_STATISTICS ) ImageLoader::printStatistics((unsigned int)allImagesCount(), initializerTimes[0]); if ( sEnv.DYLD_PRINT_STATISTICS_DETAILS ) ImageLoaderMachO::printStatisticsDetails((unsigned int)allImagesCount(), initializerTimes[0]); }
以上函式主要做了兩件事
- 1.對每一個插入進來的
dylib
呼叫runInitializers
方法進行初始化; - 2.對主程式呼叫
runInitializers
方法初始化。
4.7runInitializers
分析
void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo) { uint64_t t1 = mach_absolute_time(); mach_port_t thisThread = mach_thread_self(); ImageLoader::UninitedUpwards up; up.count = 1; up.imagesAndPaths[0] = { this, this->getPath() }; // 呼叫 processInitializers processInitializers(context, thisThread, timingInfo, up); context.notifyBatch(dyld_image_state_initialized, false); mach_port_deallocate(mach_task_self(), thisThread); uint64_t t2 = mach_absolute_time(); fgTotalInitTime += (t2 - t1); }
4.8processInitializers
實現
void ImageLoader::processInitializers(const LinkContext& context, mach_port_t thisThread, InitializerTimingList& timingInfo, ImageLoader::UninitedUpwards& images) { uint32_t maxImageCount = context.imageCount()+2; 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. for (uintptr_t i=0; i < images.count; ++i) { // 呼叫 recursiveInitialization images.imagesAndPaths[i].first->recursiveInitialization(context, thisThread, images.imagesAndPaths[i].second, timingInfo, ups); } // If any upward dependencies remain, init them. if ( ups.count > 0 ) processInitializers(context, thisThread, timingInfo, ups); }
4.9recursiveInitialization
實現
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; // 關鍵程式碼 begin ************ 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); // 關鍵程式碼 end ************ 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);
4.10notifySingle
分析
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
賦值的地方。
4.11registerObjCNotifiers
實現
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
函式
4.12_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
在啥時候呼叫了呢?
接下來我們在我們新建立的工程裡面,打個符號斷點
執行
4.13libdispatch_init
分析
我們看到在呼叫函式棧在呼叫_objc_init
之前,還呼叫了libdispatch_init
和_os_object_init
void libdispatch_init(void) { // 省略部分程式碼...... _dispatch_hw_config_init(); _dispatch_time_init(); _dispatch_vtable_init(); _os_object_init(); _voucher_init(); _dispatch_introspection_init(); }
我們在上面程式碼中找到了我們關鍵要檢視的程式碼_os_object_init()
,我們跟蹤進去看看。
4.14_os_object_init
分析
void _os_object_init(void) { _objc_init(); Block_callbacks_RR callbacks = { sizeof(Block_callbacks_RR), (void (*)(const void *))&objc_retain, (void (*)(const void *))&objc_release, (void (*)(const void *))&_os_objc_destructInstance }; _Block_use_RR2(&callbacks); #if DISPATCH_COCOA_COMPAT const char *v = getenv("OBJC_DEBUG_MISSING_POOLS"); if (v) _os_object_debug_missing_pools = _dispatch_parse_bool(v); v = getenv("DISPATCH_DEBUG_MISSING_POOLS"); if (v) _os_object_debug_missing_pools = _dispatch_parse_bool(v); v = getenv("LIBDISPATCH_DEBUG_MISSING_POOLS"); if (v) _os_object_debug_missing_pools = _dispatch_parse_bool(v); #endif }
我們看到裡面呼叫了_objc_init()
,這就證明了從_os_object_init
跳轉到_objc_init
,然後進行Runtime
的初始化操作,我們繼續下面_objc_init
的分析。
4.15_objc_init
分析
開啟Objc
原始碼,搜尋_objc_init
,看一下實現的原始碼部分
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(); runtime_init(); exception_init(); cache_init(); _imp_implementationWithBlock_init(); // 註冊回撥函式 _dyld_objc_notify_register(&map_images, load_images, unmap_image); #if __OBJC2__ didCallDyldNotifyRegister = true; #endif }
然後我們在函式內部找到了_dyld_objc_notify_register()
,我們看一下這個函式的註釋部分
/*********************************************************************** * _objc_init * Bootstrap initialization. Registers our image notifier with dyld. * Called by libSystem BEFORE library initialization time * 載入程式初始化。用 dyld 註冊我們的 image 通知程式。 * 在庫初始化之前由 libSystem 呼叫 **********************************************************************/
註釋的意思就是說這個函式_objc_init
的呼叫時機是在其他動態庫載入之前由libSystem
系統庫先呼叫的。
那麼到這裡就很明確了,其實在dyld::_main
主程式的第8
步,初始化所有動態庫及主程式的時候之前,就先註冊了load_images
的回撥,之後在Runtime
呼叫load_images
載入完所有load
方法之後,就會回撥到dyld::_main
的initializeMainExecutable()
內部執行回撥。
4.16doInitialization
分析
bool ImageLoaderMachO::doInitialization(const LinkContext& context) { CRSetCrashLogMessage2(this->getPath()); // mach-o has -init and static initializers doImageInit(context); doModInitFunctions(context); CRSetCrashLogMessage2(NULL); return (fHasDashInit || fHasInitializers); }
在doModInitFunctions
中,會呼叫c++
的構造方法。
4.17 主程式main
入口
// find entry point for main executable result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();
5、dyld
載入流程
_objc_init
的完整呼叫流程:
程式啟動
_dyld_start
->dyldbootstrap::start
->dyld::_main
->dyld::initializeMainExecutable
->ImageLoader::runInitializers
->ImageLoader::processInitializers
->ImageLoader::recursiveInitialization
->doInitialization
->libSystem_initializer(libSystem.B.dylib)
->_os_object_init(libdispatch.dylib)
->_objc_init(libobjc.A.dylib)
6、總結
注意