我们很少关注应用启动前,系统会给我们做些什么事情,可能知道+ load
和constructor
会在main方法之前执行。那么这次我们来看看main方法之前都做了哪些事情。
以下代码均经过摘取简化。
1. _dyld_start
系统启动应用的入口是_dyld_start
,是用汇编写的。
1 | // call dyldbootstrap::start(app_mh, argc, argv, slide, dyld_mh, &startGlue) |
首先dyld会调用dyldbootstrap::start
,该方法会返回main
函数的函数指针,并将其保存到x16中,然后才会继续调用main
方法。但是调用这两个方法的方式是不一样的,bl
是真正意义上的方法跳转,是会产生堆栈信息的,而br
则相当于long jump,是不会产生新的栈帧信息的,所以我们在断点的时候,只能看到main
作为程序入口的栈信息了。
那么接下来我们来详细看看dyldbootstrap::start
里面做了些什么。
2. dyldbootstrap::start
1 | // if kernel had to slide dyld, we need to fix up load sensitive locations |
首先,我们都知道系统为了安全性,其实每个程序都会有一个随机的偏移值的,那么这里首先要对应的去除这个偏移量,以及初始化mach内核。
然后调用dyld::_main
,这个最终会返回main
函数地址。
3. dyld::_main
1 | // |
1 | // add dyld itself to UUID list |
我们按照注释所说的,首先会加载inserted libraries,这个是通过运行参数中的配置,加载其中的lib,我们一般用不到。
然后是链接,也就是macho文件的初始化,绑定一些符号表等,这个在下面进行详细说明。
interpose
在iOS中是被禁用的,其功能相当于swizzle
,这里我们也不去详细说明了。
然后是执行初始化工作,包括oc的运行时初始化,c++的静态对象初始化,c的constructor方法。
最后返回main方法的地址。
3.1 dyld::link
这里我们来看看link都做了些什么。
1 | // add to list of known images. This did not happen at creation time for bundles |
3.2 ImageLoader::link
1 | this->recursiveRebase(context); |
3.3 ImageLoader::recursiveBind
1 | // Normally just non-lazy pointers are bound immediately. |
在绑定的时候会先递归绑定其依赖的动态库,然后再来绑定自身。
3.4 ImageLoaderMachOCompressed::doBind
没啥好说的,看注释吧。
1 | // run through all binding opcodes |
3.5 eachBind
1 | // resolve symbol |
4. dyld::initializeMainExecutable
我们回到初始化这里来。经过上面的macho绑定工作以后,虽然已经是一个完整的程序结构了,但是仍需要完成一些运行时的初始化。
1 | // run initialzers for any inserted dylibs |
5. ImageLoader::runInitializers
1 | // Calling recursive init on all images in images list, building a new list of |
此时依赖的动态库会递归的调用初始化方法。
1 | // initialize lower level libraries first |
这里可以看出来,动态库的初始化方法是早于自身被执行的。
1 | bool ImageLoaderMachO::doInitialization(const LinkContext& context) |
而初始化方法主要就是macho中被标识为S_MOD_INIT_FUNC_POINTERS的section中的方法,详细可以去了解下macho相关知识。
6. libSystem_initializer
以上其实就已经是整个初始化过程了,这里主要讲下一个非常重要的初始化方法。位于libSystem
动态库中的libSystem_initializer
。
大家都知道,在iOS中所有的系统基础库均出自libSystem
,所以这个库一般都是第一个被初始化的。接下来我们来看看他具体做了什么。
1 | __libkernel_init(&libkernel_funcs, envp, apple, vars); |
这里我们可以根据名字看到其初始化都做了些什么,有个关键的libdispatch_init
,我们再来看看。
1 | void libdispatch_init(void) |
最终他会去调用objc的运行时初始化。
1 | void _objc_init(void) |
在这个初始化中,会注册一个动态库初始化完成的回调。
1 | /*********************************************************************** |
在这个回调中,会去初始化objc的运行时,并且与当前objc运行时合并,这里的合并可能会导致一些方法被覆盖等问题,category会覆盖原本的方法,主应用会覆盖动态库的方法,当然你也可以利用这个特性,做一些黑科技(个人不建议这样去覆盖方法,尽可能使用runtime来做,或者不要去做)。这些都是题外话了。
然后才是调用load方法。load方法和其他constructor方法一样都是被依赖的动态库中的方法是早于依赖方调用的。
注意,dyld_image_state_dependents_initialized
这个事件是在自己doInitialization
之前被调用的,所以一个动态库中load
方法会早于自己的其他constructor
类型的方法,在做某些黑科技的时候不要搞错了。
最后
这里我们主要需要注意的就是初始化方法调用的顺序问题,在做一些初始化的时候不要出现违反顺序的情况。