dyld簡(jiǎn)介及加載過(guò)程分析

dyld

dyld(the dynamic link editor)是蘋果的動(dòng)態(tài)鏈接器郁油,是蘋果操作系統(tǒng)一個(gè)重要組成部分径筏,在系統(tǒng)內(nèi)核做好程序準(zhǔn)備工作之后腻扇,交由dyld負(fù)責(zé)余下的工作睛廊。

dyld加載過(guò)程分析

我們都知道程序的入口是main()函數(shù),因此我們?cè)诔绦虻膍ain()函數(shù)中打斷點(diǎn):

E3E3C4FF4307E91CFA80F87BA6985AC4.png

結(jié)果發(fā)現(xiàn)只有一個(gè)start函數(shù),通過(guò)lldb指令(bt,up)查看,也只能知道與libdyld.dylib有關(guān),但具體的啥也沒有旭咽。
于是我們嘗試在類的load()方法中打斷點(diǎn):
3473E87FCEE51D3F41626599771896EC.png

看到有一系列函數(shù)調(diào)用棧,點(diǎn)擊第一個(gè)函數(shù)_dyld_start:
5B256739113A7E6250E138B284B56016.png

查看匯編,發(fā)現(xiàn)是由dyldbootstrap::start(macho_header const, int, char const, long, macho_header const, unsigned long*)方法開始的贞奋。我們從該方法進(jìn)行dyld的源碼分析。
15E41F06FEA6BB6D610F32A02EBE2D69.png

從源碼中看到,dyldbootstrap::start主要做了根據(jù)滑塊算出偏移地址(ASLR),rebase dyld,消息初始化,棧溢出保護(hù), 最后調(diào)用了_main函數(shù),整個(gè)app啟動(dòng)的關(guān)鍵函數(shù)就是這個(gè)_main()函數(shù)穷绵。

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.配置相關(guān)環(huán)境操作
    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);//設(shè)置上下文(函數(shù)的參數(shù),標(biāo)識(shí)信息)

    // 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);//配置進(jìn)程相關(guān)信息轿塔,進(jìn)程是否受限

#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); //檢測(cè)環(huán)境變量
        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);//獲取相關(guān)程序架構(gòu),到這里整個(gè)環(huán)境配置完成。

源碼中分析得,_main函數(shù)開始主要是配置相關(guān)環(huán)境, 包括對(duì)主程序哈希,保存主程序MarchO的頭,保存主slider(用于做重定向),設(shè)置上下文,配置進(jìn)程相關(guān)信息(進(jìn)程是否受限),檢測(cè)環(huán)境變量,獲取相關(guān)程序架構(gòu)。
這里補(bǔ)充一下:

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

DYLD_PRINT_OPTS以及DYLD_PRINT_ENV編譯的環(huán)境變量是可以在Xcode中配置的窖梁。


FECB5322211D3F3EC686F33D44CE905C.png

配置后,在程序的啟動(dòng)過(guò)程中會(huì)輸出啟動(dòng)的相關(guān)信息:


1758CA317463883F5E9F8156B056AE27.png

_main函數(shù)中配置完環(huán)境變量后,接下來(lái)開始加載共享緩存庫(kù)。
// load shared cache
    checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);

調(diào)用函數(shù)檢查共享緩存是否被禁用,進(jìn)入checkSharedRegionDisable函數(shù),

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必須開啟共享緩存庫(kù)才能運(yùn)行舞萄。
檢查共享緩存庫(kù)開啟后,開始調(diào)用mapSharedCache()函數(shù)加載共享緩存庫(kù),mapSharedCache()函數(shù)中又調(diào)用loadDyldCache()函數(shù),

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()函數(shù)中,有三種情況,第一種僅加載到當(dāng)前進(jìn)程,第二種是已經(jīng)加載過(guò)了,不需要做任何處理,第三種是第一次加載,調(diào)用mapCacheSystemWide加載。
加載完共享緩存庫(kù)之后,接下來(lái)開始加載主程序mach-O。

sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);

_main函數(shù)中調(diào)用instantiateFromLoadedImage函數(shù)加載Match-O,進(jìn)入instantiateFromLoadedImage函數(shù),

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調(diào)用isCompatibleMachO函數(shù)檢測(cè)march-o的hader,然后調(diào)用ImageLoaderMachO::instantiateMainExecutable函數(shù),進(jìn)入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函數(shù)中調(diào)用sniffLoadCommands為compressed(取值match-O中 dyld_info_only后者dyld_info),
segCount(match-O段的數(shù)量,最大不能大于255個(gè)),
libCount(match-O依賴庫(kù)的個(gè)數(shù),最大不能大于4095個(gè)),
codeSigCmd(代碼簽名),
encryptCmd(簽名信息)
初始化幻梯。
ImageLoader是一個(gè)抽象類,ImageLoaderMachO::instantiateMainExecutable根據(jù)初始化后的值compressed分別調(diào)用不同的初始化方法進(jìn)行初始化。
初始化完成后努释,返回到instantiateFromLoadedImage函數(shù),調(diào)用addImage(image),將主程序添加sAllImages數(shù)組中礼旅。

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

這里補(bǔ)充一下,我們經(jīng)常在lldb調(diào)試中輸入image list查看所有鏡像模塊,由于主程序是第一個(gè)添加到sAllImages中的,所以image list查看的模塊第一個(gè)一定是主程序模塊。


222.png

主程序加載完畢后,_main中調(diào)用根據(jù)DYLD_INSERT_LIBRARIES個(gè)數(shù)循環(huán)調(diào)用loadInsertedDylib函數(shù),加載插入的動(dòng)態(tài)庫(kù)(越獄的插件就是修改sEnv.DYLD_INSERT_LIBRARIES值洽洁,利用這個(gè)步驟在APP中注入插件,這個(gè)是蘋果預(yù)留給自己用的,必須是root的權(quán)限的用戶才能使用,所以越獄也是獲取了root權(quán)限):

// 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中調(diào)用load方法加載插入的動(dòng)態(tài)庫(kù),并和主程序一樣加入到sAllImages中。
動(dòng)態(tài)庫(kù)插入完成后,將插入的個(gè)數(shù)記錄在sInsertedDylibCount中菲嘴。

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

然后開始調(diào)用link鏈接主程序饿自,進(jìn)入link函數(shù)中:

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);
    //在鏈接的時(shí)候,不僅僅是對(duì)主程序進(jìn)行鏈接,還有很多依賴庫(kù)也需要進(jìn)行鏈接,所以首先循環(huán)加載依賴庫(kù)
    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());//遞歸依賴層級(jí)

    __block uint64_t t2, t3, t4, t5;
    {
        dyld3::ScopedTimer(DBG_DYLD_TIMING_APPLY_FIXUPS, 0, 0, 0);
        t2 = mach_absolute_time();
        this->recursiveRebase(context); //必須對(duì)主程序和依賴庫(kù)做重定位rebase(由于ASLR的存在)
        context.notifyBatch(dyld_image_state_rebased, false);

        t3 = mach_absolute_time();
        if ( !context.linkingMainExecutable ) //符號(hào)綁定
            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); //注冊(cè)GOF
    context.registerDOFs(dofs); //注冊(cè)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函數(shù)主要做了循環(huán)加載依賴庫(kù),對(duì)主程序和依賴庫(kù)做重定位rebase,符號(hào)綁定,弱綁定,注冊(cè)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插入的動(dòng)態(tài)庫(kù)數(shù)量是否大于0,然后循環(huán)調(diào)用link進(jìn)行鏈接插入的動(dòng)態(tài)庫(kù)龄坪。
以上的所有步驟都是在加載Match-O,從initializeMainExecutable函數(shù)開始一步一步調(diào)用主程序代碼昭雌。

    // run all initializers
        initializeMainExecutable();

結(jié)合之前的函數(shù)調(diào)用棧:


E661C0F0590CC360C5AFA1B3CB5D56D6.png

我們知道在initializeMainExecutable中調(diào)用了ImageLoader::runInitializers函數(shù),ImageLoader::runInitializers函數(shù)調(diào)用了ImageLoader::processInitializers,而ImageLoader::processInitializers函數(shù)中調(diào)用了ImageLoader::recursiveInitialization:函數(shù),ImageLoader::recursiveInitialization:中又調(diào)用dyld::notifySingle:這些都可以在源碼中找到。
當(dāng)我們?cè)赿yld::notifySingle:中找load_images時(shí),卻找不到健田。

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

仔細(xì)分析源碼,發(fā)現(xiàn)了一個(gè)函數(shù)指針,(*sNotifyObjCInit)(image->getRealPath(), image->machHeader()); 我們猜測(cè),有可能這個(gè)函數(shù)指針就是load_images函數(shù)烛卧。
為了驗(yàn)證結(jié)果,我們查找一下是哪個(gè)地方對(duì)sNotifyObjCInit這個(gè)函數(shù)指針賦值妓局。

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函數(shù)為函數(shù)sNotifyObjCInit賦值的总放。
追本溯源,我們繼續(xù)查找調(diào)用registerObjCNotifiers函數(shù)的源頭,

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

發(fā)現(xiàn)是通過(guò)_dyld_objc_notify_register函數(shù)調(diào)用registerObjCNotifiers的,我們繼續(xù)查找_dyld_objc_notify_register的調(diào)用者,但是在dyld源碼中找不到。
這個(gè)時(shí)候好爬,我們直接在Xcode中下一個(gè)_dyld_objc_notify_register函數(shù)的符號(hào)斷點(diǎn)并運(yùn)行:


3F1F99D7C46B319D120E66DDEA5B1515.png

發(fā)現(xiàn)_dyld_objc_notify_register是由_objc_init函數(shù)調(diào)用的,這個(gè)時(shí)候我們只能查找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被調(diào)用了,并且函數(shù)指針是load_images,所以我們的猜測(cè)是正確的。
進(jìn)入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中調(diào)用了call_load_methods函數(shù),繼續(xù)進(jìn)入call_load_methods函數(shù),

**********************************************************************/
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找到了循環(huán)調(diào)用我們程序中所有類的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調(diào)用完dyld::notifySingle:后,會(huì)繼續(xù)調(diào)用doInitialization函數(shù),進(jìn)入doInitialization函數(shù)

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特有的函數(shù)(C++構(gòu)造函數(shù)等)
下面我們來(lái)看一個(gè)實(shí)例:
當(dāng)我們建立一個(gè)空工程,沒有寫任何代碼,編譯后的mach-o如下:


3.png

當(dāng)我們?cè)趍ain函數(shù)中加入如下代碼:

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

__attribute__((constructor)) void func1(){
    printf("func1來(lái)了");
}
__attribute__((constructor)) void func2(){
    printf("func2來(lái)了");
}

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中數(shù)據(jù)蜈漓。

initializeMainExecutable(); 執(zhí)行完后,dyld開始找主程序的入口函數(shù)(MatchO中的LC_MAIN段)

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

找到后,把結(jié)果返回到start中,由start進(jìn)行調(diào)用宫盔。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末融虽,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子灼芭,更是在濱河造成了極大的恐慌有额,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件姿鸿,死亡現(xiàn)場(chǎng)離奇詭異谆吴,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)苛预,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門句狼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人热某,你說(shuō)我怎么就攤上這事腻菇。” “怎么了昔馋?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵筹吐,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我秘遏,道長(zhǎng)丘薛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任邦危,我火速辦了婚禮洋侨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘倦蚪。我一直安慰自己希坚,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布陵且。 她就那樣靜靜地躺著裁僧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪慕购。 梳的紋絲不亂的頭發(fā)上聊疲,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音沪悲,去河邊找鬼售睹。 笑死,一個(gè)胖子當(dāng)著我的面吹牛可训,可吹牛的內(nèi)容都是我干的昌妹。 我是一名探鬼主播捶枢,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼飞崖!你這毒婦竟也來(lái)了烂叔?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤固歪,失蹤者是張志新(化名)和其女友劉穎蒜鸡,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體牢裳,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡逢防,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蒲讯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片忘朝。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖判帮,靈堂內(nèi)的尸體忽然破棺而出局嘁,到底是詐尸還是另有隱情,我是刑警寧澤晦墙,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布悦昵,位于F島的核電站,受9級(jí)特大地震影響晌畅,放射性物質(zhì)發(fā)生泄漏但指。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一抗楔、第九天 我趴在偏房一處隱蔽的房頂上張望棋凳。 院中可真熱鬧,春花似錦谓谦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至疲迂,卻和暖如春才顿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背尤蒿。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工郑气, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人腰池。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓尾组,卻偏偏與公主長(zhǎng)得像忙芒,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子讳侨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容