dyld 流程分析

前言

在编写一个应用程序时候,我们看到的入口函数都是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的关联

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343