我们很少关注应用启动前,系统会给我们做些什么事情,可能知道+ load
和constructor
会在main方法之前执行。那么这次我们来看看main方法之前都做了哪些事情。
以下代码均经过摘取简化。
1. _dyld_start
系统启动应用的入口是_dyld_start
,是用汇编写的。
// call dyldbootstrap::start(app_mh, argc, argv, slide, dyld_mh, &startGlue)
bl __ZN13dyldbootstrap5startEPK12macho_headeriPPKclS2_Pm
// LC_MAIN case, set up stack for call to main()
br x16
首先dyld会调用dyldbootstrap::start
,该方法会返回main
函数的函数指针,并将其保存到x16中,然后才会继续调用main
方法。但是调用这两个方法的方式是不一样的,bl
是真正意义上的方法跳转,是会产生堆栈信息的,而br
则相当于long jump,是不会产生新的栈帧信息的,所以我们在断点的时候,只能看到main
作为程序入口的栈信息了。
那么接下来我们来详细看看dyldbootstrap::start
里面做了些什么。
2. dyldbootstrap::start
// 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, slide);
// allow dyld to use mach messaging
mach_init();
// now that we are done bootstrapping dyld, call dyld's main
uintptr_t appsSlide = slideOfMainExecutable(appsMachHeader);
return dyld::_main(appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
首先,我们都知道系统为了安全性,其实每个程序都会有一个随机的偏移值的,那么这里首先要对应的去除这个偏移量,以及初始化mach内核。
然后调用dyld::_main
,这个最终会返回main
函数地址。
3. 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
//
// add dyld itself to UUID list
addDyldImageToUUIDList();
// instantiate ImageLoader for main executable
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
// load any inserted libraries
loadInsertedDylib(*lib);
link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
// 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
link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
// only INSERTED libraries can interpose
// register interposing info after all inserted libraries are bound so chaining works
image->registerInterposing();
// apply interposing to initial set of images
sImageRoots[i]->applyInterposing(gLinkContext);
// run all initializers
initializeMainExecutable();
// notify any montoring proccesses that this process is about to enter main()
notifyMonitoringDyldMain();
// find entry point for main executable
result = (uintptr_t)sMainExecutable->getThreadPC();
我们按照注释所说的,首先会加载inserted libraries,这个是通过运行参数中的配置,加载其中的lib,我们一般用不到。
然后是链接,也就是macho文件的初始化,绑定一些符号表等,这个在下面进行详细说明。
interpose
在iOS中是被禁用的,其功能相当于swizzle
,这里我们也不去详细说明了。
然后是执行初始化工作,包括oc的运行时初始化,c++的静态对象初始化,c的constructor方法。
最后返回main方法的地址。
3.1 dyld::link
这里我们来看看link都做了些什么。
// 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
image->link(gLinkContext, forceLazysBound, false, neverUnload, loaderRPaths, path);
3.2 ImageLoader::link
this->recursiveRebase(context);
this->recursiveBind(context, forceLazysBound, neverUnload);
if ( !context.linkingMainExecutable )
this->weakBind(context);
// interpose any dynamically loaded images
this->recursiveApplyInterposing(context);
3.3 ImageLoader::recursiveBind
// Normally just non-lazy pointers are bound immediately.
// The exceptions are:
// 1) DYLD_BIND_AT_LAUNCH will cause lazy pointers to be bound immediately
// 2) some API's (e.g. RTLD_NOW) can cause lazy pointers to be bound immediately
// bind lower level libraries first
for(unsigned int i=0; i < libraryCount(); ++i) {
ImageLoader* dependentImage = libImage(i);
if ( dependentImage != NULL )
dependentImage->recursiveBind(context, forceLazysBound, neverUnload);
}
// bind this image
this->doBind(context, forceLazysBound);
在绑定的时候会先递归绑定其依赖的动态库,然后再来绑定自身。
3.4 ImageLoaderMachOCompressed::doBind
没啥好说的,看注释吧。
// run through all binding opcodes
eachBind(context, &ImageLoaderMachOCompressed::bindAt);
// if this image is in the shared cache, but depends on something no longer in the shared cache,
// there is no way to reset the lazy pointers, so force bind them now
if ( forceLazysBound || fInSharedCache )
this->doBindJustLazies(context);
// this image is in cache, but something below it is not. If
// this image has lazy pointer to a resolver function, then
// the stub may have been altered to point to a shared lazy pointer.
if ( fInSharedCache )
this->updateOptimizedLazyPointers(context);
// set up dyld entry points in image
// do last so flat main executables will have __dyld or __program_vars set up
this->setupLazyPointerHandler(context);
3.5 eachBind
// resolve symbol
symbolAddress = this->resolve(context, symbolName, symbolFlags, libraryOrdinal, &targetImage, last, runResolver);
// do actual update
return this->bindLocation(context, addr, symbolAddress, type, symbolName, addend, this->getPath(), targetImage ? targetImage->getPath() : NULL, msg);
4. dyld::initializeMainExecutable
我们回到初始化这里来。经过上面的macho绑定工作以后,虽然已经是一个完整的程序结构了,但是仍需要完成一些运行时的初始化。
// run initialzers for any inserted dylibs
sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);
// run initializers for main executable and everything it brings up
sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
5. ImageLoader::runInitializers
// Calling recursive init on all images in images list, building a new list of
// uninitialized upward dependencies.
this->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);
此时依赖的动态库会递归的调用初始化方法。
// 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, libPath(i), timingInfo, uninitUps);
}
}
// initialize this image
bool hasInitializers = this->doInitialization(context);
这里可以看出来,动态库的初始化方法是早于自身被执行的。
bool ImageLoaderMachO::doInitialization(const LinkContext& context)
{
// mach-o has -init and static initializers
doImageInit(context);
doModInitFunctions(context);
return (fHasDashInit || fHasInitializers);
}
而初始化方法主要就是macho中被标识为S_MOD_INIT_FUNC_POINTERS的section中的方法,详细可以去了解下macho相关知识。
6. libSystem_initializer
以上其实就已经是整个初始化过程了,这里主要讲下一个非常重要的初始化方法。位于libSystem
动态库中的libSystem_initializer
。
大家都知道,在iOS中所有的系统基础库均出自libSystem
,所以这个库一般都是第一个被初始化的。接下来我们来看看他具体做了什么。
__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);
_dyld_initializer();
libdispatch_init();
_libxpc_initializer();
_container_init(apple);
__libdarwin_init();
这里我们可以根据名字看到其初始化都做了些什么,有个关键的libdispatch_init
,我们再来看看。
void libdispatch_init(void)
{
_dispatch_hw_config_init();
_dispatch_time_init();
_dispatch_vtable_init();
_os_object_init();
_voucher_init();
_dispatch_introspection_init();
}
void _os_object_init(void)
{
_objc_init();
}
最终他会去调用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();
lock_init();
exception_init();
// Register for unmap first, in case some +load unmaps something
_dyld_register_func_for_remove_image(&unmap_image);
dyld_register_image_state_change_handler(dyld_image_state_bound,
1/*batch*/, &map_2_images);
dyld_register_image_state_change_handler(dyld_image_state_dependents_initialized, 0/*not batch*/, &load_images);
}
在这个初始化中,会注册一个动态库初始化完成的回调。
/***********************************************************************
* load_images
* Process +load in the given images which are being mapped in by dyld.
* Calls ABI-agnostic code after taking ABI-specific locks.
*
* Locking: write-locks runtimeLock and loadMethodLock
**********************************************************************/
const char *
load_images(enum dyld_image_states state, uint32_t infoCount,
const struct dyld_image_info infoList[])
{
// Discover load methods
load_images_nolock(state, infoCount, infoList);
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
return nil;
}
在这个回调中,会去初始化objc的运行时,并且与当前objc运行时合并,这里的合并可能会导致一些方法被覆盖等问题,category会覆盖原本的方法,主应用会覆盖动态库的方法,当然你也可以利用这个特性,做一些黑科技(个人不建议这样去覆盖方法,尽可能使用runtime来做,或者不要去做)。这些都是题外话了。
然后才是调用load方法。load方法和其他constructor方法一样都是被依赖的动态库中的方法是早于依赖方调用的。
注意,dyld_image_state_dependents_initialized
这个事件是在自己doInitialization
之前被调用的,所以一个动态库中load
方法会早于自己的其他constructor
类型的方法,在做某些黑科技的时候不要搞错了。
以上可能是我们在做一些初始化的时候需要注意的顺序问题了。
最后
这里我们主要需要注意的就是初始化方法调用的顺序问题,在做一些初始化的时候不要出现违反顺序的情况。