应用程序加载

方法等是如何加载到内存中的呢,或者说类的加载都做了什么?在此之前,我们先探索 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() 函数

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

2、initializeMainExecutable() 初始化

initializeMainExecutable() 源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1 void initializeMainExecutable()
2 {
3 // record that we've reached this step
4 gLinkContext.startedInitializingMainExecutable = true;
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 }
16 // run initializers for main executable and everything it brings up
17 sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
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);
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() 初始化部分源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
__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() 部分源码:

1
2
3
4
5
6
7
8
9
10
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()

以上。

推荐博客