原文出自【听云技术博客】:http://blog.tingyun.com/web/a...web
前言数据结构
最近看 ObjC的runtime 是怎么实现 +load 钩子函数的实现。进而引伸分析了 dyld 处理 Mach-O 的这部分机制。架构
1.简单分析 Mach-O 在dyld 中是如何被加载到内存中的;函数
2.分析了 +load 的 特殊加载时机;spa
+ load3d
上图的调用栈告诉咱们哪些函数被调用了。指针
dyld 是Apple 的动态连接器;在 xnu 内核为程序启动作好准备后,就会将 PC 控制权交给 dyld 负责剩下的工做 (dyld 是运行在 用户态的, 这里由 内核态 切到了用户态)。blog
每当有新的镜像加载以后,都会执行 load-images 方法进行回调,这里的回调是在整个ObjC runtime 初始化时 -objc-init 注册的 :继承
有新的镜像被 map 到 runtime 时,调用 load-images 方法,并传入最新镜像的信息列表 infoList:递归
这里的镜像就是 一些 System framework 的二进制。
进入 下图函数 load-images-nolock 查找 load 函数
调用 prepare-load-methods 对 load 方法的调用进行准备(将须要调用 load 方法的类添加到一个列表中)
调用 -getObjc2NonlazyClassList 获取全部的类的列表以后,会经过 remapClass 获取类对应的指针,而后调用 schedule-class-load 递归地 将当前类和没有调用 + load 父类进入列表。
在执行 add-class-to-loadable-list(cls) 将当前类加入加载列表以前,会先把父类加入待加载的列表,保证父类在子类前调用 load 方法。
在将镜像加载到运行时、对 load 方法的准备就绪,执行 call-load-methods,开始调用 load 方法:
其中 call-class-loads 会从一个待加载的类列表 loadable-classes 中寻找对应的类,而后找到 @selector(load) 的实现并执行。
分析到这里,已经能得知 load 函数是如何被调用的。
接下来分析 dyld 这部分怎么加载镜像的
1.1 数据结构
mach-o 文件头 操做。
1.2 ImageLoader
每个加载的 Mach-O 文件都会存在这样一个ImageLoader 的 实例,上图能够看出 这里ImageLoader是一个抽象类,每一种具体的Mach-O 文件都会继承 ImageLoader类, 继承关系 以下图:
在加载时会根据Mach-O的格式不一样选择生成不用的实例。
1.3 -main
在调用-main 函数以后,作了一下几件事情:
选择运行环境(iOS 模拟器)
初始化数据、设置全局变量、上下文信息
检查文件是否Restricted
走完这些流程,就会调用 instantiateFromLoadedImage 函数,开始加载Mach-O 而且实例化 为 ImageLoader。
1.4 instantiateFromLoadedImage
这个函数作了三件事情:
检查Mach-O 文件是否合法
初始化 ImageLoader 实例
调用addImage 函数添加 初始化后的实例到管理模块中
1.5 isCompatibleMachO
Mach-O 文件的合法性检查:
mach-header 中的 cputype与当前运行的CPU 版本是否支持
mach-header 中的 subtype 在该CPU 架构下的全部版本均可以支持
cputype 就是CPU 平台, x86,ARM ,POWERPC 等, 而subtype 就是同一个平台下的不一样版本, 例如:arm7,arm7.
1.6 ImageLoaderMachO: : instantiateMainExecutable
该函数主要经过 sniffLoadCommands 函数来判断 Mach-O 文件是不是压缩过的,而后分别 选择不一样的 子类实例化。
1.7 sniffLoadCommands
这个函数主要作两件事情
判断Mach-O文件是classic的仍是compressed的。
获取mach-O文件的segment的数量。
1.8 ImageLoaderMachOClassic: :instantiateMainExecutable
classic 与 compressed 的初始化大同小异,先分析Classic 的实现
能够看到加载的核心代码 还在 instantiateStart 函数中
1.9 instantiateStart
这里仍然没有出现加载的核心代码,只是根据以前得到的数据申请分配了内存,并计算 segments的 指针。 ImageLoaderMachOClassic 的构造函数才是加载 的核心逻辑。
2.0 ImageLoaderMachOClassic
根据Mach-O 文件 segments 将数据加载到 内存中, 任何返回 调用 addImage 函数。
2.1 addImage
这个函数只是作了数据更新
将image 添加到管理容器中
更新了内存分布的信息
end
整个加载过程基本分为三个步骤:
合法加测
解析Mach-O文件头信息,将segments 的具体信息 构建到image 的实例中
添加image 到管理容器
根据 dyld的源代码的粗略分析, 更多信息须要分析 xnu 内核代码。
参考
ObjC runtime 源代码
dyld 源代码
《Mac OSX and iOS Internals》