啟動優(yōu)化(一)-理論篇
啟動優(yōu)化(二)-二進制重排篇
啟動優(yōu)化(三)-編譯期插樁篇
啟動優(yōu)化(四)-生成 Order File
學前小知識
相關文獻:
clang.llvm.org

Tracing PCs是用來跟蹤cpu將要執(zhí)行的指令代碼。

工程中配置使用它-fsanitized-coverage=trace-pc-guard,編譯器會在每個代碼邊緣插入以下代碼: __sanitizer_cov_trace_pc_guard(&guard_variable)。
在工程上配置了-fsanitized-coverage=trace-pc-guard后再編譯工程就會報錯,需要定義兩個函數(shù)__sanitizer_cov_trace_pc_guard_init和__sanitizer_cov_trace_pc_guard。
這是編譯器插入代碼的需要回調(diào)執(zhí)行的部分。這兩個函數(shù)的實現(xiàn)有一個案例了

可以自行查看這個編譯器文檔clang.llvm.org
概念
編譯器插樁就是在代碼編譯期間修改已有的代碼或生成新代碼。
編譯期時,在每一個函數(shù)內(nèi)部二進制源數(shù)據(jù)添加 hook 代碼來實現(xiàn)全局 hook 效果。
具體實現(xiàn)
新建空工程Test
添加配置 Target -> Build Setting -> Custom Complier Flags -> Other C Flags 添加 fsanitize-coverage=func,trace-pc-guard

在ViewController.m文件中添加代碼
#include <stdint.h>
#include <stdio.h>
#include <sanitizer/coverage_interface.h>
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);
char PcDescr[1024];
printf("guard: %p %x PC %s\n", guard, *guard, PcDescr);
}
工程跑起來看輸出結果:

__sanitizer_cov_trace_pc_guard_init
start 里面存的是一堆序號,而stop 里面的并不是序號

打個斷點重新運行發(fā)現(xiàn) start 和 stop 中間是 01 ~ 0e,十進制的 1~14。

說明我們空項目里有14個符號。
這會不會是函數(shù)的序號呢?給他安排個方法touchBegin試試看:
在__sanitizer_cov_trace_pc_guard_init最后添加打印N
NSLog(@"%llu", N);

注意看14+1變成了15!?。?br> 再添加一個block和一個C函數(shù)

15+2變成了17。驗證這是函數(shù)的序號。
再安排一個swfit 函數(shù)呢?
import UIKit
@objc class SwiftTest: NSObject {
@objc class public func swiftTest() {
print("SwiftTestObject打?。簍est")
}
}
配置Target -> Build Setting -> Custom Complier Flags -> Other Swift Flags 添加:
-sanitize-coverage=func
-sanitize=undefined

在ViewController.m導入swift #import "Test-Swift.h"
數(shù)量一下子增加到了0x21,因生成文件同時生成了不少自帶方法。
__sanitizer_cov_trace_pc_guard_init 函數(shù)里可以獲取到整個工程里所有函數(shù)/方法的數(shù)量。
那么肯定也有辦法獲取方法具體的相關信息。
重點就是接下來要分析的__sanitizer_cov_trace_pc_guard
__sanitizer_cov_trace_pc_guard
在[ViewController touchesBegan:withEvent:] 斷點運行后,點擊屏幕查看:

可以看到在調(diào)用方法的時候插入了__sanitizer_cov_trace_pc_guard 函數(shù)。
所以在系統(tǒng)調(diào)用每一個函數(shù)/方法之前都會先走__sanitizer_cov_trace_pc_guard 函數(shù)。
而把所有函數(shù)/方法之前插入這個函數(shù)做這件事最合適的就是編譯器啦。
在 __sanitizer_cov_trace_pc_guard 函數(shù)里面拿到 原函數(shù)/原方法 的地址!

獲取方法符號
#import <dlfcn.h>
dlfcn.h 中有一個 dladdr() 方法,可以通過函數(shù)內(nèi)部地址找到函數(shù)符號。
該方法需要用到結構體Dl_info

獲取Dl_info
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); // 符號地址
}
打印結果:

通過編譯器插樁獲取了方法符號,而且順序正是調(diào)用函數(shù)的順序!
那下一步我們可以通過獲取的方法符號,寫入link.order(動態(tài)生成link.order)