程序在执行main方法之前都在做什么

我们很少关注应用启动前,系统会给我们做些什么事情,可能知道+ loadconstructor会在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类型的方法,在做某些黑科技的时候不要搞错了。

以上可能是我们在做一些初始化的时候需要注意的顺序问题了。

最后

这里我们主要需要注意的就是初始化方法调用的顺序问题,在做一些初始化的时候不要出现违反顺序的情况。

你可能感兴趣的:(程序在执行main方法之前都在做什么)