010:dyld加载流程-(_dyld_start-dyldbootstrap::start-dyld::_main-dyld::initializeMainExecutable-ImageLoader::runInitializers-ImageLoader::processInitializers-ImageLoader::recursiveInitialization)

问题

 

目录

 

预备

 

正文

 1:main、load、C++ 的执行顺序

__attribute__((constructor)) void htFunc() {
    printf("%s \n",__func__);
}

@interface HTPerson : NSObject
@end

@implementation HTPerson

+ (void)load {
    NSLog(@"%s", __func__);
}

@end

int main(int :, const char * argv[]) {
    @autoreleasepool {
        
        NSLog(@"%s",__func__);
    }
    return 0;
}

打印顺序: load -> c++(constructor) -> main

2:编译过程及库

2.1:编译

  • 源文件:载入.h、.m、.cpp等文件
  • 预处理:替换宏,删除注释,展开头文件,产生.i文件
  • 编译:将.i文件转换为汇编语言,产生.s文件
  • 汇编:将汇编文件转换为机器码文件,产生.o文件
  • 链接:对.o文件中引用其他库的地方进行引用,生成最后的可执行文件
 

2.2:静态库 和 动态库

代码库有静态库动态库两种,在开始探索app启动流程前,我们先了解两者的区别。

2.1 静态库:

静态编译的库,在编译时就将整个函数库的所有数据都整合进目标代码中。尾缀有.a.lib.framework等。

  • 优点: 模块化,分工合作,提高了代码的复用和核心技术的保密程度
  • 缺点: 会加大包的体积。如果静态函数库被改变,程序必须重新编译

2.2 动态库:

编译时不会将函数库编译进目标代码中,只有程序执行相关函数时,才调用函数库的相应函数。尾缀有.tbd.so.framework

  • 优点: 可执行文件体积小,多个应用程序共享内存中同一份库文件,节省内存资源,支持实时模块升级。
  • 苹果的动态库支持所有APP共享内存(如UIKit),但APP动态库是写入app main bundle根目录中,运行在沙盒中,只支持当前APP内共享内存。(iOS8后App Extension功能支持主app和插件之间共享动态库)

3:dyld加载流程

1. 什么是dyld?

  dyld是英文 the dynamic link editor的简写,翻译过来就是动态链接器,是苹果操作系统的一个重要的组成部分。在 iOS/Mac OSX系统中,仅有很少量的进程只需要内核就能完成加载,基本上所有的进程都是动态链接的,所以 Mach-O镜像文件中会有很多对外部的库和符号的引用,但是这些引用并不能直接用,在启动时还必须要通过这些引用进行内容的填补,这个填补工作就是由 动态链接器dyld来完成的,也就是符号绑定动态链接器dyld在系统中以一个用户态的可执行文件形式存在,一般应用程序会在 Mach-O文件部分指定一个 LC_LOAD_DYLINKER的加载命令,此加载命令指定了 dyld的路径,通常它的默认值是 /usr/lib/dyld。系统内核在加载 Mach-O文件时,都需要用 dyld(位于 /usr/lib/dyld)程序进行链接。其实 dyld就是把应用的MachO文件加载到内存中。

2:App加载过程是:

源文件(.h .m .cpp)-> 预编译(词法语法分析) -> 编译(载入静态库) -> 汇编 -> 链接(关联动态库) -> 生成可执行文件(mach-o)

3:dyld动态链接器加载流程

配置应用环境->初始化主程序->加载共享缓存->加载动态库->链接主程序->链接动态库->弱符号绑定->执行初始化->调用main函数

4:dyld加载流程分析

动态链接器 dyld是内核执行内核命令 LC_LOAD_DYLINKER加载命令时启动的,默认使用 /usr/lib/dyld文件作为动态链接器。

运行程序,打上断点之后,然后查看函数调用栈。

 4.1:start函数分析

 在上图的第 11行处,汇编指令 callq就是调用函数的指令,这个函数也是我们 APP开始的地方。

4.2: _dyld_start分析:汇编实现

根据这个线索,我们在 dyld的源码搜索 _dyld_start函数,可以在 dyldStartup.s文件中找到入口,分析之后可以发现这个文件中按照不同架构分别作了逻辑处理,比如 i386x86_64armarm64

#if __arm64__
    .text
    .align 2
    .globl __dyld_start
__dyld_start:
    mov     x28, sp
    and     sp, x28, #~15        // force 16-byte alignment of stack
    
    // 省略部分代码......
    
    // call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
    bl    __ZN13dyldbootstrap5startEPKN5dyld311MachOLoadedEiPPKcS3_Pm
    mov    x16,x0                  // save entry point address in x16

找到关键指令 bl跳转函数,根据注释,我们可以得到这里会跳转调用 dyld的引导程序 dyldbootstrap::start

4.3:dyldbootstrap::start

dyldbootstrap::start就是指 dyldbootstrap这个命名空间作用域里的 start函数 

我们在 dyld的源码里搜索 dyldbootstrap,然后找到 start函数。

uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],
                const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue)
{

    // Emit kdebug tracepoint to indicate dyld bootstrap has started <rdar://46878536>
    dyld3::kdebug_trace_dyld_marker(DBG_DYLD_TIMING_BOOTSTRAP_START, 0, 0, 0, 0);

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

    // kernel sets up env pointer to be just past end of agv array
    const char** envp = &argv[argc+1];
    
    // kernel sets up apple pointer to be just past end of envp array
    const char** apple = envp;
    while(*apple != NULL) { ++apple; }
    ++apple;

    // set up random value for stack canary
    __guard_setup(apple);

#if DYLD_INITIALIZER_SUPPORT
    // run all C++ initializers inside dyld
    runDyldInitializers(argc, argv, envp, apple);
#endif

    // now that we are done bootstrapping dyld, call dyld's main
    uintptr_t appsSlide = appsMachHeader->getSlide();
    return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}
  • 首先调用 rebaseDyld()dyld重定位;
  • 然后调用 __guard_setup栈溢出保护;
  • 最后调用 dyld::_main进入 dyld的 _main函数

为什么要 rebaseDyld()重定位

这里要提到两种苹果用来保证应用安全的技术:ASLR和 Code Sign

ASLR: 是 Address Space Layout Randomization(地址空间布局随机化)的简称。App在被启动的时候,程序会被映射到逻辑地址空间,这个逻辑地址空间有一个起始地址,ASLR技术让这个起始地址是随机的。这个地址如果是固定的,攻击者很容易就用起始地址+函数偏移地址找到对应的函数地址。

Code Sign: 是苹果代码加密签名机制,但是在 Code Sign操作的时候,加密的哈希不是针对整个文件,而是针对每一个 Page的。这个就保证了 dyld在加载的时候,可以对每个 page进行独立的验证。

正是因为 ASLR使得地址随机化,导致起始地址不固定,以及 Code Sign,导致不能直接修改 Image。所以需要 rebase来处理符号引用问题,Rebase的时候只需要通过增加对应偏移量就行了。Rebase主要的作用就是修正内部(指向当前 Mach-O文件)的指针指向,也就是基地址复位功能。

4.4 rebaseDyld()分析

//
// On disk, all pointers in dyld's DATA segment are chained together.
// They need to be fixed up to be real pointers to run.
//
static void rebaseDyld(const dyld3::MachOLoaded* dyldMH)
{
    // walk all fixups chains and rebase dyld
    // 遍历所有固定的 chains 然后 rebase dyld
    const dyld3::MachOAnalyzer* ma = (dyld3::MachOAnalyzer*)dyldMH;
    assert(ma->hasChainedFixups());
    uintptr_t slide = (long)ma; // all fixup chain based images have a base address of zero, so slide == load address
    // 所有基于修正链的映像的基地址为零,因此slide == 加载地址
    __block Diagnostics diag;
    ma->withChainStarts(diag, 0, ^(const dyld_chained_starts_in_image* starts) {
        ma->fixupAllChainedFixups(diag, starts, slide, dyld3::Array<const void*>(), nullptr);
    });
    diag.assertNoError();

    // now that rebasing done, initialize mach/syscall layer
    mach_init();

    // <rdar://47805386> mark __DATA_CONST segment in dyld as read-only (once fixups are done)
    ma->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& info, bool& stop) {
        if ( info.readOnlyData ) {
            ::mprotect(((uint8_t*)(dyldMH))+info.vmAddr, (size_t)info.vmSize, VM_PROT_READ);
        }
    });
}

4.5 dyld::_main分析

uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
        int argc, const char* argv[], const char* envp[], const char* apple[], 
        uintptr_t* startGlue)
{
    // 第1步:初始化程序运行环境
    // 初始化运行环境配置以及拿到Mach-O头文件    (macho_header里面包含整个Mach-O文件信息其中包括所有链入的动态库信息)
    
    uint8_t mainExecutableCDHashBuffer[20];
    const uint8_t* mainExecutableCDHash = nullptr;
    if ( hexToBytes(_simple_getenv(apple, "executable_cdhash"), 40, mainExecutableCDHashBuffer) )
        mainExecutableCDHash = mainExecutableCDHashBuffer;

    notifyKernelAboutImage(mainExecutableMH, _simple_getenv(apple, "executable_file"));

    uintptr_t result = 0;
    
    // 获取主程序的macho_header结构以及主程序的slide偏移值
    
    sMainExecutableMachHeader = mainExecutableMH;
    sMainExecutableSlide = mainExecutableSlide;
    ......
    CRSetCrashLogMessage("dyld: launch started");
    
    // 设置上下文信息
    setContext(mainExecutableMH, argc, argv, envp, apple);

    // 获取主程序路径
    // Pickup the pointer to the exec path.
    sExecPath = _simple_getenv(apple, "executable_path");

    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, envp);

    // 检测环境变量
    checkEnvironmentVariables(envp);
    defaultUninitializedFallbackPaths(envp);

    // 判断是否设置了sEnv.DYLD_PRINT_OPTS以及sEnv.DYLD_PRINT_ENV,分别打印argv参数和envp环境变量
    if ( sEnv.DYLD_PRINT_OPTS )
        printOptions(argv);
    if ( sEnv.DYLD_PRINT_ENV ) 
        printEnvironmentVariables(envp);

    // 获取当前程序架构
    getHostInfo(mainExecutableMH, mainExecutableSlide);


    // load shared cache
    // 第2步、加载共享缓存 shared cache
    // 检查共享缓存是否开启,iOS必须开启!!!!!!
    checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
    if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
#if TARGET_OS_SIMULATOR
        if ( sSharedCacheOverrideDir)
            mapSharedCache();
#else
        mapSharedCache();
#endif
    }

   ......
    
    try {
        // add dyld itself to UUID list
        addDyldImageToUUIDList();
        
        // 第3步:实例化主程序,并赋值给ImageLoader::LinkContext
        
        sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
        gLinkContext.mainExecutable = sMainExecutable;
        gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);

    ......

    #if SUPPORT_VERSIONED_PATHS
        checkVersionedPaths();
    #endif
        // dyld_all_image_infos image list does not contain dyld
        // add it as dyldPath field in dyld_all_image_infos
        // for simulator, dyld_sim is in image list, need host dyld added
        
#if TARGET_OS_SIMULATOR
        // get path of host dyld from table of syscall vectors in host dyld
        void* addressInDyld = gSyscallHelpers;
#else
        // get path of dyld itself
        void*  addressInDyld = (void*)&__dso_handle;
#endif
        char dyldPathBuffer[MAXPATHLEN+1];
        int len = proc_regionfilename(getpid(), (uint64_t)(long)addressInDyld, dyldPathBuffer, MAXPATHLEN);
        if ( len > 0 ) {
            dyldPathBuffer[len] = '\0'; // proc_regionfilename() does not zero terminate returned string
            if ( strcmp(dyldPathBuffer, gProcessInfo->dyldPath) != 0 )
                gProcessInfo->dyldPath = strdup(dyldPathBuffer);
        }

       // 第4步 加载插入的动态库
       
        // 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;

        // link main executable
        //第5步:链接主程序++++++++++++++
        
        gLinkContext.linkingMainExecutable = true;
#if SUPPORT_ACCELERATE_TABLES
        if ( mainExcutableAlreadyRebased ) {
            // previous link() on main executable has already adjusted its internal pointers for ASLR
            // work around that by rebasing by inverse amount
            sMainExecutable->rebase(gLinkContext, -mainExecutableSlide);
        }
#endif
        link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
        sMainExecutable->setNeverUnloadRecursive();
        if ( sMainExecutable->forceFlat() ) {
            gLinkContext.bindFlat = true;
            gLinkContext.prebindUsage = ImageLoader::kUseNoPrebinding;
        }

    // 第6步、链接插入的动态库
    
        // 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
        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);
            }
        }

        // <rdar://problem/19315404> dyld should support interposition even without DYLD_INSERT_LIBRARIES
        for (long i=sInsertedDylibCount+1; i < sAllImages.size(); ++i) {
            ImageLoader* image = sAllImages[i];
            if ( image->inSharedCache() )
                continue;
            image->registerInterposing(gLinkContext);
        }
    
        ......
    
        // apply interposing to initial set of images
        for(int i=0; i < sImageRoots.size(); ++i) {
            sImageRoots[i]->applyInterposing(gLinkContext);
        }
        ImageLoader::applyInterposingToDyldCache(gLinkContext);

        // Bind and notify for the main executable now that interposing has been registered
        uint64_t bindMainExecutableStartTime = mach_absolute_time();
        sMainExecutable->recursiveBindWithAccounting(gLinkContext, sEnv.DYLD_BIND_AT_LAUNCH, true);
        uint64_t bindMainExecutableEndTime = mach_absolute_time();
        ImageLoaderMachO::fgTotalBindTime += bindMainExecutableEndTime - bindMainExecutableStartTime;
        gLinkContext.notifyBatch(dyld_image_state_bound, false);

        // Bind and notify for the inserted images now interposing has been registered
        if ( sInsertedDylibCount > 0 ) {
            for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
                ImageLoader* image = sAllImages[i+1];
                image->recursiveBind(gLinkContext, sEnv.DYLD_BIND_AT_LAUNCH, true);
            }
        }
        
        // 第7步、在链接所有插入的image后,执行弱绑定
        
        // <rdar://problem/12186933> do weak binding only after all inserted images linked
        sMainExecutable->weakBind(gLinkContext);
        gLinkContext.linkingMainExecutable = false;

        sMainExecutable->recursiveMakeDataReadOnly(gLinkContext);

        CRSetCrashLogMessage("dyld: launch, running initializers");
    #if SUPPORT_OLD_CRT_INITIALIZATION
        // Old way is to run initializers via a callback from crt1.o
        if ( ! gRunInitializersOldWay ) 
            initializeMainExecutable(); 
    #else
    
    // 第8步:执行所有的初始化方法
        
        // run all initializers
        initializeMainExecutable(); 
    #endif

        // notify any montoring proccesses that this process is about to enter main()
        notifyMonitoringDyldMain();
        if (dyld3::kdebug_trace_dyld_enabled(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE)) {
            dyld3::kdebug_trace_dyld_duration_end(launchTraceID, DBG_DYLD_TIMING_LAUNCH_EXECUTABLE, 0, 0, 2);
        }
        ARIADNEDBG_CODE(220, 1);

#if __MAC_OS_X_VERSION_MIN_REQUIRED
        if ( gLinkContext.driverKit ) {
            result = (uintptr_t)sEntryOveride;
            if ( result == 0 )
                halt("no entry point registered");
            *startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit;
        }
        else
#endif
        {
        
        // 第9步:查找主程序的入口点并返回
        
            // find entry point for main executable
            result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();
            
            if ( result != 0 ) {
                // main executable uses LC_MAIN, we need to use helper in libdyld to call into main()
                if ( (gLibSystemHelpers != NULL) && (gLibSystemHelpers->version >= 9) )
                    *startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit;
                else
                    halt("libdyld.dylib support not present for LC_MAIN");
            }
            else {
                // main executable uses LC_UNIXTHREAD, dyld needs to let "start" in program set up for main()
                result = (uintptr_t)sMainExecutable->getEntryFromLC_UNIXTHREAD();
                *startGlue = 0;
            }
        }
#if __has_feature(ptrauth_calls)
        // start() calls the result pointer as a function pointer so we need to sign it.
        result = (uintptr_t)__builtin_ptrauth_sign_unauthenticated((void*)result, 0, 0);
#endif
    }
    catch(const char* message) {
        syncAllImages();
        halt(message);
    }
    catch(...) {
        dyld::log("dyld: launch failed\n");
    }

    ......
    
    return result;
}

以下总结一下 dyld::_main 主要做了什么

  • 1.主程序运行环境初始化及配置,拿到 Mach-O头文件 (macho_header里面包含整个 Mach-O文件信息其中包括所有链入的动态库信息);
  • 2.加载共享缓存 shared cache
  • 3.实例化主程序,并赋值给 ImageLoader::LinkContext
  • 4.加载所有插入的动态库,将可执行文件以及相应的依赖库与插入库加载进内存生成对应的 ImageLoader类的image(镜像文件)对象;
  • 5.链接主程序(必须先链接主程序后才能插入);
  • 6.链接所有的动态库 ImageLoader的 image(镜像文件)对象,并注册插入的信息,方便后续进行绑定;
  • 7.在链接完所有插入的动态库镜像文件之后执行弱绑定;
  • 8.执行所有动态库 image的初始化方法 initializeMainExecutable
  • 9.查找主程序的入口点 LC_MAIN并返回 result结果,结束整个 _dyld_start流程,进入我们 App的 main()函数。 

接下来我们分析第 8步,initializeMainExecutable()

4.6 initializeMainExecutable分析

void initializeMainExecutable()
{
    // record that we've reached this step
    gLinkContext.startedInitializingMainExecutable = true;
    
    // run initialzers for any inserted dylibs
    // 对每一个插入进来的 dylib 调用 runInitializers 方法进行初始化
    ImageLoader::InitializerTimingList initializerTimes[allImagesCount()];
    initializerTimes[0].count = 0;
    const size_t rootCount = sImageRoots.size();
    if ( rootCount > 1 ) {
        for(size_t i=1; i < rootCount; ++i) {
            sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);
        }
    }
    
    // run initializers for main executable and everything it brings up 
    // 对主程序调用 runInitializers 方法初始化
    sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
    
    // register cxa_atexit() handler to run static terminators in all loaded images when this process exits
    // 注册 cxa_atexit() 回调以在此进程退出时在所有加载的图像中运行静态终止符
    if ( gLibSystemHelpers != NULL ) 
        (*gLibSystemHelpers->cxa_atexit)(&runAllStaticTerminators, NULL, NULL);

    // dump info if requested
    if ( sEnv.DYLD_PRINT_STATISTICS )
        ImageLoader::printStatistics((unsigned int)allImagesCount(), initializerTimes[0]);
    if ( sEnv.DYLD_PRINT_STATISTICS_DETAILS )
        ImageLoaderMachO::printStatisticsDetails((unsigned int)allImagesCount(), initializerTimes[0]);
}

以上函数主要做了两件事

  • 1.对每一个插入进来的 dylib调用 runInitializers方法进行初始化;
  • 2.对主程序调用 runInitializers方法初始化。

4.7 runInitializers分析

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

4.8 processInitializers实现

void ImageLoader::processInitializers(const LinkContext& context, mach_port_t thisThread,
                                     InitializerTimingList& timingInfo, ImageLoader::UninitedUpwards& images)
{
    uint32_t maxImageCount = context.imageCount()+2;
    ImageLoader::UninitedUpwards upsBuffer[maxImageCount];
    ImageLoader::UninitedUpwards& ups = upsBuffer[0];
    ups.count = 0;
    // Calling recursive init on all images in images list, building a new list of
    // uninitialized upward dependencies.
    for (uintptr_t i=0; i < images.count; ++i) {
        // 调用 recursiveInitialization
        images.imagesAndPaths[i].first->recursiveInitialization(context, thisThread, images.imagesAndPaths[i].second, timingInfo, ups);
    }
    // If any upward dependencies remain, init them.
    if ( ups.count > 0 )
        processInitializers(context, thisThread, timingInfo, ups);
}

4.9 recursiveInitialization实现

void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
                                          InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
    recursive_lock lock_info(this_thread);
    recursiveSpinLock(lock_info);

    if ( fState < dyld_image_state_dependents_initialized-1 ) {
        uint8_t oldState = fState;
        // break cycles
        fState = dyld_image_state_dependents_initialized-1;
        try {
            // 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.imagesAndPaths[uninitUps.count] = { dependentImage, libPath(i) };
                        uninitUps.count++;
                    }
                    else if ( dependentImage->fDepth >= fDepth ) {
                        dependentImage->recursiveInitialization(context, this_thread, libPath(i), timingInfo, uninitUps);
                    }
                }
            }
            
            // record termination order
            if ( this->needsTermination() )
                context.terminationRecorder(this);

            // let objc know we are about to initialize this image
            uint64_t t1 = mach_absolute_time();
            fState = dyld_image_state_dependents_initialized;
            oldState = fState;
            // 关键代码 begin ************
            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);
            // 关键代码 end ************
            
            if ( hasInitializers ) {
                uint64_t t2 = mach_absolute_time();
                timingInfo.addTime(this->getShortName(), t2-t1);
            }
        }
        catch (const char* msg) {
            // this image is not initialized
            fState = oldState;
            recursiveSpinUnLock();
            throw;
        }
    }
    
    recursiveSpinUnLock();
}

然后在 recursiveInitialization的实现中发现关键代码 notifySingle

context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);

4.10 notifySingle分析

static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
{
    // 省略部分代码......
    
    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);
        }
    }
    
    // 省略部分代码......
}

我们在这段代码里面找到一个关键的函数指针 *sNotifyObjCInit,我们看看这个指针是用来干嘛用的,在当前文件下,搜索找到 sNotifyObjCInit赋值的地方。

4.11 registerObjCNotifiers实现

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;

    // 省略部分代码......
}

我们继续全局搜索看看 registerObjCNotifiers这个方法会被谁调用,找到调用的地方 _dyld_objc_notify_register函数

4.12 _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::registerObjCNotifiers(mapped, init, unmapped);
}

继续搜索,发现找不到 _dyld_objc_notify_register方法的调用者,那么问题来了,_dyld_objc_notify_register在啥时候调用了呢?

接下来我们在我们新创建的工程里面,打个符号断点

 运行

4.13 libdispatch_init分析

我们看到在调用函数栈在调用 _objc_init之前,还调用了 libdispatch_init和 _os_object_init

void
libdispatch_init(void)
{
    // 省略部分代码......
    _dispatch_hw_config_init();
    _dispatch_time_init();
    _dispatch_vtable_init();
    _os_object_init();
    _voucher_init();
    _dispatch_introspection_init();
}

我们在上面代码中找到了我们关键要查看的代码 _os_object_init(),我们跟踪进去看看。

4.14 _os_object_init分析

void
_os_object_init(void)
{
    _objc_init();
    Block_callbacks_RR callbacks = {
        sizeof(Block_callbacks_RR),
        (void (*)(const void *))&objc_retain,
        (void (*)(const void *))&objc_release,
        (void (*)(const void *))&_os_objc_destructInstance
    };
    _Block_use_RR2(&callbacks);
#if DISPATCH_COCOA_COMPAT
    const char *v = getenv("OBJC_DEBUG_MISSING_POOLS");
    if (v) _os_object_debug_missing_pools = _dispatch_parse_bool(v);
    v = getenv("DISPATCH_DEBUG_MISSING_POOLS");
    if (v) _os_object_debug_missing_pools = _dispatch_parse_bool(v);
    v = getenv("LIBDISPATCH_DEBUG_MISSING_POOLS");
    if (v) _os_object_debug_missing_pools = _dispatch_parse_bool(v);
#endif
}

我们看到里面调用了 _objc_init(),这就证明了从 _os_object_init跳转到 _objc_init,然后进行 Runtime的初始化操作,我们继续下面 _objc_init的分析。

4.15 _objc_init分析

打开 Objc源码,搜索 _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();
    cache_init();
    _imp_implementationWithBlock_init();
    
    // 注册回调函数
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

然后我们在函数内部找到了 _dyld_objc_notify_register(),我们看一下这个函数的注释部分

/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time

* 引导程序初始化。用 dyld 注册我们的 image 通知程序。
* 在库初始化之前由 libSystem 调用
**********************************************************************/

注释的意思就是说这个函数 _objc_init的调用时机是在其他动态库加载之前由 libSystem系统库先调用的。

那么到这里就很明确了,其实在 dyld::_main主程序的第 8步,初始化所有动态库及主程序的时候之前,就先注册了 load_images的回调,之后在 Runtime调用 load_images加载完所有 load方法之后,就会回调到 dyld::_main的 initializeMainExecutable()内部执行回调。

4.16 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中,会调用 c++的构造方法。

4.17 主程序 main入口

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

5、dyld加载流程

_objc_init的完整调用流程

程序启动

1:_dyld_start

2:-> dyldbootstrap::start   rebaseDyld()dyld重定位、__guard_setup栈溢出保护、dyld::_main 

3:-> dyld::_main(配置应用环境->加载共享缓存->初始化主程序[将主程序Mach-O加载进内存]->加载动态库->链接主程序->链接动态库->弱符号绑定->执行初始化->调用main函数

 

4:-> dyld::initializeMainExecutable(为主可执行文件创建映像-主程序,调用所有imageInitalizer方法进行初始,再为主程序可执行文件执行初始化操作。具体流程为:ImageLoader::runInitializers-->ImageLoader::processInitializers--> ImageLoader::recursiveInitialization)

5:-> ImageLoader::runInitializers      (Initializer初始化)

6:-> ImageLoader::processInitializers

7:-> ImageLoader::recursiveInitialization(递归加载所有依赖库进内存。告诉所有人完成镜像的初始化。recursive:递归,循环)

 

8:->_dyld_objc_notify_registe(load_images->call_load_methods->call_class_loads->明确了load方法的调用。)

9:->registerObjCNotifiers

10:->notifySingle

 

11:-> doInitialization(doModInitFunctions,在 doModInitFunctions之后 会 先执行 libSystem_initializer,保证系统库优先初始化完毕,在这里初始化 libdispatch_init,进而在_os_object_init中 调用_objc_init

12:->libdispatch_init(GCD)

13:-> _os_object_init

14:-> _objc_init( static_init(); // C++   runtime_init(); // runtime 初始化   exception_init(); // 异常初始化  cache_init(); // 缓存初始化

6、总结

注意

 

引用

 1:iOS-底层原理 15:dyld加载流程

2:iOS 应用程序加载

3:OC底层-应用程序加载初探

4:启动流程分析

5:dyld 加载流程分析

6:底层原理十五:dyld 应用程序加载

7:iOS dyld加载流程

8:OC底层原理十五:dyld 应用程序加载

posted on 2020-12-01 21:13  风zk  阅读(433)  评论(0编辑  收藏  举报

导航