在程序启动的时候,我们总是认为程序的入口是从main函数开始的,但是在此之前,
dyld已经干了很多事情了, 那什么是dyld呢? 它能够干什么, 它又有什么作用呢? 接下来, 就来研究下这个神奇的东西…
Mach-O
我们先来看下Mach-O, 在OS X 与 iOS 系统上的可执行文件格式是Mach-O, 我们编译过程产生的.O文件,以及程序的可执行文件, 动态库等都是Mach-O文件. 我们来看看下Mach-O结构:

Mach-o包含三个基本区域:
- 头部(header structure)保存了一些基本信息,包括了该文件运行的平台、文件类型、LoadCommands的个数等.
- 加载命令(load commands) 在加载Mach-O文件时会使用这里的数据来确定内存的分布以及相关的加载命令, 如main函数的加载地址, 程序所需的dyld的文件路径, 以及相关依赖库的文件路径
- Data(数据) 包含多个段(segment),每个段可以拥有零个或多个区域(section)每一个段(segment)都拥有一段虚拟地址映射到进程的地址空间
我们可以从loader.h里面找到header的结构:
1 | struct mach_header_64 { |
magic: 魔数,用于确认该文件是用64位还是32位
cputype: CPU类型
cpusubtype:机器类型
filetype:文件类型
ncmds: 加载的指令条数
sizeofcmds: 所有加载的指令大小
flags: 标志位
reserved: 保留字段 (32位无此字段)
接下来我们在来看下load_command:
1 | struct load_command { |
这个很好理解, cmd 是加载指令的类型, cmdsize 是指令的总大小
64位下在segment_command结构:
1 | struct segment_command_64 { /* for 64-bit architectures */ |
cmd: 就是加载指令的类型, #define LC_SEGMENT_64 0x19 /* 64-bit segment of this file to be mapped */ 这里LC_SEGMENT_64意思是将文件中64位的段映射到进程的地址空间
vmaddr: 段虚拟内存的地址
vmsize: 虚拟内存大小
fileoff: 段在文件中的偏移
filesize: 文件大小
nsects: 该段里有多少个section
64位下的section结构:
1 | struct section_64 { /* for 64-bit architectures */ |
sectname: section的名称
segname: 该section所属的segment
addr: 该section的内存地址
size: 该section的大小
offset: 该section的文件偏移
align: 该section的内存对齐
reloff: 重定位入口的文件偏移
nreloc: 需要重定位的数量
flags: 标志位 (section类型和属性)
reserved1 保留字段(偏移和索引)
reserved2: 保留字段 (数量和大小)
reserved3: 保留字段
1 |
在头文件中, 我们还找到了一些用于存放不同类型数据的段:
"__text": 源代码编译后的机器指令就放在这个段中, 也称为代码段"__data": 初始化全局变量和静态变量就放在这个段"__bss": 未初始化的全局变量和静态变量放在这个段"__OBJC": runtime 段"__symbol_table": 符号表"__selector_strs": 方法名字符串表"__ICON": 图标段
什么是dyld
The dynamic loader for Darwin/OS X is called dyld, and it is responsible for loading all frameworks, dynamic libraries, and bundles (plug-ins) needed by a process. 苹果文档是这么解释的, 就是动态加载器, 它负责加载程序所需要的所有的frameworks, 动态库以及插件. 更多详情
由于dyld是开源的, 所以我们可以下载源码,探其究竟. 这里我用的是 dyld-97.1.tar.gz
我们都知道iOS的系统frameworks都是动态链接的,系统内核做好启动程序的初始准备后, 就会将控制权交给 dyld 负责剩下的工作, (dyld是运行在用户态的, 这里由内核态切到了用户态)
系统使用动态链接好处:
- 代码共用:多个程序都动态链接了相同的
lib, 但它们在内存和磁盘中中只有一份 - 易于维护:由于被依赖的
lib是程序执行时才链接的, 所以更新这些lib很方便 - 减少可执行文件体积:相比静态链接, 动态链接在编译时不需要打包进去, 所以可执行文件的体积要小很多
dyld接手
当dyld接手后, 它干了什么?
我们看到了这样一段注释
1 | // Entry point for dyld. The kernel loads dyld and jumps to __dyld_start which |
uintptr_t _main(const struct mach_header* mainExecutableMH, uintptr_t mainExecutableSlide, int argc, const char* argv[], const char* envp[], const char* apple[])
意思是说这个是dyld入口点, 内核加载dyld并跳去执行__dyld_start(复杂初始化一些寄存器然后执行_main函数)最后返回main()函数的地址
1 | uintptr_t _main(const struct mach_header* mainExecutableMH, uintptr_t mainExecutableSlide, int argc, const char* argv[], const char* envp[], const char* apple[]) { |
这里主要负责主要负责初始化程序环境, 将可执行文件以及相应的依赖库与插入库加载进内存生成对应的ImageLoader类的image(镜像文件)对象,对这些image进行链接,调用各image的初始化方法等(注:这里多数事情都是递归的,从底向上的方法调用),其中runtime也是在这个过程中被初始化
这里对ImageLoader的解释是:
1 | // ImageLoader is an abstract base class. To support loading a particular executable |
对上面生成的Image进行进行链接, 其主要做的事有对image进行load(加载),rebase(基地址复位),bind(外部符号绑定)
1 | void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool preflightOnly, const RPathChain& loaderRPaths) |
最后执行initializeMainExecutable (), 调用所有image的Initalizer方法进行初始化
由于 runtime 向 dyld 绑定了回调,当 image 加载到内存后, dyld 会通知 runtime . runtime的初始化方法是_objc_init, 我们可以在程序中打一个符号断点, 这个断点会先与main()函数断点断下, 说明这些方法都是在main函数之前执行


从调用栈我们可以看到dyld递归调用ImageLoader初始化方法之后, 就会调用libSystem_initializer, 之后调用libdipatch_init, 最后才会走到_objc_init也就是runtime的初始化方法, runtime的初始化之后, 接下来 load_images 中调用 call_load_methods 方法,遍历所有加载进来的 Class, 调用 类的load方法, 并初始化相应依赖库里的类结构.

到这里, 我们依然还是在main()函数之前, 当所有依赖库的Initializer都调用完后, dyld::_main()函数会返回程序的main函数地址, 然后main函数被调用.
总结
系统先读取程序的可执行文件(Mach-O), 从里面获得dyld的路径,然后加载dyld,dyld调用_main(const struct mach_header* mainExecutableMH, uintptr_t mainExecutableSlide, int argc, const char* argv[], const char* envp[], const char* apple[])去初始化运行环境,开启缓存策略,加载程序相关依赖库(其中也包含我们的可执行文件),并对这些库进行链接,最后递归调用每个依赖库的初始化方法,在这一步,runtime被初始化, 当所有依赖库的初始化后,轮到最后一位(程序可执行文件)进行初始化,在这时runtime会对项目中所有类进行类结构初始化,然后调用所有类的load方法, 最后dyld::_main()返回main函数地址,最终main函数被调用, 之后便来到了熟悉的程序入口.
最后更新: 2023年03月25日 22:39:55
本文链接: http://aeronxie.github.io/post/dd841de.html
版权声明: 本作品采用 CC BY-NC-SA 4.0 许可协议进行许可,转载请注明出处!