OC底层原理(十一):应用程序的dyld4流程上

3,942 阅读21分钟

本篇文章将探究app应用程序是如何加载的? 我们平时都认为main是程序的入口,但是实际上是这样子的吗?不是的话,应用在冷启动main之前做了什么呢,我们去探索下流程。

一、启动流程初步探索

新建iOS工程,在ViewController.m中添加一个load方法并打断点,程序main.m中添加一个C++函数kcFunc

  • ViewControler代码:
@interface ViewController ()
@end

@implementation ViewController
+ (void)load{
    NSLog(@"%s", __func__);
}
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    NSLog(@"viewDidLoad --- ");
}
@end
  • main.m代码:
// 在`main`函数之前,执行一些我们的设置,比如状态打印
__attribute__((constructor)) void kcFunc(){
    printf("C... %s \n",__func__);
}
int main(int argc, char * argv[]) {
    @autoreleasepool {
        NSLog(@"main ...");
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}
  • llvm打印:
2023-02-02 14:21:51.278198+0800 DyldTest[17170:4322854] +[ViewController load]
C... kcFunc
2023-02-02 14:21:51.280613+0800 DyldTest[17170:4322854] main ...
2023-02-02 14:21:52.835821+0800 DyldTest[17170:4322854] viewDidLoad ---
  • 结论:
  1. __attribute__((constructor))是在main函数之前执行一个函数,便于我们做一些准备工作。可以查阅了GNU的文档印证了我的想法。

  2. 运行程序,根据终端打印结果,可发现程序首先运行了load方法,然后调用了C++函数,最后才进入到main方法中。

在分析启动流程之前,先学习一些概念。

二、应用程序编译过程

静态库

在链接阶段,会将汇编生成的目标程序引用的库一起链接打包到可执行文件当中。此时的静态库就会再改了,因为它是编译时被直接拷贝一份,复制到目标程序里的。例如:.a.lib

  • 优点:编译完成后,库文件实际上就没有作用了,目标程序没有外部依赖,直接就可以运行
  • 缺点:由于静态库可能会有两份,所以会导致目标程序的体积增大,对内存、性能、速度消耗很大

动态库

程序编译时并不会链接到目标程序中,目标程序只会存储指向动态库的引用,在程序运行时才被载入。例如:.so.framwork.dll

  • 优点:减少打包之后app的大小,共享内存,节约资源,更新动态库,达到更新程序
  • 缺点:动态载入会带来一部分性能损失,使用动态库也会使得程序依赖于外部环境,如果环境缺少了动态库,或者库的版本不正确,就会导致程序无法运行。

截屏2023-02-02 下午3.08.29.png

编译过程

程序的编译过程:预编译 -> 编译 -> 汇编 -> 链接 -> 可执行文件 截屏2023-02-02 下午3.36.06.png

  • 源文件:载入.h、.m、.cpp等文件
  • 预处理:替换宏,删除注释,展开头文件,产生.i文件
  • 编译:经过词法分析、语法分析、语义分析后,将.i文件转换为汇编语言,产生.s文件
  • 汇编:将汇编文件转换为机器码文件,产生.o文件
  • 链接:对.o文件中引用其他库的地方进行引用,生成Mach-O格式的可执行文件 经过上述步骤,我们开发的代码会生成一个Mach-O格式的可执行文件。详情可阅读这里

动态链接器dyld

dyld(the dynamic link editor)是苹果的动态链接器,是苹果操作系统的重要组成部分,在app被编译打包成可执行文件格式的Mach-O文件后,交由dyld负责连接,并加载程序。

  • dyld的作用:加载各个库,也就是image镜像文件,由dyld从内存中读到表中,加载主程序,link链接各个动静态库,进行主程序初始化。

截屏2023-02-02 下午4.17.22.png

Mach-O

Mach-O: Mach Object文件格式的缩写,它是一种用于可执行文件,目标文件.o,动态库,内核转储的文件格式。作为a.out格式的替代,Mach-O提供了更强的扩展性,并提升了符号表中信息的访问速度。

Mach-O格式的文件:

  • Executable(产物为ipa包)
  • Dynamic Library(产物为动态库)
  • Bundle(产物为bundle文件)
  • Static Library(产物为静态库)
  • Relocatable Object File(重定向文件)

image.png

  • 打开ipa内容文件:

截屏2023-02-07 下午1.06.22.png

  • 通过MachOView可查看可执行文件信息:

截屏2023-02-07 下午1.15.42.png

  • Mach-O由四部分组成:Mach-O HeaderLoad CommandsSectionOther Data

典型的Mach-O文件包含三个区域:

  1. Header:保存Mach-O的一些基本信息,包括平台、文件类型、指令数、指令总大小,dyld标记Flags等等。

  2. Load Commands:紧跟Header,加载Mach-O文件时会使用这部分数据确定内存分布,对系统内核加载器和动态连接器起指导作用。

  3. Data:每个segment的具体数据保存在这里,包含具体的代码、数据等等。详情可阅读这里,了解这个部分是iOS逆向工程的基础。

App启动的过程:

当我们点击iPhone上的应用图标后,整个iOS系统启动App经历了:

  • 系统调用 exec() 分配内存、开辟进程
  • app对应的可执行文件加载到内存
  • dyld动态链接器加载到内存:load dyld
  • dyld动态链接器进行动态链接:rebase->bing->Objc->initalizers
  • 调起main()函数

启动时间的划分可以把main()函数作为关键点分割成两块

截屏2023-02-06 下午4.28.56.png

三、dyld入口探索

Viewcontrollerload方法添加断点,运行程序。在控制台输入指令bt,查看运行的函数调用堆栈信息。

  • 图:

截屏2023-01-30 上午12.26.21.png

  • llvm打印:
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x00000001047cde38 DyldTest`+[ViewController load](self=ViewController, _cmd="load") at ViewController.m:17:5
    frame #1: 0x00000001bed30a80 libobjc.A.dylib`load_images + 824
    frame #2: 0x00000001e40d83fc dyld`dyld4::RuntimeState::notifyObjCInit(dyld4::Loader const*) + 164
    frame #3: 0x00000001e40dbcc0 dyld`dyld4::Loader::runInitializersBottomUp(dyld4::RuntimeState&, dyld3::Array<dyld4::Loader const*>&) const + 204
    frame #4: 0x00000001e40e133c dyld`dyld4::Loader::runInitializersBottomUpPlusUpwardLinks(dyld4::RuntimeState&) const + 328
    frame #5: 0x00000001e4115244 dyld`dyld4::APIs::runAllInitializersForMain() + 360
    frame #6: 0x00000001e40ea66c dyld`dyld4::prepare(dyld4::APIs&, dyld3::MachOAnalyzer const*) + 3388
    frame #7: 0x00000001e40e88d4 dyld`start + 2388
(lldb)
  • 结论:
  1. 从左侧的堆栈信息可以找到了程序的入口是start,这个部分是iOS16后的启动情况。现在最新的iOS系统基本上都是dyld4流程,与网上大部分资料还是iOS15以前版本的dyld3流程是有所不同,但是这里不进行详细展开。

  2. 调用流程start -> dyld4::prepare -> dyld4::APIs::runAllInitializersForMain -> dyld4::Loader::runInitializersBottomUpPlusUpwardLinks -> dyld4::Loader::runInitializersBottomUp -> dyld4::RuntimeState::notifyObjCInit -> load_images -> [ViewController load]

四、探索dyld源码

截止目前(2023年2月)dyld已经更新至dyld4(1042.1)了,iOS15以上系统都是使用的dyld4。 下载dyld当前最新版本是dyld-1042.1 ,这部分源码是不能编译的,可以结合objc4源码分析。

  1. 打开dyld源码,虽然控制台是从start开始打印的,但是dyld4dyld3一样都是从__dyld_start。先在工程里全局搜索__dyld_start,开始探索程序加载流程。最终在dyldStartup.s文件中搜索到了伪汇编流程。
  • 见下图: 截屏2023-02-02 下午5.02.47.png

  • 结论:

    ①. 此汇编代码只是对齐堆栈并跳转到C++代码dyld:: start

  1. 针对不同的环境提供了不同的实现方式,比如,我们现在看到的就是针对arm64真机环境的实现。看注释发现,不论是何种环境,最终都会走到start流程中。这也和我们前面bt看到的函数调用堆栈继续!下面全局搜索start
  • 见下图: 截屏2023-02-02 下午5.20.37.png

start

查看dyld-1042.1工程里找到的start函数。获取Mach-O解析器,解析Mach-O文件,还有获取一个随机数(ASLR)。

namespace dyld4 {
...
...
// dyld的入口点。内核加载dyld并跳转到__dyld_start,它设置一些寄存器并调用这个函数。
// 注意:这个函数永远不会返回,它调用exit()。
// 因此,堆栈保护程序是无用的,因为永远不会执行epilog。
// 标记函数no-return禁用堆栈保护。
// 堆栈保护器也会导致armv7k代码生成的问题,因为它通过prolog中的GOT槽访问随机值,但dyld还没有rebased。
void start(const KernelArgs* kernArgs, void* prevDyldMH) __attribute__ ((noreturn)) __asm("start");
void start(const KernelArgs* kernArgs, void* prevDyldMH)
{
    // 发出kdebug跟踪点来指示dyld引导程序已经启动 <rdar://46878536>
    // 注意:这是在dyld rebased之前调用的,所以kdebug_trace_dyld_marker()不能使用任何全局变量
    dyld3::kdebug_trace_dyld_marker(DBG_DYLD_TIMING_BOOTSTRAP_START, 0, 0, 0, 0);

    // 走所有的fixups chains 和 rebase dyld
    // 注意:withChainStarts()和fixupAllChainedFixups()不能使用任何静态数据指针,因为它们还没有rebased
    const MachOAnalyzer* dyldMA = getDyldMH(); //获取Mach-O解析器,用于解析Mach-O格式文件内容
    uintptr_t            slide  = dyldMA->getSlide();// 会生成一个随机数(ASLR)
    if ( !dyldMA->inDyldCache() ) {
        assert(dyldMA->hasChainedFixups());
        __block Diagnostics diag;
        dyldMA->withChainStarts(diag, 0, ^(const dyld_chained_starts_in_image* starts) {
            dyldMA->fixupAllChainedFixups(diag, starts, slide, dyld3::Array<const void*>(), nullptr);
        });
        diag.assertNoError();

        // make __DATA_CONST read-only (kernel maps it r/w)
        dyldMA->forEachSegment(^(const MachOAnalyzer::SegmentInfo& segInfo, bool& stop) {
            if ( segInfo.readOnlyData ) {
                const uint8_t* start = (uint8_t*)(segInfo.vmAddr + slide);
                size_t         size  = (size_t)segInfo.vmSize;
                sSyscallDelegate.mprotect((void*)start, size, PROT_READ);
            }
        });
    }
    
    // 现在,我们可以调用使用DATA的函数
    mach_init();// mach消息初始化
    
    // 为stack canary设置随机值
    __guard_setup(kernArgs->findApple()); //栈溢出保护

    // 设置以便open_with_subsystem()工作
    _subsystem_init(kernArgs->findApple());

    // 在__DATA_CONST中将ProcessConfig对象变为只读之前,使用placement new来构造它
    handleDyldInCache(dyldMA, kernArgs, (MachOFile*)prevDyldMH);

    bool useHWTPro = false;
    // 在自己的分配池中创建一个分配器Allocator
    Allocator& allocator = Allocator::persistentAllocator(useHWTPro);

    // 使用placement new在Allocator池中构造ProcessConfig对象
    // 构建进程配置
    ProcessConfig& config = *new (allocator.aligned_alloc(alignof(ProcessConfig), sizeof(ProcessConfig))) ProcessConfig(kernArgs, sSyscallDelegate, allocator);//构建进程配置
    
#if !SUPPPORT_PRE_LC_MAIN
    // 堆栈分配RuntimeLocks。它们不能在通常为只读的Allocator池中
    RuntimeLocks  sLocks;
#endif

    // 在分配器中创建API(也称为RuntimeState)对象
    APIs& state = *new (allocator.aligned_alloc(alignof(APIs), sizeof(APIs))) APIs(config, allocator, sLocks);

#if !TARGET_OS_SIMULATOR
    // FIXME:我们应该更早地移动它,但目前我们需要在设置压缩信息之前将运行时状态初始化,
    // 直到我们这样做之前,压缩信息可能会错过某些早期的dyld崩溃
    auto processSnapshot = state.getCurrentProcessSnapshot();
    processSnapshot->setPlatform((uint64_t)state.config.process.platform);
    processSnapshot->setDyldState(dyld_process_state_dyld_initialized);
    FileRecord cacheFileRecord = state.fileManager.fileRecordForVolumeDevIDAndObjID(gProcessInfo->sharedCacheFSID, gProcessInfo->sharedCacheFSObjID);
    if (cacheFileRecord.exists()) {
        auto sharedCache = SharedCache(state.ephemeralAllocator, std::move(cacheFileRecord), processSnapshot->identityMapper(), (uint64_t)config.dyldCache.addr, gProcessInfo->processDetachedFromSharedRegion);
        processSnapshot->addSharedCache(std::move(sharedCache));
    }
    
    // 将dyld添加到压缩信息
    if ( dyldMA->inDyldCache() && processSnapshot->sharedCache() ) { ··· } else { ··· }
    
    // 将主可执行文件添加到压缩信息
    FileRecord mainExecutableFile;
    if (state.config.process.mainExecutableFSID && state.config.process.mainExecutableObjID) { ··· } else { ··· }
    auto mainExecutableImage = Image(state.ephemeralAllocator, std::move(mainExecutableFile), processSnapshot->identityMapper(), (const mach_header*)state.config.process.mainExecutable);
    processSnapshot->addImage(std::move(mainExecutableImage));
    processSnapshot->setInitialImageCount(state.initialImageCount());
    state.commitProcessSnapshot();
#endif

    // -------------重点入口-------------------
    // 加载所有的库与程序
    MainFunc appMain = prepare(state, dyldMA);

    // 现在将所有dyld分配的数据结构设置为只读
    state.decWritable();

    // 调用main(),如果它返回,调用exit()并返回结果
    // 注意:这是经过组织的,以便在程序的主线程中回溯时只在“main”下面显示“start”。
    int result = appMain(state.config.process.argc, state.config.process.argv, state.config.process.envp, state.config.process.apple);

    // 如果我们到达这里,main()返回(与程序调用exit()相反)
#if TARGET_OS_OSX
    // <rdar://74518676>libSystemHelpers没有为模拟器设置,因此直接调用_exit()
    if ( MachOFile::isSimulatorPlatform(state.config.process.platform) )
        _exit(result);
#endif
    state.libSystemHelpers->exit(result);
}
...
...
}// namespace

  • 结论:

    ①.dyld4dyld3不同就是,main没有了返回值,是通过exit结束!无法通过epilog退出代码,它释放堆栈空间并返回到调用者。

    ②. start中读取mach-o文件中的符号地址都是虚拟地址,在程序启动的时候,系统会生成一个随机数(ASLR),使用虚拟地址加上ASLR才是物理地址,也就是程序真正调用的地址。我们把从虚拟地址换算成物理地址的过程称之为rebase


prepare

//
// 加载任何相关的dylib并将它们绑定在一起。
// 返回main()在target中的地址。
//
__attribute__ ((noinline)) static MainFunc prepare(APIs& state, const MachOAnalyzer* dyldMH)
{
    // `gProcessInfo` 是存储dyld所有镜像信息的结构体:,保存着mach_header, dyld_uuid_info, dyldVersion等等信息。
    // 配置dyld信息中停止标识符
    gProcessInfo->terminationFlags = 0; // 默认情况下,在崩溃日志中显示回溯
    // 配置dyld信息中平台信息
    gProcessInfo->platform         = (uint32_t)state.config.process.platform;
    // 配置dyld信息中路径信息
    gProcessInfo->dyldPath         = state.config.process.dyldPath;
    
    uint64_t launchTraceID = 0;
    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)state.config.process.mainExecutable, 0, 0);
    }
    
  #if TARGET_OS_OSX //模拟环境,涉及Simulator和Sim(略过)
     const bool isSimulatorProgram = MachOFile::isSimulatorPlatform(state.config.process.platform);
    if ( const char* simPrefixPath = state.config.pathOverrides.simRootPath() ) {
#if __arm64e__
        if ( strcmp(state.config.process.mainExecutable->archName(), "arm64e") == 0 )
            halt("arm64e not supported for simulator programs");
#endif
        if ( isSimulatorProgram ) {
            char simDyldPath[PATH_MAX];
            strlcpy(simDyldPath, simPrefixPath, PATH_MAX);
            strlcat(simDyldPath, "/usr/lib/dyld_sim", PATH_MAX);
            return prepareSim(state, simDyldPath);
        }
        halt("DYLD_ROOT_PATH only allowed with simulator programs");
    }
    else if ( isSimulatorProgram ) {
        halt("DYLD_ROOT_PATH not set for simulator program");
    }
#endif

#if 0
    // 检查主可执行文件是否有效
    Diagnostics diag;
    bool validMainExec = state.config.process.mainExecutable->isValidMainExecutable(diag, state.config.process.mainExecutablePath, -1, *(state.config.process.archs), state.config.process.platform);
    if ( !validMainExec && state.config.process.mainExecutable->enforceFormat(dyld3::MachOAnalyzer::Malformed::sdkOnOrAfter2021)) {
        state.log("%s in %s", diag.errorMessage(), state.config.process.mainExecutablePath);
        halt(diag.errorMessage());
    }
#endif

// 如果询问,log打印环境env变量
    if ( state.config.log.env ) {
        for (const char* const* p=state.config.process.envp; *p != nullptr; ++p) {
            state.log("%s\n", *p);
        }
    }

    // 检查预构建pre-built Loader
    state.initializeClosureMode();
    const PrebuiltLoaderSet* mainSet    = state.processPrebuiltLoaderSet();
    Loader*                  mainLoader = nullptr;
    if ( mainSet != nullptr ) {
        mainLoader = (Loader*)mainSet->atIndex(0);
        state.loaded.reserve(state.initialImageCount()); // 帮助避免Vector的重新定位
    }
    if ( mainLoader == nullptr ) {
        // 如果没有预构建的Loader,制造一个just-in-time
        // just-in-time是dyld4的新特性:`pre-build + just-in-time`预构建+实时解析的双解析模式
        state.loaded.reserve(512);  // 猜测Vector大小的起点
        Diagnostics buildDiag;
        mainLoader = JustInTimeLoader::makeLaunchLoader(buildDiag, state, state.config.process.mainExecutable,
                                                        state.config.process.mainExecutablePath, nullptr);
        if ( buildDiag.hasError() ) {
            state.log("%s in %s\n", buildDiag.errorMessage(), state.config.process.mainExecutablePath);
            halt(buildDiag.errorMessage());
        }
    }

    state.setMainLoader(mainLoader);
    // 首先将主可执行文件添加到mainLoader预构建的镜像列表中
    state.notifyDebuggerLoad(mainLoader);
    const bool needToWritePrebuiltLoaderSet = !mainLoader->isPrebuilt && (state.saveAppClosureFile() || state.failIfCouldBuildAppClosureFile());

    // <rdar://problem/10583252> Add dyld to uuidArray to enable symbolication of stackshots (unless dyld is in the cache)
    // 装载uuid相关信息
    if ( !dyldMH->inDyldCache() ) {
        dyld_uuid_info dyldInfo;
        dyldInfo.imageLoadAddress = dyldMH;
        dyldMH->getUuid(dyldInfo.imageUUID);
        addNonSharedCacheImageUUID(state.persistentAllocator, dyldInfo);
    }

    // 加载所需要插入的动态库(dylibs)
    STACK_ALLOC_OVERFLOW_SAFE_ARRAY(Loader*, topLevelLoaders, 16);
    topLevelLoaders.push_back(mainLoader);
    Loader::LoadChain   loadChainMain { nullptr, mainLoader };
    Loader::LoadOptions options;
    options.staticLinkage   = true;
    options.launching       = true;
    options.insertedDylib   = true;
    options.canBeDylib      = true;
    options.rpathStack      = &loadChainMain;
    state.config.pathOverrides.forEachInsertedDylib(^(const char* dylibPath, bool& stop) {
        Diagnostics insertDiag;
        if ( Loader* insertedDylib = (Loader*)Loader::getLoader(insertDiag, state, dylibPath, options) ) {
            topLevelLoaders.push_back(insertedDylib);
            state.notifyDebuggerLoad(insertedDylib);
            if ( insertedDylib->isPrebuilt )
                state.loaded.push_back(insertedDylib);
        }
        else if ( insertDiag.hasError() && !state.config.security.allowInsertFailures  ) {
            state.log("terminating because inserted dylib '%s' could not be loaded: %s\n", dylibPath, insertDiag.errorMessageCStr());
            halt(insertDiag.errorMessage());
        }
    });

    // 将插入的库移到处于状态state.loaded中的主可执行文件之前,以获得正确的平面命名空间查找
    if ( topLevelLoaders.count() != 1 ) {
        state.loaded.erase(state.loaded.begin());
        state.loaded.push_back(mainLoader);
    }

    // 用于录制一定丢失的文件
    __block MissingPaths missingPaths;
    auto missingLogger = ^(const char* mustBeMissingPath) {
        missingPaths.addPath(mustBeMissingPath);
    };
    
    // 递归加载 主可执行文件 和 插入的dylib所需的所有内容
    // 记录插入信息, 遍历所有dylibs, 一些记录检查操作继续往下走
    Diagnostics depsDiag;
    options.insertedDylib = false;
    if ( needToWritePrebuiltLoaderSet )
        options.pathNotFoundHandler = missingLogger;
    for ( Loader* ldr : topLevelLoaders ) {
        ldr->loadDependents(depsDiag, state, options);
        if ( depsDiag.hasError() ) {
            //state.log("%s loading dependents of %s\n", depsDiag.errorMessage(), ldr->path());
            // 让debugger/crashreporter了解我们能够加载的dylib
            uintptr_t topCount = topLevelLoaders.count();
            STACK_ALLOC_VECTOR(const Loader*, newLoaders, state.loaded.size() - topCount);
            for (size_t i = topCount; i != state.loaded.size(); ++i)
                newLoaders.push_back(state.loaded[i]);
            state.notifyDebuggerLoad(newLoaders);
            gProcessInfo->terminationFlags = 1; // 不要显示痕迹,因为没有什么有趣的
            halt(depsDiag.errorMessage());
        }
    }

    uintptr_t topCount = topLevelLoaders.count();
    {
        STACK_ALLOC_VECTOR(const Loader*, newLoaders, state.loaded.size());
        for (const Loader* ldr : state.loaded)
            newLoaders.push_back(ldr);

        // 在主可执行文件之后通知调试器debuger所有加载的映像
        std::span<const Loader*> unnotifiedNewLoaders(&newLoaders[topCount], newLoaders.size() - topCount);
        state.notifyDebuggerLoad(unnotifiedNewLoaders);

        // 通知kernel内核任何dtrace静态用户探测
        state.notifyDtrace(newLoaders);
    }
    
    // <rdar://problem/7739489> 记录images的初始计数
    // 因此CrashReporter可以记录哪些images是动态加载的
    gProcessInfo->initialImageCount = state.loaded.size();

    // 添加到永久范围
    STACK_ALLOC_ARRAY(const Loader*, nonCacheNeverUnloadLoaders, state.loaded.size());
    for (const Loader* ldr : state.loaded) {
        if ( !ldr->dylibInDyldCache )
            nonCacheNeverUnloadLoaders.push_back(ldr);
    }
    state.addPermanentRanges(nonCacheNeverUnloadLoaders);
  
    // proactive weakDefMap means we build the weakDefMap before doing any binding
    // 主动weakDefMap意味着我们在进行任何绑定之前构建weakDefMap
    if ( state.config.process.proactivelyUseWeakDefMap ) {
        state.weakDefMap = new (state.persistentAllocator.malloc(sizeof(WeakDefMap))) WeakDefMap();
        STACK_ALLOC_VECTOR(const Loader*, allLoaders, state.loaded.size());
        for (const Loader* ldr : state.loaded)
            allLoaders.push_back(ldr);
        Loader::addWeakDefsToMap(state, allLoaders);
    }

    // 在执行修复之前检查是否插入元组
    state.buildInterposingTables();

    // do fixups做修补工作,这里也是去处理了`rebase`
    {
        dyld3::ScopedTimer timer(DBG_DYLD_TIMING_APPLY_FIXUPS, 0, 0, 0);
        // 以防万一我们需要修补这个案子
        DyldCacheDataConstLazyScopedWriter  cacheDataConst(state);

        // C++规范表示,主要可执行文件可以定义非弱函数,这些函数覆盖dylib中的weak-defs
        // 对于在启动时绑定的任何对象,这都会自动发生,但dyld缓存是预绑定pre-bound的,因此我们需要修补主可执行文件中被该非弱对象覆盖的任何绑定。
        // 注意,在macOS上,我们还允许dylib具有weak-defs的non-weak覆盖
        if ( !mainLoader->isPrebuilt )
            JustInTimeLoader::handleStrongWeakDefOverrides(state, cacheDataConst);
        for ( const Loader* ldr : state.loaded ) {
            Diagnostics fixupDiag;
            ldr->applyFixups(fixupDiag, state, cacheDataConst, true);
            if ( fixupDiag.hasError() ) {
                halt(fixupDiag.errorMessage());
            }

            // 根目录需要修补缓存中未找到的GOT
            ldr->applyCachePatches(state, cacheDataConst);
        }

        // 如果我们有单例修补
        state.doSingletonPatching();
    }

    // 如果存在插入,则将插入元组应用于dyld缓存
    if ( !state.interposingTuplesAll.empty() ) {
        Loader::applyInterposingToDyldCache(state);
    }

    // 如果mainLoader是pre-build,则可能会覆盖dyld缓存中的weak-defs
    if ( mainLoader->isPrebuilt ) {
        DyldCacheDataConstLazyScopedWriter  dataConstWriter(state);
        DyldCacheDataConstLazyScopedWriter* dataConstWriterPtr = &dataConstWriter; // 在cacheWeakDefFixup中进行变通以使其可访问
        state.processPrebuiltLoaderSet()->forEachCachePatch(^(const PrebuiltLoaderSet::CachePatch& patch) {
            uintptr_t newImpl = (uintptr_t)patch.patchTo.value(state);
            state.config.dyldCache.addr->forEachPatchableUseOfExport(patch.cacheDylibIndex, patch.cacheDylibVMOffset,
                                                                     ^(uint64_t cacheVMOffset,
                                                                       dyld3::MachOLoaded::PointerMetaData pmd, uint64_t addend) {
                uintptr_t* loc      = (uintptr_t*)(((uint8_t*)state.config.dyldCache.addr) + cacheVMOffset);
                uintptr_t  newValue = newImpl + (uintptr_t)addend;
#if __has_feature(ptrauth_calls)
                if ( pmd.authenticated )
                    newValue = MachOLoaded::ChainedFixupPointerOnDisk::Arm64e::signPointer(newValue, loc, pmd.usesAddrDiversity, pmd.diversity, pmd.key);
#endif
                // 忽略重复的修补程序条目
                if ( *loc != newValue ) {
                    dataConstWriterPtr->makeWriteable();
                    if ( state.config.log.fixups )
                        state.log("cache patch: %p = 0x%0lX\n", loc, newValue);
                    *loc = newValue;
                }
            });
        });
    }

    // call kdebug trace for each image
    if ( kdebug_is_enabled(KDBG_CODE(DBG_DYLD, DBG_DYLD_UUID, DBG_DYLD_UUID_MAP_A)) ) {
        // dyld in the cache event was sent earlier when we unmapped the on-disk dyld
        // 缓存事件中的dyld是在我们取消映射磁盘上的dyld时发送的
        if ( !dyldMH->inDyldCache() ) {
            // 为dyld本身添加跟踪
            uuid_t dyldUuid;
            dyldMH->getUuid(dyldUuid);
            struct stat        stat_buf;
            fsid_t             dyldFsid    = { { 0, 0 } };
            fsobj_id_t         dyldFfsobjid = { 0, 0 };
            if ( dyld3::stat(state.config.process.dyldPath, &stat_buf) == 0 ) {
                dyldFfsobjid  = *(fsobj_id_t*)&stat_buf.st_ino;
                dyldFsid      = { { stat_buf.st_dev, 0 } };
            }
            kdebug_trace_dyld_image(DBG_DYLD_UUID_MAP_A, state.config.process.dyldPath, &dyldUuid, dyldFfsobjid, dyldFsid, dyldMH);
        }

        // 为每个image的加载过程添加跟踪
        for ( const Loader* ldr :  state.loaded ) {
            const MachOLoaded* ml = ldr->loadAddress(state);
            fsid_t             fsid    = { { 0, 0 } };
            fsobj_id_t         fsobjid = { 0, 0 };
            struct stat        stat_buf;
            if ( !ldr->dylibInDyldCache && (dyld3::stat(ldr->path(), &stat_buf) == 0) ) { //FIXME Loader knows inode
                fsobjid = *(fsobj_id_t*)&stat_buf.st_ino;
                fsid    = { { stat_buf.st_dev, 0 } };
            }
            uuid_t uuid;
            ml->getUuid(uuid);
            kdebug_trace_dyld_image(DBG_DYLD_UUID_MAP_A, ldr->path(), &uuid, fsobjid, fsid, ml);
        }
    }

    // 通知任何其他检查此进程的进程
    // 通知跟踪此进程中负载的任何进程
    STACK_ALLOC_ARRAY(const char*, pathsBuffer, state.loaded.size());
    STACK_ALLOC_ARRAY(const mach_header*, mhBuffer, state.loaded.size());
    for ( const Loader* ldr :  state.loaded ) {
        pathsBuffer.push_back(ldr->path());
        mhBuffer.push_back(ldr->loadAddress(state));
    }
    notifyMonitoringDyld(false, (unsigned int)state.loaded.size(), &mhBuffer[0], &pathsBuffer[0]);
    
    // 将libdyld.dylib连接到dyld
    LibdyldDyld4Section* libdyld4Section = nullptr;
    if ( state.libdyldLoader != nullptr ) {
        const MachOLoaded* libdyldML = state.libdyldLoader->loadAddress(state);
        uint64_t           sectSize;
        libdyld4Section = (LibdyldDyld4Section*)libdyldML->findSectionContent("__DATA", "__dyld4", sectSize, true);

#if __has_feature(ptrauth_calls)
        if ( libdyld4Section == nullptr )
            libdyld4Section = (LibdyldDyld4Section*)libdyldML->findSectionContent("__AUTH", "__dyld4", sectSize, true);
#endif
        if ( libdyld4Section != nullptr ) {
            // 设置指向全局API对象的指针
            libdyld4Section->apis = &state;

            // 将指针设置为dyld_all_image_infosos
            libdyld4Section->allImageInfos = gProcessInfo;

            // 程序变量(例如environ)通常在libdyld.dylib中定义(但可能在旧macOS二进制文件的主可替换文件中定义)
            // 记住progams变量的位置,以便libc可以同步它们
            state.vars                 = &libdyld4Section->defaultVars;
            state.vars->mh             = state.config.process.mainExecutable;
            *state.vars->NXArgcPtr     = state.config.process.argc;
            *state.vars->NXArgvPtr     = (const char**)state.config.process.argv;
            *state.vars->environPtr    = (const char**)state.config.process.envp;
            *state.vars->__prognamePtr = state.config.process.progname;
        }
        else {
            halt("compatible libdyld.dylib not found");
        }
    }
    else {
        halt("libdyld.dylib not found");
    }
    if ( state.libSystemLoader == nullptr)
        halt("program does not link with libSystem.B.dylib");

#if !TARGET_OS_SIMULATOR
    // 如果使用JustInTimeLoader启动,可能需要将其序列化
    if ( needToWritePrebuiltLoaderSet ) {
        dyld3::ScopedTimer timer(DBG_DYLD_TIMING_BUILD_CLOSURE, 0, 0, 0);
        if ( state.config.log.loaders )
            state.log("building PrebuiltLoaderSet for main executable\n");
            
        Diagnostics              prebuiltDiag;
        const PrebuiltLoaderSet* prebuiltAppSet = PrebuiltLoaderSet::makeLaunchSet(prebuiltDiag, state, missingPaths);
        if ( (prebuiltAppSet != nullptr) && prebuiltDiag.noError() ) {
            if ( state.failIfCouldBuildAppClosureFile() )
                halt("dyld: PrebuiltLoaderSet expected but not found");
            // 将PrebuiltLoaderSet保存到磁盘以便下次启动时使用,继续使用JustInTimeLoaders运行
            if ( state.saveAppPrebuiltLoaderSet(prebuiltAppSet) )
                state.setSavedPrebuiltLoaderSet();
            prebuiltAppSet->deallocate();
            timer.setData4(dyld3::DyldTimingBuildClosure::LaunchClosure_Built);
        }
        else if ( state.config.log.loaders ) {
            state.log("could not build PrebuiltLoaderSet: %s\n", prebuiltDiag.errorMessage());
        }
    }

    // 如果应用程序启动pre-warm,请提前退出
    if ( state.config.process.environ("DYLD_JUST_BUILD_CLOSURE") != nullptr ) {
        return &fake_main;
    }
#endif

#if SUPPPORT_PRE_LC_MAIN
    uint32_t                progVarsOffset;
    dyld3::DyldLookFunc*    dyldLookupFuncAddr = nullptr;
    bool                    crtRunsInitializers = false;
    if ( state.config.process.mainExecutable->hasProgramVars(progVarsOffset, crtRunsInitializers, dyldLookupFuncAddr) ) {
        // 这是一个旧的macOS应用程序,它有自己的NXArgv等全局变量。我们需要使用它们。
        ProgramVars* varsInApp    = (ProgramVars*)(((uint8_t*)state.config.process.mainExecutable) + progVarsOffset);

        varsInApp->mh             = state.config.process.mainExecutable;
        *varsInApp->NXArgcPtr     = state.config.process.argc;
        *varsInApp->NXArgvPtr     = (const char**)state.config.process.argv;
        *varsInApp->environPtr    = (const char**)state.config.process.envp;
        *varsInApp->__prognamePtr = state.config.process.progname;
        state.vars                = varsInApp;
    }

    if ( dyldLookupFuncAddr ) {
        if ( libdyld4Section != nullptr ) {
            *dyldLookupFuncAddr = (dyld3::DyldLookFunc)libdyld4Section->dyldLookupFuncAddr;
        } else {
            halt("compatible libdyld.dylib not found");
        }
    }

    if ( !crtRunsInitializers )
        state.runAllInitializersForMain();
#else
    // 运行所有的初始化
    dyld4::notifyMonitoringDyldBeforeInitializers();
    state.runAllInitializersForMain();
    ...
    ...
    return result;
}
  • 结论:

    ①. 返回main函数地址,所有配置完后 _dyld_register_driverkit_main就是我们自定义工程的main地址。

    ②. gProcessInfo是存储dyld所有images镜像信息的结构体,保存着mach_headerdyld_uuid_infodyldVersion等等信息。

    ③. 创建just-in-time,是dyld4一个新特性。dyld4在保留了dyld3mach-o 解析器基础上,同时也引入了 just-in-time 的加载器来优化。

    • dyld3 出于对启动速度的优化的目的, 增加了预构建(闭包)App第一次启动或者App发生变化时会将部分启动数据创建为闭包存到本地,那么App下次启动将不再重新解析数据,而是直接读取闭包内容。当然前提是应用程序和系统应很少发生变化,但如果这两者经常变化等, 就会导闭包丢失或失效。

    • dyld4 采用了 pre-build + just-in-time双解析模式预构建pre-build 对应的就是 dyld3中的闭包,just-in-time可以理解为实时解析。当然just-in-time 也是可以利用 pre-build 的缓存的,所以性能可控。有了just-in-time, 目前应用首次启动、系统版本更新、普通启动,dyld4 则可以根据缓存是否有效去选择合适的模式进行解析。


runAllInitializersForMain

// 这是dyldMain.cpp中提取出来的一部分,以支持使用crt1.o的旧macOS应用程序
void APIs::runAllInitializersForMain()
{
 
    // 禁用page-in链接,不用于dlopen()加载images
    if ( !config.security.internalInstall || (config.process.pageInLinkingMode != 3) )
        config.syscall.disablePageInLinking();
        
    // 首先允许libSystem的初始化器
    const_cast<Loader*>(this->libSystemLoader)->beginInitializers(*this);
    this->libSystemLoader->runInitializers(*this);
    gProcessInfo->libSystemInitialized = true;

    // 运行libSystem的初始化程序后,告诉objc在libSystem子dylib上运行任何+load方法
    this->notifyObjCInit(this->libSystemLoader);
    // <rdar://problem/32209809> 调用所有已经images的'init' 函数 (below libSystem)
    // 使用下标索引进行迭代,以便在+load dloopen时数组不会在我们下面增长
    for ( uint32_t i = 0; i != this->loaded.size(); ++i ) {
        const Loader* ldr = this->loaded[i];
        if ( (ldr->dylibInDyldCache || ldr->analyzer(*this)->isDylib()) && (strncmp(ldr->analyzer(*this)->installName(), "/usr/lib/system/lib", 19) == 0) ) {
            // 检查安装名称而不是路径,以处理libsystem子dylibs的DYLD_LIBRARY_PATH覆盖
            const_cast<Loader*>(ldr)->beginInitializers(*this);
            this->notifyObjCInit(ldr);
            const_cast<Loader*>(ldr)->runInitializers(*this);
        }
    }
  
#if TARGET_OS_OSX
    // If we're PID 1, scan for roots
    if ( (this->config.process.pid == 1) && (this->libSystemHelpers->version() >= 5) ) {
        this->libSystemHelpers->run_async(&ProcessConfig::scanForRoots, (void*)&this->config);
    }
#endif // TARGET_OS_OSX

    //自下而上运行所有其他初始化器,首先运行插入的dylib初始化器
    //使用下标索引进行迭代,以便如果初始dloopen时数组不会向下增长
    for ( uint32_t i = 0; i != this->loaded.size(); ++i ) {
        const Loader* ldr = this->loaded[i];
        ldr->runInitializersBottomUpPlusUpwardLinks(*this);
        // stop as soon as we did main executable
        // normally this is first image, but if there are inserted N dylibs, it is Nth in the list
        if ( ldr->analyzer(*this)->isMainExecutable() )
            break;
    }
}
  • 结论:

    ①. 在libSystem的初始化时,用notifyObjCInit通知libSystem依赖的dylibs运行所有的+load方法,这里不是我们自定义工程里+load

    ②. 处理完libSystem的初始化后,再加载后自下而上运行所有其他镜像的初始化器。


runInitializersBottomUpPlusUpwardLinks

void Loader::runInitializersBottomUpPlusUpwardLinks(RuntimeState& state) const
{
    //state.log("runInitializersBottomUpPlusUpwardLinks() %s\n", this->path());
    state.incWritable();
    
    // 递归运行所有初始化程序 initializers
    STACK_ALLOC_ARRAY(const Loader*, danglingUpwards, state.loaded.size());
    this->runInitializersBottomUp(state, danglingUpwards);

    //state.log("runInitializersBottomUpPlusUpwardLinks(%s), found %d dangling upwards\n", this->path(), danglingUpwards.count());
    // 返回到所有向上链接的images,并重新检查它们是否已初始化(可能是挂起的)
    STACK_ALLOC_ARRAY(const Loader*, extraDanglingUpwards, state.loaded.size());
    for ( const Loader* ldr : danglingUpwards ) {
        //state.log("running initializers for dangling upward link %s\n", ldr->path());
        ldr->runInitializersBottomUp(state, extraDanglingUpwards);
    }
    if ( !extraDanglingUpwards.empty() ) {
        // 如果有两个向上悬挂的images,请再次检查初始化器
        danglingUpwards.resize(0);
        for ( const Loader* ldr : extraDanglingUpwards ) {
            //state.log("running initializers for dangling upward link %s\n", ldr->path());
            ldr->runInitializersBottomUp(state, danglingUpwards);
        }
    }
    state.decWritable();
}
  • 结论:

    ①.递归运行所有初始化程序initializers


runInitializersBottomUp

void Loader::runInitializersBottomUp(RuntimeState& state, Array<const Loader*>& danglingUpwards) const
{
    // 如果已运行初始化程序,则不执行任何操作
    if ( (const_cast<Loader*>(this))->beginInitializers(state) )
        return;

    //state.log("runInitializersBottomUp(%s)\n", this->path());
    // 在运行初始化程序之前,请确保此image下面的所有内容都已初始化
    const uint32_t depCount = this->dependentCount();
    for ( uint32_t i = 0; i < depCount; ++i ) {
        DependentKind childKind;
        if ( Loader* child = this->dependent(state, i, &childKind) ) {
            if ( childKind == DependentKind::upward ) {
                // 向上添加到列表以稍后处理
                if ( !danglingUpwards.contains(child) )
                    danglingUpwards.push_back(child);
            }
            else {
                child->runInitializersBottomUp(state, danglingUpwards);
            }
        }
    }

    // 告诉objc在此image映像中运行任何+load方法(在C++初始化器之前完成)
    state.notifyObjCInit(this);

    // 为此image映像运行初始化程序
    this->runInitializers(state);
}
  • 结论:

    ①. 最后通过notifyObjCInit通知objc在此image映像中运行任何+load方法(在C++初始化器之前完成)。


notifyObjCInit

void RuntimeState::notifyObjCInit(const Loader* ldr)
{
    //this->log("objc-init-notifier checking mh=%p, path=%s, +load=%d, objcInit=%p\n", ldr->loadAddress(), ldr->path(), ldr->mayHavePlusLoad, _notifyObjCInit);
    if ( (_notifyObjCInit != nullptr) && ldr->mayHavePlusLoad ) {
        const MachOLoaded* ml  = ldr->loadAddress(*this);
        const char*        pth = ldr->path();
        dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)ml, 0, 0);
        if ( this->config.log.notifications )
            this->log("objc-init-notifier called with mh=%p, path=%s\n", ml, pth);
        _notifyObjCInit(pth, ml);
    }
}
  • 结论:

    ②. 主要是遍历各个库文件,通过_notifyObjCInit来加载+load方法。


探究_notifyObjCInit来源

  1. _notifyObjCInit的定义是在RuntimeState类的声明里:

截屏2023-02-06 下午8.30.35.png

  1. _dyld_objc_notify_init 在setObjCNotifiers被赋值的:
#if BUILDING_DYLD || BUILDING_UNIT_TESTS
void RuntimeState::setObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init,
                                    _dyld_objc_notify_unmapped unmapped, _dyld_objc_notify_patch_class patchClass,
                                    _dyld_objc_notify_mapped2 mapped2)
{
    _notifyObjCMapped       = mapped;
    _notifyObjCInit         = init;
    _notifyObjCUnmapped     = unmapped;
    _notifyObjCPatchClass   = patchClass;
    _notifyObjCMapped2      = mapped2;

    withLoadersReadLock(^{
        if ( (_notifyObjCPatchClass != nullptr) && !this->objcReplacementClasses.empty() ) {
            // 告诉Symbolication象征我们正在修补类
            this->setDyldPatchedObjCClasses();

            for ( const ObjCClassReplacement& classReplacement : this->objcReplacementClasses )
                (*_notifyObjCPatchClass)(classReplacement.cacheMH, (void*)classReplacement.cacheImpl,
                                         classReplacement.rootMH, (const void*)classReplacement.rootImpl);
            if ( this->config.log.notifications ) {
                this->log("objc-patch-class-notifier called with %ld patches:\n", this->objcReplacementClasses.size());
            }

            // 清除替换类。如果发生dlopen,我们不想再次通知它们
            this->objcReplacementClasses.clear();
        }

        // 回调关于已加载图像images
        size_t maxCount = this->loaded.size();
        STACK_ALLOC_ARRAY(const mach_header*, mhs, maxCount);
        STACK_ALLOC_ARRAY(const char*, paths, maxCount);
        STACK_ALLOC_ARRAY(_dyld_objc_notify_mapped_info, infos, maxCount);
        for ( const Loader* ldr : loaded ) {
            // 这里不需要_mutex互斥锁,因为这是在进程仍然是单线程时调用的
            const MachOLoaded* ml = ldr->loadAddress(*this);
            if ( ldr->hasObjC ) {
                paths.push_back(ldr->path());
                mhs.push_back(ml);
                infos.push_back({ml, ldr->path(), ldr->dyldDoesObjCFixups(), 0});

                // 在map_images运行时内存read-write可读写
                if ( ldr->hasConstantSegmentsToProtect() && ldr->hasReadOnlyObjC )
                    ldr->makeSegmentsReadWrite(*this);
            }
        }
        if ( !mhs.empty() ) {
            if ( _notifyObjCMapped != nullptr )
            // 通过指针_notifyObjCMapped指向运行map_images
                (*_notifyObjCMapped)((uint32_t)mhs.count(), &paths[0], &mhs[0]);
            if ( _notifyObjCMapped2 != nullptr )
                (*_notifyObjCMapped2)((uint32_t)mhs.count(), &infos[0]);
            if ( this->config.log.notifications ) {
                this->log("objc-mapped-notifier called with %ld images:\n", mhs.count());
                for ( uintptr_t i = 0; i < mhs.count(); ++i ) {
                    this->log(" objc-mapped: %p %s\n", mhs[i], paths[i]);
                }
            }
            
            // 运行map_images后使内存为只读read-only
            for ( const Loader* ldr : loaded ) {
                if ( ldr->hasObjC && ldr->hasConstantSegmentsToProtect() && ldr->hasReadOnlyObjC )
                    ldr->makeSegmentsReadOnly(*this);
            }
        }
    });
}
  1. setObjCNotifiers是在_dyld_objc_notify_register被调用的:
// FIXME: 一旦libobjc移动到_dyld_objc_register_callbacks(),请删除此项
// 这个是先前的注册方法
void APIs::_dyld_objc_notify_register(_dyld_objc_notify_mapped   mapped,
                                      _dyld_objc_notify_init     init,
                                      _dyld_objc_notify_unmapped unmapped)
{
    if ( config.log.apis )
        log("_dyld_objc_notify_register(%p, %p, %p)\n", mapped, init, unmapped);
    setObjCNotifiers(mapped, init, unmapped, nullptr, nullptr);

    // If we have prebuilt loaders, then the objc optimisations may hide duplicate classes from libobjc.
    // We need to print the same warnings libobjc would have.
    if ( const PrebuiltLoaderSet* mainSet = this->processPrebuiltLoaderSet() )
        mainSet->logDuplicateObjCClasses(*this);
}

// 最新的代码objc4的注册方法
void APIs::_dyld_objc_register_callbacks(const _dyld_objc_callbacks* callbacks)
{
    if ( config.log.apis ) {
        void** p = (void**)callbacks;
        log("_dyld_objc_register_callbacks(%lu, %p, %p, %p, %p)\n", callbacks->version, p[1], p[2], p[31], p[4]);
    }

    if ( callbacks->version == 1 ) {
        const _dyld_objc_callbacks_v1* v1 = (const _dyld_objc_callbacks_v1*)callbacks;
        setObjCNotifiers(v1->mapped, v1->init, v1->unmapped, v1->patches, nullptr);
    }
    else if ( callbacks->version == 2 ) {
        const _dyld_objc_callbacks_v2* v2 = (const _dyld_objc_callbacks_v2*)callbacks;
        setObjCNotifiers(nullptr, v2->init, v2->unmapped, v2->patches, v2->mapped);
    }
    else {
    
    }

    // 如果我们有预构建的加载器,那么objc优化可能会从libobjc中隐藏重复的类。
    // 我们需要打印libobjc将具有的相同警告。
    if ( const PrebuiltLoaderSet* mainSet = this->processPrebuiltLoaderSet() )
        mainSet->logDuplicateObjCClasses(*this);
}
  • 结论:

    ①. 先前dyld3的注册方法_dyld_objc_notify_register,最新dyld4_dyld_objc_register_callbacks

  1. _dyld_objc_register_callbacks是在objc4-866.9源码里的 objc_init()里被调用的
/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**************************************************************************/
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();
    static_init();
    runtime_init();
    exception_init();
    cache_t::init();

    _imp_implementationWithBlock_init();
    _dyld_objc_callbacks_v1 callbacks = {
        1, // version
        &map_images,
        load_images,
        unmap_image,
        _objc_patch_root_of_class
    };

    _dyld_objc_register_callbacks((_dyld_objc_callbacks*)&callbacks);
    didCallDyldNotifyRegister = true;
}
  • 结论:

    ①. 这里解释了先前dyld入口探索,调用完notifyObjCInit后就是load_images

    ②.map_images映射镜像、load_images加载镜像、unmap_image解除镜像objc4源码初始化配置的重点,为了进一步了解dyld4对于images镜像们的管理,我要先去了解objc4objc_init


五、总结

  1. 分析了map_imagesload_images注册load_images调用流程,但是未具体分析objc4_objc_initmap_imagesload_images的源码与流程。

  2. 只分析了dyld4的部分流程,而未分析dyld3的流程。下面是两者对比:

  • dyld3: 截屏2023-02-07 上午3.06.29.png

  • dyld4: 截屏2023-02-06 下午10.48.12.png