美文网首页
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的关联

相关文章

  • Objective-C 类的加载原理(上)

    上篇文章中分析了dyld整个流程以及dyld与objc的交互。这篇文章将继续分析dyld调用map_images究...

  • dyld 流程分析

    前言 在编写一个应用程序时候,我们看到的入口函数都是main.m 里面的 main函数,曾以为这是程序的入口,其实...

  • dyld流程分析

    编译流程 在开始分析dyld之前,我们先看下分析下可执行文件的整个编译流程: 如上图所示,我们编写的源文件,会在预...

  • dyld加载流程

    dyld加载流程 配置环境变量依赖DYLD(dyld)dyld(the dynamic link editor)是...

  • 十、dyld流程分析

    dyld dyld(the dynamic link editor)是苹果的动态链接器,是苹果操作系统一个重要组成...

  • dyld 调用流程分析

    dyld 简介 dyld(the dynamic link editor)是苹果的动态链接器,用来加载所有的库和可...

  • iOS dyld流程分析

    本文的目的主要是分析dyld的加载流程,了解在main函数之前,底层还做了什么 引子 创建一个project,在V...

  • 类的加载(上)-- _objc_init&read_image

    前言 上一篇文章主要分析dyld的整个流程以及dyld与_objc_init之间的交互,_objc_init向dy...

  • iOS dyld与objc的关联

    本文的主要目的是理解dyld与objc是如何关联的 在上一篇文章iOS dyld流程分析[https://www....

  • dyld加载流程

    dyld加载流程 结合最新的DYLD开源库dyld-832.7.3。 程序开始于_dyld_start汇编函数--...

网友评论

      本文标题:dyld 流程分析

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