本文只讲操作,原理一带而过。原理可以搜索关键字Clang插桩、二进制重排、Page Fault(缺页异常)
llvm 官方文档:https://clang.llvm.org/docs/SanitizerCoverage.html#tracing-pcs-with-guards
1.首先在Xcode-Build Settings中搜索other c flag
添加参数:-fsanitize-coverage=func,trace-pc-guard
2.添加完成后编译会报错,提示需要实现2个函数:
分别是:
void __sanitizer_cov_trace_pc_guard_init(uint32_t *start,
uint32_t *stop) {
static uint64_t N; // Counter for the guards.
if (start == stop || *start) return; // Initialize only once.
printf("INIT: %p %p\n", start, stop);
for (uint32_t *x = start; x < stop; x++)
*x = ++N; // Guards should start from 1.
}
和
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
if (!*guard) return; // Duplicate the guard check.
// If you set *guard to 0 this code will not be called again for this edge.
// Now you can get the PC and do whatever you want:
// store it somewhere or symbolize it and print right away.
// The values of `*guard` are as you set them in
// __sanitizer_cov_trace_pc_guard_init and so you can make them consecutive
// and use them to dereference an array or a bit vector.
void *PC = __builtin_return_address(0);
char PcDescr[1024];
// This function is a part of the sanitizer run-time.
// To use it, link with AddressSanitizer or other sanitizer.
__sanitizer_symbolize_pc(PC, "%p %F %L", PcDescr, sizeof(PcDescr));
printf("guard: %p %x PC %s\n", guard, *guard, PcDescr);
}
这两个函数需要引入头文件:
#import
#import
#import
一般来讲,在app启动之后呈现的第一个页面vc中实现这段代码。
下面讲函数的具体实现__sanitizer_cov_trace_pc_guard_init
这个函数不需要做什么,按照官方文档写即可,本质是统计hook了多少个符号,以及所对应的内存地址。
重点是这个函数void __sanitizer_cov_trace_pc_guard(uint32_t *guard)
如果你需要hook + (void) load
请将 if (!*guard) return;
注释掉
导入头文件,并定义结构体和原子队列
#import
#import
//定义原子队列
static OSQueueHead symbolList = OS_ATOMIC_QUEUE_INIT;
//定义符号结构体
typedef struct {
void *pc;
void *next;
} SymbolNode;
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
// if (!*guard) return;
void *PC = __builtin_return_address(0);
Dl_info info;
dladdr(PC, &info);
SymbolNode *node = malloc(sizeof(SymbolNode));
*node = (SymbolNode){PC, NULL};
OSAtomicEnqueue(&symbolList, node, offsetof(SymbolNode, next));
}
在实现的vc中任意一个函数中继续实现代码,将所有hook到的函数进行排序去重和重命名,并生成 .order 文件,这里以vc中的- (void)touchesBegan:(NSSet
为例
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
//用来存放符号的数组
NSMutableArray *symbolNames = [NSMutableArray array];
while (YES) {
SymbolNode *node = OSAtomicDequeue(&symbolList, offsetof(SymbolNode, next));
if (node == NULL) {
break;
}
Dl_info info;
dladdr(node->pc, &info);
NSString *name = @(info.dli_sname);
//是oc方法
BOOL isObjcSymbol = [name hasPrefix:@"+["] || [name hasPrefix:@"-["];
if (isObjcSymbol) {
//是oc方法存储到数组中
[symbolNames addObject:name];
continue;
}
//c++函数
[symbolNames addObject:[@"_" stringByAppendingString:name]];
}
//反向遍历数组
NSEnumerator *reverseEm = [symbolNames reverseObjectEnumerator];
NSMutableArray *funcs = [NSMutableArray arrayWithCapacity:symbolNames.count];
NSString *name;
while (name = [reverseEm nextObject]) {
if (![funcs containsObject:name]) {
[funcs addObject:name];
}
}
//移除当前调用的方法名
[funcs removeObject:[NSString stringWithFormat:@"%s", __func__]];
//生成order文件
NSString *symbolString = [funcs componentsJoinedByString:@"\n"];
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"sorted.order"];
NSData *file = [symbolString dataUsingEncoding:NSUTF8StringEncoding];
[[NSFileManager defaultManager] createFileAtPath:filePath contents:file attributes:nil];
NSLog(@"\n%@", symbolString);
}
3.使用真机编译并启动app,启动完成后点击屏幕看到日志(使用其他方法的需要自己调用其他方法),sorted.order 文件已生成
现在需要找到这个文件,点击 xcode 真机 - add adtional simulators - devices
选择设备
右侧选中APP,点击Download Container,并保存
得到一个xcappdata文件,右键显示包内容,在AppData-tmp目录下,找到sorted.order文件,使用文本编辑打开,如果看到了刚刚和日志一样的内容,那么说明成功了。
下面把这个文件放到工程根目录,同时将刚刚添加的参数-fsanitize-coverage=func,trace-pc-guard
删掉,在Xcode-Build Settings中搜索order file
添加参数 sorted.order
并编译
验证有效性:
Xcode-Build Settings 搜索 link map
Write Link Map File
改为 YES
编译
xcode左侧products-右键Show in Finder,进入APP目录,往上层找,在
/Intermediates.noindex/工程名.build/Debug-iphoneos/工程名.build
中找到名为工程名LinkMap-normal-arm64.txt
文件,打开发现与日志中的函数顺序一致即可。
至此,Clang插桩完成