上一篇我们了解了由于页中断导致启动耗时,我们可以编译的时候根据我们.order
方法进行排列,但是我们项目比较大的话,找到方法进行排列就比较困难。
1. Clang插桩配置
LLVM
内置一个简单的代码覆盖仪表
(SanitizerCoverage)。它将用户定义的函数插入函数、基本块和边缘级别的调用。提供了这些回调的默认实现,并实现简单的覆盖报告和可视化 Clang代码覆盖文中有具体说明和演示。
1.1 配置
我们是oc项目的话在 Build Settings
里的 Other C Flags
中添加
-fsanitize-coverage=trace-pc-guard
复制代码
在任意一个控制器或者文件添加文档说明的方法实现
,编译通过。
运行后打印出方法的实现
我们看下这个方法__sanitizer_cov_trace_pc_guard_init
其中的start和stop代表是方法的个数起始和结束的个数,进行for循环,当起始地址的值和终止的相同时return。
这里0e表示14个符号表,我们添加个方法和block
表示为16个。
我们如果swift项目或者混编的话,在 Build Settings
里的 Other Swift Flags
中添加
-sanitize-coverage=func
-sanitize=undefined
复制代码
1.2 原理
我们用汇编调试下
main函数后会调用__sanitizer_cov_trace_pc_guard
[AppDelegate application:didFinishLaunchingWithOptions:]函数会调用__sanitizer_cov_trace_pc_guard
后续发现在实现我们的方法后都会调用__sanitizer_cov_trace_pc_guard
,相当于系统在编译的时候会给我们项目中方法添加hook,进行标记,定位我们的方法。我们是通过Other c Flags
添加的标记,所以肯定是在编译期做这个插入代码
的动作。
2. 具体实现
我们之前分析了只要调用我们自身的方法就会调用系统为我们添加的hook,
__builtin_return_address(0)
这个函数返回的是上一个函数的地址,也就是调用者,这个PC就是上一个函数的地址,表示第0行插入的__sanitizer_cov_trace_pc_guard
。我们取出这个地址的信息,我们通过Dl_info
来保存该方法的信息。
Dl_info info;
dladdr(PC, &info);
复制代码
导入#import <dlfcn.h>
,Dl_info
的组成,包括路径,方法名,地址。
我们只打印下方法的名字
打印的时候没有+load
方法,我们打断点发现确实进入了,但是guad为0,因此写入的时候去除
这个判断。
2.1 保存方法名
我们添加个方法进行获取方法名,我们在存储的时候,多线程调用方法,这个hook也会在多线程,这个时候写入操作的话,会造成线程不安全,因此我们采用原子操作。
2.1 原子操作保存
我们导入#import <libkern/OSAtomic.h>
定义原子队列和定义符号结构体
我们在hook中写入方法
这里添加next节点
地址也就是PC的信息,方便我们取出的时候判断是否时最后一个
,最后一个没有下个节点信息的。
我们在touchesBegan中添加for循环
结果死循环了,hook把我们的for循环也捕获了,导致了表一直插入进入死循环
,我们修改下标记
-fsanitize-coverage=func,trace-pc-guard
复制代码
我们项目中存在swift
代码的话,或者block等则需要判断,
进行添加和反向遍历,并去除本身调用的方法
最后写入order
文件
打印结果,按照执行的顺序排列
2.3 链接orderFile
我们写入在OrderFile
中进行链接
,编译后方法的顺序就是我们启动时执行的顺序
3. 总结
我们通过官方对方法的hook
,定位到实现的方法,存入数组,最后按.order
文件进行读取,从而减少page fault
的次数,提高启动速度。写入的具体代码:
static OSQueueHead symbolList = OS_ATOMIC_QUEUE_INIT;
//定义符号结构体
typedef struct {
void * pc;
void * next;
} KBNode;
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
// if (!*guard) return;
void *PC = __builtin_return_address(0);
// Dl_info info;
// dladdr(PC, &info);
// NSLog(@"%s\n,\n",info.dli_sname);
KBNode *node = malloc(sizeof(KBNode));
*node = (KBNode){PC,NULL};//赋值,强转,next表示下个节点的地址
OSAtomicEnqueue(&symbolList, node, offsetof(KBNode, next));//原子列表写入方法,并把下个节点的地址写入。
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//定义数组
NSMutableArray<NSString *> * symbleNames = [NSMutableArray array];
while (YES) {
KBNode *node = OSAtomicDequeue(&symbolList, offsetof(KBNode, next));
if (node == NULL) {
break;//没有的话结束
}
Dl_info info;
dladdr(node->pc, &info);
// printf("%s\n",info.dli_sname);
NSString * name = @(info.dli_sname);//转字符串
BOOL isObjc = [name hasPrefix:@"+["] || [name hasPrefix:@"-["];
NSString * symbolName = isObjc ? name : [@"_" stringByAppendingString:name];
[symbleNames addObject:symbolName];
}
//添加
NSEnumerator * em = [symbleNames reverseObjectEnumerator];
NSMutableArray * funcs = [NSMutableArray arrayWithCapacity:symbleNames.count];
NSString * name;
while (name = [em nextObject]) {
if (![funcs containsObject:name]) {//数组没有name
[funcs addObject:name];
}
}
//去掉自己!
[funcs removeObject:[NSString stringWithFormat:@"%s",__func__]];
//写入文件
//1.编程字符串
NSString * funcStr = [funcs componentsJoinedByString:@"\n"];
NSString * filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"test.order"];
NSData * file = [funcStr dataUsingEncoding:NSUTF8StringEncoding];
[[NSFileManager defaultManager] createFileAtPath:filePath contents:file attributes:nil];
NSLog(@"%@",funcStr);
}
复制代码