前言
上一篇文章主要介绍了iOS启动优化原理,并且了解到二进制重排
能够优化一定的启动时间。这边博客主要是对二进制重排进行实际操作。
Link Map File
link map file可以直接理解为链接映射文件,是 Xcode 生成可执行文件时一起生成的文本,用于记录链接相关信息。保存了可执行文件的路径、CPU架构、.o目标文路径、方法符号。
- 生成link map file
xcode ->Build setting
,搜索link map
。找到Write Link Map
,设置为Yes。编译项目,找到对应的文件
打开link map file 文件:
那么既然要二进制重排,那么我们如何修改link map
里面的符号顺序了,xcode
给我们提供了一个order file
,开发者可以自定义一个order file
order file
在demo
工程根目录新建一个qinhan.order
文件
xcode->build setting
设置order
文件路径
在项目添加方法,编辑qinhan.order
文件
重新编译项目,再次查看link map
文件
发现order
设置后确实有效。
既然设置order文件确实能够起到二进制重排的效果,那我们离成功已经接近了一半。但是如何知道应用启动的时候调用了那些方法呢,这个时候就需要clang插桩
clang 插桩
配置clang 插桩
Tracing PCs with guards
根据文档的意思:通过-fsanitize-coverage=trace-pc-guard
命令,编译器将在每一处代码的边界插入
__sanitizer_cov_trace_pc_guard(&guard_variable)
回调方法。
xcode->build setting
搜索Other C Flag,输入上面的命令
编译项目,报错
实际上是没有添加回调函数,文档也提供了Example。在项目中添加这两个函数,运行成功。
- __sanitizer_cov_trace_pc_guard_init
这里start
、stop
,里面存储的实际上是符号的个数。读取一下start
存储的数据,以4个字节为单位
那么如果获取总共的符号数呢,也就是stop -4
.
这个时候符号的个数就是0x11
也就是17
,如果我们再添加一个函数,再次运行。
结果0x12 = 18
,再次验证了我们的结论
- __sanitizer_cov_trace_pc_guard
在这个函数里面打断点,发现应用的每个函数都会走这个回调方法,也就是相当于hook
了应用所有的函数。
实现原理: 只要添加了clang插桩标记
,那么编译器就会在所有方法
、函数
、block
的代码实现的边缘添加上面的回调方法。
获取符号
__builtin_return_address
:获取上一个函数的地址dli_saddr
:通过地址或去符号信息,保存到Dl_info
结构题 打印结果:
dli_fname
:macho所在的路径dli_fbase
:macho的基地址dli_sname
:符号名称dli_saddr
:符号地址
总结:既然我们能够获取到启动的符号名称,如果我们能够把这些符号按照顺序保存成order文件,那不就解决了二进制重排的问题!!!!
通过队列存取符号
符号的保存:
- OSQueueHead
:由于方法的运行也可能在子线程,为了保证线程安全,定义一个原子队列symbolList
QHNode
:定义一个结构题保存符号的地址,和next执行OSAtomicEnqueue
:将所有的结构题入队列。
符号的取出:
- 通过
while
循环+OSAtomicDequeue
取出符号
坑点分析和解决
到这里的时候,满心欢喜。结果一运行,一直循环打印
为什么呢?主要是这个拦截的回调不知拦截了方法、函数、block
,在while循环体内也进行了拦截。单执行到touchesBegan
的时候加入队列,到while循环的时候touchesBegan
也加入了队列。为了解决这个问题,我需要在xcode里面设置只编译方法-fsanitize-coverage=func,trace-pc-guard
符号拼接
判断是否是oc
的符号,如果是c
的符号,需要做其他处理
过滤重复的符号和取反
下面的操作就比较简单了,主要是拿到符号进行过滤重复的符号和取反.去掉touchesBegan
自己
最后将数据保存成order文件,然后将order文件放到对应的目录。到此整个二进制重排操作就结束了。