在写 《iOS:load方法能不能被hook?》 和 《iOS启动优化:App启动耗时在线监控与AppDelegate管控》 两篇文章时都提到了动态库的加载,由于主题的原因,没有详细介绍,有同学对这个比较感兴趣,今天我们就来研究下在iOS中动态库的加载顺序是什么样子的。
1.实验篇
我们先通过demo看下几种Case:
-
没有依赖关系:
我们制作dylibA
、dylibB
、dylibB
这三个动态库(不了解动态库的制作的请问度娘...),且它们没有依赖关系,同时我们在每个库中添加一个Class,暂且以Class的load方法的调用顺序当做是动态库的加载顺序(先挖一个坑),比如dylibA
:@implementation ClassA + (void)load { NSLog(@"dylibA loaded"); } @end
将这三个库加到demo工程中,并且保证
Build Phases
-Link Binary With Libraries
中的顺序:dylibA
>dylibB
>dylibC
,运行结果:Demo[53199:17384949] dylibA loaded Demo[53199:17384949] dylibB loaded Demo[53199:17384949] dylibC loaded
我们调整下
Link Binary With Libraries
中的顺序:dylibC
>dylibB
>dylibA
,运行结果:Demo[53265:17397552] dylibC loaded Demo[53265:17397552] dylibB loaded Demo[53265:17397552] dylibA loaded
通过实验我们知道:在没有依赖关系的情况下,动态库的加载顺序由
Link Binary With Libraries
中的顺序决定,当然我们可以通过Link Binary With Libraries
来控制动态库的加载顺序。 -
单一依赖关系
dylibA
依赖dylibB
,dylibB
依赖dylibC
,我们简单改造下这三个库,如在dylibB
中import下dylibC
的头文件,dylibA
中同理:#import "ClassB.h" #import <dylibC/dylibC.h> @implementation ClassB + (void)load { NSLog(@"dylibB loaded"); } @end
Link Binary With Libraries
中的顺序:dylibA
>dylibB
>dylibC
,运行结果:Demo[53570:17450857] dylibC loaded Demo[53570:17450857] dylibB loaded Demo[53570:17450857] dylibA loaded
这次我们发现三个库的加载顺序是反的,我们修改下顺序:
dylibC
>dylibA
>dylibB
,运行结果不变。由实验结果可知:动态库的加载顺序还受依赖关系影响,被依赖的子节点优先加载。
-
组合依赖关系
其中,
dylibA
、dylibB
、dylibB
没有依赖关系,dylibA
依赖了dylibD
、dylibE
、dylibF
。Demo[97898:19286936] dylibD loaded Demo[97898:19286936] dylibF loaded Demo[97898:19286936] dylibE loaded Demo[97898:19286936] dylibA loaded Demo[97898:19286936] dylibB loaded Demo[97898:19286936] dylibC loaded
通过修改
dylibA
-Link Binary With Libraries
中dylibD
、dylibE
的顺序调整为:
Demo[97982:19305235] dylibF loaded Demo[97982:19305235] dylibE loaded Demo[97982:19305235] dylibD loaded Demo[97982:19305235] dylibA loaded Demo[97982:19305235] dylibB loaded Demo[97982:19305235] dylibC loaded
通过上面的尝试,我们可以看出动态库的加载顺序为:先根据配置的链接顺序加载,如有依赖的先递归式加载依赖。
2.源码篇
上面我们通过几个实验对动态库的加载有个大概的影响,下面我们通过源码进一步了解动态库的加载(dyld源码,本文使用版本:dyld-635.2,篇幅问题,只展示部分代码)。
为了便于的过程,我们在上面demo中的位于子节点的dylibF
库的load方法添加一个符号断点,获取下这个load方法的调用栈:
#0 +[ClassF load]
#1 load_images ()
#2 dyld::notifySingle(dyld_image_states, ImageLoader const*, ImageLoader::InitializerTimingList*) ()
#3 ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) ()
#4 ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) ()
#5 ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) ()
#6 ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) ()
#7 ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) ()
#8 ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) ()
#9 dyld::initializeMainExecutable()
#10 dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) ()
#11 dyldbootstrap::start(macho_header const*, int, char const**, long, macho_header const*, unsigned long*) ()
#12 _dyld_start ()
_dyld_start
和 dyldbootstrap::start
使用汇编实现,我们重点看下dyld::_main
开始的实现。
-
dyld::_main
,动态链接器的入口函数,代码较多,只展示部分代码:// // 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.配置环境 ...很多代码 //2.加载共享缓存 checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide); //3.实例化主程序 sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath); gLinkContext.mainExecutable = sMainExecutable; gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH); //4.插入动态库(使用动态库注入代码就是通过这个实现的) if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) { for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) loadInsertedDylib(*lib); } //5.链接主程序 gLinkContext.linkingMainExecutable = true; link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1); //6.链接插入的动态库 ...很多代码 //7.初始化主程序 initializeMainExecutable(); //...快结束了 }
和我们今天主题相关的为
link()
和initializeMainExecutable()
两个过程,分别负责链接(符号rebase
,binding
)和初始化(调用load
,__attribute__((constructor)
) 修饰的c函数等)的工作。 -
link()
void link(ImageLoader* image, bool forceLazysBound, bool neverUnload, const ImageLoader::RPathChain& loaderRPaths, unsigned cacheIndex) { // add to list of known images. This did not happen at creation time for bundles if ( image->isBundle() && !image->isLinked() ) addImage(image); // we detect root images as those not linked in yet if ( !image->isLinked() ) addRootImage(image); // process images try { const char* path = image->getPath(); #if SUPPORT_ACCELERATE_TABLES if ( image == sAllCacheImagesProxy ) path = sAllCacheImagesProxy->getIndexedPath(cacheIndex); #endif image->link(gLinkContext, forceLazysBound, false, neverUnload, loaderRPaths, path); } catch (const char* msg) { garbageCollectImages(); throw; } }
link()
中又调用ImageLoader::link
方法,ImageLoader是一个基类负责加载image文件(主程序,动态库)每个image对应一个ImageLoader子类的实例,我们继续看下它的实现: -
ImageLoader::link
void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool preflightOnly, bool neverUnload, const RPathChain& loaderRPaths, const char* imagePath) { //dyld::log("ImageLoader::link(%s) refCount=%d, neverUnload=%d\n", imagePath, fDlopenReferenceCount, fNeverUnload); // clear error strings (*context.setErrorStrings)(0, NULL, NULL, NULL); uint64_t t0 = mach_absolute_time(); this->recursiveLoadLibraries(context, preflightOnly, loaderRPaths, imagePath); context.notifyBatch(dyld_image_state_dependents_mapped, preflightOnly); // we only do the loading step for preflights if ( preflightOnly ) return; uint64_t t1 = mach_absolute_time(); context.clearAllDepths(); this->recursiveUpdateDepth(context.imageCount()); //省略符号rebase 和binding过程... }
ImageLoader::link
中使用ImageLoader::recursiveLoadLibraries
加载动态库,截取部分代码: -
ImageLoader::recursiveLoadLibraries
void ImageLoader::recursiveLoadLibraries(const LinkContext& context, bool preflightOnly, const RPathChain& loaderRPaths, const char* loadPath) { //...省略很多代码 // 获取当前image依赖的动态库 DependentLibraryInfo libraryInfos[fLibraryCount]; this->doGetDependentLibraries(libraryInfos); //...省略很多代码 for(unsigned int i=0; i < fLibraryCount; ++i){ //加载动态库 DependentLibraryInfo& requiredLibInfo = libraryInfos[i]; dependentLib = context.loadLibrary(requiredLibInfo.name, true, this->getPath(), &thisRPaths, enforceIOSMac, cacheIndex); //...省略很多代码 } //保存加载完成的动态库 setLibImage(i, dependentLib, depLibReExported, requiredLibInfo.upward); // 告诉此image依赖的动态库们递归的加载各自依赖的动态库 for(unsigned int i=0; i < libraryCount(); ++i) { ImageLoader* dependentImage = libImage(i); if ( dependentImage != NULL ) { dependentImage->recursiveLoadLibraries(context, preflightOnly, thisRPaths, libraryInfos[i].name); } } //...省略很多代码 }
此方法也比较长,它主要做的事情是:1)获取当前image依赖的的动态库;2)循环加载当前image依赖的动态库;3)告诉当前image依赖的动态库们递归的加载各自依赖的动态库;
具体的加载方法在dyld.cpp
的ImageLoader* load(const char* path, const LoadContext& context, unsigned& cacheIndex)
,我们不多做介绍。从上面的代码可以知道,dyld通过doGetDependentLibraries
获取了image依赖的动态库信息,然后循环加载,这个函数的实现会影响动态库的加载顺序,我们看下doGetDependentLibraries
的实现(doGetDependentLibraries
是个虚函数,只有在ImageLoaderMachO.cpp
中找到了它的实现): -
ImageLoaderMachO::doGetDependentLibraries
void ImageLoaderMachO::doGetDependentLibraries(DependentLibraryInfo libs[]) { if ( needsAddedLibSystemDepency(libraryCount(), (macho_header*)fMachOData) ) { DependentLibraryInfo* lib = &libs[0]; lib->name = LIBSYSTEM_DYLIB_PATH; lib->info.checksum = 0; lib->info.minVersion = 0; lib->info.maxVersion = 0; lib->required = false; lib->reExported = false; lib->upward = false; } else { uint32_t index = 0; const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; const struct load_command* cmd = cmds; for (uint32_t i = 0; i < cmd_count; ++i) { switch (cmd->cmd) { case LC_LOAD_DYLIB: case LC_LOAD_WEAK_DYLIB: case LC_REEXPORT_DYLIB: case LC_LOAD_UPWARD_DYLIB: { const struct dylib_command* dylib = (struct dylib_command*)cmd; DependentLibraryInfo* lib = &libs[index++]; lib->name = (char*)cmd + dylib->dylib.name.offset; //lib->name = strdup((char*)cmd + dylib->dylib.name.offset); lib->info.checksum = dylib->dylib.timestamp; lib->info.minVersion = dylib->dylib.compatibility_version; lib->info.maxVersion = dylib->dylib.current_version; lib->required = (cmd->cmd != LC_LOAD_WEAK_DYLIB); lib->reExported = (cmd->cmd == LC_REEXPORT_DYLIB); lib->upward = (cmd->cmd == LC_LOAD_UPWARD_DYLIB); } break; } cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } } }
dyld通过读取当前Mach-O文件的
Load Commonds
段来获取到它依赖的动态库,涉及到的字段有LC_LOAD_DYLIB
、LC_LOAD_WEAK_DYLIB
、LC_REEXPORT_DYLIB
、LC_LOAD_UPWARD_DYLIB
,遍历时通过&libs[]
记录。而影响Load Commonds
中动态库顺序的是Xcode中Link Binary With Libraries
的顺序。下图是上文Demo工程的Mach-O中Load Commonds
的示例截图:
好了,我们通过实验和源码的方式了解到了动态库的加载顺序:1)先从当前image文件(可执行文件/动态库)的Load Commonds
中获取动态库的信息(包括顺序,名字,路径等);2)然后循环加载它依赖的动态库;3)加载完后,再根据当前image依赖的动态库列表递归加载各自依赖的动态库。
3.填坑篇
文章开始我们就挖了一个坑:实验的方式是建立在“load方法的调用顺序当做是动态库的加载顺序”这一假设上的,那么这一假设成立吗?
从上文中+[ClassF load]
的调用栈可知:load
方法的调用是在initializeMainExecutable()
中,在link()
之后,initializeMainExecutable()
后又调用ImageLoader::runInitializers()
,ImageLoader::processInitializers()
,我们看下代码:
-
ImageLoader::processInitializers
中调用了ImageLoader::recursiveInitialization
,从名字看进行了递归方式的初始化。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) { images.images[i]->recursiveInitialization(context, thisThread, images.images[i]->getPath(), timingInfo, ups); } // If any upward dependencies remain, init them. if ( ups.count > 0 ) processInitializers(context, thisThread, timingInfo, ups); }
-
ImageLoader::recursiveInitialization
,截取部分代码:
从上面的代码可以看出,初始化的动态库是从void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize, InitializerTimingList& timingInfo, UninitedUpwards& uninitUps) { //优先初始化fDepth深的库 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, libPath(i), timingInfo, uninitUps); } } } // 告诉objc我们准备初始化image了 uint64_t t1 = mach_absolute_time(); fState = dyld_image_state_dependents_initialized; oldState = fState; context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo); // 初始化image bool hasInitializers = this->doInitialization(context); // 告诉objc初始化完成 fState = dyld_image_state_initialized; oldState = fState; context.notifySingle(dyld_image_state_initialized, this, NULL); //... }
libImage()
中获取,而libImage()
的数据是在加载动态库的ImageLoader::recursiveLoadLibraries
中的setLibImage()
进行保存的。通过比较fDepth
,优先初始化fDepth
大的库,我们再看下fDepth
的赋值: -
ImageLoader::recursiveUpdateDepth
在整个依赖树的关系中,层级越深,unsigned int ImageLoader::recursiveUpdateDepth(unsigned int maxDepth) { // the purpose of this phase is to make the images sortable such that // in a sort list of images, every image that an image depends on // occurs in the list before it. if ( fDepth == 0 ) { // break cycles fDepth = maxDepth; // get depth of dependents unsigned int minDependentDepth = maxDepth; for(unsigned int i=0; i < libraryCount(); ++i) { ImageLoader* dependentImage = libImage(i); if ( (dependentImage != NULL) && !libIsUpward(i) ) { unsigned int d = dependentImage->recursiveUpdateDepth(maxDepth); if ( d < minDependentDepth ) minDependentDepth = d; } } // make me less deep then all my dependents fDepth = minDependentDepth - 1; } return fDepth; }
fDepth
的值越大,ImageLoader::recursiveUpdateDepth
的调用在ImageLoader::recursiveLoadLibraries
之后,通过上面的分析过程我们知道:在加载动态库时将动态库的信息通过setLibImage()
保存,加载完成后dyld根据依赖关系递归地给各个库都计算了依赖深度:fDepth
,在初始化的时候递归地根据fDepth
进行初始化,每初始化一个image都会使用context.notifySingle()
通知Objc
通过load_images()
调用load方法。从分析结果看:不同库中的load方法的调用顺序可以当做是动态库的加载顺序。(dyld过程比较复杂,本人水平有限,如有错误敬请指出~)