iOS 底层探索篇 —— dyld加载流程(上)

iOS 底层探索篇 —— dyld加载流程(上)

    • 1. 应用程序加载
      • 1.1库
      • 1.2 编译过程
      • 1.3 DYLD(链接器)
      • 1.4 APP 启动流程
    • 2. dyld探索

1. 应用程序加载

1.1库

每个程序的运行都会依赖一些基础的库,比如说UIKitCoreFoundation等,库是一些可执行的二进制文件,能被操作系统加载到内存中。库有两种形式,就是静态库(.a , .lib)和动态库(.so , .dll),两个库主要表现在链接的区别

静态库:静态库在编译时加载,在链接时完整的复制可执行文件中,此时的静态库就不会在改变了,因为它是编译时被直接拷贝一份,复制到目标程序里的

  • 优点编译后的执行文件不需要外部库的支持直接就能使用
  • 缺点:有多个app使用就会被复制多份不能共享占用更多冗余内存。所有的函数都在库中,因此当修改函数时需要重新编译

动态库:程序编译时并不会链接到目标程序中,目标程序只会存储指向动态库的引用,在程序运行时由系统动态加载到内存

  • 优点:库是动态的,运行时才载入,因此修改库中函数时,不需要重新编译同一个库可以被多个程序使用,内存只加载一次节省内存空间。因为不需要拷贝至目标程序中,所以不会影响目标程序的体积,所以app体积相对静态库来说是减少的
  • 缺点:动态库是运行时由系统加载到内存,如果环境缺少库或者库的版本不正确,那么程序就会无法运行。并且因为在编译过程中没有整合到目标代码中,只有执行到该函数时候才去调用库中的函数,所以首次加载比较耗时

iOS 底层探索篇 —— dyld加载流程(上)_第1张图片

1.2 编译过程

其中编译过程如下图所示,主要分为以下几步:

源文件:载入.h、.m、.cpp等文件
预编译:替换宏,删除注释,展开头文件,产生.i文件
编译:编译器将.i文件转换为汇编语言,产生.s文件
汇编:将汇编文件转换为机器码文件,产生.o文件
链接:对.o文件中引用其他库的地方进行引用,生成最后的可执行文件
iOS 底层探索篇 —— dyld加载流程(上)_第2张图片

1.3 DYLD(链接器)

介绍完了库,那么库是怎么加载到内存里的呢?这就要说到一个非常牛逼且重要的东西:DYLD(链接器)

DYLD(the dynamic link editor)是苹果的动态链接器,是苹果操作系统的重要组成部分,在app被编译打包成可执行文件格式的Mach-O文件后,交由dyld负责连接,加载程序

1.4 APP 启动流程

在探索之前,先看一下APP的启动流程图。

iOS 底层探索篇 —— dyld加载流程(上)_第3张图片

2. dyld探索

这里正式开始dyld的探索,打开程序,然后在main函数里面打下断点然后运行。运行之后可以看到,在main函数之前有调用一个start,那么在前面是否还有什么东西呢 ?
iOS 底层探索篇 —— dyld加载流程(上)_第4张图片
点进去start看到是libdyld,也就是这次的主角,DYLD链接器。那么接下来该怎么探索呢?
iOS 底层探索篇 —— dyld加载流程(上)_第5张图片
试着添加一个start的断点,运行后发现并没有卡住,还是停在了之前的main函数的地方,证明真正在下面调用的函数名不叫start,但是发现了load方法被调用了。说明load方法是在main函数之前的
iOS 底层探索篇 —— dyld加载流程(上)_第6张图片
所以我们试着在main函数打下一个断点然后运行。可以看到最开始的地方是_dyld_start。也就是之前在main函数那里看到的start。
可以在侧边栏查看
iOS 底层探索篇 —— dyld加载流程(上)_第7张图片
或者lldb输入bt查看
iOS 底层探索篇 —— dyld加载流程(上)_第8张图片
接下来就去看dyld源码。打开源码,搜索dyld_start,发现架构不同,写的汇编也不同,但是大体上都是调用了dyldbootstrap::start这个方法,这里以arm64为例。
iOS 底层探索篇 —— dyld加载流程(上)_第9张图片

看到调用了dyldbootstrap::start方法,接下来就去搜索dyldbootstrap这个命名空间,然后继续在其中找到start方法,发现其是返回了dyld::_main函数的结果。
iOS 底层探索篇 —— dyld加载流程(上)_第10张图片
点击去dyld::_main,发现有将近千行的代码。
iOS 底层探索篇 —— dyld加载流程(上)_第11张图片
这里,就直接从返回值进行反推
iOS 底层探索篇 —— dyld加载流程(上)_第12张图片
查找哪里对result进行赋值,看到两个赋值都和sMainExecutable有关。
iOS 底层探索篇 —— dyld加载流程(上)_第13张图片
接下来搜索sMainExecutable。
中途看到sMainExecutable做的一些bind的工作,证明确实是我们要找的。
iOS 底层探索篇 —— dyld加载流程(上)_第14张图片
在这里插入图片描述
最终找到sMainExecutable赋值的地方。
iOS 底层探索篇 —— dyld加载流程(上)_第15张图片
点击查看instantiateFromLoadedImage
iOS 底层探索篇 —— dyld加载流程(上)_第16张图片
点进instantiateMainExecutable查看,其中sniffLoadCommands中是获取Mach-O类型文件的Load Command的相关信息,并对其进行各种校验。
iOS 底层探索篇 —— dyld加载流程(上)_第17张图片
接下来回到dyld::_main继续看到 initializeMainExecutable()
在这里插入图片描述
首先看到会拿到镜像文件的个数,然后挨个对镜像文件进行runInitializers,然后对sMainExecutable进行runInitializers。
iOS 底层探索篇 —— dyld加载流程(上)_第18张图片
点击runInitializers。既然是初始化,那么其核心就是processInitializers
iOS 底层探索篇 —— dyld加载流程(上)_第19张图片
processInitializers点进去看到,这里对images进行了挨个的调用recursiveInitialization函数进行递归实例化
iOS 底层探索篇 —— dyld加载流程(上)_第20张图片
接下来搜索recursiveInitialization。发现这里对依赖文件的递归初始化,然后调用了2个重要的函数:
notifySingledoInitialization
iOS 底层探索篇 —— dyld加载流程(上)_第21张图片
先来看notifySingle,发现点不进去,接下来就搜索notifySingle看看是在哪里被赋值的。
iOS 底层探索篇 —— dyld加载流程(上)_第22张图片
发现赋值的地方后点进去。这里的重点在(*sNotifyObjCInit)(image->getRealPath(), image->machHeader())
iOS 底层探索篇 —— dyld加载流程(上)_第23张图片
接下来搜索sNotifyObjCInit,发现类型是_dyld_objc_notify_init
iOS 底层探索篇 —— dyld加载流程(上)_第24张图片
继续找,发现这里有赋值操作,也就是说, 这里说将registerObjCNotifiers的第二个参数赋值给sNotifyObjCInit
iOS 底层探索篇 —— dyld加载流程(上)_第25张图片
接下来寻找registerObjCNotifiers,发现在_dyld_objc_notify_register调用。
iOS 底层探索篇 —— dyld加载流程(上)_第26张图片
然后在源码中搜索_dyld_objc_notify_register,发现在_objc_init中对调用了_dyld_objc_notify_register。
iOS 底层探索篇 —— dyld加载流程(上)_第27张图片
接着在这里打个断点然后运行。_dyld_start到recursiveInitialization已经探索过了,接下来要探索_objc_init到recursiveInitialization的过程
iOS 底层探索篇 —— dyld加载流程(上)_第28张图片
iOS 底层探索篇 —— dyld加载流程(上)_第29张图片
_objc_init的上一个函数_os_object_initlibdispatch,所以下载libdispatch源码然后搜索_os_object_init。发现确实调用了_objc_init。
iOS 底层探索篇 —— dyld加载流程(上)_第30张图片
接着搜索libdispatch_init,发现确实调用了_os_object_init。
iOS 底层探索篇 —— dyld加载流程(上)_第31张图片
接着下载libSystem源码,然后寻找libSystem_initializer,发现确实调用了libdispatch_init。
iOS 底层探索篇 —— dyld加载流程(上)_第32张图片
接下来,又回到了dyld,查找doModInitFunctions。这里注释了libSystem initializer必须先跑起来 ,也就是说无论是libdispatch还是objc,都是依赖libSystem。所以这里烘托了doModInitFunctions是在做libSystem的加载

在这里插入图片描述
这里的func就是libSystem 的initializer,func的调用也就是调用了libSystem 的initializer。
iOS 底层探索篇 —— dyld加载流程(上)_第33张图片
在看到哪里调用了doModInitFunctions,发现是在doInitialization里面。
iOS 底层探索篇 —— dyld加载流程(上)_第34张图片
在看哪里调用了doInitialization,就回到了之前的recursiveInitialization
iOS 底层探索篇 —— dyld加载流程(上)_第35张图片
之前说过,notifySingle中的sNotifyObjCInit最终会在_dyld_objc_notify_register赋值,而_dyld_objc_notify_register则在_objc_init里面调用,_objc_init则是doInitialization调用的,那么doInitialization和notifySingle有什么关系呢?
_objc_init中调用了_dyld_objc_notify_register(&map_images, load_images, unmap_image),其中map_images是沟通前面的内容的一些非常关键的数据,但是只是对方法进行了赋值,有没有调用方法确是不知道的。只有当调用sNotifyObjCMapped的时候,才执行map_images方法的。

其实notifySingle就是相当于注册了一个通知,当image加载完毕了之后,就发一个通知给notifySingle。
那么是如何通知的呢,请听下回分解。

你可能感兴趣的:(iOS底层,ios,objective-c,swift,xcode)