接上一片文章
上一篇演示了实现静态插桩步骤和原理,这片文章基于静态插桩来实现如何拿到App启动时期的所有符号(函数);
理论性的内容不写了,直接上代码说明:
思路
关键代码
void *PC = __builtin_return_address(0);
它的作用其实就是当前这个函数(__sanitizer_cov_trace_pc_guard)执行完毕后需要返回到哪里去,*PC指向的就是需要返回的符号地址;
//伪代码
void methodOne() {
//插桩代码
__sanitizer_cov_trace_pc_guard()
==>
//原函数逻辑代码
NSLog(@"%s",__FUNCTION__);
}
也就是说,拿到被clang插桩的函数的符号地址,在这里就是methodOne;
根据函数内存地址获取函数名称
- 引入头文件
#import <dlfcn.h>、#import <libkern/OSAtomic.h>,其中有一个方法:
typedef struct dl_info {
const char *dli_fname; /* 所在文件 */
void *dli_fbase; /* 文件地址 */
const char *dli_sname; /* 符号名称 */
void *dli_saddr; /* 函数起始地址 */
} Dl_info;
//这个函数能通过函数内部地址找到函数符号
int dladdr(const void *, Dl_info *);
- 在demo中做如下修改:
- (void)buttonAction {
//遍历出队
while (true) {
//offsetof 就是针对某个结构体找到某个属性相对这个结构体的偏移量
SymbolNode * node = OSAtomicDequeue(&symboList, offsetof(SymbolNode, next));
if (node == NULL) break;
Dl_info info;
dladdr(node->pc, &info);
printf("%s \n",info.dli_sname);
}
}
//原子队列
static OSQueueHead symboList = OS_ATOMIC_QUEUE_INIT;
//定义符号结构体
typedef struct{
void * pc;
void * next;
}SymbolNode;
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
//调用 load() 方法时 , __sanitizer_cov_trace_pc_guard 函数的参数 guard 是 0,如果要对load()进行入队,就需要要屏蔽掉这行代码!
//if (!*guard) return; // Duplicate the guard check.
void *PC = __builtin_return_address(0);
SymbolNode * node = malloc(sizeof(SymbolNode));
*node = (SymbolNode){PC,NULL};
//入队
// offsetof 用在这里是为了入队添加下一个节点找到 前一个节点next指针的位置
OSAtomicEnqueue(&symboList, node, offsetof(SymbolNode, next));
}
考虑到 __sanitizer_cov_trace_pc_guard这个方法会被调用多次 , 使用锁会影响性能,这里使用苹果底层的原子队列 ( 底层实际上是个栈结构 , 利用队列结构 + 原子性来保证顺序 ) 来实现,所以上述代码引入了头文件 #import <libkern/OSAtomic.h>
还没结束,如果上述代码直接运行的话会导致死循环,原因是:
通过汇编会查看到 一个带有 while 循环的方法 , 会被静态加入多次 __sanitizer_cov_trace_pc_guard 调用 , 导致死循环.
解决方法:
修改 Other C Flags
-fsanitize-coverage=func,trace-pc-guard
仅针对func进行hook;
对比使用-fsanitize-coverage=trace-pc-guard生成的汇编代码,如图:
调用了三次
__sanitizer_cov_trace_pc_guard;
再看下使用-fsanitize-coverage=func,trace-pc-guard生成的汇编代码,如图:
仅调用了一次
__sanitizer_cov_trace_pc_guard;
重新运行,控制台输出如下:
(ps:如果再点击一次按钮,只会输出一句
-[ViewController buttonAction] ,这是因为之前的内容已经从队列里移除掉了,只有-[ViewController buttonAction] 是刚加入队列的!)
swift工程/混编工程的兼容处理
以上方式适合纯 OC 工程获取符号方式 . 搜索 Other Swift Flags , 添加两条配置即可 :
-sanitize-coverage=func
-sanitize=undefined
cocoapod工程的兼容处理
在相应的target中做相应处理即可,其它不变;
手动导入库的兼容处理
会使用主工程的配置,无需特殊处理;
后续如果需要将以上符号输出到order文件中来实现二进制重排,还需要一些细化操作,比如对输出符号的顺序取反、去重和函数写法调整,这个比较简单就不写了~
Demo
参考:
参照原作者的讲解,然后经过自己完全消化吸收后,做了一些实践操作和记录!