Mach-O
在Xcode中构建的程序,经由预处理、编译、汇编、链接将源文件(.m和.h文件)转变为Mach-O 二进制可执行文件。Mach-O 中代码被划分为不同的Segment,而每个Segment又包含多个Section。
Mach-O的架构:
Segment
执行一个可执行文件时。虚拟内存系统会将Segment映射到进程的地址空间。在虚拟内存系统进行映射时,不同的Segment会以不同的参数(权限)被映射。常见的Segment有:
- __TEXT :__TEXT段包含了可执行的代码。它们被以只读和可执行的方式映射。进程被允许执行这些代码,但是不能修改。
- __DATA:程序数据段以可读写但不可执行的方式映射。(本文讨论的重点)。
- __PAGEZERO: 空指针陷阱段,映射到虚拟内存空间的第一页,用于捕捉对 NULL 指针的引用。
- __LINKEDIT: 链接器使用的符号以及其他表。
Section
Section 是具体有用的数据存放的地方,下表列出了__DATA 和 __TEXT下的Section:

__attribute__
__attribute__是一种编译器指令,可以让编译器做更多地错误检查和代码优化,其中就包括大家常见的废弃掉方法、类或者变量的指令__attribute__ ((deprecated(“”)))。
__attribute__的一般语法形式是__attribute__关键字后面跟两个小括号,在小括号里是逗号分隔的选项。这里我们重点关注section() 函数。
section() 函数
前面提到__DATA段为可读写程序数据段,而section() 函数提供了二进制段的读写能力,它可以将一些编译期就可以确定的常量写入数据段。在阅读runtime源码的过程中,可以发现苹果大量利用__attribute__和section()函数在编译器将数据写入__DATA段。
比如,下述代码编译器在__DATA段下的objc_catlist section里保存了一个大小为1的category_t的数组,用于运行期category的加载:
1 | static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= { |
同样,我们也可以利用__attribute__和section()将数据提前写入__DATA段,以达到程序优化的目的(比如,去+load)。+load()提供了一个非常靠前的执行时机,在实际开发中很多基础库的初始化工作都集中在这个时机执行。而过重的+load()会拖慢程序的premain的加载时间,有了上述的理论基础我们可以在将相关初始化工作封装在函数内并在编译期将相关函数指针写入__DATA段,从而达到延迟加载的效果,节省启动时间。
Coding
受够了理论基础,是时候敲一段代码了~ ,以下代码我们在__DATA段新增section __bwkcfunction__,并将func()的函数指针写入__bwkcfunction__中。
1 | typedef void (* BWKCFunction) (void); |
接着,我们通过命令行编译该代码生成可执行文件a.out(a.out 是clang的默认命名)。
1 | xcrun clang test_attribute.c |
最后通过命令行看一下我们的成果吧~
1 | $ xcrun size -x -l -m a.out |
可以看到,在_DATA段,我们成功插入了\_bwkcfunction__ section,且该section的大小为8字节(一个c语言指针的大小)。然后我们就可以在程序运行的过程中在需要的时候取出对应的函数指针,从而实现懒加载~