设置检查代码


对运行做设置
在Argument标签下添加DYLD_PRINT_STATISTICS作为key
运行后效果如下

-
Total pre-main time :
程序运行到main函数之前的时间
-
dylib loading time :
动态库的加载时间
-
rebase/binding time :
内部方法(自己定义的函数等)的偏移量修正+外部方法(系统方法,如NSLog)的绑定
当应用加载到内存的时候,系统会分配一个随机的内存地址,放在你文件的开头 —— ASLR
真实地址 = ASLR + 文件偏移地址
-
ObjC setup time :
OC每个类注册时间
-
initializer time :
load方法和initializer方法的执行时间
动态&静态 区别
- 动态:方法 申明不实现 编译不报错
- 函数:方法 申请不实现 编译报错 名称=指针
- 总结:汇编时动态通过sender massage转汇编,静态的直接取函数地址使用
优化方案
main函数之前
- 尽量减少或合并动态库,苹果官方建议动态库少于6个(dylib loading time)
- 减少类的数量,冗余代码删除(rebase/binding time / ObjC setup time)
- Swift语言是静态语言,所以在rebase/binding time耗时相对较少
main函数之后
- 减少初始化流程 —— 懒加载/后台加载
- 优化代码逻辑(主要的方案)
- 启动阶段尽量使用多线程,尽量将CPU的性能发挥出来
- 减少xlb/storyboard的使用
二进制重排 (重点)
iOS每个应用都有4G的连续虚拟内存空间

分页管理 iOS每一页16kb

优化了内存 但是存在部分安全问题
为了解决安全问题 使用ASLR技术
每次虚拟内存地址添加了一个随机的偏移值,防止通过固定值访问某些数据
缺页异常次数检查 command+i

冷启动:内存中没有应用对应的数据或对应的数据被覆盖(并非杀死进程再启动就是冷启动)
热启动:内存中有应用对应的数据
程序运行的顺序
为了验证函数的执行顺序先分别在 AppDelegate.m 和 第一个加载的ViewController.m中添加Load方法

查看.m的存放顺序

系统会根据.m文件存放顺序加载
-
获取当前应用的符号顺序
在设置中将Write Link Map File调整为YES,编译后就可以到处对应的文件
导出的文件
打开后能查看对应的文件加载顺序

找到对应的方法执行

这个顺序就是方法执行的顺序
我们可以手动的调整顺序
在工程目录下新建一个.order文件

在order文件中写入以下函数

在工程中指定order文件

重新编译下代码,并重新打开linkmap文件可以看到

运行的顺序已经根据我们设置的order文件来调整了
-
备注 order 中如果写入了不存在的函数 不会被执行 系统默认会忽略
-
因此,只要在order中调整启动时的函数执行顺序,就能对应用进行优化。
HOOK 所有方法
通过clang插桩
Clang相关文档
项目中设置 Other C Flags —— -fsanitize-coverage=trace-pc-guard
因为只查找方法 调整为 -fsanitize-coverage=func,trace-pc-guard

编译后报了错误

但是程序中没有用到这两个符号
通过文档得知,需要添加几个方法
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);
printf("总计:%x",*(stop -1));
for (uint32_t *x = start; x < stop; x++)
*x = ++N; // Guards should start from 1.
}
// This callback is inserted by the compiler on every edge in the
// control flow (some optimizations apply).
// Typically, the compiler will emit the code like this:
// if(*guard)
// __sanitizer_cov_trace_pc_guard(guard);
// But for large functions it will emit a simple call:
// __sanitizer_cov_trace_pc_guard(guard);
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
if (!*guard) return; // Duplicate the guard check.
// If you set *guard to 0 this code will not be called again for this edge.
// Now you can get the PC and do whatever you want:
// store it somewhere or symbolize it and print right away.
// The values of `*guard` are as you set them in
// __sanitizer_cov_trace_pc_guard_init and so you can make them consecutive
// and use them to dereference an array or a bit vector.
// void *PC = __builtin_return_address(0);
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);
}
头文件添加
#include <stdint.h>
#include <stdio.h>
#include <sanitizer/coverage_interface.h>
编译后正常,然后控制台有以下输出

进入断点后打印结束指针的内容
内容 = 结束地址 - 0x4

得到一个 37
之后在代码中添加一个函数

再次执行

变成了 38
再试着添加一个block

再次执行

变成了 39
由此可知 Clang能抓取到所有的方法函数
直接修改汇编代码,在所有方法开头加入一段汇编代码,调用到一个函数里面去 —— 全局的AOP
深入研究下
建立原子队列和结构体
//原子队列
static OSQueueHead symbolList = OS_ATOMIC_QUEUE_INIT;
//定义符号结构体
typedef struct {
void *pc;
void *next;
}SYNode;
在函数void __sanitizer_cov_trace_pc_guard(uint32_t *guard)中植入
void *PC = __builtin_return_address(0);
SYNode *node = malloc(sizeof(SYNode));
*node = (SYNode){PC,NULL};
//进入
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;
dladdr(node->pc, &info);
NSString * name = @(info.dli_sname);
//判断?
BOOL isObjc = [name hasPrefix:@"+["] || [name hasPrefix:@"-["];
NSString * symbolName = isObjc ? name: [@"_" stringByAppendingString:name];
[symbolNames addObject:symbolName];
}
//取反
NSEnumerator * emt = [symbolNames reverseObjectEnumerator];
//去重
NSMutableArray<NSString *> *funcs = [NSMutableArray arrayWithCapacity:symbolNames.count];
NSString * name;
while (name = [emt nextObject]) {
if (![funcs containsObject:name]) {
[funcs addObject:name];
}
}
//干掉自己!
[funcs removeObject:[NSString stringWithFormat:@"%s",__FUNCTION__]];
//将数组变成字符串
NSString * funcStr = [funcs componentsJoinedByString:@"\n"];
NSString * filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"snow.order"];
NSData * fileContents = [funcStr dataUsingEncoding:NSUTF8StringEncoding];
[[NSFileManager defaultManager] createFileAtPath:filePath contents:fileContents attributes:nil];
NSLog(@"%@",funcStr);
}
运行程序,点击屏幕后就能得到对应的方法列表

之后在工程使用导出的order文件,就完成了二进制重排的操作。
网友评论