iOS底层原理_12应用程序加载

第十二节课 应用程序加载

应用程序的加载原理

首先,我们每次Xcode跑程序的时候不知道大家有没有好奇它这个启动流程到底是什么样子的?

编译过程:


12-加载流程.png

!

  • 源文件:载入.h、.m、.cpp等文件
  • 预处理:替换宏,删除注释,展开头文件,产生.i文件
  • 编译:将.i文件转换为汇编语言,产生.s文件
  • 汇编:将汇编文件转换为机器码文件,产生.o文件
  • 链接:对.o文件中引用其他库的地方进行引用,生成最后的可执行文件

那么什么是可执行文件呢?我们来到项目中,编译一下,在Products目录下,会出现一个黑不溜秋的文件,这个就是可执行文件。

注:Xcode没有显示Products文件夹的情况下:
1.找到项目文件.xcodeproj
2.右击「显示包内容」
3.打开 project.pbxproj 文件
4.搜索mainGroup
5.将mainGroup后面的value串,作为productRefGroup后面的value串
库:可执行的二进制文件,能够被操作系统加载到内存

静态库与动态库

静态库:在链接阶段,会将可汇编生成的目标程序与引用的库一起链接打包到可执行文件当中。此时的静态库就不会在改变了,因为它是编译时被直接拷贝一份,复制到目标程序里的

  • 优点:编译完成后,库文件实际上就没有作用了,目标程序没有外部依赖,直接就可以运行
  • 缺点:由于静态库会有两份,所以会导致目标程序的体积增大,耗费性能

动态库:程序编译时并不会链接到目标程序中,目标程序只会存储指向动态库的引用,在程序运行时才被载入

  • 优点
  1.  减少打包之后app的大小:因为不需要拷贝至目标程序中,所以不会影响目标程序的体积,与静态库相比,减少了app的体积大小。
    
  2.  共享内存,节约资源:同一份库可以被多个程序使用
    
  3.  通过更新动态库,达到更新程序的目的:由于运行时才载入的特性,可以随时对库进行替换,而不需要重新编译代码,这也是之前总听到的热更新,但是由于苹果的政策后续也进制使用了。
    
  • 缺点:动态载入会带来一部分性能损失,使用动态库也会使得程序依赖于外部环境,如果环境缺少了动态库,或者库的版本不正确,就会导致程序无法运行

静态库和动态库的图示如图所示


12-静态库动态库.png

加载流程我们知道了,动静态库我们也知道了,但是这些库是怎么加载到程序中的呢?这就是我们今天要研究的dyld链接器了。

dyld::_main函数源码分析

12-dyld.png

dyld(the dynamic link editor)是苹果的动态链接器,主要是链接一些动静态库的一个工具。那我们怎么去找到并分析了解dyld呢?这就需要我们通过源码的形式去了解了。

我们先写一个main函数,并打一个断点.断住后我们去看下进程

int main(int argc, char * argv[]) {
}

我们发现在main之前还有一个start的进程


12-start.png

而这个start,我们只能看到


12-dyld_start.png

如果我们想更精准的定位到的话,按照以前的经验就是下一个start的符号断点,但是,尝试后发现并没有断到start,那就说明,这个dylib.start底层并不是start。那么我们只能通过main函数之前现执行的load方法来进行断点了。

12-load.png

断住后,控制台输入bt查看


12-bt.png

在最下面我们可以看到,_dyld_start,这个其实就是我们要找的start方法。
接下来我们需要下载一下dyld的源码进行下一步的分析(官方网址:https://opensource.apple.com/tarballs/dyld/)

dyld流程

打开源码后,我们搜索_dyld_start,发现有很多结果,但是仔细看一下,其实是区分了不同架构的。我们选择一个进行分析

12-dyld源码.png

这么些汇编语言,我们又不懂,怎么办呢?其实我们只要看它的主要调用方法就行了。也就是下图中的call后面的方法dyldbootstrap::start

12-dyldbootstrap.png

接下来因为C++的一些语法问题,我们如果直接搜索dyldbootstrap::start是搜不到的,我们只能先搜索dyldbootstrap,再去找start函数。

12-dyldbootstrapstart.png

![12-return.png](https://upload-images.jianshu.io/upload_images/24944531-435327e63c9ec942.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

根据我们经验,直接看return,然后进入_main函数查看

进入dyld::_main后,代码很长,我们还是通过看return的返回值,进行反推,我们来到最下面看到的是return result;,所以我们全局搜索一下result,先从这里入手

12-result.png

我们会看到这样两个地方,可以看出,关键的点就在sMainExecutable这个地方。
继续搜索sMainExecutable =,搜索后发现了一条实例化的语法如下图

实例化对象

12-sMainExecutable .png
12-instantiateFromLoadedImage.png

进入instantiateMainExecutable源码,其作用是为主可执行文件创建映像,返回一个ImageLoader类型的image对象,即主程序。其中sniffLoadCommands函数时获取Mach-O类型文件的Load Command的相关信息,并对其进行各种校验。

12-instantiateMainExecutable.png

现在我们看完了实例化主程序instantiateMainExecutable,再回到_main函数往下看

插入动态库

遍历DYLD_INSERT_LIBRARIES环境变量,调用loadInsertedDylib加载


12-loadInsertedDylib.png

link 主程序和动态库

12-link主程序和动态库.png

弱符号绑定

12-weakBind.png

执行初始化

12-initializeMainExecutable.png

进入后发现里面有一个循环遍历执行的方法runInitializers

全局搜索runInitializers(cons,找到如下源码,其核心代码是processInitializers函数的调用

12-runInitializers.png

进入processInitializers函数的源码实现,其中对镜像列表调用recursiveInitialization函数进行递归实例化

12-processInitializers.png

全局搜索recursiveInitialization(cons函数,其源码实现如下

12-recursiveInitialization.png

可以看到上半部分的for循环是先做一些初始化低级库的过程,所以不是很重要。而下面的注释也提提醒了我们let objc know we are about to initialize this image所以下面的这部分是这段源码的重点。

全局搜索notifySingle(函数,进入notifySingle源码,依旧是根据找重点代码
第一段if函数是拿到各项数据,第二段if函数是处理共享缓存与UUID的,第三段if函数当中我们看到了在上一层出现的state == dyld_image_state_dependents_initialized,而最后一段if函数是unloaded images的处理。所以重点函数必定在第三段if函数中

12-notifySingle.png

全局搜索sNotifyObjCInit,只找到一个赋值操作

12-registerObjCNotifiers.png

被赋予的值是来自于registerObjCNotifiers的第二个参数,我们再继续反推上去

12-_dyld_objc_notify_register.png

在objc源码中搜索_dyld_objc_notify_register,发现在_objc_init源码中调用了该方法,并传入了参数,所以sNotifyObjCInit的赋值的就是objc中的load_images,而load_images会调用所有的+load方法。所以综上所述,notifySingle是一个回调函数

12-_objc_init.png

通过断点定位到_objc_init上一级发起的点libdispatch源码当中

12-libdispatch.png

进入libdispatch源码中,搜索_objc_init

12-_os_object_init.png

12-bt线程.png

根据堆栈信息继续往前推,来到了libdispatch_init

12-libdispatch_init.png

再往前,来到了LibsystemlibSystem_initializer

12-libSystem_initializer.png

继续看堆栈,发现又来到了dylddoModInitFunctions

12-doModInitFunctions.png

我们可以看到有这么一段


12-判断libSystem是否加载完成.png

判断libSystem是否加载完成,否则报错,这也证明我们之前的推断是正确的,libSystem_initializer加载之后才能继续加载别的库。

继续往前推,我们来到了doInitialization,同时在bt的堆栈中也可以看到相应的线程

12-doInitialization.png

再往前找到doInitialization被调用的地方

12-recursiveInitialization.png

我们发现,又回来了~这不就是之前开始地方,哈哈哈。完美的形成了一个闭环,验证了我们之前的所有推断。

通知dyld可以进main函数了

回到我们的_main函数当我们跑完了所有的初始化,我们也就可以通知其他监听者,即将进入main函数。

12-notifyMonitoringDyldMain.png

整体流程图整理如下:


12-dyld流程总结.png

其实转了这么一大圈,我们还是懵逼的状态,我们根本不知道它到底是怎么做到的,只知道这是启动前的这一个必要的工序,但是这个探索的过程,让我们了解到了dyld的大致流程,同时我们也学习到了一种反推模式的推敲方式,这也是我们之后经常会用到的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容