美文网首页
13.iOS底层探究之dyld补充及load、main函数调用探

13.iOS底层探究之dyld补充及load、main函数调用探

作者: 牛牛大王奥利给 | 来源:发表于2021-10-11 21:02 被阅读0次

    本篇文章主要去研究一下main函数以及load的具体调用,我们知道load是在main函数之前调用,我们通过源码来了解一下具体的过程。
    通过这篇文章我们将了解到:
    1、load在源码中是如何被调用的,具体的加载过程是?
    2、在调用main函数之前具体都做了什么?

    load

    我们还是回归到dyld源码,来具体的研究一下load是如何被调用的。上一篇我们了解到启动大致有9个步骤,总结来说就是先进行一些环境的读取然后设置的准备工作,然后进行共享缓存的初始化,然后是main的初始化准备工作,然后是一些需要的动态库的插入以及链接等等这样一个过程。

    那么我们了解这个流程以后,要去研究main具体做了些什么,我们去看跟main相关的步骤,也就是main的初始化以及一些准备工作,因为我们了解到load是在main之前,所以我们去看main相关之前的有可能找到load相关的操作,于是我们就定位到代码:

    // instantiate ImageLoader for main executable 
            sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
    

    为main的可执行初始化一些镜像加载器

    instantiateFromLoadedImage
    // The kernel maps in main executable before dyld gets control.  We need to 
    // make an ImageLoader* for the already mapped in main executable.
    static ImageLoaderMachO* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path)
    {
        // try mach-o loader
    //  if ( isCompatibleMachO((const uint8_t*)mh, path) ) {
            ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext);
            addImage(image);
            return (ImageLoaderMachO*)image;
    //  }
        
    //  throw "main executable not a known format";
    }
    

    了解下这个方法的注释:在dyld获得控制之前,内核映射到主可执行文件中。我们需要为已映射到主可执行文件中的文件创建ImageLoader*。
    主要的方法是调用了一个instantiateMainExecutable这个,::(双冒号) 这是c++的语法,\color{red}{作用域符号},前边一般是类名,后边是类的方法或者函数。于是我们搜一下ImageLoaderMachO:

    image.png
    找到这个文件ImageLoaderMachO,然后去文件里找到方法instantiateMainExecutable
    image.png
    然后我们深入到方法里进去看,发现里面的具体内容是创建images的过程,没有相关main还有load的内容,这一个步骤为了sMainExecutable根据路径去读取images的一个过程。下面截取部分代码。
    ImageLoader* ImageLoaderMachO::instantiateMainExecutable(const macho_header* mh, uintptr_t slide, const char* path, const LinkContext& context)
    {
        //dyld::log("ImageLoader=%ld, ImageLoaderMachO=%ld, ImageLoaderMachOClassic=%ld, ImageLoaderMachOCompressed=%ld\n",
        //  sizeof(ImageLoader), sizeof(ImageLoaderMachO), sizeof(ImageLoaderMachOClassic), sizeof(ImageLoaderMachOCompressed));
        bool compressed;
        unsigned int segCount;
        unsigned int libCount;
        const linkedit_data_command* codeSigCmd;
        const encryption_info_command* encryptCmd;
        sniffLoadCommands(mh, path, false, &compressed, &segCount, &libCount, context, &codeSigCmd, &encryptCmd);
        // instantiate concrete class based on content of load commands
        if ( compressed ) 
            return ImageLoaderMachOCompressed::instantiateMainExecutable(mh, slide, path, segCount, libCount, context);
        else
    #if SUPPORT_CLASSIC_MACHO
            return ImageLoaderMachOClassic::instantiateMainExecutable(mh, slide, path, segCount, libCount, context);
    #else
            throw "missing LC_DYLD_INFO load command";
    #endif
    }
    
    // create image for main executable
    ImageLoaderMachOCompressed* ImageLoaderMachOCompressed::instantiateMainExecutable(const macho_header* mh, uintptr_t slide, const char* path, 
                                                                            unsigned int segCount, unsigned int libCount, const LinkContext& context)
    {
        ImageLoaderMachOCompressed* image = ImageLoaderMachOCompressed::instantiateStart(mh, path, segCount, libCount);
    
        // set slide for PIE programs
        image->setSlide(slide);
    
        // for PIE record end of program, to know where to start loading dylibs
        if ( slide != 0 )
            fgNextPIEDylibAddress = (uintptr_t)image->getEnd();
    
        image->disableCoverageCheck();
        image->instantiateFinish(context);
        image->setMapped(context);
    
        if ( context.verboseMapping ) {
            dyld::log("dyld: Main executable mapped %s\n", path);
            for(unsigned int i=0, e=image->segmentCount(); i < e; ++i) {
                const char* name = image->segName(i);
                if ( (strcmp(name, "__PAGEZERO") == 0) || (strcmp(name, "__UNIXSTACK") == 0)  )
                    dyld::log("%18s at 0x%08lX->0x%08lX\n", name, image->segPreferredLoadAddress(i), image->segPreferredLoadAddress(i)+image->segSize(i));
                else
                    dyld::log("%18s at 0x%08lX->0x%08lX\n", name, image->segActualLoadAddress(i), image->segActualEndAddress(i));
            }
        }
    
        return image;
    }
    

    所以哇,不是这个地方,这个地方只是加载了需要的镜像文件,应该还没有运行,接着往下看!之前有一个流程是运行所有的初始化,initializeMainExecutable这个方法,然后详细看下这个看看有没有我们需要的。

    initializeMainExecutable
    image.png

    这里是实际运行了初始化设置的默认项,翻译一下标记的注释:为主可执行文件及其带来的所有内容运行初始值设定项
    。查看里面的具体实现发现:

    void runInitializers(ImageLoader* image)
    {
        // do bottom up initialization
        ImageLoader::InitializerTimingList initializerTimes[allImagesCount()];
        initializerTimes[0].count = 0;
        image->runInitializers(gLinkContext, initializerTimes[0]);
    }
    

    这是一个镜像运行的递归调用。
    然后我们结合上下文去查找,上面是一个关于ImageLoader的初始化:ImageLoader::InitializerTimingList initializerTimes[allImagesCount()];
    在文件ImageLoader中查看具体实现:

    void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo)
    {
        uint64_t t1 = mach_absolute_time();
        mach_port_t thisThread = mach_thread_self();
        ImageLoader::UninitedUpwards up;
        up.count = 1;
        up.imagesAndPaths[0] = { this, this->getPath() };
        processInitializers(context, thisThread, timingInfo, up);
        context.notifyBatch(dyld_image_state_initialized, false);
        mach_port_deallocate(mach_task_self(), thisThread);
        uint64_t t2 = mach_absolute_time();
        fgTotalInitTime += (t2 - t1);
    }
    

    然后我们顺着方法runInitializers继续往里看里层的方法调用processInitializers->recursiveInitialization->notifySingle->sNotifyObjCInit(_dyld_objc_notify_init),sNotifyObjCInit= init;的赋值又在registerObjCNotifiers方法中调用,点进去registerObjCNotifiers发现它是由方法_dyld_objc_notify_register调用的。

    而void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init,_dyld_objc_notify_unmapped unmapped),这个时候在dyld里就没有关于_dyld_objc_notify_register的其他线索了,我们去objc的源码里通过符号断点的方式去照一下_dyld_objc_notify_register,发现_dyld_objc_notify_register通过_objc_init调用了_dyld_objc_notify_register(&map_images, load_images, unmap_image);

    也刚好是三个参数,和dyld里面的_dyld_objc_notify_register正好可以对应上。

    image.png

    由此可以进行推测dyld和objc就通过_dyld_objc_notify_register有了关联,然后我们再具体去看在objc里_dyld_objc_notify_register具体做了什么,我们点进去参数map_images,还有load_images,发现分别是方法的实现。

    map_images
    void
    map_images(unsigned count, const char * const paths[],
               const struct mach_header * const mhdrs[])
    {
        mutex_locker_t lock(runtimeLock);
        return map_images_nolock(count, paths, mhdrs);
    }
    

    核心函数是** map_images_nolock,而通过查看map_images_nolock的实现发现里面的核心方法是_read_images**,这个我们下一篇文章进行深入的学习。

    再结合dyld中的方法_dyld_objc_notify_register的第一个参数mapped,在方法registerObjCNotifiers中赋值

    sNotifyObjCMapped   = mapped;
    sNotifyObjCInit     = init;
    sNotifyObjCUnmapped = unmapped;
    

    mapped赋值给sNotifyObjCMapped,经过查询sNotifyObjCMapped在方法notifyBatchPartial有调用,然后notifyBatchPartial 又被registerObjCNotifiers,也就是把mapped赋值以后,再去调用mapImages。

    load_images

    根据赋值可以了解到sNotifyObjCInit对应的就是load_images参数,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);
        }
    
        // Call +load methods (without runtimeLock - re-entrant)
        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();
            }
    
            // 2. Call category +loads ONCE
            more_categories = call_category_loads();
    
            // 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_images是通过call_load_methods调用所有的+load方法;
    call_load_methods先去调用”call class +loads until there aren't any more“的+load,然后调用”2. Call category +loads ONCE”分类的+load;

    所以,到这里我们终于找到了load的调用。
    load的调用是通过dyld中的runInitializers->processInitializers->recursiveInitialization->notifySingle->sNotifyObjCInit-> registerObjCNotifiers-> _dyld_objc_notify_register-> load_images-> call_load_methods去调用的所有的+loads方法!

    而在调用main函数之前,细节也是如上runInitializers->processInitializers->recursiveInitialization->notifySingle->sNotifyObjCInit-> registerObjCNotifiers-> _dyld_objc_notify_register到这里objc层_objc_init调用了_dyld_objc_notify_register是第一个参数map_images,然后进行回调再赋值,再继续调用registerObjCNotifiers,然后是第二个参数load_images的调用。

    相关文章

      网友评论

          本文标题:13.iOS底层探究之dyld补充及load、main函数调用探

          本文链接:https://www.haomeiwen.com/subject/bllliltx.html