iOS 启动优化-Clang 插桩

前言

APP启动过程详解+优化(二进制重排)
一文中了解了由于缺页中断导致启动耗时,我们可以编译的时候根据我们.order方法进行排列,但是我们项目比较大的话,找到方法进行排列就比较困难。而这篇文章就是解决这个问题,拭目以待吧。

准备工作

1. Clang插桩配置

LLVM内置一个简单的代码覆盖仪表SanitizerCoverage)。它将用户定义的函数插入函数、基本块和边缘级别的调用。提供了这些回调的默认实现,并实现简单的覆盖报告和可视化。Clang代码覆盖文章一文有详细的说明,如下:

1.1 配置

如果开发的是oc项目的话,在 Build Settings 里的 Other C Flags 中添加如下配置:

-fsanitize-coverage=trace-pc-guard

Other C Flags配置

在任意一个控制器或者文件添加文档说明的方法实现,编译通过。如下:
编译方法

运行后打印出方法的实现,如下:
运行打印方法的实现

__sanitizer_cov_trace_pc_guard_init其中的startstop代表是方法的个数起始和结束的个数,进行for循环,当起始地址的值和终止的相同时return。如下:
查看起止个数

这里0e表示14个符号,我们添加个 方法 和block,如下:
添加block与方法测试

十六进制10表示16,那么添加一个方法和block相当于添加了两个符号进去了。

补充:
我们如果swift项目或者混编的话,在 Build Settings 里的 Other Swift Flags 中添加如下配置:

-sanitize-coverage=func
-sanitize=undefined
swift环境配置

1.2 原理

我们使用惯用的手法打开汇编断点查看如下:

main函数中查看汇编断点

汇编断点中可以看出main函数后会调用__sanitizer_cov_trace_pc_guard,然后继续追踪断点,如下:
断点追踪

发现[AppDelegate application:didFinishLaunchingWithOptions:]函数会调用__sanitizer_cov_trace_pc_guard

经过多次的断点跟踪,后续发现在实现我们的方法后都会调用__sanitizer_cov_trace_pc_guard,相当于系统在编译的时候会给我们项目中方法添加hook,进行标记,定位我们的方法。我们是通过Other c Flags添加的标记,所以肯定是在编译期做这个插入代码的动作

2. 具体实现

之前分析了只要调用我们自身的方法就会调用系统为我们添加的hook
__builtin_return_address(0)这个函数返回的是上一个函数的地址,也就是调用者,这个PC就是上一个函数的地址,表示第0行插入的__sanitizer_cov_trace_pc_guard。我们取出这个地址的信息,我们通过Dl_info来保存该方法的信息。

Dl_info info;
dladdr(PC, &info);

导入#import <dlfcn.h>Dl_info的组成,包括路径方法名地址。如下:

dl_info结构体

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

打印的时候没有+load方法,我们打断点发现确实进入了,但是guad0,因此写入的时候去除这个判断。如下:
查看+load方法

2.1 原子操作保存方法名

我们添加1个方法进行获取方法名,我们在存储的时候,多线程调用方法,这个hook也会在多线程,这个时候写入操作的话,会造成线程不安全,因此我们采用原子操作

我们导入#import <libkern/OSAtomic.h>定义原子队列定义符号结构体,如下:

声明院子队列和符号结构体

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

这里添加next节点地址也就是PC的信息,方便我们取出的时候判断是否时最后一个最后一个没有下个节点信息的

touchesBegan中添加for循环,如下:

touchesBegan添加for循环查看

结果死循环了,hook把我们的for循环也捕获了,导致了表一直插入进入死循环,我们修改下标记,如下:

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

运行结果

如果项目中存在swift代码的话,或者block等则需要判断,如下:
swift与block判断添加

然后进行添加反向遍历,并去除本身调用的方法,如下:
添加和反向遍历

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

运行并打印结果,按照执行的顺序排列,如下:
打印结果

2.3 链接orderFile

写入在OrderFile中进行链接,编译后方法的顺序就是我们启动时执行的顺序,如下:

链接orderFile

3. 总结

通过官方对方法的hook,定位到实现的方法,存入数组,最后按.order文件进行读取,从而减少page fault的次数提高启动速度。写入的具体代码:

static OSQueueHead symbolList = OS_ATOMIC_QUEUE_INIT;

//定义符号结构体

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};//赋值,强转,next表示下个节点的地址

    

    OSAtomicEnqueue(&symbolList, node, offsetof(KBNode, next));//原子列表写入方法,并把下个节点的地址写入。

}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event

{

    //定义数组

    NSMutableArray<NSString *> * symbleNames = [NSMutableArray array];

    while (YES) {

        KBNode *node = OSAtomicDequeue(&symbolList, offsetof(KBNode, next));

        if (node == NULL) {

            break;//没有的话结束

        }

        Dl_info info;

        dladdr(node->pc, &info);

//        printf("%s\n",info.dli_sname);

        NSString * name = @(info.dli_sname);//转字符串

        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]) {//数组没有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);

}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容