iOS二进制重排(clang插桩)

1,737 阅读4分钟

前言

上一篇文章主要介绍了iOS启动优化原理,并且了解到二进制重排能够优化一定的启动时间。这边博客主要是对二进制重排进行实际操作。

Link Map File

link map file可以直接理解为链接映射文件,是 Xcode 生成可执行文件时一起生成的文本,用于记录链接相关信息。保存了可执行文件的路径、CPU架构、.o目标文路径、方法符号。

  • 生成link map file xcode ->Build setting ,搜索link map。找到Write Link Map,设置为Yes。编译项目,找到对应的文件

file map.png

打开link map file 文件:

link map.png

那么既然要二进制重排,那么我们如何修改link map里面的符号顺序了,xcode 给我们提供了一个order file,开发者可以自定义一个order file

order file

demo工程根目录新建一个qinhan.order文件

order.png xcode->build setting设置order 文件路径

order path.png 在项目添加方法,编辑qinhan.order文件

edit order.png 重新编译项目,再次查看link map文件

修改后文件.png 发现order设置后确实有效。

既然设置order文件确实能够起到二进制重排的效果,那我们离成功已经接近了一半。但是如何知道应用启动的时候调用了那些方法呢,这个时候就需要clang插桩

clang 插桩

clang 文档

配置clang 插桩

Tracing PCs with guards

guards.png 根据文档的意思:通过-fsanitize-coverage=trace-pc-guard命令,编译器将在每一处代码的边界插入 __sanitizer_cov_trace_pc_guard(&guard_variable)回调方法。

xcode->build setting 搜索Other C Flag,输入上面的命令

截屏2021-09-16 上午9.41.07.png 编译项目,报错

截屏2021-09-16 上午9.41.52.png 实际上是没有添加回调函数,文档也提供了Example。在项目中添加这两个函数,运行成功。

  • __sanitizer_cov_trace_pc_guard_init

截屏2021-09-16 上午10.12.52.png 这里startstop,里面存储的实际上是符号的个数。读取一下start存储的数据,以4个字节为单位

截屏2021-09-16 上午10.15.15.png 那么如果获取总共的符号数呢,也就是stop -4.

截屏2021-09-16 上午10.19.31.png 这个时候符号的个数就是0x11 也就是17,如果我们再添加一个函数,再次运行。

截屏2021-09-16 上午10.23.03.png 结果0x12 = 18,再次验证了我们的结论

  • __sanitizer_cov_trace_pc_guard

在这个函数里面打断点,发现应用的每个函数都会走这个回调方法,也就是相当于hook了应用所有的函数。

实现原理: 只要添加了clang插桩标记,那么编译器就会在所有方法函数block的代码实现的边缘添加上面的回调方法。

获取符号

截屏2021-09-16 下午3.44.23.png

  • __builtin_return_address:获取上一个函数的地址
  • dli_saddr:通过地址或去符号信息,保存到Dl_info结构题 打印结果:

符号信息.png

  • dli_fname:macho所在的路径
  • dli_fbase:macho的基地址
  • dli_sname :符号名称
  • dli_saddr :符号地址

总结:既然我们能够获取到启动的符号名称,如果我们能够把这些符号按照顺序保存成order文件,那不就解决了二进制重排的问题!!!!

通过队列存取符号

符号的保存: 结构体入栈.png - OSQueueHead:由于方法的运行也可能在子线程,为了保证线程安全,定义一个原子队列symbolList

  • QHNode:定义一个结构题保存符号的地址,和next执行
  • OSAtomicEnqueue:将所有的结构题入队列。

符号的取出:

截屏2021-09-16 下午9.01.22.png

  • 通过while循环+OSAtomicDequeue取出符号

坑点分析和解决

到这里的时候,满心欢喜。结果一运行,一直循环打印

循环打印.png 为什么呢?主要是这个拦截的回调不知拦截了方法、函数、block,在while循环体内也进行了拦截。单执行到touchesBegan的时候加入队列,到while循环的时候touchesBegan也加入了队列。为了解决这个问题,我需要在xcode里面设置只编译方法-fsanitize-coverage=func,trace-pc-guard

截屏2021-09-16 下午9.22.30.png

符号拼接

截屏2021-09-16 下午9.29.02.png 判断是否是oc的符号,如果是c的符号,需要做其他处理

过滤重复的符号和取反

下面的操作就比较简单了,主要是拿到符号进行过滤重复的符号和取反.去掉touchesBegan自己

截屏2021-09-16 下午9.34.38.png

最后将数据保存成order文件,然后将order文件放到对应的目录。到此整个二进制重排操作就结束了。