美文网首页iOS_UI
dyld简介及加载过程分析

dyld简介及加载过程分析

作者: king_jensen | 来源:发表于2018-12-22 14:54 被阅读92次

    dyld

    dyld(the dynamic link editor)是苹果的动态链接器,是苹果操作系统一个重要组成部分,在系统内核做好程序准备工作之后,交由dyld负责余下的工作。

    dyld加载过程分析

    我们都知道程序的入口是main()函数,因此我们在程序的main()函数中打断点:

    E3E3C4FF4307E91CFA80F87BA6985AC4.png
    结果发现只有一个start函数,通过lldb指令(bt,up)查看,也只能知道与libdyld.dylib有关,但具体的啥也没有。
    于是我们尝试在类的load()方法中打断点:
    3473E87FCEE51D3F41626599771896EC.png
    看到有一系列函数调用栈,点击第一个函数_dyld_start:
    5B256739113A7E6250E138B284B56016.png
    查看汇编,发现是由dyldbootstrap::start(macho_header const, int, char const, long, macho_header const, unsigned long*)方法开始的。我们从该方法进行dyld的源码分析。
    15E41F06FEA6BB6D610F32A02EBE2D69.png
    从源码中看到,dyldbootstrap::start主要做了根据滑块算出偏移地址(ASLR),rebase dyld,消息初始化,栈溢出保护, 最后调用了_main函数,整个app启动的关键函数就是这个_main()函数。
    if (dyld3::kdebug_trace_dyld_enabled(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE)) {
            launchTraceID = dyld3::kdebug_trace_dyld_duration_start(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE, (uint64_t)mainExecutableMH, 0, 0);
        }
        
        // Grab the cdHash of the main executable from the environment
        //1.配置相关环境操作
        uint8_t mainExecutableCDHashBuffer[20];
        const uint8_t* mainExecutableCDHash = nullptr;//主程序的哈希
        if ( hexToBytes(_simple_getenv(apple, "executable_cdhash"), 40, mainExecutableCDHashBuffer) )
            mainExecutableCDHash = mainExecutableCDHashBuffer;
    
        // Trace dyld's load
        notifyKernelAboutImage((macho_header*)&__dso_handle, _simple_getenv(apple, "dyld_file"));
    #if !TARGET_IPHONE_SIMULATOR
        // Trace the main executable's load
        notifyKernelAboutImage(mainExecutableMH, _simple_getenv(apple, "executable_file"));
    #endif
    
        uintptr_t result = 0;
        sMainExecutableMachHeader = mainExecutableMH;//主程序MarchO的头
        sMainExecutableSlide = mainExecutableSlide;//拿到主程序的slider,用于做重定向
    #if __MAC_OS_X_VERSION_MIN_REQUIRED
        // if this is host dyld, check to see if iOS simulator is being run
        const char* rootPath = _simple_getenv(envp, "DYLD_ROOT_PATH");
        if ( (rootPath != NULL) ) {
            // look to see if simulator has its own dyld
            char simDyldPath[PATH_MAX]; 
            strlcpy(simDyldPath, rootPath, PATH_MAX);
            strlcat(simDyldPath, "/usr/lib/dyld_sim", PATH_MAX);
            int fd = my_open(simDyldPath, O_RDONLY, 0);
            if ( fd != -1 ) {
                const char* errMessage = useSimulatorDyld(fd, mainExecutableMH, simDyldPath, argc, argv, envp, apple, startGlue, &result);
                if ( errMessage != NULL )
                    halt(errMessage);
                return result;
            }
        }
    #endif
    
        CRSetCrashLogMessage("dyld: launch started");
    
        setContext(mainExecutableMH, argc, argv, envp, apple);//设置上下文(函数的参数,标识信息)
    
        // Pickup the pointer to the exec path.
        sExecPath = _simple_getenv(apple, "executable_path");
    
        // <rdar://problem/13868260> Remove interim apple[0] transition code from dyld
        if (!sExecPath) sExecPath = apple[0];
        
        if ( sExecPath[0] != '/' ) {
            // have relative path, use cwd to make absolute
            char cwdbuff[MAXPATHLEN];
            if ( getcwd(cwdbuff, MAXPATHLEN) != NULL ) {
                // maybe use static buffer to avoid calling malloc so early...
                char* s = new char[strlen(cwdbuff) + strlen(sExecPath) + 2];
                strcpy(s, cwdbuff);
                strcat(s, "/");
                strcat(s, sExecPath);
                sExecPath = s;
            }
        }
    
        // Remember short name of process for later logging
        sExecShortName = ::strrchr(sExecPath, '/');
        if ( sExecShortName != NULL )
            ++sExecShortName;
        else
            sExecShortName = sExecPath;
    
        configureProcessRestrictions(mainExecutableMH);//配置进程相关信息,进程是否受限
    
    #if __MAC_OS_X_VERSION_MIN_REQUIRED
        if ( !gLinkContext.allowEnvVarsPrint && !gLinkContext.allowEnvVarsPath && !gLinkContext.allowEnvVarsSharedCache ) {
            pruneEnvironmentVariables(envp, &apple);
            // set again because envp and apple may have changed or moved
            setContext(mainExecutableMH, argc, argv, envp, apple);
        }
        else
    #endif
        {
            checkEnvironmentVariables(envp); //检测环境变量
            defaultUninitializedFallbackPaths(envp);
        }
    #if __MAC_OS_X_VERSION_MIN_REQUIRED
        if (  ((dyld3::MachOFile*)mainExecutableMH)->supportsPlatform(dyld3::Platform::iOSMac)
          && !((dyld3::MachOFile*)mainExecutableMH)->supportsPlatform(dyld3::Platform::macOS)) {
            gLinkContext.rootPaths = parseColonList("/System/iOSSupport", NULL);
            gLinkContext.marzipan = true;
            if ( sEnv.DYLD_FALLBACK_LIBRARY_PATH == sLibraryFallbackPaths )
                sEnv.DYLD_FALLBACK_LIBRARY_PATH = sRestrictedLibraryFallbackPaths;
            if ( sEnv.DYLD_FALLBACK_FRAMEWORK_PATH == sFrameworkFallbackPaths )
                sEnv.DYLD_FALLBACK_FRAMEWORK_PATH = sRestrictedFrameworkFallbackPaths;
        }
    #endif
        if ( sEnv.DYLD_PRINT_OPTS )
            printOptions(argv);
        if ( sEnv.DYLD_PRINT_ENV ) 
            printEnvironmentVariables(envp);
        getHostInfo(mainExecutableMH, mainExecutableSlide);//获取相关程序架构,到这里整个环境配置完成。
    

    源码中分析得,_main函数开始主要是配置相关环境, 包括对主程序哈希,保存主程序MarchO的头,保存主slider(用于做重定向),设置上下文,配置进程相关信息(进程是否受限),检测环境变量,获取相关程序架构。
    这里补充一下:

    if ( sEnv.DYLD_PRINT_OPTS )
            printOptions(argv);
        if ( sEnv.DYLD_PRINT_ENV ) 
            printEnvironmentVariables(envp);
    

    DYLD_PRINT_OPTS以及DYLD_PRINT_ENV编译的环境变量是可以在Xcode中配置的。


    FECB5322211D3F3EC686F33D44CE905C.png

    配置后,在程序的启动过程中会输出启动的相关信息:


    1758CA317463883F5E9F8156B056AE27.png
    _main函数中配置完环境变量后,接下来开始加载共享缓存库。
    // load shared cache
        checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
    

    调用函数检查共享缓存是否被禁用,进入checkSharedRegionDisable函数,

    static void checkSharedRegionDisable(const dyld3::MachOLoaded* mainExecutableMH, uintptr_t mainExecutableSlide)
    {
    #if __MAC_OS_X_VERSION_MIN_REQUIRED
        // if main executable has segments that overlap the shared region,
        // then disable using the shared region
        if ( mainExecutableMH->intersectsRange(SHARED_REGION_BASE, SHARED_REGION_SIZE) ) {
            gLinkContext.sharedRegionMode = ImageLoader::kDontUseSharedRegion;
            if ( gLinkContext.verboseMapping )
                dyld::warn("disabling shared region because main executable overlaps\n");
        }
    #if __i386__
        if ( !gLinkContext.allowEnvVarsPath ) {
            // <rdar://problem/15280847> use private or no shared region for suid processes
            gLinkContext.sharedRegionMode = ImageLoader::kUsePrivateSharedRegion;
        }
    #endif
    #endif
        // iOS cannot run without shared region
    }
    

    iOS必须开启共享缓存库才能运行。
    检查共享缓存库开启后,开始调用mapSharedCache()函数加载共享缓存库,mapSharedCache()函数中又调用loadDyldCache()函数,

    bool loadDyldCache(const SharedCacheOptions& options, SharedCacheLoadInfo* results)
    {
        results->loadAddress        = 0;
        results->slide              = 0;
        results->errorMessage       = nullptr;
    
    #if TARGET_IPHONE_SIMULATOR
        // simulator only supports mmap()ing cache privately into process
        return mapCachePrivate(options, results);
    #else
        if ( options.forcePrivate ) {
            // mmap cache into this process only
            return mapCachePrivate(options, results);
        }
        else {
            // fast path: when cache is already mapped into shared region
            bool hasError = false;
            if ( reuseExistingCache(options, results) ) {
                hasError = (results->errorMessage != nullptr);
            } else {
                // slow path: this is first process to load cache
                hasError = mapCacheSystemWide(options, results);
            }
            return hasError;
        }
    #endif
    }
    

    loadDyldCache()函数中,有三种情况,第一种仅加载到当前进程,第二种是已经加载过了,不需要做任何处理,第三种是第一次加载,调用mapCacheSystemWide加载。
    加载完共享缓存库之后,接下来开始加载主程序mach-O。

    sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
    

    _main函数中调用instantiateFromLoadedImage函数加载Match-O,进入instantiateFromLoadedImage函数,

    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";
    }
    

    在instantiateFromLoadedImage调用isCompatibleMachO函数检测march-o的hader,然后调用ImageLoaderMachO::instantiateMainExecutable函数,进入ImageLoaderMachO::instantiateMainExecutable

    // create image for main executable
    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
    }
    

    ImageLoaderMachO::instantiateMainExecutable函数中调用sniffLoadCommands为compressed(取值match-O中 dyld_info_only后者dyld_info),
    segCount(match-O段的数量,最大不能大于255个),
    libCount(match-O依赖库的个数,最大不能大于4095个),
    codeSigCmd(代码签名),
    encryptCmd(签名信息)
    初始化。
    ImageLoader是一个抽象类,ImageLoaderMachO::instantiateMainExecutable根据初始化后的值compressed分别调用不同的初始化方法进行初始化。
    初始化完成后,返回到instantiateFromLoadedImage函数,调用addImage(image),将主程序添加sAllImages数组中。

    static void addImage(ImageLoader* image)
    {
        // add to master list
        allImagesLock();
            sAllImages.push_back(image);
        allImagesUnlock();
        
        // update mapped ranges
        uintptr_t lastSegStart = 0;
        uintptr_t lastSegEnd = 0;
        for(unsigned int i=0, e=image->segmentCount(); i < e; ++i) {
            if ( image->segUnaccessible(i) ) 
                continue;
            uintptr_t start = image->segActualLoadAddress(i);
            uintptr_t end = image->segActualEndAddress(i);
            if ( start == lastSegEnd ) {
                // two segments are contiguous, just record combined segments
                lastSegEnd = end;
            }
            else {
                // non-contiguous segments, record last (if any)
                if ( lastSegEnd != 0 )
                    addMappedRange(image, lastSegStart, lastSegEnd);
                lastSegStart = start;
                lastSegEnd = end;
            }       
        }
        if ( lastSegEnd != 0 )
            addMappedRange(image, lastSegStart, lastSegEnd);
    
        
        if ( gLinkContext.verboseLoading || (sEnv.DYLD_PRINT_LIBRARIES_POST_LAUNCH && (sMainExecutable!=NULL) && sMainExecutable->isLinked()) ) {
            dyld::log("dyld: loaded: %s\n", image->getPath());
        }   
    }
    

    这里补充一下,我们经常在lldb调试中输入image list查看所有镜像模块,由于主程序是第一个添加到sAllImages中的,所以image list查看的模块第一个一定是主程序模块。


    222.png

    主程序加载完毕后,_main中调用根据DYLD_INSERT_LIBRARIES个数循环调用loadInsertedDylib函数,加载插入的动态库(越狱的插件就是修改sEnv.DYLD_INSERT_LIBRARIES值,利用这个步骤在APP中注入插件,这个是苹果预留给自己用的,必须是root的权限的用户才能使用,所以越狱也是获取了root权限):

    // load any inserted libraries
            if  ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
                for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) 
                    loadInsertedDylib(*lib);
            }
            // record count of inserted libraries so that a flat search will look at 
            // inserted libraries, then main, then others.
            sInsertedDylibCount = sAllImages.size()-1;
    

    在loadInsertedDylib中调用load方法加载插入的动态库,并和主程序一样加入到sAllImages中。
    动态库插入完成后,将插入的个数记录在sInsertedDylibCount中。

    link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
    

    然后开始调用link链接主程序,进入link函数中:

    void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool preflightOnly, bool neverUnload, const RPathChain& loaderRPaths, const char* imagePath)
    {
        //dyld::log("ImageLoader::link(%s) refCount=%d, neverUnload=%d\n", imagePath, fDlopenReferenceCount, fNeverUnload);
        
        // clear error strings
        (*context.setErrorStrings)(0, NULL, NULL, NULL);
    
        uint64_t t0 = mach_absolute_time();
        this->recursiveLoadLibraries(context, preflightOnly, loaderRPaths, imagePath);
        //在链接的时候,不仅仅是对主程序进行链接,还有很多依赖库也需要进行链接,所以首先循环加载依赖库
        context.notifyBatch(dyld_image_state_dependents_mapped, preflightOnly);
    
        // we only do the loading step for preflights
        if ( preflightOnly )
            return;
    
        uint64_t t1 = mach_absolute_time();
        context.clearAllDepths();
        this->recursiveUpdateDepth(context.imageCount());//递归依赖层级
    
        __block uint64_t t2, t3, t4, t5;
        {
            dyld3::ScopedTimer(DBG_DYLD_TIMING_APPLY_FIXUPS, 0, 0, 0);
            t2 = mach_absolute_time();
            this->recursiveRebase(context); //必须对主程序和依赖库做重定位rebase(由于ASLR的存在)
            context.notifyBatch(dyld_image_state_rebased, false);
    
            t3 = mach_absolute_time();
            if ( !context.linkingMainExecutable ) //符号绑定
                this->recursiveBindWithAccounting(context, forceLazysBound, neverUnload);
    
            t4 = mach_absolute_time();
            if ( !context.linkingMainExecutable )
                this->weakBind(context); //弱绑定
            t5 = mach_absolute_time();
        }
    
        if ( !context.linkingMainExecutable )
            context.notifyBatch(dyld_image_state_bound, false);
        uint64_t t6 = mach_absolute_time(); 
    
        std::vector<DOFInfo> dofs;
        this->recursiveGetDOFSections(context, dofs); //注册GOF
        context.registerDOFs(dofs); //注册GOF
        uint64_t t7 = mach_absolute_time(); 
    
        // interpose any dynamically loaded images
        if ( !context.linkingMainExecutable && (fgInterposingTuples.size() != 0) ) {
            dyld3::ScopedTimer timer(DBG_DYLD_TIMING_APPLY_INTERPOSING, 0, 0, 0);
            this->recursiveApplyInterposing(context);
        }
    
        // clear error strings
        (*context.setErrorStrings)(0, NULL, NULL, NULL);
    
        fgTotalLoadLibrariesTime += t1 - t0;
        fgTotalRebaseTime += t3 - t2;
        fgTotalBindTime += t4 - t3;
        fgTotalWeakBindTime += t5 - t4;
        fgTotalDOF += t7 - t6;
        
        // done with initial dylib loads
        fgNextPIEDylibAddress = 0;
    }
    

    link函数主要做了循环加载依赖库,对主程序和依赖库做重定位rebase,符号绑定,弱绑定,注册GOF。

    if ( sInsertedDylibCount > 0 ) {
                for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
                    ImageLoader* image = sAllImages[i+1];
                    link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
                    image->setNeverUnloadRecursive();
                }
                // only INSERTED libraries can interpose
                // register interposing info after all inserted libraries are bound so chaining works
                for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
                    ImageLoader* image = sAllImages[i+1];
                    image->registerInterposing(gLinkContext);
                }
            }
    

    链接主程序完成后,判断sInsertedDylibCount插入的动态库数量是否大于0,然后循环调用link进行链接插入的动态库。
    以上的所有步骤都是在加载Match-O,从initializeMainExecutable函数开始一步一步调用主程序代码。

        // run all initializers
            initializeMainExecutable();
    

    结合之前的函数调用栈:


    E661C0F0590CC360C5AFA1B3CB5D56D6.png

    我们知道在initializeMainExecutable中调用了ImageLoader::runInitializers函数,ImageLoader::runInitializers函数调用了ImageLoader::processInitializers,而ImageLoader::processInitializers函数中调用了ImageLoader::recursiveInitialization:函数,ImageLoader::recursiveInitialization:中又调用dyld::notifySingle:这些都可以在源码中找到。
    当我们在dyld::notifySingle:中找load_images时,却找不到。

    static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
    {
        //dyld::log("notifySingle(state=%d, image=%s)\n", state, image->getPath());
        std::vector<dyld_image_state_change_handler>* handlers = stateToHandlers(state, sSingleHandlers);
        if ( handlers != NULL ) {
            dyld_image_info info;
            info.imageLoadAddress   = image->machHeader();
            info.imageFilePath      = image->getRealPath();
            info.imageFileModDate   = image->lastModified();
            for (std::vector<dyld_image_state_change_handler>::iterator it = handlers->begin(); it != handlers->end(); ++it) {
                const char* result = (*it)(state, 1, &info);
                if ( (result != NULL) && (state == dyld_image_state_mapped) ) {
                    //fprintf(stderr, "  image rejected by handler=%p\n", *it);
                    // make copy of thrown string so that later catch clauses can free it
                    const char* str = strdup(result);
                    throw str;
                }
            }
        }
        if ( state == dyld_image_state_mapped ) {
            // <rdar://problem/7008875> Save load addr + UUID for images from outside the shared cache
            if ( !image->inSharedCache() ) {
                dyld_uuid_info info;
                if ( image->getUUID(info.imageUUID) ) {
                    info.imageLoadAddress = image->machHeader();
                    addNonSharedCacheImageUUID(info);
                }
            }
        }
        if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && image->notifyObjC() ) {
            uint64_t t0 = mach_absolute_time();
            dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
            (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
            uint64_t t1 = mach_absolute_time();
            uint64_t t2 = mach_absolute_time();
            uint64_t timeInObjC = t1-t0;
            uint64_t emptyTime = (t2-t1)*100;
            if ( (timeInObjC > emptyTime) && (timingInfo != NULL) ) {
                timingInfo->addTime(image->getShortName(), timeInObjC);
            }
        }
        // mach message csdlc about dynamically unloaded images
        if ( image->addFuncNotified() && (state == dyld_image_state_terminated) ) {
            notifyKernel(*image, false);
            const struct mach_header* loadAddress[] = { image->machHeader() };
            const char* loadPath[] = { image->getPath() };
            notifyMonitoringDyld(true, 1, loadAddress, loadPath);
        }
    }
    

    仔细分析源码,发现了一个函数指针,(*sNotifyObjCInit)(image->getRealPath(), image->machHeader()); 我们猜测,有可能这个函数指针就是load_images函数。
    为了验证结果,我们查找一下是哪个地方对sNotifyObjCInit这个函数指针赋值。

    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());
            }
        }
    }
    

    查找到是在registerObjCNotifiers函数为函数sNotifyObjCInit赋值的。
    追本溯源,我们继续查找调用registerObjCNotifiers函数的源头,

    void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                    _dyld_objc_notify_init      init,
                                    _dyld_objc_notify_unmapped  unmapped)
    {
        dyld::registerObjCNotifiers(mapped, init, unmapped);
    }
    

    发现是通过_dyld_objc_notify_register函数调用registerObjCNotifiers的,我们继续查找_dyld_objc_notify_register的调用者,但是在dyld源码中找不到。
    这个时候,我们直接在Xcode中下一个_dyld_objc_notify_register函数的符号断点并运行:


    3F1F99D7C46B319D120E66DDEA5B1515.png

    发现_dyld_objc_notify_register是由_objc_init函数调用的,这个时候我们只能查找objc源码了。
    在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();
    
        _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    }
    

    我们看到了_dyld_objc_notify_register被调用了,并且函数指针是load_images,所以我们的猜测是正确的。
    进入load_images:

    load_images(const char *path __unused, const struct mach_header *mh)
    {
        // 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();
    }
    

    在load_images中调用了call_load_methods函数,继续进入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;
    }
    

    终于在call_load_methods找到了循环调用我们程序中所有类的Load方法。

    // let objc know we are about to initialize this image
                uint64_t t1 = mach_absolute_time();
                fState = dyld_image_state_dependents_initialized;
                oldState = fState;
                context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
                
                // initialize this image
                bool hasInitializers = this->doInitialization(context);
    

    ImageLoader::recursiveInitialization调用完dyld::notifySingle:后,会继续调用doInitialization函数,进入doInitialization函数

    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);
    }
    

    doModInitFunctions作用是加载Match-O特有的函数(C++构造函数等)
    下面我们来看一个实例:
    当我们建立一个空工程,没有写任何代码,编译后的mach-o如下:


    3.png

    当我们在main函数中加入如下代码:

    #import <UIKit/UIKit.h>
    #import "AppDelegate.h"
    
    __attribute__((constructor)) void func1(){
        printf("func1来了");
    }
    __attribute__((constructor)) void func2(){
        printf("func2来了");
    }
    
    int main(int argc, char * argv[]) {
        @autoreleasepool {
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        }
    }
    

    编译后的mach-o如下:


    D02BB1977C8C5BEB33C3A9D85E24E31D.png

    在MatchO文件DATA段_la_symbol_ptr和_objc_classlist多了_mod_init_func组。doModInitFunctions加载的就是_mod_init_func中数据。

    initializeMainExecutable(); 执行完后,dyld开始找主程序的入口函数(MatchO中的LC_MAIN段)

    // find entry point for main executable
            result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();
    

    找到后,把结果返回到start中,由start进行调用。

    相关文章

      网友评论

        本文标题:dyld简介及加载过程分析

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