系统底层源码分析(20)——dyld加载流程

  • 概念

Mach-O是一种文件格式,是mac上可执行文件的格式。编写的CC++swiftOC,最终编译链接生成Mach-O可执行文件。

链接的共用库分为静态库和动态库:静态库是编译时链接的库,需要链接进你的 Mach-O 文件里,如果需要更新就要重新编译一次,无法动态加载和更新;而动态库是运行时链接的库,使用 dyld 就可以实现动态加载。

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

编译过程
  • 加载流程

为了辅助探索底层,我们可以用符号断点看一下堆栈信息:

  1. 首先调用的是_dyld_start,我们马上进入dyld源码开始探究:
  1. 然后从汇编跳转调用start
uintptr_t start(const struct macho_header* appsMachHeader, int argc, const char* argv[], 
                intptr_t slide, const struct macho_header* dyldsMachHeader,
                uintptr_t* startGlue)
{
    slide = slideOfMainExecutable(dyldsMachHeader);//通过一个随机值来实现地址空间配置随机加载, 防止被攻击
    bool shouldRebase = slide != 0;
    ...
    // allow dyld to use mach messaging
    mach_init();//开放函数消息使用
    ...
    // set up random value for stack canary
    __guard_setup(apple);//设置堆栈保护
    ...
    // now that we are done bootstrapping dyld, call dyld's main
    uintptr_t appsSlide = slideOfMainExecutable(appsMachHeader);
    return dyld::_main(appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);//开始链接共享对象
}
  1. 接着进入_main,代码很多所以只看流程代码:
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
        int argc, const char* argv[], const char* envp[], const char* apple[], 
        uintptr_t* startGlue)
{
    ...
    {
        checkEnvironmentVariables(envp);//检查环境变量
        defaultUninitializedFallbackPaths(envp);
    }
    ...
    // load shared cache
    checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);//验证共享缓存路径
    ...
    if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
        mapSharedCache();//加载共享缓存
    }
    ...
    try {
        // add dyld itself to UUID list
        addDyldImageToUUIDList();//添加dyld到UUID列表
        ...
reloadAllImages:
        ...
        sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);//初始化主程序;内核会映射到可执行文件中
        ...
        // load any inserted libraries
        if  ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
            for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) 
                loadInsertedDylib(*lib);//加载插入的动态库
        }
        ...
        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();
            }
            ...
        }
        ...
        ImageLoader::applyInterposingToDyldCache(gLinkContext);//插入任何动态加载的镜像文件
        ...
        // run all initializers
        initializeMainExecutable(); //运行所有初始化程序
        ...
        notifyMonitoringDyldMain();//通知监听dyld的main

        // find entry point for main executable
        result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();//找到真正 main 函数入口并返回
        ...
    }
    ...
}
  • 流程:配置环境变量 -> 加载共享缓存 -> 实例化主程序 -> 加载动态库 -> 链接动态库 -> 运行所有初始化程序 -> 通知监听dyldmain -> 找到真正 main 函数入口
  1. 然后就开始初始化:
static ImageLoaderMachO* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path)
{
    // try mach-o loader
    if ( isCompatibleMachO((const uint8_t*)mh, path) ) {
        ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext);//通过instantiateMainExecutable中的sniffLoadCommands加载主程序其实就是对MachO文件中LoadCommons段的一系列加载
        addImage(image);//生成镜像文件后,添加到sAllImages全局镜像中,主程序永远是sAllImages的第一个对象
        return (ImageLoaderMachO*)image;
    }
    
    throw "main executable not a known format";
}
  1. 接着下一步就是加载插入的动态库:
static void loadInsertedDylib(const char* path)
{
    ImageLoader* image = NULL;
    unsigned cacheIndex;
    try {
        image = load(path, context, cacheIndex);
    }
    ...
}
  1. 最后就是链接动态库,插入任何动态加载的镜像文件。

  2. 然后就开始运行:

void initializeMainExecutable()
{
    ...
    if ( rootCount > 1 ) {
        for(size_t i=1; i < rootCount; ++i) {
            sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);//先为所有插入并链接完成的动态库执行初始化操作
        }
    }
    
    // run initializers for main executable and everything it brings up 
    sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);//为主要可执行文件及其带来的一切运行初始化程序
    ...
}
void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo)
{
    ...
    processInitializers(context, thisThread, timingInfo, up);//初始化准备
    ...
}
void ImageLoader::processInitializers(const LinkContext& context, mach_port_t thisThread,
                                     InitializerTimingList& timingInfo, ImageLoader::UninitedUpwards& images)
{
    ...
    // Calling recursive init on all images in images list, building a new list of
    // uninitialized upward dependencies.
    for (uintptr_t i=0; i < images.count; ++i) {//遍历image.count
        images.images[i]->recursiveInitialization(context, thisThread, images.images[i]->getPath(), timingInfo, ups);//递归初始化镜像
    }
    ...
}
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
                                          InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
    ...
        try {
            ...
            context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);//单个镜像通知;获取到镜像的回调
            
            // initialize this image
            bool hasInitializers = this->doInitialization(context);
            ...
        }
    ...
}
bool ImageLoaderMachO::doInitialization(const LinkContext& context)
{
    CRSetCrashLogMessage2(this->getPath());

    // mach-o has -init and static initializers
    doImageInit(context);
    doModInitFunctions(context);//比如会调用c++全局构造函数-初始化
    
    CRSetCrashLogMessage2(NULL);
    
    return (fHasDashInit || fHasInitializers);
}
void ImageLoaderMachO::doModInitFunctions(const LinkContext& context)
{
    ...
    if ( type == S_MOD_INIT_FUNC_POINTERS ) {
        Initializer* inits = (Initializer*)(sect->addr + fSlide);
        ...
        for (size_t j=0; j < count; ++j) {
            Initializer func = inits[j];
            ...
            {
                dyld3::ScopedTimer(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)fMachOData, (uint64_t)func, 0);
                func(context.argc, context.argv, context.envp, context.apple, &context.programVars);//就是调用libSystem_initializer
            }
            ...
        }
        ...
}
  1. 到这里就要跳转到Libsystem源码
__attribute__((constructor))
static void
libSystem_initializer(int argc,
              const char* argv[],
              const char* envp[],
              const char* apple[],
              const struct ProgramVars* vars)
{
    ...
    libdispatch_init();
    ...
}
  1. 接着又跳转到libdispatch源码
void
libdispatch_init(void)
{
    ...
    _dispatch_hw_config_init();
    _dispatch_time_init();
    _dispatch_vtable_init();
    _os_object_init();
    _voucher_init();
    _dispatch_introspection_init();
}
void
_os_object_init(void)
{
    _objc_init();
    ...
}
  1. 最后的最后终于调用了_objc_init,接着又要跳转到objc4源码
void _objc_init(void)
{
    ...
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

map_images : dyldimage 加载进内存时 , 会触发该函数.
load_images : dyld 初始化 image 会触发该方法. ( 我们所熟知的 load 方法也是在此处调用 ) .
unmap_image : dyldimage 移除时 , 会触发该函数 .

  1. 很不幸,又要跳回到dyld源码中:
void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped)
{
    dyld::registerObjCNotifiers(mapped, init, unmapped);
}

void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
    // record functions to call
    sNotifyObjCMapped   = mapped;
    sNotifyObjCInit     = init;//load_images函数
    ...
    for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) {
        ImageLoader* image = *it;
        if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() ) {
            dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
            (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());//获取镜像文件真实地址; 比如会调用[ViewController load]
        }
    }
}

这里可以看到调用的sNotifyObjCInit就是load_images函数。

(dyld -> libSystem -> libDispatch -> libObjc -> _objc_init)

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

推荐阅读更多精彩内容