启动优化3

2022-06-23  本文已影响0人  f8d1cf28626a

二进制重排 & clang插桩

注意:在iOS生产环境的app,在发生Page Fault进行重新加载时,iOS系统还会对其做一次签名验证,因此 iOS 生产环境的 Page Fault 比Debug环境下所产生的耗时更多。

下面,我们来进行具体的实践,首先理解几个名词

Link Map

Linkmap是iOS编译过程的中间产物,记录了二进制文件的布局,需要在Xcode的Build Settings里开启Write Link Map File,Link Map主要包含三部分:

ld

ld是Xcode使用的链接器,有一个参数order_file,我们可以通过在Build Settings -> Order File配置一个后缀为order的文件路径。在这个order文件中,将所需要的符号按照顺序写在里面,在项目编译时,会按照这个文件的顺序进行加载,以此来达到我们的优化

所以二进制重排的本质就是对启动加载的符号进行重新排列

到目前为止,原理我们基本弄清楚了,如果项目比较小,完全可以自定义一个order文件,将方法的顺序手动添加,但是如果项目较大,涉及的方法特别多,此时我们如何获取启动运行的函数呢?有以下几种思路

Tracing PCs with guards
With -fsanitize-coverage=trace-pc-guard the compiler will insert the following code on every edge:


// 使用两个回调函数

// trace-pc-guard-cb.cc
#include <stdint.h>
#include <stdio.h>
#include <sanitizer/coverage_interface.h>
#import <dlfcn.h>
#import <libkern/OSAtomic.h>


// 1.这个函数可以获取项目中符号的个数(函数个数)
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.
}

// 2.这个函数可以拦截到当前项目(machO中)所有正在执行的函数,属性
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {

  if (!*guard) return;  // Duplicate the guard check.
  
// 返回地址return_address的意思是:调用了__sanitizer_cov_trace_pc_guard的函数,谁调用了__sanitizer_cov_trace_pc_guard,谁的地址就是return_address

  void *PC = __builtin_return_address(0);

  // Dl_info info;

  // dladdr(PC,&info);

  // 创建结构体
  SYNode *node = malloc(sizeof(SYNode));
  *node = (SYNode){PC,NULL}

  // 结构体入栈
  OSAtomicEnqueue(&symbolList,node,offsetof(SYNode,next));

  
  
  printf("%s\n %s\n %s\n %s\n",
                               info.dli_frame,
                               info.dli_fbase
                               info.dli_sname
                               info.dli_saddr);
  // info.bli_sname 是符号名称

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



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

// 定义一个符号的结构体
typedef struct {
  void *pc;
  void *next;
}SYNode;

// 取出symbolList中的数据


-(void)roc_getSymbolList{

   while(YES){

   SYNode *node = OSAtomicDequeue(&symbolList,offsetof(SYNode,next));
   
   if (node == NULL) break;

   Dl_info info;

   dladdr(node->pc,&info);

   printf("%s \n",info.dli_sname);

 }
}

   // 1.运行之后 死循环,OMG -> 永远在执行 打印roc_getSymbolList
   // 2.在 while(YES){} 循环体内部也进行了拦截,导致死循环
   // 3.解决:在 Build Setting / Other C Flag 中配置 `-fsanitize-coverage=func,trace-pc-guard`

start & stop debug

start数据 stop数据 stop最后一个数据 stop读取图解
结果 0e 00 00 00 (14个符号,即`是dyld之后调用的函数个数`)

Hook all 的原理

编译之前只要添加了clang插桩的标记:-fsanitize-coverage=trace-pc-guard

编译之后编译器就会在实现函数的edge插入一个函数,这个函数就是__sanitizer_cov_trace_pc_guard
so 起到拦截所有函数的效果,在执行函数内部内容之前

汇编形式的拦截原理

这也是苹果官方对app上线之前做代码审查的方案,所以在发布产品时记得去掉 -fsanitize-coverage=trace-pc-guard 配置

提示:如果需要制作乌龟壳这是至关重要的一部分

拦截获取函数符号

已拦截的all符号

1.在 多线程 会影响执行的顺序,需要捋正

2.在 while(YES){} 循环体内部也进行了拦截,会导致死循环,这个坑好大吗

需要将函数符号按照顺序添加到other文件中
通过原子队列保存符号(看上面代码部分的操作 OSAtomicEnqueue & OSAtomicDequeue)
  arm64汇编 `bl` == x86_64汇编 `call`
在 Build Setting / Other C Flag 中配置 `-fsanitize-coverage=func,trace-pc-guard` 添加了`func,`
func, 完美解决坑点之后的打印

取反 & 去重


-(void)roc_getSymbolList{


   NSMutableArray<NSString*> *symbolNames = [NSMutableArray array];


   while(YES){

   SYNode *node = OSAtomicDequeue(&symbolList,offsetof(SYNode,next));
   
   if (node == NULL) break;

   Dl_info info;

   dladdr(node->pc,&info);

   printf("%s \n",info.dli_sname);

   // const chanr* 转 oc 字符串
   NSString *symbolName = @(info.dli_sname);
   // 如果是OC的方法直接存储
   if ([symbolName hasPrefix@"+["] || [symbolName hasPrefix@"+["]){
      
       [symbolNames addObject:symbolName];
       continue;
   }
   
   [symbolNames addObject:[@"_" stringByAppendingString:symbolName]];
 }

  // 反向 & 去重

  // symbolNames = (NSMutableArray<NSSting*> *)[[symbolNames reverseObjectEnumerator] allObjects];

  NSEnumerator *em = [symbolNames reverseObjectEnumerator];

  // symbolNames.count 节约内存开销

  NSMutableArray *funcs = [NSMutableArray arrayWithCapacity:symbolNames.count];

  NSString *s_name;

  while(s_name = [em nextObject]){

    if (![funcs containsObject:s_name]){

       [funcs addObject:s_name];
   }
 }

  NSLog(@"%@",funcs);
  NSString *funcsStr = [funcs componentsJoinedByString:@"\n"];
  NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"hank.other"];
  NSData *fileData = [funcsStr dataUsingEncoding:NSUTF8StringEncoding];
  [[NSFileManager defaultManager] createFileAtPath:filePath contents:fileData attributes:nil];

}

   // 1.运行之后 死循环,OMG -> 永远在执行 打印roc_getSymbolList
   // 2.在 while(YES){} 循环体内部也进行了拦截,导致死循环
   // 3.解决:在 Build Setting / Other C Flag 中配置 `-fsanitize-coverage=func,trace-pc-guard`

// 秀代码

-(void)roc_getSymbolList{

   NSMutableArray<NSString*> *symbolNames = [NSMutableArray array];

   while(YES){

   SYNode *node = OSAtomicDequeue(&symbolList,offsetof(SYNode,next));
   
   if (node == NULL) break;

   Dl_info info;

   dladdr(node->pc,&info);

   printf("%s \n",info.dli_sname);

   // const chanr* 转 oc 字符串

   NSString *symbolName = @(info.dli_sname);

   // 如果是OC的方法直接存储

   BOOL hasObject = [symbolName hasPrefix@"+["] || [symbolName hasPrefix@"+["];

   NSString *s_name = hasObject ? symbolName : [@"_" stringByAppendingString:symbolName];

   [symbolNames addObject:s_name];

 }

   // 反向 & 去重

   NSEnumerator *em = [symbolNames reverseObjectEnumerator];

  // symbolNames.count 节约内存开销

  NSMutableArray *funcs = [NSMutableArray arrayWithCapacity:symbolNames.count];

  NSString *s_name;

  while(s_name = [em nextObject]){

    if (![funcs containsObject:s_name]){

       [funcs addObject:s_name];
   }
 }

  NSLog(@"%@",funcs);

  // 去掉自己这个调用函数
  [funcs removeObject:[NSString stringWithFormat:@"%s",__func__]];

  // 写入otther文件
  // 把数组里面的内容 变成 字符串加换行
  NSString *funcsStr = [funcs componentsJoinedByString:@"\n"];
  NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"hank.other"];
  NSData *fileData = [funcsStr dataUsingEncoding:NSUTF8StringEncoding];
  [[NSFileManager defaultManager] createFileAtPath:filePath contents:fileData attributes:nil];
   
}


funcs 写入 other文件

真机沙盒 hankOther 验证linkmap

1.找到 产品.app /show in finder/ 找到 [项目名称.build] 找到 [项目名称-LinkMap-normal-arm64.txt]

验证结果

swift 符号覆盖

otherSwift
上一篇下一篇

猜你喜欢

热点阅读