1. 准备工作
在开始探究之前,先提供需要的源码以及扩展一些储备知识
1.1 源码
在探究iOS应用启动过程中
,需要使用到的源码
1.2 扩展知识
- 应用在运行过程中不能独立运行,还需要依赖一些其他的库
- 什么是库?可以加载到内存中的二进制文件
- iOS中分为
静态库
和动态库
,那么静态库
和动态库
有什么区别呢,主要是链接方式的不同,静态库是静态链接
,动态库是动态链接
下面用一张图总结一下静态库
和动态库
:
- 什么是
macho
?实际上就是可执行的二进制文件
,dyld加载的时候会按照它特定的格式去解析
它 - 什么是
image
(镜像文件)?也就是我们的二进制文件映像到内存中
- 什么是
dyld
?实际上动态链接加载器
,我们的app之所以能够加载,就是通过dyld
来操作的
2. dyld的引出
我们都知道,我们的应用程序加载的入口是main
函数,但是main
函数又是谁调用的呢,main
打一个断点:
发现来自libdyld
的start
函数,我们继续通过符号断点start
,发现无法断点。
根据开发的经验我们知道load
方法,实际上是早于main
方法调用的,尝试在load方法打断点,查看调用堆栈:
发现最终是有调用_dyld_start
方法,下面我们从dyld
源码的这个方法开始继续探索
3.dyld主流程
- 由于
dyld
依赖的东西太多,没法编译,只能通过函数去找全局搜索_dyld_start
函数 - 搜索发现
i386
、x86_64
、arm
不同的架构都实现了,流程都差不多,我们主要看arm
的,也就是真机
// call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
bl __ZN13dyldbootstrap5startEPKN5dyld311MachOLoadedEiPPKcS3_Pm
通过注释和汇编bl
跳转dyldbootstrap::start
,这个方法主要做了啥
- 发出kdebug跟踪点以指示dyld引导已启动
- 重新绑定修复dyld位置
- 设置堆栈canary的随机值
- _subsystem_init 系统相关初始化
- 调用
dyld
main
函数,传入app 的macho
和ASLR
dyld::main 函数
main函数的主流程我之前的博客也有讲到,可以参考dyld 加载App流程源码分析 ,dyld的细节很多,如果每个点都去分析,篇幅会很长
4.初始化主程序(initializeMainExecutable)
void initializeMainExecutable(){
// run initialzers for any inserted dylibs
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
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 )
ImageLoader::printStatistics((unsigned int)allImagesCount(), initializerTimes[0]);
if ( sEnv.DYLD_PRINT_STATISTICS_DETAILS )
ImageLoaderMachO::printStatisticsDetails((unsigned int)allImagesCount(), initializerTimes[0]);
}
- 调用
runInitializers
方法初始化所有的动态库镜像文件 - 调用
runInitializers
方法初始化主程序 - 注册
cxa_atexit
方法,程序退出时终止所有镜像 - 环境变量,打印设置
进入
ImageLoader::runInitializers
方法调用
void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo)
{
//...
processInitializers(context, thisThread, timingInfo, up);
context.notifyBatch(dyld_image_state_initialized, false);
//...
}
调用processInitializers
,初始化,然后通过notifyBatch
通知出去
继续跟踪processInitializers
方法,
for (uintptr_t i=0; i < images.count; ++i) {
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);
- 对所有image(镜像)
递归
初始化, 继续进入ImageLoader::recursiveInitialization
方法:
void ImageLoader::recursiveInitialization {
1.// initialize lower level libraries first
for(unsigned int i=0; i < libraryCount(); ++i){
dependentImage->recursiveInitialization(context, this_thread, libPath(i), timingInfo, uninitUps);
}
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);
- 先初始化底层依赖文件
- 依赖库初始化通知
context.notifySingle(dyld_image_state_dependents_initialized
- 镜像文件初始化
doInitialization
- 再次调用通知
context.notifySingle(dyld_image_state_initialized
继续跟进先看一下notifySingle
,可以看到会调用sNotifyObjCInit
,继续查找发现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
调用。
那么这个方法又是在何时调用的呢,找到objc
源码,打开,搜索
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();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
在_objc_init
的时候就会处方该方法,也就是load_images
sNotifyObjCMapped ->map_images
sNotifyObjCInit->load_images
sNotifyObjCUnmapped ->unmapped
那么_objc_init
又是何时调起来的呢,打断点,然后根据堆栈信息开始反推
_objc_init(objc) ->_os_object_init(libdispathc)->libdispatch_init(libdispathc)->libSystem_initializer->doModInitFunctions
验证libSystem_initializer->doModInitFunctions
if ( ! dyld::gProcessInfo->libSystemInitialized ) {
// <rdar://problem/17973316> libSystem initializer must run first
const char* installPath = getInstallPath();
if ( (installPath == NULL) || (strcmp(installPath, libSystemPath(context)) != 0) )
dyld::throwf("initializer in image (%s) that does not link with libSystem.dylib\n", this->getPath());
}
- 通过这里
libSystem initializer must run first
,我们得知调用了libSystem_initializer
。 - 继续反推
doModInitFunctions
方法实际上是由doInitialization
方法调用,这个方法是不是很熟悉,这个就是我们上面推出来的方法
- 所以实际山是
doModInitFunctions->_objc_init->_dyld_objc_notify_register
,在这里就会通过_dyld_objc_notify_register
方法,吧objc
的load_images
赋值给dyld
源码里面的sNotifyObjCInit
, - 然后通过调用
context.notifySingle
,调用sNotifyObjCInit
,实际上就会调用objc
的load_images
方法 用一张图总结一下主程序初始化过程:
案例(load、c++构造函数、 main)
新建一个demo项目,运行后分别打印:
__attribute__((constructor)) void qhFunc(){
printf("来了 : %s \n",__func__);
}
+ (void)load{
NSLog(@"%s",__func__);
}
发现运行的顺序是load->c++构造函数->main
,那么这是为什么了,既然有了源码一切都好说,下面我们通过源码分析一下.
mian
函数在最后面,这个应该很好理解。main
函数的地址是有,dyld::main
返回。所以最后执行.
- 在构造函数打断点:
通过调用栈,c++构造函数
由doInitialization
调用。
再看:recursiveInitialization
函数
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);
而notifySingle
回调起load_image
方法,继而调用load
方法。那么这里就有一个疑问了,在doInitialization
方法都没有初始化load_image,之前sNotifyObjCInit
为空怎么可能调用load
方法。
下面我们用一个方法做演示:我们在objc
源码里面也添加c++构造函数。
然后在我们主程序也添加load
方法和c++构造函数
,运行
打印结果:
现打印依赖库的C++构造函数
,然后后主程序load
,主程序C++构造方法
。
- 由于第一次递归会调用依赖镜像的
doInitialization
,这个时候就已经初始化。也就是sNotifyObjCInit
会赋值。然后第二次进来调用context.notifySingle
,就会调起load方法,让后再调用主程序的doInitialization
方法。