美文网首页底层
iOS-OC底层10:dyld加载流程分析

iOS-OC底层10:dyld加载流程分析

作者: MonKey_Money | 来源:发表于2020-10-12 17:45 被阅读0次

    前沿

    我们实现ViewController的+(void)load方法,在main函数中添加c++方法

    //ViewController的load方法
    + (void)load{
        NSLog(@"%s",__func__);
    }
    //程序入口
    int main(int argc, char * argv[]) {
        NSString * appDelegateClassName;
        
        NSLog(@"1223333");
        
        @autoreleasepool {
            // Setup code that might create autoreleased objects goes here.
            appDelegateClassName = NSStringFromClass([AppDelegate class]);
        }
        return UIApplicationMain(argc, argv, nil, appDelegateClassName);
    }
    
    //c++方法
    __attribute__((constructor)) void kcFunc(){
        printf("来了 : %s \n",__func__);
    }
    打印结果
    2020-09-29 15:13:42.259577+0800 002-应用程加载分析[81605:302871] +[ViewController load]
    来了 : kcFunc 
    2020-09-29 15:13:42.268476+0800 002-应用程加载分析[81605:302871] 1223333
    

    我们可以看出先load然后c++,最后main方法,接下来我们围绕这个打印顺序就行分析。
    我们在c++方法处打断点,然后查看堆栈信息如下

    * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
      * frame #0: 0x0000000102858174 002-应用程加载分析`kcFunc at main.m:30:5
        frame #1: 0x00000001028786d9 dyld_sim`ImageLoaderMachO::doModInitFunctions(ImageLoader::LinkContext const&) + 513
        frame #2: 0x0000000102878ace dyld_sim`ImageLoaderMachO::doInitialization(ImageLoader::LinkContext const&) + 40
        frame #3: 0x0000000102873868 dyld_sim`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 456
        frame #4: 0x0000000102871d2c dyld_sim`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 188
        frame #5: 0x0000000102871dcc dyld_sim`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 82
        frame #6: 0x0000000102866270 dyld_sim`dyld::initializeMainExecutable() + 199
        frame #7: 0x000000010286a1bb dyld_sim`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 3662
        frame #8: 0x00000001028651cd dyld_sim`start_sim + 122
        frame #9: 0x0000000108e4e85c dyld`dyld::useSimulatorDyld(int, macho_header const*, char const*, int, char const**, char const**, char const**, unsigned long*, unsigned long*) + 2308
        frame #10: 0x0000000108e4c4f4 dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 837
        frame #11: 0x0000000108e47227 dyld`dyldbootstrap::start(dyld3::MachOLoaded const*, int, char const**, dyld3::MachOLoaded const*, unsigned long*) + 453
        frame #12: 0x0000000108e47025 dyld`_dyld_start + 37
    

    编译过程

    我们写的oc代码怎么能显示到手机上呢?


    image.png

    我们写的hm文件先要经过预编译,预编译主要是语法分析,语义分析,编译是生成中间代码然后汇编链接到可执行文件

    iOS的库

    静态库通常以.a,.lib或者.framework结尾,动态库以.tbd,.so,.framework结尾
    1.静态库:链接时,静态库会被完整的复制到可执行文件中,被多次使用就会有多份冗余拷贝
    2.动态库:链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序公用,节省内存


    image.png

    我们通过一个图来区分静态库和动态库
    在静态库中B和D分别被完整的复制到可执行文件,而动态库中则由系统动态加载到内存。

    dyld加载流程

    我们要先在官网上下载一份dyld源码

    概念什么是dyld

    dyld 是英文 the dynamic link editor 的简写,翻译过来就是动态链接器,是苹果操作系统的一个重要的组成部分。在 iOS/Mac OSX 系统中,仅有很少量的进程只需要内核就能完成加载,基本上所有的进程都是动态链接的,所以 Mach-O 镜像文件中会有很多对外部的库和符号的引用,但是这些引用并不能直接用,在启动时还必须要通过这些引用进行内容的填补,这个填补工作就是由 动态链接器dyld 来完成的,也就是符号绑定。动态链接器dyld 在系统中以一个用户态的可执行文件形式存在,一般应用程序会在 Mach-O 文件部分指定一个 LC_LOAD_DYLINKER 的加载命令,此加载命令指定了 dyld 的路径,通常它的默认值是 /usr/lib/dyld 。系统内核在加载 Mach-O 文件时,都需要用 dyld(位于 /usr/lib/dyld )程序进行链接。

    总括加载流程

    image.png

    _dyld_start源码

    __dyld_start:
    ...
      //调用dyldbootstrap:start函数
        # call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
        subl    $L__dyld_start_picbase-__dyld_start, %ebx # ebx = &__dyld_start
        subl    $0x1000, %ebx   # ebx = load address of dyld
        movl    %edx,(%esp) # param1 = app_mh
        movl    4(%ebp),%eax
        movl    %eax,4(%esp)    # param2 = argc
        lea     8(%ebp),%eax
        movl    %eax,8(%esp)    # param3 = argv
        movl    %ebx,12(%esp)   # param4 = dyld load address
        lea 28(%esp),%eax
        movl    %eax,16(%esp)   # param5 = &startGlue
        call    __ZN13dyldbootstrap5startEPKN5dyld311MachOLoadedEiPPKcS3_Pm
        movl    28(%esp),%edx
        cmpl    $0,%edx
        jne Lnew
    ...
    //调用main函数
        # LC_MAIN case, set up stack for call to main()
    Lnew:   movl    4(%ebp),%ebx
        movl    %ebx,(%esp) # main param1 = argc
        leal    8(%ebp),%ecx
        movl    %ecx,4(%esp)    # main param2 = argv
        leal    0x4(%ecx,%ebx,4),%ebx
        movl    %ebx,8(%esp)    # main param3 = env
    ...
    

    _dyld_start=====>dyldbootstrap::start======>main
    dyldbootstrap::start最主要的就是调用dyld::_main
    dyldbootstrap::start===>dyld::_main

    dyld::_main流程

    Dyld.png

    我们可以根据注释去了解大致流程,我们最主要研究的是initializeMainExecutable函数

    initializeMainExecutable

    initializeMainExecutable=====>runInitializers=====>processInitializers=======>recursiveInitialization

    recursiveInitialization

       context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);       
       // initialize this image
       bool hasInitializers = this->doInitialization(context);
    

    context.notifySingle中
    (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
    我们追踪sNotifyObjCInit发现赋值

    _dyld_objc_notify_register====> dyld::registerObjCNotifiers(mapped, init, unmapped);==>(sNotifyObjCInit = init;)
    在objc源码中搜索_dyld_objc_notify_register,发现此方法是在_objc_init中调用的
    并且sNotifyObjCInit的回调实现为
    调用load方法

    void
    load_images(const char *path __unused, const struct mach_header *mh)
    {
        if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
            didInitialAttachCategories = true;
            loadAllCategories();
        }
    
        // Return without taking locks if there are no +load methods here.
        if (!hasLoadMethods((const headerType *)mh)) return;
    
        recursive_mutex_locker_t lock(loadMethodLock);
    
        // Discover load methods
        {
            mutex_locker_t lock2(runtimeLock);
            prepare_load_methods((const headerType *)mh);
        }
    
        // Call +load methods (without runtimeLock - re-entrant)
        call_load_methods();
    }
    

    我们在_objc_init打断点

    * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
      * frame #0: 0x00000001002d461d libobjc.A.dylib`_objc_init at objc-os.mm:920:5
        frame #1: 0x000000010042b0bc libdispatch.dylib`_os_object_init + 13
        frame #2: 0x000000010043bafc libdispatch.dylib`libdispatch_init + 282
        frame #3: 0x00007fff6ad08791 libSystem.B.dylib`libSystem_initializer + 220
        frame #4: 0x000000010002f1d3 dyld`ImageLoaderMachO::doModInitFunctions(ImageLoader::LinkContext const&) + 535
        frame #5: 0x000000010002f5de dyld`ImageLoaderMachO::doInitialization(ImageLoader::LinkContext const&) + 40
        frame #6: 0x0000000100029ffb dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 493
        frame #7: 0x0000000100029f66 dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 344
        frame #8: 0x00000001000280b4 dyld`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 188
        frame #9: 0x0000000100028154 dyld`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 82
        frame #10: 0x0000000100016662 dyld`dyld::initializeMainExecutable() + 129
        frame #11: 0x000000010001bbba dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 6667
        frame #12: 0x0000000100015227 dyld`dyldbootstrap::start(dyld3::MachOLoaded const*, int, char const**, dyld3::MachOLoaded const*, unsigned long*) + 453
        frame #13: 0x0000000100015025 dyld`_dyld_start + 37
    

    系统会优先初始化libSystem,libdispatch,libdispatch libobjc,然后注册一个通知sNotifyObjCInit,当我们初始化主程序或者我们写的库时,会发通知让先加载该哭的load方法,然后doInitialization进行调用c++方法


    dyld流程分析图.png

    相关文章

      网友评论

        本文标题:iOS-OC底层10:dyld加载流程分析

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