美文网首页
dyld 流程分析

dyld 流程分析

作者: 猿人 | 来源:发表于2020-10-14 17:12 被阅读0次

    前言

    在编写一个应用程序时候,我们看到的入口函数都是main.m 里面的 main函数,曾以为这是程序的入口,其实不然,程序在执行main函数之前已经执行了+loadconstructor构造函数。下面让我们一起来分析

    dyld简介

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

    分析

    我们知道+load函数在main函数之前调用 我随意实现 一个load函数并断言在此。

    截屏2020-10-14 下午3.01.49.png

    可以看出程序是从_dyld_start 为入口 ,我们准备好dyld代码可以从苹果开源网站下载(https://opensource.apple.com/tarballs/dyld/,这里我下载的是773.6版本)。

    全局搜索_dyld_start 发现如下汇编
    截屏2020-10-14 下午3.13.38.png
    在晦涩难懂的汇编我们一眼就可以看到bl跳转 虽说代码看不懂可以看注释呀
    // call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
    全局搜索dyldbootstrap

    dyld 引导配置


    截屏2020-10-14 下午3.29.28.png

    根据断言及源代码搜索 _main 漫长的搜索 在 dyld2.cpp里 这里有些长精简代码

    
    // Entry point for dyld.  The kernel loads dyld and jumps to __dyld_start which
    // sets up some registers and call this function.
    //
    // Returns address of main() in target program which __dyld_start jumps to
    //
    uintptr_t
    _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
            int argc, const char* argv[], const char* envp[], const char* apple[], 
            uintptr_t* startGlue)
    {
    ... 省略
    ///  6158 - 6160 行
        uintptr_t result = 0;  
    /// 保存执行文件头部,后续可以根据头部访问其他信息
         sMainExecutableMachHeader = mainExecutableMH;
         sMainExecutableSlide = mainExecutableSlide; 
    ...
    ///  6233 -6226
    ///  设置上下文信息
        setContext(mainExecutableMH, argc, argv, envp, apple);
        // Pickup the pointer to the exec path.
     /// 获取可执行文件路径 
        sExecPath = _simple_getenv(apple, "executable_path");
    ...
    ///6242 - 6262
    ///将相对路径转换成绝对路径
    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);
    ...
    /// 6298 -6302
    #endif
        {
    ///检查设置环境变量
            checkEnvironmentVariables(envp);
    ///如DYLD_FALLBACK为nil,将其设置为默认值
            defaultUninitializedFallbackPaths(envp);
        }
    ...
    /// 6317 -6320
    ///如果设置了DYLD_PRINT_OPTS环境变量 则打印参数
        if ( sEnv.DYLD_PRINT_OPTS )
            printOptions(argv);
    ///如果设置了DYLD_PRINT_ENV环境变量 则打印参数
        if ( sEnv.DYLD_PRINT_ENV ) 
            printEnvironmentVariables(envp);
    ...
    ///6347 - 6356
    /// 获取当前运行架构的信息
    getHostInfo(mainExecutableMH, mainExecutableSlide);
    
    ////检査共享缓存是否开启,在iOS中必须开启
    checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
    
    if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
    #if TARGET_OS_SIMULATOR
            if ( sSharedCacheOverrideDir)
                mapSharedCache();
    #else
    //检査共享缓存是否映射到了共享区域
            mapSharedCache();
    ...
    /// 6517- 6520
    ///加载可执行文件并生成一个ImageLoader实例对象
    // instantiate ImageLoader for main executable
            sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
            gLinkContext.mainExecutable = sMainExecutable;
            gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);
    ...
    /// 6558 - 6561 
    // Now that shared cache is loaded, setup an versioned dylib overrides
        #if SUPPORT_VERSIONED_PATHS
    ///检查库的版本是否有更新,如有则覆盖原有的
            checkVersionedPaths();
        #endif
    ...
    /// 6582 - 6589
    /// 加载所有的DYLD_INSERT_LIBRARIES指定的库
    // 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;
    ....
    /// 6592- 6605
    ///连接主程序
        // link main executable
            gLinkContext.linkingMainExecutable = true;
    ...
            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;
            }
    
    /// 连接插入的动态库  6607 -6630
        // 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);
            }
    ...
    ///6664- 6689 
        // apply interposing to initial set of images
            for(int i=0; i < sImageRoots.size(); ++i) {
                sImageRoots[i]->applyInterposing(gLinkContext);
            }
    ///应用插入到Dyld缓存
            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);
                }
            }
            
            // <rdar://problem/12186933> do weak binding only after all inserted images linked
            sMainExecutable->weakBind(gLinkContext);
            gLinkContext.linkingMainExecutable = false;
    
            sMainExecutable->recursiveMakeDataReadOnly(gLinkContext);
    ···
    /// 6696 - 6702
        #else
    ///执行初始化方法
            // run all initializers
            initializeMainExecutable(); 
        #endif
    ///通知即将进入 main()
            // notify any montoring proccesses that this process is about to enter main()
            notifyMonitoringDyldMain();
    ...
    ///查找主可执行文件的入口点
    // 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;
                }
    
    

    通过代码可以知道,dyld的加载流程主要包括9个步骤
    01:设置上下文信息,配置进程是否受限制
    02:配置环境变量,获取当前运行架构
    03:检查共享缓存是否映射到了共享区域
    04:加载可执行文件,生成一个imageLoader实例对象
    05:加载所有插入的库
    06:连接主程序
    07:连接所有插入的库,执行符号替换
    08:执行初始化方法
    09 : 寻找主程序入口

    initializeMainExecutable(); 执行初始化方法分析第八步分析

    • 眼睛聚焦到高亮位置 截屏2020-10-15 上午11.11.43.png
    • 找到runInitializers ,全局搜索runInitializers(cons 发现其核心实现为 processInitializers函数调用

      截屏2020-10-15 上午11.27.37.png
    • 进入 processInitializers函数源码

      截屏2020-10-15 上午11.38.44.png
    • 找到recursiveInitialization , 全局搜索

      recursiveInitialization.png
    • 全局搜索 notifySingle 截屏2020-10-15 下午1.16.05.png

      这里我们定位到了 一个 sNotifyObjCInit 指针函数调用 可是它是什么?

    • 全局搜索 sNotifyObjCInit,发现在dyld源码中并未找到,只定位到了 registerObjCNotifiers函数内部赋值

      截屏2020-10-15 下午1.21.57.png
      这里我们定位到了 这个可能是一个外部传进来 回调函数
    • 全局搜索 registerObjCNotifiers 函数


      截屏2020-10-15 下午1.26.47.png
    • 全局搜索 _dyld_objc_notify_register dyld源码并未找到只找到了相关注释


      截屏2020-10-15 下午2.00.21.png
    • 那么到底谁调用了_dyld_objc_notify_register()呢?静态分析已经无法得知,只能对_dyld_objc_notify_register()下个符号断点观察一下了,
      点击Xcode的“Debug”菜单,然后点击“Breakpoints”,接着选择“Create Symbolic Breakpoint...”。在弹出窗如下图所示。


      5b715ef3815bb245803411.jpg
    • 运行 成功断住 bt命令打印当前堆栈信息


      截屏2020-10-15 下午2.12.46.png
    • 可以看到当前是 libobjc 的 _objc_init 发起的调用 在objc源码里进行搜索 _objc_init


      截屏2020-10-15 下午2.17.31.png
    • 我们再来看一遍上面的sNoitfyObjcInit 截屏2020-10-15 下午2.25.50.png

      1、由此我们可以得出结论 此处注册的init函数就是load_images,所以上面的sNotifyObjCInit调用的就是 objc中的 load_images, 所以notifySingle是一个回调函数
      2、_dyld_objc_notify_register 这个方法的调用为 _objc_init函数
      3、_objc_init 引导初始化。注册我们的镜像通知 与 dyld
      4、在库初始化时间之前由libSystem调用

    • 搜索 load_images 底层实现 实现所有的load方法
    截屏2020-10-15 下午3.47.47.png

    再次回到

    截屏2020-10-16 下午1.21.57.png
    根据方法名及源代码 我们知道此方法为递归的初始化
    _dyld_objc_notify_register符号断点 实现构造函数constructor断点load方法 断言
    截屏2020-10-16 下午4.51.34.png
    调用栈对比图 流程
    截屏2020-10-16 下午5.01.51.png

    无论是 libSystem的初始化, 还是 constructor 函数的调用 都
    是由 doInitialization函数发起调用 当然看 调用栈和上面的源码我们也知道它的入口来自 recursiveInitialization函数

    查看doInitialization函数
    截屏2020-10-16 下午5.11.52.png
    doImageInit 函数 for循环初始化镜像 libSystem初始化优先级最高
    截屏2020-10-16 下午5.14.59.png
    doModInitFunctions 初始化所有 cxx文件 libSystem初始化优先级最高
    12857030-63550552d91d2610.png

    流程图


    dyldbootstrap:start.png

    下一篇 dyld与objc的关联

    相关文章

      网友评论

          本文标题:dyld 流程分析

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