二进制重排与Clang插桩

  • 背景
  • 优化方案
  • 准备
  • 认识
    • 插件安装
    • 启动优化
    • 操作系统
      • 演进史
      • 进程通信
  • 二进制重排
    • .Order文件
    • 小节问题
  • Clang插桩
    • Clang插桩配置
    • Clang插桩原理(获取及通过原子队列保存符号)
    • 坑点
    • 取反&去重
    • 生成.order文件
    • Swift方法处理(符号覆盖)
      • Swift环境配置

背景

  • 随着app的迭代,日常业务变多,项目复杂度变高,引起app的启动越来越缓慢
    那怎么优化app的启动呢?
    一般app启动分main函数之前, 和main函数之后,那么App的启动优化也可在main之前(pre-main)和main之后。
    最早执行代码的地方为+load方法。那么有什么方式可以控制这些顺序吗?
  • 大民哥带你认识iOS中二进制重排与Clang插桩

优化方案

Dyld反馈资源浪费:毫秒级
业务逻辑
Main函数之前 pre-main
Main函数之后

准备

  • objc源码
  • clang脚本

认识

插件安装

启动优化

  • iOS检测:dyld会把app启动耗时反馈给我们
    1,dyld重签名,然后设置PRINT_STSTISTICS

    运行:

    Dyld反馈等都是资源的浪费,基本上也是毫秒级的浪费!
    我们需要看的是
  • dylib loading:苹果建议的动态库载入不超过6个
  • rebase:虚拟内存,虚拟内存载入到物理内存发生缺页异常时,根据ASLR重写矫正地址,随机起始值 不同偏移量,ASLR+offsert
  • binding:绑定,以懒加载绑定
  • ObjC:OC类的注册,减少类的定义,分类的定义(实际项目中去除废弃不用的类)
  • initalizer:执行+load、构造函数的耗时
    下面的几个系统库已做了高度优化,不做研究。

操作系统

演进史

  • 物理内存的时代:内存不足,不安全,
  • 物理内存切片时代:使用懒加载,但内存不连续了,麻烦和不安全
  • 虚拟内存时代:使用虚拟内存 存放于映射表中,CPU的MMU(翻译地址)翻译成物理地址,然后到物理内存中找到物理内存Page(页表),PageiOS中16k,Mac中4k,解决内存不够用的问题,进程与进程之间的安全隔离保证了内存的安全,每次访问只访问虚拟内存对应的物理地址的那一页数据。
  • 按需加载,分页加载
PAGESIZE

  • 当虚拟页表中的内存在物理内存中没有的时候会进入缺页中断Pagefault
  • 这个时候新的虚拟内存要加入物理内存中,执行LRU算法,在物理内存中覆盖不活跃的进程
  • 虚拟页表中最后会有些空的内存空间,访问时候为NULL
  • 在64位系统里面,虚拟内存8G可以访问小于4G物理内存,因为有4个G的空间不让使用,因为要隔离兼容前面32位系统的4个G,

    段:MachO文件格式,可变
    页:内存里面的单位,固定
    CPU数据吞吐量(数据总线),32位4字节,64位8字节

进程通信

  • 进程间通信,是通过kernel发信号,虚拟内存的共享缓存空间 访问物理内存的共享访问空间

二进制重排

如果同时有大量缺页异常,那就影响启动时间了(冷启动)

  • PageFault(缺页异常/中断)毫秒级

引入脚本appSign.sh,注入WeChat二进制文件.app



第一次启动(冷启动),我们看到缺页异常6000+次,启动耗时1秒多,当再启动时(热启动),缺页异常1000+次,启动耗时3.多毫秒。
那么我们怎么优化冷启动时间呢?(主要跟PageFault浪费有关)


  • 排列二进制时,把启动时需要调用的方法全部向前排,最大化优化启动时Pagefault次数

.Order文件


order文件里的顺序就是给编译器看的编译顺序文件。

  • 第一步:工程目录文件建立.order文件
  • 然后,在.order文件中写入编译顺序
  • Xcode->Build Settings -> order File写入(./文件名称.order)

小节问题

二进制重排是没问题了,那么问题来了,App启动时方法的调用顺序又是什么呢?
还有OC与Swift混编的调用顺序呢?
方法,c函数,Block这些顺序怎么看呢?

  • hook objc_msgSend();能解决OC方法,但其他的又都hook不到,那么Clang就来了

Clang插桩

Clang会读程序中所有的代码

Clang插桩配置

Clang找到Trance PC
添加-fsanitize-coverage=trace-pc-guard到工程Other C Flag:

Clang插桩原理(获取及通过原子队列保存符号)

然后在ViewController里面声明头文件和实现:

#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);//指针偏移4字节间隔,最后一个数据为stop - 4,防止内存溢出for (uint32_t *x = start; x < stop; x++)*x = ++N;
}//启动时调用了哪些方法函数以及顺序,在这里都会被hook,一切的回调函数,无论线程
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};//结构体入栈,加入结构(链表结构)!- 列表头,存入node,标记SYNode类型,next:把下一个内存地址返回给node的nextOSAtomicEnqueue(&symbolList, node, offsetof(SYNode, next));
}
//写入.order文件
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{//定义数组NSMutableArray<NSString *> * symbolNames = [NSMutableArray array];while (YES) {//一次循环!也会被HOOK一次!!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);free(node);//给函数添加_BOOL isObjc = [name hasPrefix:@"+["]||[name hasPrefix:@"-["];NSString * symbolName = isObjc ? name : [@"_" stringByAppendingString:name];//是否去重??[symbolNames addObject:symbolName];/*if ([name hasPrefix:@"+["]||[name hasPrefix:@"-["]) {//如果是OC方法名称直接存![symbolNames addObject:name];continue;}//如果不是OC直接加个_存![symbolNames addObject:[@"_" stringByAppendingString:name]];*/}//反向数组
//    symbolNames = (NSMutableArray<NSString *>*)[[symbolNames reverseObjectEnumerator] allObjects];NSEnumerator * enumerator = [symbolNames reverseObjectEnumerator];//创建一个新数组NSMutableArray * funcs = [NSMutableArray arrayWithCapacity:symbolNames.count];NSString * name;//去重!while (name = [enumerator nextObject]) {if (![funcs containsObject:name]) {//数组中不包含name[funcs addObject:name];}}[funcs removeObject:[NSString stringWithFormat:@"%s",__FUNCTION__]];//数组转成字符串NSString * funcStr = [funcs componentsJoinedByString:@"\n"];//字符串写入文件//文件路径NSString * filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"hank.order"];//文件内容NSData * fileContents = [funcStr dataUsingEncoding:NSUTF8StringEncoding];[[NSFileManager defaultManager] createFileAtPath:filePath contents:fileContents attributes:nil];
}


监控到方法个数。
我们生成的项目方法函数调用及顺序表:

  • 只要添加Clang插桩标记,编译器会在所有方法,函数,block代码实现边缘添加一句代码__sanitizer_cov_trace_pc_guard,代表其方法的调用

坑点

坑点Clang在做代码跟踪时,不仅把方法,函数,block拦截,进入循环体内也进行了拦截,然后一直死循环,比如:touchesBegan
解决方式:
在Xcode->TARGETS->Build Settings的Other C Flags添加参数func设置仅拦截方法:

取反&去重

数组取反:

 NSEnumerator * enumerator = [symbolNames reverseObjectEnumerator];

去重:

//创建一个新数组NSMutableArray * funcs = [NSMutableArray arrayWithCapacity:symbolNames.count];
NSString * name;//去重!while (name = [enumerator nextObject]) {if (![funcs containsObject:name]) {//数组中不包含name[funcs addObject:name];}}

生成.order文件

生成文件:

//文件路径NSString * filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"hank.order"];//文件内容NSData * fileContents = [funcStr dataUsingEncoding:NSUTF8StringEncoding];[[NSFileManager defaultManager] createFileAtPath:filePath contents:fileContents attributes:nil];

Swift方法处理(符号覆盖)

因为 Swift与OC不是同一个编译器。
创建添加.Swift文件,然后在OC代码中导入工程名- Swift.h并build

Swift环境配置

Xcode->Build Setting -> Other Swift Flags(添加-sanitize-coverage=func-sanitize=undefined)

然后编译:

  • 看到Swift相应的类和方法,由于做了混淆,优化了性能。

iOS启动优化-二进制重排与Clang插桩相关推荐

  1. iOS启动优化 —— 二进制重排

    iOS启动优化 -- 理论 1. app启动 2. 虚拟内存 & 物理内存 3. 缺页中断(pagefault) 4. 二进制重排 1. app启动 启动的过程一般是指从用户点击app图标开始 ...

  2. APP启动优化——二进制重排,从入门到精通

    一 理论介绍 1.1缺页中断 1.2 Linkmap 1.3 看二进制文件布局 二 探索重排方案 静态扫描+运行时trace. 思维方式,自顶向下的思维方式 Clang SanitizerCovera ...

  3. vm磁盘映射 不能启动_iOS 启动优化之Clang插桩实现二进制重排

    前言 原文作者:李斌同学 原文链接:https://juejin.im/post/6844904130406793224 自从抖音团队分享了这篇 抖音研发实践:基于二进制文件重排的解决方案 APP启动 ...

  4. 抖音品质建设 - iOS启动优化《实战篇》

    前言 启动是 App 给用户的第一印象,启动越慢,用户流失的概率就越高,良好的启动速度是用户体验不可缺少的一环.启动优化涉及到的知识点非常多,面也很广,一篇文章难以包含全部,所以拆分成两部分:原理和实 ...

  5. iOS 启动优化和安装包瘦身

    iOS 启动优化和安装包瘦身 1 启动优化 在iPhone的启动方式中,分为冷启动和热启动两种方式: 1.冷启动(Cold Launch):从零开始启动APP ,需要系统新创建一个进程进行启动,这是一 ...

  6. iOS启动优化之——如何使用Xcode Log、App Launch、代码来计算启动时间 Launch Time

    在iOS启动优化之--如何使用MetricKit 来计算启动时间 Launch Time ,我们提到,可以使用MetricKit 在Organizer中或者直接代码统计,那么还能用什么来统计呢? 配置 ...

  7. iOS 性能优化-启动优化、main函数之前优化-二进制重排

    一个app的启动时间,很大程度会影响用户的体验,所以能优化还是尽量优化的.之前我们已经探究过dyld加载的流程,启动流程分为main函数之前和main函数之后.这里主要做main函数之前的优化建议. ...

  8. 抖音品质建设 - iOS启动优化之原理篇

    前言 启动是 App 给用户的第一印象,启动越慢用户流失的概率就越高,良好的启动速度是用户体验不可缺少的一环.启动优化涉及到的知识点非常多面也很广,一篇文章难以包含全部,所以拆分成两部分:原理和实战. ...

  9. iOS启动优化(一)

    1.启动优化 我们的App如果启动时间过长,会出现白屏的问题.在我们App中,我们一般会集成很多的功能,在启动时,会加载很多的组件以及初始化,这样耗费的时间越多,白屏时间就会越长,用户体验相对来说就会 ...

最新文章

  1. 在VMWare中配置SQLServer2005集群 Step by Step(四)——集群安装
  2. Docker 1.7.0 深度解析
  3. Java在MVC开发模式中使用try-catch以及throws避免踩坑
  4. SpringBoot上传图片的示例
  5. 塔式Server 服务器ESXI6.5安装
  6. nuxt解决首屏加载慢问题_一个 Node 脚本让你的前端项目加载速度飞起来
  7. [javascript|基本概念|Number]学习笔记
  8. 新计算机 安装win2000,图文教程!Windows 2000安装过程全接触
  9. Spring Cloud 服务消费者 Feign (三)
  10. js对本地文件进行加密_怎么对电脑文件进行加密
  11. Atitit 上传进度的实现与原理 目录 1.1. 前端 1 1.2. 读取进度 1 1.3. 后端 定时注入进度 1 1.1.前端                         wind
  12. 【组合数学】递推方程 ( 特特解示例 1 汉诺塔 完整求解过程 | 特解示例 2 特征根为 1 的情况下的特解处理 )
  13. 十二导联动态心电图技术参数
  14. 【大数据语言】怎样利用Python爬虫,高效获取大规模数据
  15. 51单片机小车—循迹温湿度检测显示
  16. UltraISO软碟通安装与刻盘以及安装镜像
  17. PMP 项目沟通管理
  18. 微信小程序-JavaScript 3DES对称加密算法加密使用
  19. PHP - 性能测试工具
  20. css浮动清除以及BFC

热门文章

  1. 国网GIM设备三维模型要求细则 - 框架式电容器
  2. 利用Arduino uno控制24BYJ-48电机正反转停止( 不使用步进库实现方法二)
  3. G304电量查询与灯光讲解(驱动下载见上篇文章)
  4. 常用的设计模式(泡妞经典版)
  5. 人生苦短我用Python 五:ERROR: No matching distribution found for REfo==0.13
  6. word水印 禁止复制水印
  7. 修改了DNS服务器网速慢,如何修改DNS让网速快到飞起!教你两招让电视、盒子告别卡顿...
  8. apche和nginx分别与php的连接方式区别
  9. 收集的开源代码下载网站
  10. Android开发项目——智能农业(知识点整理回顾)