iOS 启动优化-生成 Order File
启动优化-理论篇
启动优化-二进制重排篇
启动优化-编译期插桩篇
启动优化-生成 Order File
先附上demo代码
SwiftTest.swift
文件
import UIKit
@objc class SwiftTest: NSObject {
@objc class public func swiftTest() {
print("SwiftTestObject打印:test")
}
}
ViewController.m
文件
#import "ViewController.h"
#import "Test-Swift.h"
#import <dlfcn.h>
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self test];
}
// 声明一个Objective-C方法,调用C函数
- (void)test {
func();
}
// 声明一个C函数调用block
void func() {
block();
}
// 声明一个block调用swift类的类方法
void(^block)(void) = ^(void) {
[SwiftTest swiftTest];
};
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.
}
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("fname=%s \n fbase=%p \n sname=%s \n saddr=%p \n",
info.dli_fname,
info.dli_fbase,
info.dli_sname,
info.dli_saddr);
}
@end
接下来进入正题
收集符号
启动的相关方法可能在不同的线程执行,如果我们用一个数组直接收集这些符号,会出现线程问题。
多线程问题会想到锁,但是锁耗费性能比较多不推荐使用。建议使用原子队列
解决这个问题。
原子队列是栈结构,通过 队列结构 + 原子性 保证顺序。
接下来ViewController.m
#import <libkern/OSAtomic.h>
// 初始化院子队列
static OSQueueHead list = OS_ATOMIC_QUEUE_INIT;
// 定义节点结构体
typedef struct {
void *pc; // 存下获取到的PC
void *next; // 指向下一个节点
} Node;
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSMutableArray *arr = [NSMutableArray array];
while (1) {
Node *node = OSAtomicDequeue(&list, offsetof(Node, next));
// 退出机制
if (node == NULL) { break; }
// 获取函数信息
Dl_info info;
dladdr(node->pc, &info);
NSString *sname = [NSString stringWithCString:info.dli_sname encoding:NSUTF8StringEncoding];
// 去重复
if (![arr containsObject:sname]) {
[arr insertObject:sname atIndex:0]; // 入栈
}
printf("%s \n", info.dli_sname);
}
}
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
if (!*guard) return;
void *PC = __builtin_return_address(0);
Node *node = malloc(sizeof(Node));
*node = (Node){PC, NULL};
//offsetOf()计算出列尾 OSAtomicEnqueue()把node加入list尾巴
OSAtomicEnqueue(&list, node, offsetof(Node, next));
}
运行查看打印结果:一直在-[ViewController touchesBegan:withEvent:]
里死循环
处理这个while死循环,touchesBegan上打断点查看
image.png文档里说 __sanitizer_cov_trace_pc_guard
会在每个边缘级别插入,那么每执行一次 while 循环应该算是一次边界!
解决方案: 修改为只 hook 函数, Target
-> Build Setting
-> Custom Complier Flags
-> Other C Flags
修改为 -fsanitize-coverage=func,trace-pc-guard
再运行代码,获取到了正确的方法符号:
image.png处理load函数
在当前类添加 load 方法后执行看输出,发现 load 并没有被打印。
load
方法调用时插入的 __sanitizer_cov_trace_pc_guard
参数 guard 为0
,默认的函数实现会直接return,导致无法捕获到 load。
只需要屏蔽掉 __sanitizer_cov_trace_pc_guard
中的 if (!*guard) return;
即可:
符号缺失处理
可以看到输出的函数符号里面缺少了之前声明的函数(test、func、swiftTest)
和 block
是因为我没有去调用他们。在viewDidLoad
方法上去调用[self test];
(只有被调用的函数才会被__sanitizer_cov_trace_pc_guard捕获)
再运行查看输出
处理c函数
和block
的符号
生成 Order File 前还需要对 c函数 和 block 做特殊处理
// 添加处理c函数以及block前缀部分内容
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSMutableArray *arr = [NSMutableArray array];
while (1) {
Node *node = OSAtomicDequeue(&list, offsetof(Node, next));
if (node == NULL) { // 退出机制
break;
}
// 获取函数信息
Dl_info info;
dladdr(node->pc, &info);
NSString *sname = [NSString stringWithCString:info.dli_sname encoding:NSUTF8StringEncoding];
// 处理c函数以及block前缀
BOOL isObjc = [sname hasPrefix:@"+["] || [sname hasPrefix:@"-["];
// c函数及block需要在开头添加下划线
sname = isObjc ? sname : [@"_" stringByAppendingString:sname];
// 去重复
if (![arr containsObject:sname]) {
// 入栈
[arr insertObject:sname atIndex:0];
}
// 打印看看
printf("%s \n", info.dli_sname);
}
}
生成Order File
生成 Order File
就是把上面加工好的方法符号集合拼接成字符串并写入文件。
但是记得移除掉点击触发的touchesBegan:withEvent:的符号
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSMutableArray *arr = [NSMutableArray array];
while (1) {
Node *node = OSAtomicDequeue(&list, offsetof(Node, next));
if (node == NULL) { // 退出机制
break;
}
// 获取函数信息
Dl_info info;
dladdr(node->pc, &info);
NSString *sname = [NSString stringWithCString:info.dli_sname encoding:NSUTF8StringEncoding];
// 处理c函数以及block前缀
BOOL isObjc = [sname hasPrefix:@"+["] || [sname hasPrefix:@"-["];
// c函数及block需要在开头添加下划线
sname = isObjc ? sname : [@"_" stringByAppendingString:sname];
// 去重复
if (![arr containsObject:sname]) {
// 入栈
[arr insertObject:sname atIndex:0];
}
// 打印看看
// printf("%s \n", info.dli_sname);
}
// 去掉touchBegan方法(因为启动时,不会调用它)
[arr removeObject:[NSString stringWithFormat:@"%s", __FUNCTION__]];
// 将数组合成字符串
NSString *funcStr = [arr componentsJoinedByString:@"\n"];
// 写入文件
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"link.order"];
NSLog(@"path: %@", filePath);
NSData *fileContents = [funcStr dataUsingEncoding:NSUTF8StringEncoding];
[[NSFileManager defaultManager] createFileAtPath:filePath contents:fileContents attributes:nil];
}
运行输出路径:
image.png打开link.order
文件:
将link.order
复制到工程配置link.order目录(我们这里是根目录)
添加环境变量
DYLD_PRINT_STATISTICS : YES
image.png
image.png
clear一下command+shift+K,再重新编译command+B后,查看Link Map File
大功告成。
最后附上ViewController.m的代码
#import "ViewController.h"
#import "BLStopwatch.h"
#import "Test-Swift.h"
#import <dlfcn.h>
#import <libkern/OSAtomic.h>
@interface ViewController ()
@end
@implementation ViewController
+(void)load {
NSLog(@"load");
}
- (void)viewDidLoad {
[super viewDidLoad];
[self test];
}
- (void)test {
func();
}
void func() {
block();
}
void(^block)(void) = ^(void) {
[SwiftTestObject swiftTest];
};
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.
}
// 初始化院子队列
static OSQueueHead list = OS_ATOMIC_QUEUE_INIT;
// 定义节点结构体
typedef struct {
void *pc; // 存下获取到的PC
void *next; // 指向下一个节点
} Node;
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// [self test];
NSMutableArray *arr = [NSMutableArray array];
while (1) {
Node *node = OSAtomicDequeue(&list, offsetof(Node, next));
if (node == NULL) { // 退出机制
break;
}
// 获取函数信息
Dl_info info;
dladdr(node->pc, &info);
NSString *sname = [NSString stringWithCString:info.dli_sname encoding:NSUTF8StringEncoding];
// 处理c函数以及block前缀
BOOL isObjc = [sname hasPrefix:@"+["] || [sname hasPrefix:@"-["];
// c函数及block需要在开头添加下划线
sname = isObjc ? sname : [@"_" stringByAppendingString:sname];
// 去重复
if (![arr containsObject:sname]) {
// 入栈
[arr insertObject:sname atIndex:0];
}
// 打印看看
// printf("%s \n", info.dli_sname);
}
// 去掉touchBegan方法(因为启动时,不会调用它)
[arr removeObject:[NSString stringWithFormat:@"%s", __FUNCTION__]];
// 将数组合成字符串
NSString *funcStr = [arr componentsJoinedByString:@"\n"];
// 写入文件
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"link.order"];
NSLog(@"path: %@", filePath);
NSData *fileContents = [funcStr dataUsingEncoding:NSUTF8StringEncoding];
[[NSFileManager defaultManager] createFileAtPath:filePath contents:fileContents attributes:nil];
}
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
// if (!*guard) return; // guard为0会直接return,不会捕获load
void *PC = __builtin_return_address(0);
// char PcDescr[1024];
// printf("guard: %p %x PC %s\n", guard, *guard, PcDescr);
// Dl_info info;
// dladdr(PC, &info);
//
// printf("fname=%s \n fbase=%p \n sname=%s \n saddr=%p \n",
// info.dli_fname,
// info.dli_fbase,
// info.dli_sname,
// info.dli_saddr);
Node *node = malloc(sizeof(Node));
*node = (Node){PC, NULL};
//offsetOf() 计算出列尾,OSAtomicEnqueue() 把node加入list尾巴
OSAtomicEnqueue(&list, node, offsetof(Node, next));
}