OC底层原理探索之启动优化Clang插桩

1,192 阅读3分钟

Trancing PCs

打开Clang文档介绍链接

 -fsanitize-coverage=trace-pc-guard

首先在设置里面配置上面那一行代码 image.png 然后根据示例导入

#include <stdint.h>
#include <stdio.h>
#include <sanitizer/coverage_interface.h>

以及实现下面的这两个方法:

void __sanitizer_cov_trace_pc_guard_init(uint32_t *start, uint32_t *stop)
void __sanitizer_cov_trace_pc_guard(uint32_t *guard)   

__sanitizer_cov_trace_pc_guard_init

看注释:编译器将此回调作为模块构造函数插入到每个DSO中。'start'和'stop'对应的是整个二进制文件(可执行文件或DSO)的开始和结束部分,也就是说这个函数反映了符号调用的次数,我们可以来验证一下 image.png start:开始部分 stop:这里是个开区间所以需要减去4,所以这里整个符号的调用次数是25个 我们再加一个C函数和一个Block看下,这里的数量是不是等于27. image.png 所以只要我们的项目中新增一个方法这里都会加1,都能监控到

__sanitizer_cov_trace_pc_guard

看注释:只要你添加了Clang插桩的标记,那么编译器就会在所有的方法、函数、block的代码实现的边缘添加这行代码。检测到当前调用的函数,我们看下函数调用栈就知道,这里可以hook住一起的回调函数(方法,函数和block) image.png Debug->Debug Workflow-> Always Show Disassembly可以在汇编中看到在每一个符号开始调用的时候,都插入了这个函数,我们可以从这个函数中拿到调用者的地址 image.png 在这个函数里,我们根据地址可以拿到调用符号的一些信息 image.png 拿到符号的名字之后,就可以重新排列了。但是如果hook的方法是在子线程,那么这里也会是在子线程回调。所以在这个方法里存取符号的时候,就要考虑到线程安全的问题。 ​

while循环问题

我们在这个方法里面把所有的符号先收集起来

void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
    void *PC = __builtin_return_address(0);
    //创建结构体
    SYNode * node = malloc(sizeof(SYNode));
    *node = (SYNode){PC,NULL};
    //结构体入栈
    OSAtomicEnqueue(&symbolList, node, offsetof(SYNode, next));
    
}

然后在while循环的时候取出来 Build运行,发现死循环,调用的都是touchBegin这个方法,next为什么一直是它? image.png 在这里断点,打开汇编,我们发现这个方法里面,自动加入了__sanitizer_cov_trace_pc_guard方法,而且还不止一次,意思就是while循环也被该方法hook住了。那么怎么破解呢? image.png 我们可以在设置里面新增一个范围限定func

-fsanitize-coverage=func,trace-pc-guard

image.png 运行,再点击屏幕发现就解决了死循环的问题。 image.png

符号取反和去重

上面拿到的符号有重复的,同时也需要按照正确的调用顺序来取

  1. 上面的打印我们知道main函数没有正确的下标,所以我们这里给函数名称添加 _
BOOL isObjc = [name hasPrefix:@"+["] || [name hasPrefix:@"-["];
NSString * symbolName = isObjc ? name : [@"_" stringByAppendingString:name];
[symbleNames addObject:symbolName];
  1. 反向遍历数组
  2. 去掉当前的自己
[funcs removeObject:[NSString stringWithFormat:@"%s",__func__]]

4.生成order文件并写入

NSString * funcStr = [funcs componentsJoinedByString:@"\n"];
NSString * filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"hank.order"];
NSData * file = [funcStr dataUsingEncoding:NSUTF8StringEncoding];
[[NSFileManager defaultManager] createFileAtPath:filePath contents:file attributes:nil];
NSLog(@"%@",funcStr);

image.png

  1. 找到order文件的路径

image.png

配置生成的Order文件

  1. 把该文件放到项目的根目录之下

image.png

  1. 删除设置-fsanitize-coverage=func,trace-pc-guard,同时引入自制的order文件

image.png

image.png

Swift符号覆盖

在设置里面覆盖Swift代码

-sanitize-coverage=func 
-sanitize=undefined

image.png 配置好之后重新打印下符号 image.png 这里的swift自带的混淆,相对于OC来说更安全,性能更高。每次编译的时候并不会变化。