zoukankan      html  css  js  c++  java
  • OC 底层探索 12、应用程序加载

    方法等是如何加载到内存中的呢,或者说类的加载都做了什么?在此之前,我们先探索 APP 从响应用户点击到完全启动的过程 即 应用程序加载 都做了什么事情。

    首先我们准备一个 iOS 的 APP 工程,ViewController.m 中添加 load 方法,main.m中添加一个 C++ 的 方法,代码如下图:

    执行流程: load --> C++ -->  main() .

    为何 load 和 C++ 方法在函数之前呢?

    在此之前,我们要先大概回顾下编译流程。

    一、编译过程

    /* 书籍推荐:《程序员的自我修养》*/

    .h .m 等源文件的预编译 --> 编译 --> 汇编 --> 链接 --> 可执行文件

    1、动静态库

    静态库:.a .lib 等文件 - 编译器链接

    在链接阶段,将 汇编生成的一些目标文件,与引进的一些库链接在一起,打包成可执行文件,也称为静态链接,链接的是静态库。

    但静态库会同时生成多份,对内存和性能消耗比较大。

    动态库:.framework .so 等文件 - 运行期链接

    动态库,在编译时并不会编译到目标代码中,而是在程序被载入的时候,把相同的库用一份共享库的实例将其将在进去。 

    同样的库只存在一份大大节省了内存(增量更新),共享内存节约了资源,通过更新动态库达到更新程序的效果;同时减小了整个APP打包后的大小。例如:UIKint UIFoundation CoreFoundation libobjc 等。

    这些动态库如何加载呢?通过 dyld 链接器进行链接。

    2、dyld 动态链接器

    app 启动 --> 下层的很多动静态库:镜像文件(images) --> 由 dyld 进行处理:从加载的内存中读出来,读到相应的表中;然后加载主程序 --> 之后进行相应的 link --> 库的初始化(例 runtime 的 _objc_init) --> 这些必然要通过 dyld 来处理的,下面进行 dyld 的探究

    二、源码分析

    1、主流程

    dyld-750.6 源码下载,源码文件很多,我们如何入手呢?

    如上图,我们已知 load 方法首先执行,可打印下堆栈信息,可知,在此之前最初走了 _dyld_start .

    全局搜索 dyld_start,源码 是通过汇编编写:

    1、全局搜索 dyldbootstrap 方法

    命名空间包含整个代码文件,我们找到start,从上面打印的堆栈信息中也可得知:dyldbootstrap::start() 之后走 dyld::_main()

    2、dyld::_main()

     

    代码有点长 6192~6828, _main 函数返回值 是return result,搜索 result,找到下面几处赋值:

    我们直接从 6780 行开始读代码,可知 result = (uintptr_t)sMainExecutable->getEntryFromLC_xxx. 可执行主程序。(第6796行是个特殊特性的判断,我们暂不管它)

    2.1)文件内搜索 sMainExecutable

    由上源码,可知 instantiateFromLoadedImage 方法主要做的是镜像文件加载,继续进入方法:instantiateMainExecutable:可以看到是一些 command 处理。我们打开工具 machOView,将可执行文件拖入,如下图:

    简单来讲,即:instantiateFromLoadedImage 过程主要是做了:初始化创建了一个 imageLoader,加在了 image 里面,然后返回了一个主程序 sMainExecutable.

    dyld 做了什么(源码这里不再贴过来):

    1. 环境变量配置 - 
    2. 共享缓存 - 
    3. 主程序的初始化 - 
    4. 插入动态库 -  . 
    5. link 主程序 - 
    6. link 动态库
    7. dyld 的 main() 函数

    versionplatformpath 上下文context 等 --> mapSharedCache() --> 6577行 sMainExecutable --> 6641行  for循环的- loadInsertedDylib() --> 6659行 link(): sMainExecutable/images --> run all initializers: initializeMainExecutable() --> dyld 的 main: notifyMonitoringDyldMain() .

    2、initializeMainExecutable() 初始化

    initializeMainExecutable() 源码:

     1 void initializeMainExecutable()
     2 {
     3     // record that we've reached this step
     4     gLinkContext.startedInitializingMainExecutable = true;
     5 
     6     // run initialzers for any inserted dylibs
     7     ImageLoader::InitializerTimingList initializerTimes[allImagesCount()];
     8     initializerTimes[0].count = 0;
     9     const size_t rootCount = sImageRoots.size();
    10     if ( rootCount > 1 ) {
    11         for(size_t i=1; i < rootCount; ++i) {
    12             sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);
    13         }
    14     }
    15     
    16     // run initializers for main executable and everything it brings up 
    17     sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
    18     
    19     // register cxa_atexit() handler to run static terminators in all loaded images when this process exits
    20     if ( gLibSystemHelpers != NULL ) 
    21         (*gLibSystemHelpers->cxa_atexit)(&runAllStaticTerminators, NULL, NULL);
    22 
    23     // dump info if requested
    24     if ( sEnv.DYLD_PRINT_STATISTICS )
    25         ImageLoader::printStatistics((unsigned int)allImagesCount(), initializerTimes[0]);
    26     if ( sEnv.DYLD_PRINT_STATISTICS_DETAILS )
    27         ImageLoaderMachO::printStatisticsDetails((unsigned int)allImagesCount(), initializerTimes[0]);
    28 }

    1、递归进行 runInitializers() .

    2、跳到 processInitializers():

    3、搜索 recursiveInitialization()

    3.1)搜索 notifySingle()

    查找 sNotifyObjCInit

    sNotifyObjCInit 是通过函数 registerObjCNotifiers() 传过来的,

    _dyld_objc_notify_register 在 dyld 源码中找不到,我们去 objc 源码中查找试试:

    找到了 _objc_init 里面去了,那我们要探索的 initial 在哪呢?

    通过 3.1 我们知道 notifySingle 回调,下面继续探索初始化流程。

    3.2)回到 recursiveInitialization() 代码

    1598行:

    跳转 doInitialization() :

    doImageInit():

    通过 dyld 源码流程分析,可对应下面堆栈信息的执行流程:

    下载 libsystem 源码 - libsyscall_initializer() 初始化部分源码:

    __attribute__((constructor))
    static void libSystem_initializer(int argc,
                  const char* argv[],
                  const char* envp[],
                  const char* apple[],
                  const struct ProgramVars* vars)
    {
        ...... // 更多代码这里不全部展示了
    
        // No ASan interceptors are invoked before this point. ASan is normally initialized via the malloc interceptor:
        // _dyld_initializer() -> tlv_load_notification -> wrap_malloc -> ASanInitInternal
    
        _dyld_initializer();
        _libSystem_ktrace_init_func(DYLD);
    
        libdispatch_init();
        _libSystem_ktrace_init_func(LIBDISPATCH);
    
        ...... // 更多代码这里不全部展示了
    }

    从上源码可以看到 _dyld_initializer() --> libdispatch_init() .

    下载 libdispatch 源码 - libdispatch_init() 部分源码:

    void libdispatch_init(void)
    {
        ...... // 更多代码这里不做展示
        _dispatch_hw_config_init();
        _dispatch_time_init();
        _dispatch_vtable_init();
        _os_object_init();
        _voucher_init();
        _dispatch_introspection_init();    
    }        

    _os_onjc_init(): --> _objc_init()

    由此,整个执行流程实现了一个闭环(下图堆栈信息来自 objc 源码工程):

    总结:应用程序整个加载流程:

      dyld_start --> ...... --> libsystem: libsyscall_initializer() --> _dyld_initializer() --> libdispatch_init() --> libDispatch: _os_onjc_init() --> libobjc.A.dylib: _objc_init() --> _dyld_objc_notify_register(参数1, 参数2) 回调函数

      notifySingle() --> sNotifyObjCInit() --> registerObjCNotifiers() : 参数2

    3、下面继续探索

    我们通过文章顶部已知,方法的执行顺序是 load --> C++ --> main 下面对其原因进行简单探究。 

    1)load 方法调用

    _objc_init() 源码 中 load_images:

    跳转 call_load_methods():

    call_class_loads():

    2)Cxx 方法调用 

    doInitialization 源码中 doModInitFunctions(): 全部 Cxx 函数调用

    通过堆栈信息验证:

    3)main 函数

    如下,dyldbootstrap::start() 执行完毕后,跳转到 寄存器 rax 的位置,rax 即 main 函数:

    tip: main 函数作为入口函数,它是一个写定的函数,它的名字是固定的main。

    文章上面 dyld_start 源码中可知: 

    汇编源码中:call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue) --> LC_MAIN case, set up stack for call to main()

    以上。

    推荐博客

  • 相关阅读:
    我的黑客偶像
    2020-2021-1学期 学号20201222 《信息安全专业导论》第5周学习总结
    XOR加密
    pep/9
    我的黑客偶像
    学年2020-2021,1 学号:20201222《信息安全专业导论》第4周学习总结”
    IEEE754浮点数转换
    师生关系
    罗马数字转阿拉伯数字
    第三周总结
  • 原文地址:https://www.cnblogs.com/zhangzhang-y/p/13740071.html
Copyright © 2011-2022 走看看