查看原文
其他

开源|WBBlades:基于Mach-O文件解析的APP分析工具

邓竹立 58技术 2022-03-15




开源项目专题系列(三)
1.开源项目名称:WBBlades2.github地址:

https://github.com/wuba/WBBlades

3.简介:WBBlades是58同城推出的一款Mach-O文件解析的APP分析工具集,包含以Pod为维度的APP大小分析、无用类检测以及无符合表情景下的崩溃解析。目前WBBlades已经成为58同城客户端在并行研发和多app研发场景下性能检测与质量保证的不可或缺工具。



WBBlades中的工具,基于相同的Mach-O文件解析技术,主要具备以下特点:
Ø  App大小分析工具
  • 无需编译链接,可直接对静态库文件进行分析,相对基于linkmap文件的分析更高效和灵活;

  • 实现了模拟链接器的工作流程,对静态库二进制进行数据提取和合并,从而直接输出静态库链接后的大小,模拟后的大小与真实编译链接的大小无显著性差异。

Ø  无用类检测工具
  • 相比已公开的常见方案,支持对类的继承、动态调用、自身类调用、属性及成员变量等情景下的无用类检测,无用类识别精度更高。

Ø  无符号表崩溃解析工具
  • 在无符号表的情况下,通过对二进制文件的解析实现OC代码的日志符号化;
  • 易扩展、易推广,具备可在终端上独立运行能力,可通过脚本调用、传参。

背景介绍

WBBlades工具集基于不同时间点的需求背景,不断探索,产出了相应的工具。

1. 大小分析工具

首先诞生的APP大小分析工具。58同城作为一个平台型的APP,经常需要接入或更新SDK,在接入SDK之前,我们需要明确SDK或更新的大小成本,并与相关方评估成本和收益。在此之前,如果要评估一个SDK的接入大小,我们往往需要编译SDK提供的demo,通过demo的大小来估算SDK的大小,或者通过linkmap来分析SDK的大小。其弊端在于对demo进行编译链接,长此以往是个耗费体力和时间的工作。

我们期望能够根据静态库文件直接分析出静态库链接后的大小,无需编译链接,因此我们仿照模拟链接器的工作流程,对静态库二进制进行数据提取和合并,从而直接输出静态库链接后的大小;此外,我们将工具推广到58同城APP的大小分析,通过对cocoapods所产生的静态库产物进行大小分析,从而实现了58同城APP的大小分布统计,实现了一套方案多处复用。


2. 无用类检测

通过对58同城APP大小分布统计,我们发现58同城的APP代码大小和资源大小比例为:13:5,打破了我们以往认为资源瘦身是最有效的手段的思维惯式。58同城的几个主要业务线代码和资源大小在arm64+3x屏手机上数据对比如下图所示:





图 1业务线代码与资源大小对比

因此,从代码的角度实现瘦身是十分必要的。我们首先调研了业界的常用技术手段,分别是:

  • 脚本分析源代码

  • 基于linkmap+Clang的检测技术

  • 针对framework目标文件优化的技术方案

  • 基于otool+Mach-O的检测技术

前三种技术方案的优缺点由于篇幅限制,在此不做赘述。第四种方案也是业界使用最广泛的方案,通过otool命令提取可执行文件的classlist section 和 classref section。形成classlist和classref的地址差集。但是此方案存在以下问题:

(1)动态调用的类并不会被加入到classref中。
(2)作为基类、作为属性、成员变量不会被加入到classref。
(3)调用了load方法,将自身注册到其他数据中(如RN的module注册)不会被放入到classref中。
(4)类自身内部调用,外界并没有调用,这个类也被放入classref中。

为了解决上述问题,我们通过解析Mach-O文件,从一定程度上解决前3个问题,并通过对反汇编引擎capstone的应用,解决问题(4)。


3. 日志解析工具

58同城在业务开发阶段提供给测试同学的测试包都是通过Jenkins服务打包。随着业务的发展,58同城APP的大小越来越庞大,这就导致测试同学从Jenkins服务器上下载APP的时间较长。为了能够尽可能的减小下载大小,58同城将APP的符号表在打包期间从应用程序中剥离出来形成dSYM文件,保存在打包服务器中。因此测试同学下载的Jenkins包是不包含符号表信息的。由于剥离出来的dSYM文件较大,为了节省服务器空间,dSYM在保留2天后会自动清除。假设有这样一个场景,即测试同学下载了一个测试包,在测试到第三天时发生了不可稳定复现的崩溃,在这种即没有dSYM文件也没有bugly的symbol文件的情况下,如何才能恢复堆栈符号?为此,我们通过解析Mach-O文件,查找崩溃偏移地址位于哪个函数的指令区间范围内,从而实现崩溃日志的符号化。


技术原理

WBBlades工具集的技术原理是一致的,都是基于Mach-O文件的解析。整个项目的核心是基于类是如何在Mach-O文件中存储的。在了解类在Mach-O文件中存储结构后,我们就能通过Mach-O文件获取当前项目中有哪些类,每个类的类名及方法名,以及方法的指令存储范围。如果能清楚准确的知道以上信息,那么项目代码基本上可以说是半透明的了,我们能做的事情也就非常多了。

Mach-O文件可以分为两大段:文本段和数据段。在文本段中主要存储的是字符串及指令,类名、方法名最终都是以字符串的形式存储在文本段中。方法编译后也以指令的形式存储在文本段中,以arm64架构为例,方法编译后,会形成N条4字节的指令存储到__text section中。而在DATA段中,本质上存储的都是地址,这些地址通过直接或多次寻址后会索引到TEXT段。例如(__DATA,__objc_classlist)节,该节数据存储的是所有类的地址(这里所说的地址是指距Mach-O文件起始地址的偏移地址)。arm64的二进制文件中,此地址指向一个class64的结构体。class64结构体起始8字节用于记录类的isa指针,指向类的元类。末尾8字节用于记录data,指向类的class64Info结构体。class64Info存储类名的字符串地址及方法列表地址。class64结构体和class64Info结构体如下:

struct class64 {    unsigned long long isa;//元类在文件中的地址    unsigned long long superClass;    unsigned long long cache;    unsigned long long vtable;    unsigned long long data;//class64Info在文件中的地址};struct class64Info {    unsigned int flags;    unsigned int instanceStart;    unsigned int instanceSize;    unsigned int reserved;    unsigned long long  instanceVarLayout;    unsigned long long  name;//类名在文件中的地址    unsigned long long  baseMethods;//方法列表在文件中的地址    unsigned long long  baseProtocols;    unsigned long long  instanceVariables;    unsigned long long  weakInstanceVariables;    unsigned long long  baseProperties;};

方法列表中存储的是类的实例方法,如果想要查找类方法,那么需要先跳转到元类的class64Info结构体,再查找方法列表。根据上面的寻址后,我们能够找到每个类的所有类方法和实例方法。那么方法在方法列表中是如何存储的呢?首先来看一张简图:





图 2 方法列表

从上图可以看出,在方法列表中,前8字节为method64_list_t结构体,用于说明方法的数量,此后文件中连续存储method64_t结构体,通过method64_t我们可以找到这个方法的名称和函数起始地址。在这里,我们先简单总结下前面的内容:首先根据Mach-O文件的(__DATA,__objc_classlist)节获取所有类的地址,根据地址从Mach-O文件中读取class64结构体,根据class64结构体data成员记录的地址获取class64Info结构体。最后再根据class64Info结构体的baseMethods成员获取到实例方法列表(类方法需要读取元类数据,读取方法相同)。获取到方法列表的起始地址后,即可根据method64_list_t + N*method64_t的方式从文件中读取每个方法的函数地址。获取到的函数地址即为函数距离Mach-O文件的偏移地址,如函数地址为0xAAAA,则说明在Mach-O文件,从0xAAAA字节开始是某函数的地址,此后连续N字节为该函数的函数指令。





图 3 如何在Mach-O文件中获取函数地址

假设函数起始地址为0xAAAA,函数结束地址为0xBBBB,那么当崩溃堆栈显示偏移地址为0xAABB时,则说明崩溃指令位于该函数之类范围内,也就是说此函数发生崩溃。那么问题来了,根据上面所述的方法,可以通过Mach-O文件定位到函数的起始地址,可以通过崩溃日志确定崩溃发生时的指令偏移地址,那么如何才能确定函数的结束地址呢?在iOS系统中,一条arm64的指令为4字节,当函数结束时会执行一条ret指令,当我们从函数起始地址开始扫描,扫描到ret指令时即可认为函数结束。ret指令为无操作码指令,其指令固定为0xC0035FD6。我们从0xAAAA开始读取指令,每次按序获取4字节并读取这4字节的内容,如果内容为0xC0035FD6则认为函数结束。同理,在遍历类的过程中我们也可以获取成员变量、继承关系等信息。

在这里留个悬念,上文中主要是以OC语言作为示例,并且本质上是利用了OC语言的动态特性来实现文件解析的。换言之,如果是C/C++语言,那么上面的过程是无法生效的,那么作为苹果极力推崇的Swift,上面的寻址过程是否能生效呢?感兴趣的同学可以按上文中的过程尝试下。


技术方案对比

1. 大小分析

主流的大小分析方案是通过linkmap文件分析,本方案与linkmap文件分析相比,使用较为简单,无需编译链接即可分析统计。





图 4 linkmap与WBBlades对比

在准确性方面,linkmap为链器件记录的数据,理论上来说更为准确。但是我们随机选取了19个SDK,通过linkmap(以https://github.com/huanxsd/LinkMap为例)和WBBlades分别进行分析,两个方案的数据比较接近,偏差在10%以内。





图 5 linkmap与WBBlades 数据对比

2. 无用类检测

基于otool+Mach-O的检测技术是目前使用较多的方案,58同城APP早期也是选用此方案,其优点为简单快速,但是58同城APP大小较为庞大,内部代码复杂多样,因此解析出的数据误差较大,准确率较低。





图 6 WBBlades与otool 对比

由于WBBlades的无用类检测在检测debug包时,为了提高精确度遍历了符号表和指令。在58同城APP中存在2800万条指令以及百万级别的符号表,非常耗时。为了解决耗时问题,我们提供了圈选功能,开发者查看某几个SDK或某几个Pod的无用类。


3. 日志解析工具

WBBlades的日志解析功能最大的特点是不依赖任何符号表,58同城APP具备dSYM文件符号化、bugly的symbol文件符号化、无符号表符号化多维度多场景的日志解析能力。除了不依赖符号表之外,WBBlades的日志解析功能可以集成到APP中,在APP内实现崩溃拦截+崩溃解析的一条龙服务。





图 7 WBBlades日志解析Mac工具


如何使用

WBBlades 提供了2种使用方式,开发可以通过Mac应用直接使用相应的功能:





图 8 Mac 可视化工具主界面

也可以通过终端调用,输入相应的参数即可使用相应的功能。如果需要实现自动化流程,那么可以通过脚本调用并赋参。





10 终端调用

后期规划

我们将持续优化和改进工具的准确性和易用性,目前大小分析工具对C++/Swift编译的文件存在一定的误差,这是我们后期要重点关注的问题。现阶段工具集只支持arm64架构的Mach-O文件,如果有必要我们将增加对Fat文件及armv7架构的支持。另外,APP内日志解析相关代码将在后期开源。

如何贡献&问题反馈

我们诚挚地希望开发者提出宝贵的意见和建议,您可以在 https://github.com/wuba/WBBlades 了解项目源码、使用方法、启动方式等。欢迎提交 PR 或者 Issue,向我们反馈建议和问题。

作者简介

邓竹立,58同城用户价值增长部-iOS技术部资深研发工程师,WBBlades主要开发者之一,主要负责WBBlades的底层开发与调优。

朴惠姝,58同城用户价值增长部-iOS技术部高级研发工程师,主要负责客户端性能优化及工具研发,跨平台库的研发。

林雅明,58同城用户价值增长部-iOS技术部开发工程师,主要负责WBBlades的Mac版应用开发。


参考文献

1. Capstone: https://github.com/aquynh/capstone

2. LinkMap: https://github.com/huanxsd/LinkMap

3. MachOView: https://github.com/gdbinit/MachOView

4. iOS代码瘦身实践:删除无用的类:

https://juejin.im/post/5d5d1a92e51d45620923886a

5. 趣探 Mach-O:文件格式分析:

https://www.jianshu.com/p/54d842db3f69

6. iOS Crash 捕获及堆栈符号化思路剖析:

https://www.jianshu.com/p/29051908c74b


想了解更多开源项目信息?
与项目成员零距离交流?
扫码加小秘书微信
一切应有尽有




微信号 : jishu-58
添加小秘书微信后由小秘书拉您进项目交流群

END


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存