程序在执行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类型的方法,在做某些黑科技的时候不要搞错了。

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

最后

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容