010:dyld加载流程-(_dyld_start-dyldbootstrap::start-dyld::_main-dyld::initializeMainExecutable-ImageLoader::runInitializers-ImageLoader::processInitializers-ImageLoader::recursiveInitialization)
问题
目录
预备
正文
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.4 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); } }); }
4.5 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
主要做了什么
- 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.6 initializeMainExecutable
分析
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.7 runInitializers
分析
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.8 processInitializers
实现
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.9 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; // 关键代码 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.10 notifySingle
分析
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.11 registerObjCNotifiers
实现
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.13 libdispatch_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.16 doInitialization
分析
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
的完整调用流程:
程序启动
1:_dyld_start
2:-> dyldbootstrap::start
rebaseDyld()
dyld重定位、__guard_setup
栈溢出保护、dyld::_main
3:->
dyld::_main(
配置应用环境
->加载共享缓存->
->初始化主程序[将
主程序Mach-O
加载进内存]加载动态库
->链接主程序
->链接动态库
->弱符号绑定
->执行初始化
->调用main函数
)
4:-> dyld::initializeMainExecutable(为主可执行文件创建映像-主程序,调用所有
具体流程为:image
的Initalizer
方法进行初始,再为主程序可执行文件执行初始化操作。ImageLoader::runInitializers
-->ImageLoader::processInitializers
--> ImageLoader::recursiveInitialization)
5:-> ImageLoader::runInitializers (Initializer初始化)
6:-> ImageLoader::processInitializers
7:-> ImageLoader::recursiveInitialization(递归加载所有依赖库进内存。告诉所有人完成镜像的初始化。recursive:递归,循环)
8:->_dyld_objc_notify_registe(load_images->call_load_methods->call_class_loads->明确了
load
方法的调用。)
9:->registerObjCNotifiers
10:->notifySingle
11:-> doInitialization(
doModInitFunctions
,在 doModInitFunctions
之后 会 先执行 libSystem_initializer
,保证系统库优先初始化完毕,在这里初始化 libdispatch_init
,进而在_os_object_init
中 调用_objc_init
。)
12:->libdispatch_init(GCD)
13:-> _os_object_init
14:-> _objc_init(
static_init(); // C++
runtime_init(); // runtime 初始化
exception_init(); // 异常初始化
cache_init(); // 缓存初始化
)
6、总结
注意