iOS 启动优化

本文只讲操作,原理一带而过。原理可以搜索关键字Clang插桩、二进制重排、Page Fault(缺页异常)

llvm 官方文档:https://clang.llvm.org/docs/SanitizerCoverage.html#tracing-pcs-with-guards

1.首先在Xcode-Build Settings中搜索other c flag

image

添加参数:-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 *)touches withEvent:(UIEvent *)event为例

- (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,并保存

image.png

得到一个xcappdata文件,右键显示包内容,在AppData-tmp目录下,找到sorted.order文件,使用文本编辑打开,如果看到了刚刚和日志一样的内容,那么说明成功了。

下面把这个文件放到工程根目录,同时将刚刚添加的参数-fsanitize-coverage=func,trace-pc-guard删掉,在Xcode-Build Settings中搜索order file

image.png

添加参数 sorted.order 并编译

验证有效性:

Xcode-Build Settings 搜索 link map
Write Link Map File 改为 YES

image.png

编译

xcode左侧products-右键Show in Finder,进入APP目录,往上层找,在
/Intermediates.noindex/工程名.build/Debug-iphoneos/工程名.build中找到名为工程名LinkMap-normal-arm64.txt 文件,打开发现与日志中的函数顺序一致即可。

image.png

至此,Clang插桩完成

你可能感兴趣的:(iOS 启动优化)