原理
App包数据并不是在启动的时候一次全部加载到内存中的,而是类似于懒加载的方式,以每页16KB的数据进行分页加载。启动的时刻,也是缺页加载次数最多的时刻。因为启动用到的类和方法,并不是全部集中在某几页数据中,而是根据编译顺序,分散到不确定的分页数据中。我们做二进制重拍,也就是要让启动用到的函数,集中到最前边的几张表中,减少分页加载的次数,也就节约了启动时间。
那么为什么减少分页加载的次数,可以节省启动时间呢?
这是因为,每页数据加载到内存中,还需要进行重绑定的过程,因为ASLR(地址空间布局随机化),每次启动后指针地址值并不是MachO中编译后的地址,还需要加上这个随机偏移地址,也就是rebase(重绑定)的过程。启动时刻加载的分页越多,重绑定的地址也就越多,拖慢了应用的启动时间。
查看排列顺序
① 打开编译配置 Build Settings —> Write Link Map File —> 修改成 YES。
② 打开编译后生产的 .app 文件,在上两层目录找 Intermediates.noindex
Intermediates.noindex/xxx(项目根目录名称).build/Debug-iphoneos/xxx(项目根目录名称).build/xxx-LinkMap-normal-arm64.txt
这个txt就是默认的排列顺序,我们配置.order后,这个文件的排列顺序会按照.order给定的顺序编译后排序。
配置.order 文件
打开 Build Setting,搜索 Order File 配置生成的.order文件路径。
.order里主要写启动时调用的方法(获取在后面会说)
利用插桩获取重排符号
注意的是:获取到符号表后,以下配置我们就可以删除掉了。
插桩获取符号可以参考LLVM官网的介绍。
原理就是在每一个OC的方法、函数、block 的汇编代码中,插入一条__sanitizer_cov_trace_pc_guard汇编指令,读取pc寄存器的指令指针,回调到我们自己添加的C函数__sanitizer_cov_trace_pc_guard,然后根据指针恢复出OC的方法名、函数名、block。
配置pc寄存器跟踪配置
Build Settings —> Other C Flags —> 添加 -fsanitize-coverage=trace-pc-guard
引入插桩函数在AppDelegate里面
#include <stdint.h>
#include <stdio.h>
#include <sanitizer/coverage_interface.h>
#import <dlfcn.h>
#import <libkern/OSAtomic.h>
/*
- start:起始位置
- stop:并不是最后一个符号的地址,而是整个符号表的最后一个地址,最后一个符号的地址=stop-4(因为是从高地址往低地址读取的,且stop是一个无符号int类型,占4个字节)。stop存储的值是符号的
*/
void __sanitizer_cov_trace_pc_guard_init(uint32_t *start,
uint32_t*stop) {
staticuint64_tN;
if(start == stop || *start)return;
printf("INIT: %p - %p\n", start, stop);
for(uint32_t*x = start; x < stop; x++) {
*x = ++N;
}
}
/*
可以全面hook方法、函数、以及block调用,用于捕捉符号,是在多线程进行的,这个方法中只存储pc,以链表的形式
- guard 是一个哨兵,告诉我们是第几个被调用的
*/
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
// if (!*guard) return;//将load方法过滤掉了,所以需要注释掉
//获取PC
/*
- PC 当前函数返回上一个调用的地址
- 0 当前这个函数地址,即当前函数的返回地址
- 1 当前函数调用者的地址,即上一个函数的返回地址
*/
void *PC = __builtin_return_address(0);
Dl_infoinfo;
dladdr(PC, &info);
printf("%s \n", info.dli_sname);
}
然后把打印出来的方法写入到order文件
网友评论