一、前言

上一篇的办法给到的是个数,但不是符号,个数并没有什么作用,甚至给了全部的符号也没什么用,由于二进制重排仅仅需求的是发动阶段所需求的符号,这就需求下面这个函数:

void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
}

增加断点,点击箭头,能够看到绿色框中的函数调用栈:

启动优化clang插桩(二)

函数调用栈跟之前讲到的app称号.LinkMap-normal-arm64.txt文件里边的数据格式相同。

把上面断点过掉,给touchBegan办法增加断点::

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
///
}

点击上面的绿色箭头:

启动优化clang插桩(二)

这儿就出现了touchesBegan,那大概推出下面这个函数是系统每调用一个办法,都会调用这个__sanitizer_cov_trace_pc_guard函数:

void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
}

二、调试验证

验证是否为真给下面两个函数增加打印办法:

void test(void) {
    NSLog(@"%s",__func__);
}
void (^block) (void) = ^{
    NSLog(@"%s",__func__);
};

一起在touchesBegan办法里边为增加打印和调用两个函数:

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"%s",__func__);
    test();
    block();
}

这个追寻办法也增加打印:

void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
    NSLog(@"%s",__func__);
}

cmd + r发动,先清空打印,点击屏幕:

TraceDemo[20273:399962] __sanitizer_cov_trace_pc_guard
TraceDemo[20273:399962] -[ViewController touchesBegan:withEvent:]
TraceDemo[20273:399962] __sanitizer_cov_trace_pc_guard
TraceDemo[20273:399962] test
TraceDemo[20273:399962] __sanitizer_cov_trace_pc_guard
TraceDemo[20273:399962] block_block_invoke

能够看出先__sanitizer_cov_trace_pc_guard 再调用办法、函数、block,这说明不管是办法、函数、block它都会去回调这个函数,而且这个函数的调用是咱们在调用这个函数之前,也便是这个函数阻拦或许hook了一切的办法、函数包含block, 这就搞定了咱们没有其他操作就能阻拦到app发动时分调用了那些办法和函数

void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
NSLog(@"%s",__func__);
}

咱们在程序发动时分__sanitizer_cov_trace_pc_guard阻拦到的办法函数:

启动优化clang插桩(二)

把这些写入到.order文件里边这样二进制重排就搞定了

_sanitizer_cov_trace_pc_guard,这个函数是怎么做到这一点的?给_sanitizer_cov_trace_pc_guard增加断点,在Xcode的Debug挑选Debug WorkFlow挑选显现汇编,挑选main

启动优化clang插桩(二)

能够看到在main之前,系统插入了_sanitizer_cov_trace_pc_guard这个符号:

启动优化clang插桩(二)

AppDelegate页面也是插入了这个符号:

启动优化clang插桩(二)

SceneDelegate相同如此:

启动优化clang插桩(二)

也便是说,在编译器clang增加下面这个标记后,编译器会给函数办法前面都会调用_sanitizer_cov_trace_pc_guard这个函数:

启动优化clang插桩(二)

这样,咱们确实在打断点看到发动阶段的一切符号。

三、怎么把符号都打印出来

先翻开注释的代码:

void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
    NSLog(@"%s",__func__);
    if (!*guard) return;
    void *PC = __builtin_return_address(0);
//    char PcDescr[1024];
//    printf("guard: %p %x PC %s\n", guard, *guard, PcDescr);
}

上面的PC指的是上一个函数的地址,有了这个函数地址,就能够拿到这个函数的符号,这儿需求用到dladdr函数:

#import <dlfcn.h>

dladdr(const void *, Dl_info *),这个函数能够获得一个函数的称号以及地址,第一个参数传入PC,第二个参数界说一个变量 Dl_info info传进去:

Dl_info info;
dladdr(PC, &info);

检查Dl_info,它是一个结构体:

typedef struct dl_info {
        const char      *dli_fname;     /* Pathname of shared object */
        void            *dli_fbase;     /* Base address of shared object */
        const char      *dli_sname;     /* Name of nearest symbol */
        void            *dli_saddr;     /* Address of nearest symbol */
} Dl_info;

dli_sname便是所需求符号

删去其他打印,把调试代码改为如下:

void __sanitizer_cov_trace_pc_guard_init(uint32_t *start,
                                         uint32_t *stop) {
        static uint64_t N;
        if (start == stop || *start) return;
        printf("INIT: %p %p\n", start, stop);
        for (uint32_t *x = start; x < stop; x++)
            *x = ++N;
        NSLog(@"%d",N);
}
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("dli_sname -- %s\n",info.dli_sname);
}

运转:

dli_sname -- main
dli_sname -- -[AppDelegate application:didFinishLaunchingWithOptions:]
dli_sname -- -[SceneDelegate window]
dli_sname -- -[SceneDelegate setWindow:]
dli_sname -- -[SceneDelegate window]
dli_sname -- -[SceneDelegate scene:willConnectToSession:options:]
dli_sname -- -[SceneDelegate window]
dli_sname -- -[ViewController viewDidLoad]
dli_sname -- -[SceneDelegate sceneWillEnterForeground:]
dli_sname -- -[SceneDelegate sceneDidBecomeActive:]

得到了发动时分所需求的一切符号和发动次序,拿到这些符号之后,把它复制粘贴到.order文件中,就能够实现之前需求的方针,便是拿到把发动所需求的符号和次序加载在前面的page里边,就实现了二进制重排。在放进.order文件之前,需求把重复的符号删掉,而且关于函数或许block,需求在前面加个“_”,这样整个clang插桩就现已完结。

四、获取符号方式优化

可是,上面手动的办法不够灵活,应该让计算机去做这些操作,用代码完结上面的操作。

方针:

  1. 去掉重复的符号
  2. 如果是函数就前面加上“_”
  3. 生成一个.order文件

那第一步便是要对下面的符号进行搜集,然后存储起来:

void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
//    NSLog(@"%s",__func__);
    if (!*guard) return;
    void *PC = __builtin_return_address(0);
    Dl_info info;
    dladdr(PC, &info);
    printf("dli_sname -- %s\n",info.dli_sname);
}

这就需求有一个全局的容器来寄存,而且这儿会涉及到多线程的状况,由于一个app发动不大可能只要一个线程在跑,由于函数、办法和block在哪个线程跑,这个获取符号的回调函数也在那个线程运转,所以在符号搜集的时分,需求考虑线程安全问题。

所以,这边运用线程安全的原子行列,导入头文件:

#import <libkern/OSAtomic.h>

界说一个全局容器结构体:

typedef struct {
  void * pc;
  void *next;
} SYNode;

然后在回调函数里边进行操作:

void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
    if (!*guard) return;
    void *PC = __builtin_return_address(0);
//拓荒空间
    SYNode *node = malloc(sizeof(SYNode));
//赋值
    *node = (SYNode){PC,NULL};
    //结构体存入原子行列
    OSAtomicEnqueue(&symbolList, node, offsetof(SYNode, next ));
}

offsetof里边的next是下一个存储方位的偏移量,这样咱们就把符号都放进了symbolList里边,在合适的方位把它取出来就行。