美文网首页
iOS 启动优化

iOS 启动优化

作者: SoldOut | 来源:发表于2022-07-12 15:26 被阅读0次

    本文只讲操作,原理一带而过。原理可以搜索关键字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 <stdint.h>
    #import <stdio.h>
    #import <sanitizer/coverage_interface.h>
    

    一般来讲,在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 <dlfcn.h>
    #import <libkern/OSAtomic.h>
    
    //定义原子队列
    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<UITouch *> *)touches withEvent:(UIEvent *)event为例

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        //用来存放符号的数组
        NSMutableArray<NSString *> *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方法存储到数组中
                [symbleNames addObject:name];
                continue;
            }
            //c++函数
            [symbleNames addObject:[@"_" stringByAppendingString:name]];
    
        }
        
        
        //反向遍历数组
        NSEnumerator *reverseEm = [symbleNames reverseObjectEnumerator];
        NSMutableArray *funcs = [NSMutableArray arrayWithCapacity:symbleNames.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%@", funcString);
    
    }
    

    3.使用真机编译并启动app,启动完成后点击屏幕看到日志(使用其他方法的需要自己调用其他方法),sorted.order 文件已生成

    现在需要找到这个文件,点击 xcode 真机 - add adtional simulators - devices
    选择设备


    devices@2x.png

    右侧选中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 启动优化

          本文链接:https://www.haomeiwen.com/subject/lswsbrtx.html