iOS 底层原理探索 之 应用程序加载原理dyld (下)

前言: iOS底层原理探究是本人在平时的开发和学习中不断积累的一段进阶之
路的。 记录我的不断探索之旅,希望能有帮助到各位读者朋友。

目录如下:

  1. iOS 底层原理探索 之 alloc
  2. iOS 底层原理探索 之 结构体内存对齐
  3. iOS 底层原理探索 之 对象的本质 & isa的底层实现
  4. iOS 底层原理探索 之 isa - 类的底层原理结构(上)
  5. iOS 底层原理探索 之 isa - 类的底层原理结构(中)
  6. iOS 底层原理探索 之 isa - 类的底层原理结构(下)
  7. iOS 底层原理探索 之 Runtime运行时&方法的本质
  8. iOS 底层原理探索 之 objc_msgSend
  9. iOS 底层原理探索 之 Runtime运行时慢速查找流程
  10. iOS 底层原理探索 之 动态方法决议
  11. iOS 底层原理探索 之 消息转发流程
  12. iOS 底层原理探索 之 应用程序加载原理dyld (上)

前言

接着上一篇的内容我们从 _dyld_objc_notify_register(&map_images, load_images, unmap_image)开始:

_dyld_objc_notify_register(&map_images, load_images, unmap_image)

  1. 这个方法中的 map_images是在什么时候调用的呢?
  2. load_images方法执行的时候其内部的一个流程是什么样的?
  3. 最后,我们现在所在的 dyld 阶段是如何进入到 main 函数主程序的呢?

带着这些疑问开始我们今天的内容。

承接上节课的内容 在 doModInitFunctions 之后 我们加载 libSystem.B.dylib libSystem_initializerlibsidpatch.dylib libdispatch_initlibsidpatch.dylib _os_object_init 接着执行到 _objc_init。 在 _objc_init 中,我们调用 了 _dyld_objc_notify_register

_dyld_objc_notify_register

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);
}

registerObjCNotifiers

// _dyld_objc_notify_init
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
    // 记录要调用的函数,完成赋值
    sNotifyObjCMapped   = mapped;
    sNotifyObjCInit     = init;
    sNotifyObjCUnmapped = unmapped;
        
        // 调用“映射”函数与所有镜像映射到目前为止
    try {
        notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);
    }
    ...
}

notifyBatchPartial

此函数中 通过 (*sNotifyObjCMapped)(objcImageCount, paths, mhs); 完成了对 map_images 的调用。

static void notifyBatchPartial(dyld_image_states state, bool orLater, dyld_image_state_change_handler onlyHandler, bool preflightOnly, bool onlyObjCMappedNotification)
{
    ... 
        
        // 告诉objc关于新的镜像                  
        if ( (onlyHandler == NULL) && ((state == dyld_image_state_bound) || (orLater && (dyld_image_state_bound > state))) && (sNotifyObjCMapped != NULL) ) {
        const char* paths[imageCount];
    const mach_header* mhs[imageCount];
    unsigned objcImageCount = 0;
        for (int i=0; i < imageCount; ++i) { ... }
        if ( objcImageCount != 0 ) {
            dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_MAP, 0, 0, 0);
    uint64_t t0 = mach_absolute_time();
    (*sNotifyObjCMapped)(objcImageCount, paths, mhs);
    uint64_t t1 = mach_absolute_time();         ImageLoader::fgTotalObjCSetupTime += (t1-t0);
    }
      }
    }
    allImagesUnlock();
    if ( dontLoadReason != NULL )
            throw dontLoadReason;
    if ( !preflightOnly && (state == dyld_image_state_dependents_mapped) ) {
    const struct mach_header* loadAddresses[imageCount];
    const char* loadPaths[imageCount];
    for(uint32_t i = 0; i<imageCount; ++i) {
    loadAddresses[i] = infos[i].imageLoadAddress;
    loadPaths[i] = infos[i].imageFilePath;
    }
    notifyMonitoringDyld(false, imageCount, loadAddresses, loadPaths);
    }
    }
}       

也就是 _dyld_objc_notify_register方法进来之后,系统记录下要调用的函数后 就 try map_images 完成调用, 下面看下 load_images

在上一篇的 recursiveInitialization 环节,

ImageLoader::recursiveInitialization

void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
                                          InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
    recursive_lock lock_info(this_thread);
    recursiveSpinLock(lock_info);

    if ( fState < dyld_image_state_dependents_initialized-1 ) {
    uint8_t oldState = fState;
    // break cycles
    fState = dyld_image_state_dependents_initialized-1;
    try {
            // initialize lower level libraries first
            for(unsigned int i=0; i < libraryCount(); ++i) {... }
            
    // 记录终止订单
    if ( this->needsTermination() )             context.terminationRecorder(this);
    // 让objc知道我们将要初始化这个镜像
    uint64_t t1 = mach_absolute_time();
    fState = dyld_image_state_dependents_initialized;
    oldState = fState;
       
        context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);


        // 初始化镜像
        // 初始化所有images load_images 赋值
        // map load - map() --> cxx = objc
        // notifySingle : load() 
        // cxx = SMobjcBuild
    bool hasInitializers = this->doInitialization(context);

    //  让任何人知道我们已经初始化了这个镜像
    fState = dyld_image_state_initialized;
    oldState = fState;
        
        //objc - SMObjc
        context.notifySingle(dyld_image_state_initialized, this, NULL);
            
    if ( hasInitializers ) { ... }
    }   
    catch (const char* msg) {
        // this image is not initialized
        fState = oldState;
        recursiveSpinUnLock();
        throw;
        }
    }
    
    recursiveSpinUnLock();
}

就会分析进入到 notifySingle:

notifySingle

static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
{
    //dyld::log("notifySingle(state=%d, image=%s)\n", state, image->getPath());
    std::vector<dyld_image_state_change_handler>* handlers = stateToHandlers(state, sSingleHandlers);
    if ( handlers != NULL ) { ... }
    if ( state == dyld_image_state_mapped ) { ... }
    if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && image->notifyObjC() ) {
        uint64_t t0 = mach_absolute_time();
        dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
        (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
        uint64_t t1 = mach_absolute_time();
        uint64_t t2 = mach_absolute_time();
        uint64_t timeInObjC = t1-t0;
        uint64_t emptyTime = (t2-t1)*100;
    if ( (timeInObjC > emptyTime) && (timingInfo != NULL)) { ... }
   }
}

也就是在 notifySingle 中调用了load_images

接下来我们看看,应用程序加载过程中,是如何从 dyld 进入到 main() 的。

dyld_start 汇编源码

通过分析 dyld_start 源码,发现在dyld的最后,会调起系统的 main()函数。

#if __arm64__ && !TARGET_OS_SIMULATOR
    .text
    .align 2
    .globl __dyld_start
__dyld_start:
    mov     x28, sp
    and     sp, x28, #~15       // force 16-byte alignment of stack
    mov x0, #0
    mov x1, #0
    stp x1, x0, [sp, #-16]! // make aligned terminating frame
    mov fp, sp          // set up fp to point to terminating frame
    sub sp, sp, #16             // make room for local variables
#if __LP64__
    ldr     x0, [x28]               // get app's mh into x0
    ldr     x1, [x28, #8]           // get argc into x1 (kernel passes 32-bit int argc as 64-bits on stack to keep alignment)
    add     x2, x28, #16            // get argv into x2
#else
    ldr     w0, [x28]               // get app's mh into x0
    ldr     w1, [x28, #4]           // get argc into x1 (kernel passes 32-bit int argc as 64-bits on stack to keep alignment)
    add     w2, w28, #8             // get argv into x2
#endif
    adrp    x3,___dso_handle@page
    add     x3,x3,___dso_handle@pageoff // get dyld's mh in to x4
    mov x4,sp                   // x5 has &startGlue

    // 调用 dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
    bl  __ZN13dyldbootstrap5startEPKN5dyld311MachOLoadedEiPPKcS3_Pm
    mov x16,x0                  // save entry point address in x16
#if __LP64__
    ldr     x1, [sp]
#else
    ldr     w1, [sp]
#endif
    cmp x1, #0
    b.ne    Lnew

    // LC_UNIXTHREAD way, 清理堆栈然后跳转到结果
#if __LP64__
    add sp, x28, #8             // restore unaligned stack pointer without app mh
#else
    add sp, x28, #4             // restore unaligned stack pointer without app mh
#endif
#if __arm64e__
    braaz   x16                     // jump to the program's entry point
#else
    br      x16                     // jump to the program's entry point
#endif

    // LC_MAIN case, 设置调用main()的堆栈
Lnew:   mov lr, x1          // simulate return address into _start in libdyld.dylib
#if __LP64__
    ldr x0, [x28, #8]       // main param1 = argc
    add x1, x28, #16        // main param2 = argv
    add x2, x1, x0, lsl #3
    add x2, x2, #8          // main param3 = &env[0]
    mov x3, x2
Lapple: ldr x4, [x3]
    add x3, x3, #8
#else
    ldr w0, [x28, #4]       // main param1 = argc
    add x1, x28, #8         // main param2 = argv
    add x2, x1, x0, lsl #2
    add x2, x2, #4          // main param3 = &env[0]
    mov x3, x2
Lapple: ldr w4, [x3]
    add x3, x3, #4
#endif
    cmp x4, #0
    b.ne    Lapple          // main param4 = apple
#if __arm64e__
    braaz   x16
#else
    br      x16
#endif

#endif // __arm64__ && !TARGET_OS_SIMULATOR

接下来真机验证:

验证

我们知道 我们准备的项目 先是 [viewcontroller load] 调用 然后是 cxx 方法 ,最后进入到 main()函数,那么,在项目中,我们在cxx方法处打一个断点,开始调试看看:

image.png

断点进来之后,debug显示汇编代码,然后逐步执行,看到在 dyld_start 流程的最后,会主动的去调起 main 函数

image.png

到这里,我们就解释了上篇文章抛出的三个问题。
所以,补充一下昨天的 dyld流程图:

dyld流程图

myNoteBase_副本.001.jpeg

补充

首先要探索的流程就是 map_images

map_images

/***********************************************************************
* map_images
* 处理dyld映射到的给定镜像
* 在使用特定于abi的锁后调用与abi无关的代码
*
*  锁定:写锁runtimeLock
**********************************************************************/
void
map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    mutex_locker_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}

之后就从 map_images 来到了 map_images_nolock

map_images_nolock

/***********************************************************************
* map_images_nolock
* 处理dyld映射到的给定镜像
* 所有的类注册和修正都被执行(或延迟等待)
* 发现丢失的超类等,并调用+load方法
*
* Info[]是自下而上的顺序,即libobjc将在早些时候
* 数组比任何链接到libobjc的库
*
* 锁定:loadMethodLock(旧的)或runtimeLock(新的)由map_images获取
**********************************************************************/
#if __OBJC2__
#include "objc-file.h"
#else
#include "objc-file-old.h"
#endif

void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    static bool firstTime = YES;
    header_info *hList[mhCount];
    uint32_t hCount;
    size_t selrefCount = 0;

    // 如果需要,执行首次初始化
    // 该函数在普通库初始化器之前调用
    // 修复延迟初始化直到找到一个使用objc的镜像?
    if (firstTime) { ... }
    
    if (PrintImages) { ... }
    
    // 找到所有带有Objective-C元数据的镜像
    hCount = 0;

    // Count classes. Size various table based on the total.
    int totalClasses = 0;
    int unoptimizedTotalClasses = 0;
    {
        uint32_t i = mhCount;
        while (i--) { ... }
    }
    
    //  执行必须延迟到的一次性运行时初始化 
    //  可执行文件本身被找到。这需要提前完成
    //  进一步的初始化
    //  可执行文件可能不在这个infoList中,如果

    //  可执行程序不包含Objective-C代码,而是Objective-C
    //  稍后动态加载
    if (firstTime) { ... }
    
    
    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

    firstTime = NO;
    
    // 在一切设置完成后调用镜像加载函数
    for (auto func : loadImageFuncs) {
        for (uint32_t i = 0; i < mhCount; i++) {
            func(mhdrs[i]);
        }
    }

找到所有带有Objective-C元数据的镜像后,之后就开始对镜像内容进行初始处理。

总结

用户将我们开发的app安装到手机后,一直存储在手机的磁盘中,一旦用户点击了我们的app,那么,系统就会进入到dyld流程中,为我们的app中所使用的各种动静态库做链接,加载各种镜像文件,接着就是加载我们项目中的各种类文件,最终,进入到 main()函数,之后是 AppDelegateSecen 来处理我们为用户开发的各种功能。

本篇内容算是,上述流程中 dyld 流程探索的一个总结, 下一篇,我们开始类的加载流程探索。 大家,加油!!!

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

推荐阅读更多精彩内容