前言
在APP啟動過程詳解+優(yōu)化(二進制重排)
一文中了解了由于缺頁中斷導(dǎo)致啟動耗時,我們可以編譯的時候根據(jù)我們.order方法進行排列,但是我們項目比較大的話,找到方法進行排列就比較困難。而這篇文章就是解決這個問題,拭目以待吧。
準備工作
1. Clang插樁配置
LLVM內(nèi)置一個簡單的代碼覆蓋儀表(SanitizerCoverage)。它將用戶定義的函數(shù)插入函數(shù)、基本塊和邊緣級別的調(diào)用。提供了這些回調(diào)的默認實現(xiàn),并實現(xiàn)簡單的覆蓋報告和可視化。Clang代碼覆蓋文章一文有詳細的說明,如下:

1.1 配置
如果開發(fā)的是oc項目的話,在 Build Settings 里的 Other C Flags 中添加如下配置:
-fsanitize-coverage=trace-pc-guard

在任意一個控制器或者文件添加
文檔說明的方法實現(xiàn),編譯通過。如下:
運行后打印出方法的實現(xiàn),如下:

__sanitizer_cov_trace_pc_guard_init其中的start和stop代表是方法的個數(shù)起始和結(jié)束的個數(shù),進行for循環(huán),當(dāng)起始地址的值和終止的相同時return。如下:
這里
0e表示14個符號,我們添加個 方法 和block,如下:
十六進制
10表示16,那么添加一個方法和block相當(dāng)于添加了兩個符號進去了。
補充:
我們?nèi)绻?code>swift項目或者混編的話,在 Build Settings 里的 Other Swift Flags 中添加如下配置:
-sanitize-coverage=func
-sanitize=undefined

1.2 原理
我們使用慣用的手法打開匯編斷點查看如下:

匯編斷點中可以看出
main函數(shù)后會調(diào)用__sanitizer_cov_trace_pc_guard,然后繼續(xù)追蹤斷點,如下:
發(fā)現(xiàn)
[AppDelegate application:didFinishLaunchingWithOptions:]函數(shù)會調(diào)用__sanitizer_cov_trace_pc_guard。
經(jīng)過多次的斷點跟蹤,后續(xù)發(fā)現(xiàn)在實現(xiàn)我們的方法后都會調(diào)用__sanitizer_cov_trace_pc_guard,相當(dāng)于系統(tǒng)在編譯的時候會給我們項目中方法添加hook,進行標(biāo)記,定位我們的方法。我們是通過Other c Flags添加的標(biāo)記,所以肯定是在編譯期做這個插入代碼的動作。
2. 具體實現(xiàn)
之前分析了只要調(diào)用我們自身的方法就會調(diào)用系統(tǒng)為我們添加的hook,
__builtin_return_address(0)這個函數(shù)返回的是上一個函數(shù)的地址,也就是調(diào)用者,這個PC就是上一個函數(shù)的地址,表示第0行插入的__sanitizer_cov_trace_pc_guard。我們?nèi)〕鲞@個地址的信息,我們通過Dl_info來保存該方法的信息。
Dl_info info;
dladdr(PC, &info);
導(dǎo)入#import <dlfcn.h>,Dl_info的組成,包括路徑,方法名,地址。如下:

那么知道了信息的保存,就可以運行項目查看方法的名字,如下:

打印的時候沒有
+load方法,我們打斷點發(fā)現(xiàn)確實進入了,但是guad為0,因此寫入的時候去除這個判斷。如下:
2.1 原子操作保存方法名
我們添加1個方法進行獲取方法名,我們在存儲的時候,多線程調(diào)用方法,這個hook也會在多線程,這個時候?qū)懭氩僮鞯脑?,會造?code>線程不安全,因此我們采用原子操作。
我們導(dǎo)入#import <libkern/OSAtomic.h>定義原子隊列和定義符號結(jié)構(gòu)體,如下:

在
hook中寫入方法,如下:
這里添加
next節(jié)點地址也就是PC的信息,方便我們?nèi)〕龅臅r候判斷是否時最后一個,最后一個沒有下個節(jié)點信息的。
在touchesBegan中添加for循環(huán),如下:

結(jié)果
死循環(huán)了,hook把我們的for循環(huán)也捕獲了,導(dǎo)致了表一直插入進入死循環(huán),我們修改下標(biāo)記,如下:
-fsanitize-coverage=func,trace-pc-guard

如果項目中存在
swift代碼的話,或者block等則需要判斷,如下:
然后進行
添加和反向遍歷,并去除本身調(diào)用的方法,如下:
最后寫入
order文件,如下:
運行并打印結(jié)果,按照執(zhí)行的順序排列,如下:

2.3 鏈接orderFile
寫入在OrderFile中進行鏈接,編譯后方法的順序就是我們啟動時執(zhí)行的順序,如下:

3. 總結(jié)
通過官方對方法的hook,定位到實現(xiàn)的方法,存入數(shù)組,最后按.order文件進行讀取,從而減少page fault的次數(shù),提高啟動速度。寫入的具體代碼:
static OSQueueHead symbolList = OS_ATOMIC_QUEUE_INIT;
//定義符號結(jié)構(gòu)體
typedef struct {
void * pc;
void * next;
} KBNode;
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
// if (!*guard) return;
void *PC = __builtin_return_address(0);
// Dl_info info;
// dladdr(PC, &info);
// NSLog(@"%s\n,\n",info.dli_sname);
KBNode *node = malloc(sizeof(KBNode));
*node = (KBNode){PC,NULL};//賦值,強轉(zhuǎn),next表示下個節(jié)點的地址
OSAtomicEnqueue(&symbolList, node, offsetof(KBNode, next));//原子列表寫入方法,并把下個節(jié)點的地址寫入。
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//定義數(shù)組
NSMutableArray<NSString *> * symbleNames = [NSMutableArray array];
while (YES) {
KBNode *node = OSAtomicDequeue(&symbolList, offsetof(KBNode, next));
if (node == NULL) {
break;//沒有的話結(jié)束
}
Dl_info info;
dladdr(node->pc, &info);
// printf("%s\n",info.dli_sname);
NSString * name = @(info.dli_sname);//轉(zhuǎn)字符串
BOOL isObjc = [name hasPrefix:@"+["] || [name hasPrefix:@"-["];
NSString * symbolName = isObjc ? name : [@"_" stringByAppendingString:name];
[symbleNames addObject:symbolName];
}
//添加
NSEnumerator * em = [symbleNames reverseObjectEnumerator];
NSMutableArray * funcs = [NSMutableArray arrayWithCapacity:symbleNames.count];
NSString * name;
while (name = [em nextObject]) {
if (![funcs containsObject:name]) {//數(shù)組沒有name
[funcs addObject:name];
}
}
//去掉自己!
[funcs removeObject:[NSString stringWithFormat:@"%s",__func__]];
//寫入文件
//1.編程字符串
NSString * funcStr = [funcs componentsJoinedByString:@"\n"];
NSString * filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"test.order"];
NSData * file = [funcStr dataUsingEncoding:NSUTF8StringEncoding];
[[NSFileManager defaultManager] createFileAtPath:filePath contents:file attributes:nil];
NSLog(@"%@",funcStr);
}