iOS 啟動優(yōu)化-Clang 插樁

前言

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

Other C Flags配置

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

運行后打印出方法的實現(xiàn),如下:
運行打印方法的實現(xiàn)

__sanitizer_cov_trace_pc_guard_init其中的startstop代表是方法的個數(shù)起始和結(jié)束的個數(shù),進行for循環(huán),當(dāng)起始地址的值和終止的相同時return。如下:
查看起止個數(shù)

這里0e表示14個符號,我們添加個 方法 和block,如下:
添加block與方法測試

十六進制10表示16,那么添加一個方法和block相當(dāng)于添加了兩個符號進去了。

補充:
我們?nèi)绻?code>swift項目或者混編的話,在 Build Settings 里的 Other Swift Flags 中添加如下配置:

-sanitize-coverage=func
-sanitize=undefined
swift環(huán)境配置

1.2 原理

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

main函數(shù)中查看匯編斷點

匯編斷點中可以看出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的組成,包括路徑,方法名,地址。如下:

dl_info結(jié)構(gòu)體

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

打印的時候沒有+load方法,我們打斷點發(fā)現(xiàn)確實進入了,但是guad0,因此寫入的時候去除這個判斷。如下:
查看+load方法

2.1 原子操作保存方法名

我們添加1個方法進行獲取方法名,我們在存儲的時候,多線程調(diào)用方法,這個hook也會在多線程,這個時候?qū)懭氩僮鞯脑?,會造?code>線程不安全,因此我們采用原子操作。

我們導(dǎo)入#import <libkern/OSAtomic.h>定義原子隊列定義符號結(jié)構(gòu)體,如下:

聲明院子隊列和符號結(jié)構(gòu)體

hook中寫入方法,如下:
hook操作

這里添加next節(jié)點地址也就是PC的信息,方便我們?nèi)〕龅臅r候判斷是否時最后一個,最后一個沒有下個節(jié)點信息的。

touchesBegan中添加for循環(huán),如下:

touchesBegan添加for循環(huán)查看

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

-fsanitize-coverage=func,trace-pc-guard

運行結(jié)果

如果項目中存在swift代碼的話,或者block等則需要判斷,如下:
swift與block判斷添加

然后進行添加反向遍歷,并去除本身調(diào)用的方法,如下:
添加和反向遍歷

最后寫入order文件,如下:
寫入order文件

運行并打印結(jié)果,按照執(zhí)行的順序排列,如下:
打印結(jié)果

2.3 鏈接orderFile

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

鏈接orderFile

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);

}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容