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()

以上。

推荐博客

原文地址:https://www.cnblogs.com/zhangzhang-y/p/13740071.html