美文网首页
iOS 启动优化-生成 Order File

iOS 启动优化-生成 Order File

作者: 顶级蜗牛 | 来源:发表于2021-08-03 14:43 被阅读0次

    启动优化-理论篇
    启动优化-二进制重排篇
    启动优化-编译期插桩篇
    启动优化-生成 Order File

    先附上demo代码

    SwiftTest.swift文件

    import UIKit
    
    @objc class SwiftTest: NSObject {
        @objc class public func swiftTest() {
            print("SwiftTestObject打印:test")
        }
    }
    

    ViewController.m文件

    #import "ViewController.h"
    #import "Test-Swift.h"
    #import <dlfcn.h>
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        [self test];
    }
    
    // 声明一个Objective-C方法,调用C函数
    - (void)test {
        func();
    }
    
    // 声明一个C函数调用block
    void func() {
        block();
    }
    
    // 声明一个block调用swift类的类方法
    void(^block)(void) = ^(void) {
        [SwiftTest swiftTest];
    };
    
    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;
        void *PC = __builtin_return_address(0);
        
        Dl_info info;
        dladdr(PC, &info);
        printf("fname=%s \n fbase=%p \n sname=%s \n saddr=%p \n",
               info.dli_fname,
               info.dli_fbase,
               info.dli_sname,
               info.dli_saddr);
    }
    @end
    

    接下来进入正题

    收集符号

    启动的相关方法可能在不同的线程执行,如果我们用一个数组直接收集这些符号,会出现线程问题。

    多线程问题会想到锁,但是锁耗费性能比较多不推荐使用。建议使用原子队列解决这个问题。

    原子队列是栈结构,通过 队列结构 + 原子性 保证顺序。

    接下来ViewController.m

    #import <libkern/OSAtomic.h>
    
    // 初始化院子队列
    static OSQueueHead list = OS_ATOMIC_QUEUE_INIT;
    // 定义节点结构体
    typedef struct {
        void *pc; // 存下获取到的PC
        void *next; // 指向下一个节点
    } Node;
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        NSMutableArray *arr = [NSMutableArray array];
        while (1) {
            Node *node = OSAtomicDequeue(&list, offsetof(Node, next));
            // 退出机制
            if (node == NULL) { break; } 
            // 获取函数信息
            Dl_info info;
            dladdr(node->pc, &info);
            NSString *sname = [NSString stringWithCString:info.dli_sname encoding:NSUTF8StringEncoding];
            
            // 去重复
            if (![arr containsObject:sname]) {
                [arr insertObject:sname atIndex:0]; // 入栈
            }
             printf("%s \n", info.dli_sname);
        }
    }
    
    void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
        if (!*guard) return;
        void *PC = __builtin_return_address(0);
        
        Node *node = malloc(sizeof(Node));
        *node = (Node){PC, NULL};
        //offsetOf()计算出列尾  OSAtomicEnqueue()把node加入list尾巴
        OSAtomicEnqueue(&list, node, offsetof(Node, next));
    }
    

    运行查看打印结果:一直在-[ViewController touchesBegan:withEvent:]死循环

    image.png

    处理这个while死循环,touchesBegan上打断点查看

    image.png

    文档里说 __sanitizer_cov_trace_pc_guard 会在每个边缘级别插入,那么每执行一次 while 循环应该算是一次边界

    解决方案: 修改为只 hook 函数, Target -> Build Setting -> Custom Complier Flags -> Other C Flags 修改为 -fsanitize-coverage=func,trace-pc-guard

    image.png

    再运行代码,获取到了正确的方法符号:

    image.png
    处理load函数

    在当前类添加 load 方法后执行看输出,发现 load 并没有被打印。
    load 方法调用时插入的 __sanitizer_cov_trace_pc_guard 参数 guard 为0,默认的函数实现会直接return,导致无法捕获到 load。

    只需要屏蔽掉 __sanitizer_cov_trace_pc_guard中的 if (!*guard) return; 即可:

    image.png
    符号缺失处理

    可以看到输出的函数符号里面缺少了之前声明的函数(test、func、swiftTest)block
    是因为我没有去调用他们。在viewDidLoad方法上去调用[self test];
    (只有被调用的函数才会被__sanitizer_cov_trace_pc_guard捕获)
    再运行查看输出

    image.png
    处理c函数block的符号

    生成 Order File 前还需要对 c函数 和 block 做特殊处理

    // 添加处理c函数以及block前缀部分内容
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        NSMutableArray *arr = [NSMutableArray array];
        while (1) {
            Node *node = OSAtomicDequeue(&list, offsetof(Node, next));
            if (node == NULL) { // 退出机制
                break;
            }
            // 获取函数信息
            Dl_info info;
            dladdr(node->pc, &info);
            NSString *sname = [NSString stringWithCString:info.dli_sname encoding:NSUTF8StringEncoding];
            
            // 处理c函数以及block前缀
            BOOL isObjc = [sname hasPrefix:@"+["] || [sname hasPrefix:@"-["];
            // c函数及block需要在开头添加下划线
            sname = isObjc ? sname : [@"_" stringByAppendingString:sname];
            
            // 去重复
            if (![arr containsObject:sname]) {
                // 入栈
                [arr insertObject:sname atIndex:0];
            }
            // 打印看看
             printf("%s \n", info.dli_sname);
        }
    }
    
    生成Order File

    生成 Order File 就是把上面加工好的方法符号集合拼接成字符串并写入文件。
    但是记得移除掉点击触发的touchesBegan:withEvent:的符号

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        NSMutableArray *arr = [NSMutableArray array];
        while (1) {
            Node *node = OSAtomicDequeue(&list, offsetof(Node, next));
            if (node == NULL) { // 退出机制
                break;
            }
            // 获取函数信息
            Dl_info info;
            dladdr(node->pc, &info);
            NSString *sname = [NSString stringWithCString:info.dli_sname encoding:NSUTF8StringEncoding];
            
            // 处理c函数以及block前缀
            BOOL isObjc = [sname hasPrefix:@"+["] || [sname hasPrefix:@"-["];
            // c函数及block需要在开头添加下划线
            sname = isObjc ? sname : [@"_" stringByAppendingString:sname];
            
            // 去重复
            if (![arr containsObject:sname]) {
                // 入栈
                [arr insertObject:sname atIndex:0];
            }
            // 打印看看
            // printf("%s \n", info.dli_sname);
        }
        // 去掉touchBegan方法(因为启动时,不会调用它)
        [arr removeObject:[NSString stringWithFormat:@"%s", __FUNCTION__]];
        // 将数组合成字符串
        NSString *funcStr = [arr componentsJoinedByString:@"\n"];
        // 写入文件
        NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"link.order"];
        NSLog(@"path: %@", filePath);
        NSData *fileContents = [funcStr dataUsingEncoding:NSUTF8StringEncoding];
        [[NSFileManager defaultManager] createFileAtPath:filePath contents:fileContents attributes:nil];
    }
    

    运行输出路径:

    image.png

    打开link.order文件:

    image.png

    link.order复制到工程配置link.order目录(我们这里是根目录)

    image.png image.png

    添加环境变量

    DYLD_PRINT_STATISTICS : YES
    
    image.png image.png

    clear一下command+shift+K,再重新编译command+B后,查看Link Map File

    Test-LinkMap-normal-x86_64.txt

    大功告成。
    最后附上ViewController.m的代码

    #import "ViewController.h"
    #import "BLStopwatch.h"
    #import "Test-Swift.h"
    #import <dlfcn.h>
    #import <libkern/OSAtomic.h>
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    +(void)load {
        NSLog(@"load");
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        [self test];
    }
    
    - (void)test {
        func();
    }
    
    void func() {
        block();
    }
    
    void(^block)(void) = ^(void) {
        [SwiftTestObject swiftTest];
    };
    
    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 list = OS_ATOMIC_QUEUE_INIT;
    // 定义节点结构体
    typedef struct {
        void *pc; // 存下获取到的PC
        void *next; // 指向下一个节点
    } Node;
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        // [self test];
        NSMutableArray *arr = [NSMutableArray array];
        while (1) {
            Node *node = OSAtomicDequeue(&list, offsetof(Node, next));
            if (node == NULL) { // 退出机制
                break;
            }
            // 获取函数信息
            Dl_info info;
            dladdr(node->pc, &info);
            NSString *sname = [NSString stringWithCString:info.dli_sname encoding:NSUTF8StringEncoding];
            
            // 处理c函数以及block前缀
            BOOL isObjc = [sname hasPrefix:@"+["] || [sname hasPrefix:@"-["];
            // c函数及block需要在开头添加下划线
            sname = isObjc ? sname : [@"_" stringByAppendingString:sname];
            
            // 去重复
            if (![arr containsObject:sname]) {
                // 入栈
                [arr insertObject:sname atIndex:0];
            }
            // 打印看看
            // printf("%s \n", info.dli_sname);
        }
        // 去掉touchBegan方法(因为启动时,不会调用它)
        [arr removeObject:[NSString stringWithFormat:@"%s", __FUNCTION__]];
        // 将数组合成字符串
        NSString *funcStr = [arr componentsJoinedByString:@"\n"];
        // 写入文件
        NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"link.order"];
        NSLog(@"path: %@", filePath);
        NSData *fileContents = [funcStr dataUsingEncoding:NSUTF8StringEncoding];
        [[NSFileManager defaultManager] createFileAtPath:filePath contents:fileContents attributes:nil];
    }
    
    void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
       // if (!*guard) return; // guard为0会直接return,不会捕获load
       void *PC = __builtin_return_address(0);
        
    //  char PcDescr[1024];
    //  printf("guard: %p %x PC %s\n", guard, *guard, PcDescr);
        
    //    Dl_info info;
    //    dladdr(PC, &info);
    //
    //    printf("fname=%s \n fbase=%p \n sname=%s \n saddr=%p \n",
    //           info.dli_fname,
    //           info.dli_fbase,
    //           info.dli_sname,
    //           info.dli_saddr);
        
        Node *node = malloc(sizeof(Node));
        *node = (Node){PC, NULL};
        //offsetOf() 计算出列尾,OSAtomicEnqueue() 把node加入list尾巴
        OSAtomicEnqueue(&list, node, offsetof(Node, next));
    }
    

    demo

    相关文章

      网友评论

          本文标题:iOS 启动优化-生成 Order File

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