Hook一切的终极武器--Clang插桩

706 阅读2分钟

为了使用二进制重排优化应用的启动时间,我们需要知道应用在启动时到底执行了哪些方法或函数,通过将这些方法或函数进行重排,并放在.order文件中并配置到项目工程,来达到加快应用启动时间的目的.有关详情请查看iOS启动优化之二进制重排

这里有一篇文章介绍了Clang插桩的相关方案

Clang插桩

在项目工程的如下位置添加-fsanitize-coverage=func,trace-pc-guard的编绎设置的标记:

在工程中创建一个文件,并将如下代码copy到文件中,这里以我自己创建的文件为例:

//
//  XJHClangHookFuncTool.m
//  clangDemo
//
//  Created by 熊进辉 on 2020/11/22.
//

#import "XJHClangHookFuncTool.h"
#import <dlfcn.h>
#import <libkern/OSAtomic.h>

@implementation XJHClangHookFuncTool
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.
}

//原子队列
static  OSQueueHead symbolList = OS_ATOMIC_QUEUE_INIT;
//定义符号结构体
typedef struct {
    void *pc;
    void *next;
}SYNode;

void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
//    if (!*guard) return;  // Duplicate the guard check.
    /*  精确定位 哪里开始 到哪里结束!  在这里面做判断写条件!*/
    
    void *PC = __builtin_return_address(0);
    SYNode *node = malloc(sizeof(SYNode));
    *node = (SYNode){PC,NULL};
    //进入
    OSAtomicEnqueue(&symbolList, node, offsetof(SYNode, next));
    
}


+(NSString *)getClangHookFuncsListFilePath{
    
    NSMutableArray <NSString *> * symbolNames = [NSMutableArray array];

    while (YES) {
        SYNode * node = OSAtomicDequeue(&symbolList, offsetof(SYNode, next));
        if (node == NULL) {
            break;
        }
        Dl_info info;
        dladdr(node->pc, &info);
        NSString * name = @(info.dli_sname);
        BOOL  isObjc = [name hasPrefix:@"+["] || [name hasPrefix:@"-["];
        NSString * symbolName = isObjc ? name: [@"_" stringByAppendingString:name];
        [symbolNames addObject:symbolName];
    }
    //取反
    NSEnumerator * emt = [symbolNames reverseObjectEnumerator];
    //去重
    NSMutableArray<NSString *> *funcs = [NSMutableArray arrayWithCapacity:symbolNames.count];
    NSString * name;
    while (name = [emt nextObject]) {
        if (![funcs containsObject:name]) {
            [funcs addObject:name];
        }
    }
    //干掉自己!
    [funcs removeObject:[NSString stringWithFormat:@"%s",__FUNCTION__]];
    //将数组变成字符串
    NSString * funcStr = [funcs  componentsJoinedByString:@"\n"];
    
    NSString * filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"hank.order"];
    NSData * fileContents = [funcStr dataUsingEncoding:NSUTF8StringEncoding];
    [[NSFileManager defaultManager] createFileAtPath:filePath contents:fileContents attributes:nil];
    NSLog(@"%@",funcStr);
    return filePath;
}
@end

通过上面创建的类,你就可以在你工程的第一个界面的viewDidLoad里面调用getClangHookFuncsListFilePath,从而拿到二进制重排的.order文件了,通过在项目中配置这个文件我们就完成了二进制重排的启动优化了。

重排后的方法打印如下:

打开hank.order文件如下:

记得把最后一个符号删除,它是你外部调用的位置,不需要写进二进制重排里面。

如果你是用真机来调试的,那么你要拿到对应的hank.order文件,方法如下: 打开xcode工程->Window->Devices And Simulators ->选中对应的工程 ->点击工程列表下的“齿轮”图标->Download Continaer,然后显示包内容,到tmp文件夹中找到对应的hank.order文件.