dyld 流程分析

2020-10-14  本文已影响0人  猿人

前言

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

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 下午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的关联

上一篇下一篇

猜你喜欢

热点阅读