iOS 底层探索(二十六) 启动优化☞二进制重排

在iOS 底层探索(二十五)启动优化方案中我们得知,二进制重排是有效优化启动时间的操作,但是这个方案需要对所有启动所需的方法进行二进制重排,即自己编辑order_file文件。那么就有一下几个点

  • 完全自己手写,并且对自己的项目特别熟,还要随时与其他开发人员交流。
  • 找到一种自动生成文件的方案。

SanitizerCoverage

查看SanitizerCoverage网站,查看Tracing PCs with guards内容,根据其内容修改TestDemo工程。

复制官网中的-fsanitize-coverage=trace-pc-guard内容到工程中。

二进制问题 ios提审_#import

然后Command + B编译一下,编译之后发现会报错如下错误

二进制问题 ios提审_ios_02

这是因为有两个方法没有实现,我们在ViewController.m文件中实现这两个方法,代码如下:

#include <stdint.h>
#include <stdio.h>
#include <sanitizer/coverage_interface.h>

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;
}

void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
// 当前方法为子线程回调。
  if (!*guard) return;
  // 当前函数返回上一个调用的地址
  void *PC = __builtin_return_address(0);
  char PcDescr[1024];
  printf("guard: %p %x PC %s\n", guard, *guard, PcDescr);
}

再次编译就没有错误了。

运行,打印__sanitizer_cov_trace_pc_guard_init方法中的startstop的值,如下

二进制问题 ios提审_#include_03

这个值为方法的符号个数。

__sanitizer_cov_trace_pc_guard方法为,当调用某方法时执行。

查看汇编

二进制问题 ios提审_#import_04

这个方法的意义为Hook汇编的跳转,在模拟器运行为callq,在真机上为bbl,即方法的调用。
那么现在已经知道这个方法可以HOOK住所有方法的调用,那么剩下的就是拿到方法名,并生成文件即可。
引入#import <dlfcn.h>框架,为动态库的显示调用。使用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;

引入#import <libkern/OSAtomic.h>创建原子队列。
取出内容,代码如下:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    while (YES) {
        SYNode *node = OSAtomicDequeue(&symbolList, offsetof(SYNode, next));
        if (node == NULL) {
            break;
        }
        Dl_info info = {0};
        dladdr(node->PC, &info);
        
    }
}

当取出时,会进入死循环,查看汇编可知。

二进制问题 ios提审_#include_05

搜索__sanitizer_cov_trace_pc_guard方法的调用,发现这里有三个__sanitizer_cov_trace_pc_guard方法的调用,这是因为while循环导致的,因为while循环也使用了callqbbl等汇编跳转方法。这样就导致了循环一次就会被HOOK一次。这也是因为我们使用的是pcHOOK的原因。
修改方法为将-fsanitize-coverage=trace-pc-guard修改为-fsanitize-coverage=func,trace-pc-guard,该问题就解决了。
整体代码如下

#include <stdint.h>
#include <stdio.h>
#include <sanitizer/coverage_interface.h>
#import <dlfcn.h>
#import <libkern/OSAtomic.h>

// 定义原子队列
static OSQueueHead symbolList = OS_ATOMIC_QUEUE_INIT;

// 定义符号结构体
typedef struct {
    void *PC;
    void *next;
}SYNode;

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;
}

void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
    void *PC = __builtin_return_address(0);
    // 创建结构体
    SYNode *node = malloc(sizeof(SYNode));
    *node = (SYNode){PC, NULL};
    // 加入结构体
    // offsetof 第一个参数为结构体,第二个参数为偏移到下一个节点。
    OSAtomicEnqueue(&symbolList, node, offsetof(SYNode, next));
    
}

// 这个函数为执行的最后一个函数。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 定义数组
    NSMutableArray<NSString *> *symbolNames = [NSMutableArray array];
    while (YES) {
        SYNode *node = OSAtomicDequeue(&symbolList, offsetof(SYNode, next));
        if (node == NULL) {
            break;
        }
        Dl_info info = {0};
        dladdr(node->PC, &info);
        printf("%s\n", info.dli_sname);
        NSString *name = @(info.dli_sname);
        if (![name hasPrefix:@"+["] && ![name hasPrefix:@"-["]) {
            // 非oc方法添加 _
            name = [@"_" stringByAppendingString:name];
        }
        if (name && ![symbolNames containsObject:name]) {
            // 去重
            [symbolNames addObject:name];
        }
    }
    // 数组倒叙
    symbolNames = (NSMutableArray<NSString *> *)[[symbolNames reverseObjectEnumerator] allObjects];
    // 数组转字符串
    NSString *funcString = [symbolNames componentsJoinedByString:@"\n"];
    // 字符串写入文件
    NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"testDemo.order"];
    NSData *fileContents = [funcString dataUsingEncoding:NSUTF8StringEncoding];
    [[NSFileManager defaultManager] createFileAtPath:filePath contents:fileContents attributes:nil];
    NSLog(@"funcString = %@", funcString);
}

内容如下

二进制问题 ios提审_objective-c_06

混编Swift代码时,在Other Swift中添加-sanitize=undefined-sanitize-coverage=func,如下

二进制问题 ios提审_objective-c_07

创建swift文件,并在load中调用swift方法
以上就是全部代码,去沙盒中找到该文件,利用上一篇文章的内容进行植入。