库是已写好的、供使用的 可复用代码,每一个程序都要依赖不少基础的底层库。c++
从本质上,库是一种可执行代码的二进制形式。能够被操做系统
载入内存执行。库分为两种:静态库(.a .lib)和 动态库 (framework .so .dll)。bootstrap
所谓的静态、动态指的是 连接的过程
。windows
将一个程序编译成可执行程序的步骤以下:缓存
之因此称之为【静态库】,是由于在连接阶段,会将汇编生成的目标文件.o 与 引用的库一块儿连接到可执行文件中。对应的连接方式称为 静态连接。安全
若是多个进程须要引用到【静态库】,在内存中就会存在多份拷贝,如上图中进程1 用到了静态库一、5,进程2也用到了静态库一、5,那么静态库一、5在编译期
就分别被连接到了进程1和进程2中,假设静态库1占用2M内存,若是有20个这样的进程须要用到静态库1,将占用40M的空间。markdown
【静态库】的特色以下:闭包
编译期
完成的。执行期间代码装载速度快。占空间
)。全量更新
。若是 某一个静态库更新了,全部使用它的应用程序都须要从新编译、发布给用户。【动态库】在程序编译时并不会连接到目标代码中,而是在运行时
才被载入。不一样的应用程序若是调用相同的库,那么在内存中只须要有一份该共享库的实例,避免了空间浪费问题。同时也解决了静态库对程序的更新的依赖,用户只需更新动态库便可。架构
【动态库】在内存中只存在一份拷贝,若是某一进程须要用到动态库,只需在运行时动态载入便可。app
【动态库】的特色:dom
运行时
期(占时间
)。资源共享
。(所以动态库也称为共享库)增量更新
。
程序想要运行起来,它的可执行文件格式就要被操做系统所理解,好比 ELF
(Executable and Linking Format) 是 Linux
下可执行文件的格式,PE32/PE32+
(Portable Executable) 是 windows
的可执行文件的格式,那么对于 OS X
和 iOS
来讲 Mach-O
是其可执行文件的格式。
【Mach-O】 为 Mach Object 文件格式的缩写,是 iOS 系统不一样运行时期 可执行文件 的文件类型统称。它是一种用于 可执行文件、目标代码、动态库、内核转储的文件格式。
【Mach-O】 的三种文件类型:Executable、Dylib、Bundle
Executable 是 app
的二进制主文件,咱们能够在 Xcode 项目中的 products 文件中找到它:
Dylib 是动态库,动态库分为 动态连接库
和 动态加载库
。
动态连接库
:在没有被加载到内存的前提下,当可执行文件被加载,动态库也随着被加载到内存中。【随着程序启动而启动】
动态加载库
:当须要的时候再使用dlopen
等经过代码或者命令的方式加载。【程序启动以后】
Bundle 是一种特殊类型的Dylib,你没法对其进行连接。所能作的是在Runtime运行时经过dlopen
来加载它,它能够在macOS 上用于插件。
Image (镜像文件)包含了上述的三种类型;
Framework 能够理解为动态库。
【Mach-O】是一个以数据块
分组的二进制字节流,每一个【Mach-O】文件包括一个Mach-O头,而后是一系列的载入命令,再是一个或多个段,每一个段包括0到255个块。
保存【Mach-O】的一些基本信息,包括运行平台、文件类型、LoadCommands指令的个数、指令总大小,dyld标记Flags
等等。
紧跟Header,这些加载指令清晰地告诉加载器如何处理二进制数据,有些命令是由内核处理的,有些是由动态连接器处理的。加载【Mach-O】文件时会使用这部分数据肯定内存分布
以及相关的加载命令,对系统内核加载器和动态链接器起指导做用。好比咱们的main()
函数的加载地址、程序所需的dyld的文件路径、以及相关依赖库的文件路径。
每一个segment的具体数据保存在这里,包含具体的代码、数据
等等。
【Mach-O】 镜像文件 是由 segments
段组成的。
全部的段都是 page size
的倍数。
16kB
4KB
这里在普及一下 虚拟内存 和 内存页 的知识:
具备
VM
机制的操做系统,会对每一个运行的进程建立一个逻辑地址空间logical address space
或者叫 虚拟地址空间virtual address space
;该空间的大小由操做系统位数决定。
虚拟地址空间 会被分为相同大小的块
,这些块被称为内存页
(page)。计算机处理器和它的内存管理单元(MMU - memory management unit)维护着一张将程序的 虚拟地址空间 映射到 物理地址 上的分页表 page table
。
在 macOS
和早版本的 iOS
中,分页大小为 4kb
。在以后的基于A7
和 A8
的系统中,虚拟内存(64位的地址空间)地址空间的分页大小变为了16kb
,而物理RAM上的内存分页大小仍然维持在 4kb
;基于 A9
及之后的系统,虚拟内存和物理内存的分页都是16kb
。
在 segment
段内部还有许多的 section
区。section
名称为小写格式。 section
节 实际上只是一个 segment
段的子范围,它们没有页面大小的任何限制,可是它们是不重叠的。
代码段
,包含头文件、代码和只读常量
。只读
不可修改数据段
,包含全局变量,静态变量
等。可读可写
如何加载程序
,包含了方法和变量的元数据
(位置,偏移量),以及代码签名
等信息。只读
不可修改。【Mach-O】 通用文件,将多种架构的 Mach-O 文件合并而成。它经过 header
来记录不一样架构在文件中的偏移量,segment
占多个分页,header
占一页的空间。header
单独占一页 有利于 虚拟内存
的实现。
虚拟内存是一层 间接寻址 。
【虚拟内存】是在物理内存上创建的一个逻辑地址空间。创建在进程
和物理内存
之间的中间层
,它向上(应用)提供了一个连续的逻辑地址空间,向下隐藏了物理内存的细节。
虚拟内存被划分为一个个大小相同的Page
(64位系统上是16KB),提升管理和读写
的效率。 Page又分为只读
和读写
的Page。
虚拟内存解决的是管理全部进程使用 物理RAM 的问题。经过添加间接层来让每一个进程使用 逻辑地址空间,它能够映射到RAM 上的某个物理页上。这种映射 不是一对一
的,逻辑地址可能映射不到 RAM 上,也有可能有多个逻辑地址映射到同一个物理RAM 上。
虚拟内存使得逻辑地址能够没有实际的物理地址,也可让多个逻辑地址对应到一个物理地址。
- 针对第一种状况(逻辑地址可能映射不到 RAM ):在应用执行的时候,它被分配的逻辑地址空间都是能够访问的,当应用访问一个逻辑Page,而在对应的物理内存中并不存在的时候,这时候就发生了一次
Page fault
。当Page fault发生的时候,会中断当前的程序,在物理内存中寻找一个可用的Page,而后从磁盘中读取数据到物理内存,接着继续执行当前程序。- 而第二种状况(多个逻辑地址映射到同一个物理RAM 上)就是
多进程共享内存
。
对于文件能够不用一次性读入整个文件,可使用分页映射 mmap()
的方式获取。也就是把文件 某个片断 映射到进程逻辑内存的 某个页 上。当某个想要读取的页没有在内存中,就会触发 page fault
,内核只会读入那一页,实现文件的 懒加载。也就是说 【Mach-O】 文件中的 __TEXT 段能够映射到多个进程,并能够懒加载,且进程之间 共享内存。
__DATA 段是可读写的。这里使用到了Copy-On-Write
技术,简称【COW】。 也就是多个进程共享一页内存空间时,一旦有进程要作写操做,它会先将这页内存内容复制一份出来,而后从新映射逻辑地址到新的RAM 页上。也就是这个进程本身拥有了那页内存的拷贝。这就涉及到了 clean/dirty page
的概念。dirty page
含有进程本身的信息,而clean page
能够被内核从新生成(从新读磁盘)。多以 dirty page
的代价大于 clean page
。
共享内存
的,读取速度就会很快。dirty page
,若是检测有 clean page 就能够直接使用,反之就须要从新读取 DATA page。一旦产生了 dirty page,当dyld
执行结束后,__LINKEDIT 须要通知内核
当前页面再也不须要了,当别人须要使用的时候就能够从新 clean 这些页面。有两种主要的技术来保证应用的安全:ASLR 和 Code Sign。
【ASLR】的全称是Address space layout randomization
,翻译过来就是“地址空间布局随机化”
。App
被启动的时候,程序会被映射到逻辑的地址空间,这个逻辑的地址空间有一个起始地址,而【ASLR】技术使得这个起始地址是随机的。若是是固定的,那么黑客很容易就能够由起始地址+偏移量找到函数的地址。
【Code Sign】相信大多数开发者都知晓,这里要提一点的是,为了在运行时 验证【Mach-O】 文件的签名,在进行【Code Sign】的时候,加密哈希不是针对于整个文件,而是针对于每个Page
的。并存储在 __LINKEDIT 中。这就保证了在dyld
进行加载的时候,能够对每个page
进行独立的验证
。
exec()
是一个系统调用。系统内核把应用程序映射到新的地址空间,且每次起始位置都是随机的(由于ASLR
)。并将起始位置到0x000000
这段范围的进程权限都标记为不可读写不可执行。若是是32
位进程,这个范围至少是4kb
;若是是64
位进程则至少是4GB
。NULL
指针引用和指针截断偏差都是会被它捕获,这个范围也叫作 PAGEZERO
。
当内核
完成映射进程的工做后,会将名字为 dyld
的 Mach-O
文件映射到进程中的随机地址,它将PC 寄存器设为 dyld
的地址并运行。dyld
在应用进程中运行的工做是加载应用依赖的全部动态连接库,准备好运行所需的一切,它拥有的权限跟应用程序同样。
dyld(the dynamic link editor),【动态连接器】是苹果操做系统一个重要部分,在 iOS / macOS
系统中,仅有不多的进程只需内核就能够完成加载,基本上全部的进程都是动态连接的,因此 Mach-O
镜像文件中会有不少对外部的库和符号
的引用,可是这些引用并不能直接用,在启动时还必需要经过这些引用进行内容填充,这个填充的工做就是由 dyld
来完成的。
【动态连接加载器】在系统中以一个用户态
的可执行文件形式存在,通常应用程序会在Mach-O
文件部分指定一个 LC_LOAD_DYLINKER 的加载命令,此加载命令指定了dyld
的路径,一般它的默认值是“/usr/lib/dyld”
。系统内核在加载Mach-O
文件时,会使用该路径指定的程序做为动态库的加载器来加载dylib
。
dyld
加载时,为了优化程序启动,启用了共享缓存
(shared cache)技术。共享缓存会在进程启动时被dyld
映射到内存中,以后,当任何Mach-O
镜像加载时,dyld
首先会检查该Mach-O
镜像与所需的动态库是否在共享缓存
中,若是存在,则直接将它在共享内存中的内存地址映射到进程的内存地址空间。在程序依赖的系统动态库不少的状况下,这种作法对程序启动性能是有明显提高的。
从主执行文件header
获取到须要加载的所依赖的动态库列表,而header早就被内核映射过。而后它须要找到每一个dylib
,而后打开文件,读取文件起始位置,确保它是Mach-O
文件。接着会找到代码签名
并将其注册到内核
。而后在dylib文件的每一个segment
上调用 mmap()
。应用所依赖的dylib文件可能会再依赖其余dylib,因此dyld
所须要加载的是动态库列表一个递归
依赖的集合。通常应用会加载100到400 个dylib文件,但大部分都是系统的dylib,它们会被预先计算和缓存起来,加载速度很快。
在加载全部的动态连接库以后,它们只是处在相互独立的状态,须要将它们绑定起来,这就是Fix-ups
。代码签名使得咱们不能修改指令,那样就不能让一个dylib
调用另外一个 dylib
,这是就须要不少间接层。
Mach-O
中有不少符号,有指向当前 Mach-O 的,也有指向其余 dylib 的,好比printf。那么,在运行时,代码如何准确的找到printf
的地址呢?
Mach-O
中采用了PIC技术,全称是Position Independ code。意味着代码能够被加载到间接的地址上。当你的程序要调用printf
的时候,会先在 __DATA 段中创建一个指针指向printf
,在经过这个指针实现间接调用。dyld
这时候须要作一些fix-up
工做,即帮助应用程序找到这些符号的实际地址。主要包括两部分:rebasing
和binding
。
Rebasing:在镜像内部调整指针的指向。 Binding: 将指针指向镜像外部的内容。
之因此须要Rebase
,是由于刚刚提到的 ASLR 使得地址随机化,致使起始地址不固定,另外因为 Code Sign,致使不能直接修改 Image
。Rebase
的时候只须要增长对应的偏移量便可。(待Rebase的数据都存放在 __LINKEDIT中,能够经过MachOView查看:Dynamic Loader Info -> Rebase Info)
Binding
就是将这个二进制调用的外部符号进行绑定的过程。 好比咱们objc代码中须要使用到NSObject, 即符号_OBJC_CLASS_$_NSObject,可是这个符号又不在咱们的二进制中,在系统库 Foundation.framework中,所以就须要Binding
这个操做将对应关系绑定到一块儿。
Rebase
解决了内部的符号引用问题,而外部的符号引用则是由Bind
解决。在解决Bind
的时候,是根据字符串匹配的方式查找符号表,因此这个过程相对于Rebase
来讲是略慢的。
在 iOS 13
以前,全部的第三方App
都是经过dyld 2
来启动 App
的,主要过程以下:
Mach-O
的Header
和 Load Commands
,找到其依赖的库,并递归找到全部依赖的库dyld 3
被分为了三个组件:
Mach-O
解析器预先处理了全部可能影响启动速度的search path、@rpaths
和环境变量 而后分析Mach-O
的Header
和依赖,并完成了全部符号查找的工做 最后将这些结果建立成一个启动闭包 这是一个普通的daemon
进程,可使用一般的测试架构
这部分在进程中处理 验证启动闭包的安全性,而后映射到dylib之中,再跳转到main函数 不须要解析Mach-O
的 Header
和依赖,也不须要符号查找。
系统App的启动闭包被构建在一个Shared Cache
中,咱们甚至不须要打开一个单独的文件 对于第三方的App
,咱们会在App
安装或者升级的时候构建这个启动闭包。 在iOS、tvOS、watchOS
中,这一切都是App
启动以前完成的。在macOS
上,因为有Side Load App
,进程内引擎会在首次启动的时候启动一个daemon
进程,以后就可使用启动闭包启动了。
dyld 3
把不少耗时的查找、计算和I/O 的事件都预先处理好,这使得启动速度有了很大的提高。
有了前面的知识储备,接下来将探索app
的加载流程。
在应用程序的入口 main()
函数以前断点,查看堆栈信息
能够看到,先于main
函数调用的是 start
,同时,这一流程是由libdyld.dylib
库执行的。dyld
是开源库,能够下载源码探索。点击下载dyld 源码
为了看到更详细的调用过程,咱们在项目中的 ViewController 的 + (void) load
方法打断点。详细堆栈信息以下
可见,调用流程是从 _dyld_start
开始的,咱们在下载好的源码中搜索 _dyld_start
。在 dyldStartup.s
文件中找到了入口,这里是用汇编实现的,尽管在不一样架构下有所区别,但都是会调用 dyldbootstrap
命名空间下的start
方法,这和上面的堆栈顺序也是相同的。
call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
复制代码
uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[], const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue) {
// Emit kdebug tracepoint to indicate dyld bootstrap has started <rdar://46878536>
dyld3::kdebug_trace_dyld_marker(DBG_DYLD_TIMING_BOOTSTRAP_START, 0, 0, 0, 0);
// if kernel had to slide dyld, we need to fix up load sensitive locations
// we have to do this before using any global variables
rebaseDyld(dyldsMachHeader);
// kernel sets up env pointer to be just past end of agv array
const char** envp = &argv[argc+1];
// kernel sets up apple pointer to be just past end of envp array
const char** apple = envp;
while(*apple != NULL) { ++apple; }
++apple;
// set up random value for stack canary
__guard_setup(apple);
#if DYLD_INITIALIZER_SUPPORT
// run all C++ initializers inside dyld
runDyldInitializers(argc, argv, envp, apple);
#endif
// now that we are done bootstrapping dyld, call dyld's main
uintptr_t appsSlide = appsMachHeader->getSlide();
return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}
复制代码
dyldbootstrap::start中,主要过程为:
①使用全局变量以前,对dyld
进行rebase
操做,以修复为 real pointer
来运行;
②设置参数和环境变量;
③读取 app
二进制文件 Mach-O
的header
获得偏移量 appSlide
,而后调用dyld
命名空间下的_main
方法。
这里是dyld
的入口。内核加载了dyld
而后跳转到 _dyld_start
来设置一些寄存器的值以后 进入这个方法。返回 _dyld_start
所跳转到的目标程序的main
函数地址。精简的代码以下:
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide,
int argc, const char* argv[], const char* envp[], const char* apple[],
uintptr_t* startGlue)
{
......
// 设置运行环境,可执行文件准备工做
......
// load shared cache 加载共享缓存
mapSharedCache();
......
reloadAllImages:
......
// instantiate ImageLoader for main executable 加载可执行文件并生成一个ImageLoader实例对象
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
......
// load any inserted libraries 加载插入的动态库
if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib)
loadInsertedDylib(*lib);
}
// link main executable 连接主程序
link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
......
// link any inserted libraries 连接全部插入的动态库
if ( sInsertedDylibCount > 0 ) {
for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
ImageLoader* image = sAllImages[i+1];
link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
image->setNeverUnloadRecursive();
}
if ( gLinkContext.allowInterposing ) {
// only INSERTED libraries can interpose
// register interposing info after all inserted libraries are bound so chaining works
for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
ImageLoader* image = sAllImages[i+1];
// 注册符号插入
image->registerInterposing(gLinkContext);
}
}
}
......
//弱符号绑定
sMainExecutable->weakBind(gLinkContext);
sMainExecutable->recursiveMakeDataReadOnly(gLinkContext);
......
// run all initializers 执行初始化方法
initializeMainExecutable();
// notify any montoring proccesses that this process is about to enter main()
notifyMonitoringDyldMain();
return result;
}
复制代码
主要过程:
①第一步: 设置运行环境,为可执行文件的加载作准备工做;
②第二步: 映射共享缓存到当前进程的逻辑内存空间;
③第三步: 实例化主程序;
④第四步: 加载插入的动态库;
⑤第五步: 连接主程序;
⑥第六步: 连接插入的动态库;
⑦第七步: 执行弱符号绑定(weakBind);
⑧第八步: 执行初始化方法;
⑨第九步: 查找程序入口并返回main( ).
这一步 dyld
将咱们可执行文件以及插入的 lib
加载进内存,生成对应的image
。 sMainExecutable
对应着咱们的可执行文件,里面包含了咱们项目中全部新建的类。 InsertDylib
一些插入的库,他们配置在全局的环境变量 sEnv 中,咱们能够在项目中设置环境变量 DYLD_PRINT_ENV 为1来打印该 sEnv 的值。
static ImageLoaderMachO* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path) {
// try mach-o loader
if ( isCompatibleMachO((const uint8_t*)mh, path) ) {
ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext);
addImage(image);
return (ImageLoaderMachO*)image;
}
throw "main executable not a known format";
}
复制代码
isCompatibleMachO
是检查Mach-O的subtype是不是当前cpu能够支持; 内核会映射到主可执行文件中,咱们须要为映射到主可执行文件的文件,建立ImageLoader。
instantiateMainExecutable 就是实例化可执行文件, 这个期间会解析LoadCommand
, 这个以后会发送 dyld_image_state_mapped
通知; 在此方法中,读取image,而后addImage()
到镜像列表。
对上面生成的 Image
进行连接。这个过程就是将加载进来的二进制变为可用状态的过程。其主要作的事有对image
进行 load
(加载),rebase
(基地址复位),bind
(外部符号绑定),咱们能够查看源码:
void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool preflightOnly, bool neverUnload, const RPathChain& loaderRPaths, const char* imagePath) {
......
this->recursiveLoadLibraries(context, preflightOnly, loaderRPaths, imagePath);
......
this->recursiveRebaseWithAccounting(context);
......
this->recursiveBindWithAccounting(context, forceLazysBound, neverUnload);
}
复制代码
递归加载
全部依赖库
进内存。
递归
对本身以及依赖库进行rebase操做
。在之前,程序每次加载其在内存中的堆栈基地址都是同样的,这意味着你的方法,变量等地址每次都同样的,这使得程序很不安全,后面就出现 ASLR(Address space layout randomization,地址空间布局随机化),程序每次启动后地址都会随机变化,这样程序里全部的代码地址都是错的,须要从新对代码地址进行计算修复才能正常访问。
对库中全部nolazy的符号进行bind
,通常的状况下多数符号都是lazybind的,他们在第一次使用的时候才进行bind。
void initializeMainExecutable() {
// record that we've reached this step
gLinkContext.startedInitializingMainExecutable = true;
// run initialzers for any inserted dylibs
ImageLoader::InitializerTimingList initializerTimes[allImagesCount()];
initializerTimes[0].count = 0;
const size_t rootCount = sImageRoots.size();
if ( rootCount > 1 ) {
for(size_t i=1; i < rootCount; ++i) {
sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);
}
}
// run initializers for main executable and everything it brings up
sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
// register cxa_atexit() handler to run static terminators in all loaded images when this process exits
if ( gLibSystemHelpers != NULL )
(*gLibSystemHelpers->cxa_atexit)(&runAllStaticTerminators, NULL, NULL);
// dump info if requested
if ( sEnv.DYLD_PRINT_STATISTICS )
ImageLoader::printStatistics((unsigned int)allImagesCount(), initializerTimes[0]);
if ( sEnv.DYLD_PRINT_STATISTICS_DETAILS )
ImageLoaderMachO::printStatisticsDetails((unsigned int)allImagesCount(), initializerTimes[0]);
}
复制代码
这一步主要是调用全部image
的Initalizer
方法进行初始化。先为全部插入并连接完成的动态库执行初始化操做
sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);
复制代码
再为主程序可执行文件执行初始化操做
sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
复制代码
具体流程为: ImageLoader::runInitializers --> ImageLoader::processInitializers --> ImageLoader::recursiveInitialization
详细代码以下:
void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo) {
uint64_t t1 = mach_absolute_time();
mach_port_t thisThread = mach_thread_self();
ImageLoader::UninitedUpwards up;
up.count = 1;
up.imagesAndPaths[0] = { this, this->getPath() };
// 重点
processInitializers(context, thisThread, timingInfo, up);
context.notifyBatch(dyld_image_state_initialized, false);
mach_port_deallocate(mach_task_self(), thisThread);
uint64_t t2 = mach_absolute_time();
fgTotalInitTime += (t2 - t1);
}
复制代码
调用 processInitializers
void ImageLoader::processInitializers(const LinkContext& context, mach_port_t thisThread, InitializerTimingList& timingInfo, ImageLoader::UninitedUpwards& images) {
uint32_t maxImageCount = context.imageCount()+2;
ImageLoader::UninitedUpwards upsBuffer[maxImageCount];
ImageLoader::UninitedUpwards& ups = upsBuffer[0];
ups.count = 0;
// Calling recursive init on all images in images list, building a new list of
// uninitialized upward dependencies.
for (uintptr_t i=0; i < images.count; ++i) {
// 重点
images.imagesAndPaths[i].first->recursiveInitialization(context, thisThread, images.imagesAndPaths[i].second, timingInfo, ups);
}
// If any upward dependencies remain, init them.
if ( ups.count > 0 )
processInitializers(context, thisThread, timingInfo, ups);
}
复制代码
在这里,对镜像表中的全部镜像执行recursiveInitialization
,建立一个未初始化的向上依赖新表。若是依赖中未初始化完毕,则继续执行processInitializers
,直到所有初始化完毕。
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize, InitializerTimingList& timingInfo, UninitedUpwards& uninitUps) {
recursive_lock lock_info(this_thread);
recursiveSpinLock(lock_info);
if ( fState < dyld_image_state_dependents_initialized-1 ) {
uint8_t oldState = fState;
// break cycles
fState = dyld_image_state_dependents_initialized-1;
try {
// initialize lower level libraries first
for(unsigned int i=0; i < libraryCount(); ++i) {
ImageLoader* dependentImage = libImage(i);
if ( dependentImage != NULL ) {
// don't try to initialize stuff "above" me yet
if ( libIsUpward(i) ) {
uninitUps.imagesAndPaths[uninitUps.count] = { dependentImage, libPath(i) };
uninitUps.count++;
}
else if ( dependentImage->fDepth >= fDepth ) {
dependentImage->recursiveInitialization(context, this_thread, libPath(i), timingInfo, uninitUps);
}
}
}
// record termination order
if ( this->needsTermination() )
context.terminationRecorder(this);
// 重点 1: let objc know we are about to initialize this image
uint64_t t1 = mach_absolute_time();
fState = dyld_image_state_dependents_initialized;
oldState = fState;
context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
// 重点 2: initialize this image
bool hasInitializers = this->doInitialization(context);
// 重点 3: let anyone know we finished initializing this image
fState = dyld_image_state_initialized;
oldState = fState;
context.notifySingle(dyld_image_state_initialized, this, NULL);
if ( hasInitializers ) {
uint64_t t2 = mach_absolute_time();
timingInfo.addTime(this->getShortName(), t2-t1);
}
}
catch (const char* msg) {
// this image is not initialized
fState = oldState;
recursiveSpinUnLock();
throw;
}
}
recursiveSpinUnLock();
}
复制代码
在 recursiveInitialization 函数中,咱们重点关注
- context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);,
- doInitialization(context)
- context.notifySingle(dyld_image_state_initialized, this, NULL);
通知objc咱们要初始化这个镜像,这里 经过 notifySingle
函数对sNotifyObjCInit
进行函数调用。
static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo) {
......
if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && image->notifyObjC() ) {
uint64_t t0 = mach_absolute_time();
(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
}
......
}
复制代码
获取镜像文件的真实地址 【*sNotifyObjCInit)(image->getRealPath(), image->machHeader() 】,而 sNotifyObjCInit
是 经过 registerObjCNotifiers
中传递的参数(_dyld_objc_notify_init)进行赋值的。
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped) {
// record functions to call
sNotifyObjCMapped = mapped;
sNotifyObjCInit = init;
sNotifyObjCUnmapped = unmapped;
......
}
复制代码
继而找到,registerObjCNotifiers
的 拉起函数 _dyld_objc_notify_register
.
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
_dyld_objc_notify_init init,
_dyld_objc_notify_unmapped unmapped)
{
dyld::registerObjCNotifiers(mapped, init, unmapped);
}
复制代码
_dyld_objc_notify_register
函数是供 objc runtime 使用的,当objc镜像被映射,取消映射,和初始化时 被调用的注册处理器。咱们能够在 libobjc.A.dylib 库里,_objc_init
函数中找到其调用。
/*********************************************************************** * _objc_init * Bootstrap initialization. Registers our image notifier with dyld. * Called by libSystem BEFORE library initialization time **********************************************************************/
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init(); // 环境变量
tls_init();
static_init(); // C++
runtime_init(); // runtime 初始化
exception_init(); // 异常初始化
cache_init(); // 缓存初始化
_imp_implementationWithBlock_init(); //
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
复制代码
runtime初始化后,在_objc_init
中注册了几个通知,从dyld
这里接手了几个活,其中包括负责初始化相应依赖库里的类结构,调用依赖库里全部的load方法等。
就拿sMainExcuatable来讲,它的initializer方法是最后调用的,当initializer方法被调用前dyld
会通知runtime进行类结构初始化,而后再通知调用load
方法,这些目前还发生在main函数前,但因为lazy bind机制,依赖库多数都是在使用时才进行bind
,因此这些依赖库的类结构初始化都是发生在程序里第一次使用到该依赖库时才进行的。
当全部的依赖库的lnitializer都调用完后,dyld::main 函数会返回程序的main()
函数地址,main函数被调用,从而代码来到了咱们熟悉的程序入口。
那么 _objc_init
又是如何被调用的呢?
看调用堆栈,在 ImageLoader::recursiveInitialization
函数中,咱们以前关注的重点2: doInitialization
// 重点 2: initialize this image
bool hasInitializers = this->doInitialization(context);
复制代码
bool ImageLoaderMachO::doInitialization(const LinkContext& context) {
CRSetCrashLogMessage2(this->getPath());
// mach-o has -init and static initializers
doImageInit(context);
doModInitFunctions(context);
CRSetCrashLogMessage2(NULL);
return (fHasDashInit || fHasInitializers);
}
复制代码
在 doModInitFunctions以后 会 先执行 libSystem_initializer
,保证系统库优先初始化完毕,在这里初始化 libdispatch_init
,进而在_os_object_init
中 调用 _objc_init
。
因为 runtime 向 dyld 绑定了回调,当 image 加载到内存后,dyld 会通知 runtime 进行处理
runtime 接手后调用 map_images
作解析和处理,接下来 load_images
中调用 call_load_methods
方法,遍历
全部加载进来的 Class,按继承
层级依次调用 Class 的 +load
方法和其 Category 的 +load
方法。
至此,可执行文件和动态库中全部的符号(Class,Protocol,Selector,IMP,…)都已经按格式成功加载到内存中,被 runtime
所管理,在这以后,runtime 的那些方法(动态添加 Class、swizzle 等等才能生效)
APP是由内核引导启动的,kernel内核作好全部准备工做后会获得线程入口及main入口,可是线程不会立刻进入main入口,由于还要加载动态连接器(dyld),dyld会将入口点保存下来,等dyld加载完全部动态连接库等工做以后,再开始执行main函数。
系统kernel作好启动程序的初始准备后,交给dyld负责。
dyld接手后,系统先读取 App 的可执行文件(Mach-O
文件),从里面获取dyld
的路径,而后加载dyld
,dyld
去初始化运行环境,开启缓存策略,配合 ImageLoader 将二进制文件按格式加载到内存,加载程序相关依赖库(其中也包含咱们的可执行文件),并对这些库进行连接
,最后调用每一个依赖库的初始化
方法,在这一步,runtime
被初始化。当全部依赖库初始化后,轮到最后一位(程序可执行文件)进行初始化,在这时runtime会对项目中全部类进行类结构初始化
,而后调用全部的load
方法。最后dyld
返回main()
函数地址,main()
函数被调用。
这个过程远比写出来的要复杂,这里只提到了 runtime 这个分支,还有像 GCD、XPC 等重头的系统库初始化分支没有说起(固然,有缓存机制在,它们也不会玩命初始化),总结起来就是 main 函数执行以前,系统作了茫茫多的加载和初始化工做,最终引入那个熟悉的main函数。