引子
准备工作, 本文涉及到的Apple 开源源码如下, 这些开源库在 https://opensource.apple.com 都能下载:
- dyld
- objc4-750
- libdispatch
- libSystem
我们在一个Demo APP的AppDelegate.m
中增加+load
方法并且打上断点, 并且在main()
函数第一语句也打上断点.
在APP运行以后, 我们可以看到在xcode调用栈中有以下内容:
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
我们发现, APP暂停在了+[AppDelegate load]
而不是main()
函数中, 也就是说+load
方法会在main()
函数之前执行. 那么为什么会出现这种情况呢.
注意使用真机调试和Simulator调试可能调用栈不太一样, 这里建议使用真机调试.
dyld的_main
方法
dyld
被称为动态链接器
, 在dyldStartup.s
文件中, 有一个_dyld_start
方法, 这个方法是一个汇编方法
__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
会调用dyldbootstrap::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.
//
// 核心方法: dyldbootstrap::start(...), 首先bootstrapping dyld, 然后调用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中的内容.
if ( slide != 0 ) {
rebaseDyld(dyldsMachHeader, slide);
}
// allow dyld to use mach messaging
mach_init();
// 内核设置的env pointers, 也就是环境参数
// 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
// 当前这里 已经完成了 bootstrapping dyld过程, 后面需要调用 dyld 的_main函数
// now that we are done bootstrapping dyld, call dyld's main
uintptr_t appsSlide = slideOfMainExecutable(appsMachHeader);
// 前面经过很多设置最后调用 dyld::_main 方法
return dyld::_main(appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}
代码逻辑比较清晰, 经过很多配置的参数然后调用dyld::_main()
方法:
注意
dyld::_main(...)
App中的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 设置运行环境,处理环境变量
uintptr_t result = 0;
sMainExecutableMachHeader = mainExecutableMH;
CRSetCrashLogMessage("dyld: launch started");
// 设置上下文
setContext(mainExecutableMH, argc, argv, envp, apple);
// Pickup the pointer to the exec path.
// 获取可执行文件的路径
sExecPath = _simple_getenv(apple, "executable_path");
// <rdar://problem/13868260> Remove interim apple[0] transition code from dyld
if (!sExecPath) sExecPath = apple[0];
// 将可执行文件的路径由相对路径转化成绝对路径
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
// 获取可执行文件去除前面的路径, 获取它的name,
sExecShortName = ::strrchr(sExecPath, '/');
if ( sExecShortName != NULL )
++sExecShortName;
else
sExecShortName = sExecPath;
// 配置进程是否受到限制
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);// 检查环境变量
defaultUninitializedFallbackPaths(envp);
}
if ( sEnv.DYLD_PRINT_OPTS )
printOptions(argv);
if ( sEnv.DYLD_PRINT_ENV )
printEnvironmentVariables(envp);
// 获取当前设备的CPU架构信息
getHostInfo(mainExecutableMH, mainExecutableSlide);
// install gdb notifier
// 注册gdb的监听者, 用于调试
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路径下的可执行文件, 实例化一个ImageLoader对象.
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
// 设置上下文, 将MainExecutable 这个 ImageLoader设置给链接上下文, 配置链接上下文其他变量
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 加载插入的动态库
//变量 `DYLD_INSERT_LIBRARIES` 环境变量, 调用`loadInsertedDylib`方法加载所有要插入的库, 这些库都被加入到`sAllImages`数组中
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.mainExecutable中, 调用 link 链接主程序。内核调用的是ImageLoader::link 函数。
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 链接插入的动态库
//对 sAllimages (除了主程序的Image外)中的库调用link进行链接,然后调用 registerInterposing 注册符号插入, 例如是libSystem就是此时加入的
// 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, 是通过编写与函数库同名的函数来取代函数库的行为.
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 执行弱符号绑定
// <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 执行初始化方法
// 执行初始化方法, 其中`+load` 和constructor方法就是在这里执行, `initializeMainExecutable`方法先是内部调用动态库的初始化方法, 然后调用主程序的初始化方法
// run all initializers
initializeMainExecutable();
#endif
//9 查找APP入口点并返回
// 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. 设置运行环境,处理环境变量
代码在开始时候, 将传入的变量mainExecutableMH
赋值给了sMainExecutableMachHeader
, 这是一个macho_header
类型的变量, 其结构体内容就是本章前面介绍的mach_header
结构体, 表示的是当前主程序的Mach-O
头部信息, 有了头部信息, 加载器就可以从头开始, 遍历整个Mach-O
文件的信息.
接着执行了setContext()
, 此方法设置了全局一个链接上下文, 包括一些回调函数, 参数与标志设置信息, 设置的回调函数都是dyld
本模块实现的, 如loadLibrary
方法就是本模块的libraryLocator()
方法, 负责加载动态库。在设置完这些信息后, 执行processRestricted()
方法判断进程是否受限, 如果进程受限后,执行了以下三个方法:
-
checkLoadCommandEnvironmentVariables():
遍历Mach-O
中所有的LC_DYLD_ENVIRONMENT
加载命令, 然后调用processDyldEnvironmentVariable()
对不同的环境变量做相应的处理。 -
pruneEnvironmentVariables()
:删除进程的LD_LIBRARY_PATH与所有以DYLD_开头的环境变量, 这样以后创建的子进程就不包含这些环境变量了. - setContext(): 重新设置链接上下文。这一步执行的主要目的是由于环境变量发生变化了, 需要更新进程的envp与apple参数。
2. 初始化主程序
这一步主要执行instantiateFromLoadedImage()
, 它的代码如下:
isCompatibleMachO()
判断程序与当前的系统是否兼容, 如果兼容接下来就调用instantiateMainExecutable()
实例化主程序, 接着调用addImage()
, 会将主程序添加到全局主列表sAllImages
中, 最后调用addMappedRange()
申请内存, 更新主程序映像映射的内存区,做完这些工作,第二步初始化主程序就算完成了.
3. 加载共享缓存
这一步主要执行mapSharedCache()
来映射共享缓存, 该函数先通过_shared_region_check_np()
来检查缓存是否已经映射到了共享区域了, 如果已经映射了, 就更新缓存的slide
与UUID
, 然后返回.
反之, 判断系统是否处于安全启动模式(safe-boot mode)下,如果是就删除缓存文件并返回, 正常启动的情况下, 接下来调用openSharedCacheFile()
打开缓存文件, 该函数在sSharedCacheDir
路径下, 打开与系统当前cpu架构匹配的缓存文件,也就是/var/db/dyld/dyld_shared_cache_x86_64h
, 接着读取缓存文件的前8192字节, 解析缓存头dyld_cache_header
的信息, 将解析好的缓存信息存入mappings变量, 最后调用_shared_region_map_and_slide_np()
完成真正的映射工作.
共享缓存加载完毕后, 接着进行动态库的版本化重载,这主要通过函数checkVersionedPaths()
完成, 该函数读取DYLD_VERSIONED_LIBRARY_PATH
与DYLD_VERSIONED_FRAMEWORK_PATH
环境变量, 将指定版本的库比当前加载的库的版本做比较, 如果当前的库版本更高的话, 就使用新版本的库来替换掉旧版本的。
4 加载插入的动态库
这一步循环遍历DYLD_INSERT_LIBRARIES
环境变量中指定的动态库列表, 并调用loadInsertedDylib()
将其加载, 该函数调用load()
完成加载工作. load()
会调用loadPhase0()
尝试从文件加载,loadPhase0()
会向下调用下一层phase来查找动态库的路径, 直到loadPhase6()
,查找的顺序为DYLD_ROOT_PATH->LD_LIBRARY_PATH->DYLD_FRAMEWORK_PATH->原始路径->DYLD_FALLBACK_LIBRARY_PATH
,找到后调用ImageLoaderMachO::instantiateFromFile()
来实例化一个ImageLoader
, 之后调用checkandAddImage()
验证映像并将其加入到全局映像列表中. 如果loadPhase0()
返回为空, 表示在路径中没有找到动态库, 就尝试从共享缓存中查找, 找到就调用ImageLoaderMachO::instantiateFromCache()
从缓存中加载, 否则就抛出没找到映像的异常.
5 链接主程序
这一步执行link()
完成主程序的链接操作, 该函数调用了ImageLoader
自身的link()
函数, 主要的目的是将实例化的主程序的动态数据进行修正, 达到让进程可用的目的, 典型的就是主程序中的符号表修正操作.
6 链接插入的动态库
链接插入的动态库与链接主程序一样, 都是使用的link()
, 插入的动态库列表是前面调用addImage()
保存到sAllImages
中的, 之后, 循环获取每一个动态库的ImageLoader
, 调用link()
对其进行链接, 注意: sAllImages
中保存的第一项是主程序的映像. 接下来调用每个映像的registerInterposing()
方法来注册动态库插入与调用applyInterposing()
应用插入操作.
registerInterposing()
查找__DATA段
的__interpose
节区, 找到需要应用插入操作(也可以叫作符号地址替换)的数据, 然后做一些检查后, 将要替换的符号与被替换的符号信息存入fgInterposingTuples
列表中, 供以后具体符号替换时查询. applyInterposing()
调用了虚方法doInterpose()
来做符号替换操作, 在ImageLoaderMachOCompressed
中实际是调用了eachBind()
与eachLazyBind()
分别对常规的符号与延迟加载的符号进行应用插入操作, 具体使用的是interposeAt()
, 该方法调用interposedAddress()
在fgInterposingTuples
中查找要替换的符号地址, 找到后然后进行最终的符号地址替换.
注意是
libSystem
就是此时加入的,runtime
的初始化函数_objc_init()
就是libSystem
调用的
7 执行弱符号绑定
weakBind()
函数执行弱符号绑定. 首先通过调用context的getCoalescedImages()
将sAllImages
中所有含有弱符号的映像合并成一个列表,合并完后调用initializeCoalIterator()
对映像进行排序,排序完成后调用incrementCoalIterator()
收集需要进行绑定的弱符号,后者是一个虚函数,在ImageLoaderMachOCompressed中
,该函数读取映像动态链接信息的weak_bind_off
与weak_bind_size
来确定弱符号的数据偏移与大小,然后挨个计算它们的地址信息. 之后调用getAddressCoalIterator()
,按照映像的加载顺序在导出表中查找符号的地址,找到后调用updateUsesCoalIterator()
执行最终的绑定操作,执行绑定的是bindLocation()
,前面有讲过,此处不再赘述.
8 执行初始化方法
执行初始化的方法是:
initializeMainExecutable()
ImageLoader::runInitializers()
ImageLoader::processInitializers()
ImageLoader::recursiveInitialization()
- 先调用
image
自己的初始化函数initializer
, 然后发出通知, 告知观察者当前image已经完成加载, 状态是dyld_image_state_initialized
-
doInitialization
-
doImageInit
, 获取mach-o的init
方法的地址并调用 -
doModInitFunctions
, 获取mach-o的static initializer
的地址并调用
-
-
context.notifySingle(dyld_image_state_initialized, this)
, 对外发出通知,当前image已经初始化完毕.
-
9 APP的main函数地址入口点并返回
这一步调用主程序映像的getThreadPC()
函数来查找主程序的LC_MAIN
加载命令获取程序的入口点, 没找到就调用getMain()
到LC_UNIXTHREAD
加载命令中去找, 找到后就跳到入口点指定的地址, dyld::main函数会返回程序的main函数地址, main函数被调用, 从而代码来到了我们熟悉的程序入口。
到这里, dyld整个加载动态库的过程就算完成了。
关于initializeMainExecutable
方法中, image的初始化详解
前面一节中第8步的内容, 是针对所有image执行initializeMainExecutable
方法, 具体的代码如下:
/**
initializeMainExecutable 执行初始化方法,其中 +load 和 constructor 方法就是在这里执行。
initializeMainExecutable 内部先调用了动态库的初始化方法,后调用主程序的初始化方法。
*/
void initializeMainExecutable() {
// record that we've reached this step
// 进行标志 gLinkContext, 已经开始 initialize MainExecutable这个image.
gLinkContext.startedInitializingMainExecutable = true;
// run initialzers for any inserted dylibs
// 给被插入的所有的 dylibs 进行初始化 -- 调用 initialzers
ImageLoader::InitializerTimingList initializerTimes[sAllImages.size()];
initializerTimes[0].count = 0;
const size_t rootCount = sImageRoots.size();
if ( rootCount > 1 ) {
// 从 sImageRoots 中的第一个变量是 MainExcutable image, 因此这里初始化的时候需要跳过第一个数据, 对其他后面插入的dylib进行调用ImageLoader::runInitializers进行初始化
for(size_t i=1; i < rootCount; ++i) {
sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);
}
}
// 单独对 main executable调用ImageLoader::runInitializers进行初始化
// 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]);
}
其中, 主要是调用所有image
的ImageLoader::runInitializers()
方法, 代码如下:
/**
初始化过程如下, 实际调用ImageLoader::processInitializers
@param context
@param timingInfo
*/
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.images[0] = this;
// 初始化当前 imageLoader 中的 image镜像的实际调用方法 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.
// 调用 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.
// 处理当前image依赖 dylib动态库, 调用 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
方法中, 会递归的调用当前image
的依赖的dylib动态库
的初始化函数进行初始化, 然后才调用doInitialization
来调用自己的初始化函数, 在中间image的state状态切换时,对外通过notifySingle
方法给外部环境contenxt
发出状态变化的通知(外部如果有内容监听了相关通知, 那么会执行相应回调, runtime就是如此):
/**
递归调用 image 进行初始化, 先调用image依赖的image进行初始化. 直到自己
*/
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
recursive_lock lock_info(this_thread);
recursiveSpinLock(lock_info);
// 当前ImageLoader依赖的Image还没有初始化完, 进入if中, 如果执行完成, 直接返回
if ( fState < dyld_image_state_dependents_initialized-1 ) {
uint8_t oldState = fState;
// break cycles -> 这是设置当前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底层的依赖库都递归调用, 初始化完成.
// 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, 当前状态发生变化 -- image的依赖已经完全加载. 注意这里可能在runtime中注册了状态监听, 注册了callback函数, 当状态发送变化时, 会触发回调函数.
context.notifySingle(dyld_image_state_dependents_initialized, this);
// 初始化当前image, `ImageLoaderMachO::doInitialization`方法内部会调用image的"Initializer", 这是一个函数指针, 实际是image的初始化方法. 例如 `libSystem.dylib`, 它的初始化方法就比较特殊, 我们可以参考libSystem的init.c源码, 内部的`libsystem_initializer`函数就是初始化真正调用的函数
// 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, 档期那状态发送变化 -- image自己已经完成初始化!!!!
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
方法, 它会调用doImageInit
和doModInitFunctions
方法, 这两个方法做的工作就是从image镜像
中获取这个镜像的真正的入口初始化方法initializer
并调用.
注意,
initalizer
并非名为initalizer
的方法, 而是C++静态对象初始化构造器,atribute((constructor)
进行修饰的方法, 在ImageLoader
类中initializer
函数指针所指向该初始化方法的地址
/**
image 真正的初始化调用方法, 内部会调用`doImageInit`, 这个方法会调用 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
函数指针的的定义和调用的过程, 首先Initializer
是一个函数指针, 在doModInitFunctions
方法中是获取image中Initializer
方法地址, 然后直接执行.
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);
...
}
在最后会调用notifySingle
通知方法, 会触发外部监听dyld_image_state_initialized
状态的callback回调方法.
libdiaptch的初始化和runtime的初始化
上一节中, 我们知道一个image动态库要被初始化并且加载完成, 需要先递归加载它依赖的image动态库.
那么一个最简单的iOS程序依赖哪些动态库呢?
我们可以在一个iOS APP中设置环境变量DYLD_PRINT_INITIALIZERS
为1, 然后编译运行, 控制台就会打印出当前APP依赖的所有的动态库.
具体步骤是: Edit-Scheme->Arguments->Enviroments Vairables
增加一个参数 --- Name: DYLD_PRINT_INITIALIZERS, Value: 1
, 然后编译, 控制台会打印如下内容:
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
...
我们看到第一个initializer function
是libSystem.B.dylib
.
我们在Apple官网找到libSystem.dylib
开源库(可能和实际有所不同), 我们看一下它的初始化方法具体做了什么工作, 我们找到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)
{
...
// 内核初始化
__libkernel_init(&libkernel_funcs, envp, apple, vars);
// 平台信息初始化
__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里调用了到了runtime初始化方法_objc_init.我们可以、在程序中打个符号断点来验证:
libdispatch_init();
_libxpc_initializer();
// must be initialized after dispatch
_libtrace_init();
...
}
libdispatch_init
里调用了到了runtime初始化方法_objc_init
.
我们可以、在程序中打个符号断点来验证, 在Xcode设置Symbolic Breakpoint
, 然后设置Symblic = _objc_init
:
Apple官网开源了
libsystem
的源码
https://opensource.apple.com/tarballs/Libsystem/
从图中我们看到, 先libSystem_initializer
调用libdispatch_init
再到_objc_init
初始化runtime
runtime
初始化后不会闲着, 在_objc_init
中注册了几个通知, 从dyld这里接手了几个活, 其中包括负责初始化相应依赖库里的类结构, 调用依赖库里所有的+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 方法是如何被调用的?
A:_objc_init(OBJC runtime)是 libSystem 在library初始化之前调用, 进行runtime的初始化!!!! 当 Objective-C runtime初始化的时候, 会通过 dyld_register_image_state_change_handler注册针对一些事件的监听, 在每次有新的镜像加入运行时的时候,进行回调调用`load_images`(也有其他事件触发 map_images和 unmap_image方法).
执行 load_images 将所有包含 load 方法的文件加入列表 loadable_classes ,然后从这个列表中找到对应的 load 方法的实现,调用 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中加入一个状态监听器, 一旦dyld监听到有新的镜像image加载完成, 就调用 load_images 方法, 并传入最新镜像的信息类别 infoList
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
前面说到image在被dyld
加载的时候, notifySingle
会通知image的状态变化, 这样_objc_init
中最后通过_dyld_objc_notify_register
方法注册的监听器, 后面一旦有新的image被加载到dyld时, 就会调用objc::load_images
方法!!!
关于
runtime
中的load_images
, 其他文章会进行详解解答后面的内容.
启动流程总结
一个 iOS 程序会依赖于某些动态库, 可以通过otool -v -L debug-objc
命令查看, 在启动的时候需要由动态链接器进行动态链接,而动态链接器自身也是动态库,因此动态链接器会首先自举自身,然后再将程序依赖的动态库依次加载进内存进行链接,动态链接做完之后,把控制权交给程序的main
函数。
通过 otool
命令可以查看到依赖列表中有 /usr/lib/libSystem.B.dylib
和 /usr/lib/libobjc.A.dylib
这样两条记录.libSystem.B.dylib
在编译的时候依赖于很多系统级别的 lib
, 如 libdispatch
,libsystem_c
, libcommonCrypto
等等.而这个libobjc.A.dylib
就是objc-runtime
, 其初始化函数是 _objc_init
, 前面说过, 可以根据_objc_init
设置symbol breakpoint
进行后续的调试.
- 首先系统内核建立进程, 创建虚拟空间, 读取
Mach-O
文件头,并建立虚拟空间与Mach-O
文件的映射关系, 然后找到 dyld 动态链接器入口_dyld_start
之后, 控制权交给动态链接器, 接下来就是 dyld 的自举, 即dyldbootstrap::start()
. - 自举过程完成, 即在
dyldbootstrap::start()
函数的最末尾会调用dyld::_main
函数, 并把此函数的返回值返还给_dyld_start
, 这个返回值是 iOS 可执行程序main
函数的地址,_dyld_start
会跳到该地址继续执行,进入程序的主逻辑. - 不过在这个地址返回之前, 也就是在
dyld::_main
函数中需要做两个特别重要的工作:- 重定位和初始化, 也就是动态链接的过程. 在完成了自举之后, 链接器需要装载动态链接库, 然后完成符号的重定位.
- 如果动态链接库文件中还有相应的初始化信息,即含有
__mod_init_func section
,就再需要调用其相应的初始化函数.
- 在
dyld::initializeMainExecutable
函数中, 程序所依赖的动态库进行各自的初始化, 因此ImageLoader::runInitializers
函数被调用, 这个函数调用ImageLoader::processInitializers
来处理初始化, 而某个动态库可能依赖于其它的动态库, 那么它所依赖的动态库就需要先初始化, 这里形成一个递归, 也就是ImageLoader::recursiveInitialization
- 动态库初始化函数的真正调用是在
ImageLoaderMachO::doModInitFunctions
函数中, 对于libSystem.B.dylib
来说其初始化函数是libSystem_initializer
, 在这个函数中libdispatch_init
被调用,libSystem
以及libdispatch
也是开源的, 可以查看相关源码. - 在
libdispatch_init
中, 对Runtime
的_objc_init
进行了调用, 而在Runtime
的初始化过程中, 查看源码可以看到Runtime
向dyld
绑定了回调, 当image
加载到内存后,dyld
会通知Runtime
进行处理,Runtime
接手后调用map_images
做解析和处理, 把Category
的实例方法, 协议以及属性添加到类上, 把Category
的类方法和协议添加到类的metaclass
上; 接下来load_images
中调用call_load_methods
方法, 遍历所有加载进来的Class
, 按继承层级依次调用Class
的load
方法和其Category
的load
方法(这部分内容,会在后面针对runtime的文章进行分析) - 在所有的动态库做好符号重定位和初始化工作之后, 也就是
dyld::_main
临近末尾的时候,dyld
会获取main
函数的地址返回给dyld
,dyld
紧接着调用main
函数, 将控制权交换给主程序, 程序开始真正的执行.
结语
最后一句话概括APP的启动:
内核exec做好准备工作,转移控制权给dyld->dyld加载依赖库->libdispatch初始化->runtime初始化->main开始执行
APP的启动优化
可以参考: https://www.jianshu.com/p/7096478ccbe7
参考文章
https://www.jianshu.com/p/7096478ccbe7
https://www.jianshu.com/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
https://www.jianshu.com/p/885c8077b27d
https://www.cnblogs.com/maizi008/p/5086103.html
https://blog.csdn.net/TuGeLe/article/details/81609604
https://www.jianshu.com/p/5f337da8fbef
https://www.jianshu.com/p/231b1cebf477
https://www.cnblogs.com/xs514521/p/7010458.html
https://www.jianshu.com/p/73a99303cd91