应用程序加载(下)

767 阅读4分钟

回顾

前面整个dyld加载流程,我们通过_dyld_start ->dyldbootstrap::start->dyld::_main ->dyld::initializeMainExecutable()->ImageLoader::runInitializers->ImageLoader::processInitializers->ImageLoader::processInitializers->ImageLoader::recursiveInitialization->dyld::notifySingle
最终notifySingle调用了sNotifyObjCInit,全局搜索sNotifyObjCInit找到函数registerObjCNotifiersregisterObjCNotifiers函数的调用是在_dyld_objc_notify_register,最终我们得到_objc_init函数

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();
#if __OBJC2__
    cache_t::init();
#endif
    _imp_implementationWithBlock_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

可以看到_dyld_objc_notify_register函数有三个参数,&map_imagesload_imagesunmap_image,他们分别在什么时候调用了。

map_imagesload_images调用流程

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;

	// call 'mapped' function with all images mapped so far
	try {
		notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);
	}
	catch (const char* msg) {
		// ignore request to abort during registration
	}

	// <rdar://problem/32209809> call 'init' function on all images already init'ed (below libSystem)
	for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) {
		ImageLoader* image = *it;
		if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() ) {
			dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
			(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
		}
	}
}

可以看到sNotifyObjCMapped = mapped,全局搜索sNotifyObjCMapped,找到函数notifyBatchPartial中调用(*sNotifyObjCMapped)(objcImageCount, paths, mhs);,搜索notifyBatchPartial,我们发现又回到了registerObjCNotifiers函数,

        // call 'mapped' function with all images mapped so far
	try {
		notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);
	}

原来,只要赋值了map_images就会立即调用。

sNotifyObjCInit = init,全局搜索sNotifyObjCInit,发现调用完map_images之后,load_images也会被调用

       for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) {
		ImageLoader* image = *it;
		if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() ) {
			dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
			(*sNotifyObjCInit)(image->getRealPath(), image->machHeader()); //load_images调用
		}
	}

sNotifyObjCUnmapped全局搜索,找到removeImage函数,发现在程序关闭时才会调用unmap_image

if ( sNotifyObjCUnmapped !=  NULL && image->notifyObjC() )
	(*sNotifyObjCUnmapped)(image->getRealPath(), image->machHeader());

load函数调用流程

前面我们在load函数添加断点,得到load函数流程

  • dyld`_dyld_start ->
  • dyld`dyldbootstrap::start ->
  • dyld`dyld::_main ->
  • dyld`dyld::initializeMainExecutable() ->
  • dyld`ImageLoader::runInitializers ->
  • dyld`ImageLoader::processInitializers
  • dyld`ImageLoader::recursiveInitialization ->
  • dyld`dyld::notifySingle ->
  • libobjc.A.dylibload_images 最终我们是走到load_images函数,然后调用load方法,查看load_images`函数,
void
load_images(const char *path __unused, const struct mach_header *mh)
{
    if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
        didInitialAttachCategories = true;
        loadAllCategories();
    }

    // Return without taking locks if there are no +load methods here.
    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh); //准备load函数
    }

    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();  //调用load函数
}

可以看到先调用prepare_load_methods函数,递归将包含load方法的方法添加到add_class_to_loadable_list表或者add_category_to_loadable_list分类表里面,然后直接调用call_load_methods方法。

void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();

    do {
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            call_class_loads();   //调用类的load方法
        }

        // 2. Call category +loads ONCE
        more_categories = call_category_loads(); //调用分类的load方法

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}

递归分别调用load方法和分类load方法。

static void call_class_loads(void)
{
    int i;
    
    // Detach current loadable list.
    struct loadable_class *classes = loadable_classes;
    int used = loadable_classes_used;
    loadable_classes = nil;
    loadable_classes_allocated = 0;
    loadable_classes_used = 0;
    
    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Class cls = classes[i].cls;
        load_method_t load_method = (load_method_t)classes[i].method;
        if (!cls) continue; 

        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
        }
        (*load_method)(cls, @selector(load));   //给类发送消息load
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}

然后给对应的或者分类发送消息(*load_method)(cls, @selector(load))

cxx函数调用流程

main文件添加下面cxx函数,打上断点。

__attribute__ ((constructor)) void LKTeacher(){
    printf("______%s_____",__func__);
}

bt打印堆栈信息

image.png

  • dyld`_dyld_start ->
  • dyld`dyldbootstrap::start ->
  • dyld`dyld::_main ->
  • dyld`dyld::initializeMainExecutable() ->
  • dyld`ImageLoader::runInitializers ->
  • dyld`ImageLoader::processInitializers
  • dyld`ImageLoader::recursiveInitialization ->
  • dyld`ImageLoaderMachO::doInitialization ->
  • dyldImageLoaderMachO::doModInitFunctions 搜索doModInitFunctions`函数
bool ImageLoaderMachO::doInitialization(const LinkContext& context)
{
	CRSetCrashLogMessage2(this->getPath());

	// mach-o has -init and static initializers
	doImageInit(context);
	doModInitFunctions(context);
	
	CRSetCrashLogMessage2(NULL);
	
	return (fHasDashInit || fHasInitializers);
}

doInitialization中调用doModInitFunctions(context)方法,搜索doInitialization

void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
										  InitializerTimingList& timingInfo, UninitedUpwards& 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);
                        //中间代码省略
}

回到recursiveInitialization,我们发现notifySingle->sNotifyObjCInit->registerObjCNotifiers->_dyld_objc_notify_register即调用完load方法之后,然后调用 doInitialization(context)方法,接着又会再次调用notifySingle方法。

分析整个加载流程是一个递归的流程

//let objc know we are about to initialize this image
// initialize this image
// let anyone know we finished initializing this image
第一次调用时,image并没有被初始化,所以load方法并不会调用,然后调用doInitialization方法,初始化image,完成image初始化之后,再次调用load方法。

验证在任一objc文件中添加cxx函数

__attribute__ ((constructor)) void objcFunc(){
    printf("______%s_____\n",__func__);
}

打印结果

______objcFunc_____
2021-07-16 16:33:36.482889+0800 KCObjcBuild[8825:231456] ______+[LKPerson load]______
______LKTeacher_____

可以看到,先调用本镜像文件的cxx函数,然后调用load函数,最后调用程序的cxx函数。

main函数调用流程

在load函数处打下断点,打开汇编,一步步往下走 image.png 读取寄存器信息

image.png 可以看到,x16 = 0x0000000100003e60  KCObjcBuild main at main.m:31main函数地址存在x16中,0x100015078 <+120>: braaz  x16可知,当执行完_dyld_start之后,会跳转至main函数。

总结

引用自KC课件 image.png loadcxxmain函数加载顺序
先加载镜像文件cxx函数,然后加载load函数,再加载主程序cxx函数,最后执行main函数