ipa 包分析教程
ipa解包后结构:
· 可执行文件
§ Mach-O 64-bit executable arm64
§ Mach-O executable arm_v7
· .car,资源打包文件 .nib、.bundle、Localizable.strings
· _CodeSignature签名文件
§ 文件hash列表:存放每个文件的hash值;
§ XML结构
· .mobileprovision
可执行文件Mach-O通常有三部分组成
· 头部 (Header): Mach-O文件的架构 比如Mac的 PPC, PPC64, IA-32, x86-64,ios的arm系列。
· 加载命令(Load commands): 在虚拟内存中指定文件的逻辑结构和布局。
· 原始段数据(Raw segment data):可以拥有多个段(segment),每个段可以拥有零个或多个区域(section)。每一个段(segment)都拥有一段虚拟地址映射到进程的地址空间。
·
1. XCode开启编译选项Write Link Map File
XCode -> Project -> Build Settings -> 搜map -> 把Write Link Map File选项设为YES,并指定好linkMap的存储位置
特别提醒:打包发布前记得还原为NO
2. 编译后,到编译目录里找到该txt文件,文件名和路径就是上述的Path to Link Map File位于
·
这个LinkMap里展示了整个可执行文件的全貌,列出了编译后的每一个.o目标文件的信息(包括静态链接库.a里的),以及每一个目标文件的代码段,数据段存储详情。
01LinkMap 结构
1.
首先列出来的是目标文件列表(中括号内为文件编号):
2.
2. 接着是一个段表,描述各个段在最后编译成的可执行文件中的偏移位置及大小,包括了代码段(TEXT,保存程序代码段编译后的机器码)和数据段(DATA,保存变量值)
首列是数据在文件的偏移位置,第二列是这一段占用大小,第三列是段类型,代码段和数据段,第四列是段名称。
每一行的数据都紧跟在上一行后面,如第二行stubs的地址0x10304FD9C就是第一行text的地址0x100005B00加上大小0x0304A29C,整个可执行文件大致数据分布就是这样。
这里可以清楚看到各种类型的数据在最终可执行文件里占的比例,例如text表示编译后的程序执行语句,data表示已初始化的全局变量和局部静态变量,bss表示未初始化的全局变量和局部静态变量,cstring表示代码里的字符串常量,等等。
3. 接着就是按上表顺序,列出具体的按每个文件列出每个对应字段的位置和占用空间
同样首列是数据在文件的偏移地址,第二列是占用大小,第三列是所属文件序号,对应上述Object files列表,最后是名字。
4. 已废弃&多余重复的字段
这个文件可以让你了解整个APP编译后的情况,也许从中可以发现一些异常,还可以用这个文件计算静态链接库在项目里占的大小,有时候我们在项目里链了很多第三方库,导致APP体积变大很多,我们想确切知道每个库占用了多大空间,可以给我们优化提供方向。LinkMap里有了每个目标文件每个方法每个数据的占用大小数据,所以只要写个脚本,就可以统计出每个.o最后的大小,属于一个.a静态链接库的.o加起来,就是这个库在APP里占用的空间大小。
02关于 Xcode 的 Other Linker Flags
· 背景
在ios开发过程中,有时候会用到第三方的静态库(.a文件),然后导入后发现编译正常但运行时会出现selector not recognized的错误,从而导致app闪退。接着仔细阅读库文件的说明文档,你可能会在文档中发现诸如在Other Linker Flags中加入-ObjC或者-all_load这样的解决方法。
那么,Other Linker Flags到底是用来干什么的呢?还有-ObjC和-all_load到底发挥了什么作用呢?
· 链接器
首先,要说明一下Other Linker Flags到底是用来干嘛的。说白了,就是ld命令除了默认参数外的其他参数。ld命令实现的是链接器的工作,详细说明可以在终端man ld查看。
如果有人不清楚链接器是什么东西的话,我可以作个简单的说明。
一个程序从简单易读的代码到可执行文件往往要经历以下步骤:
·
源文件经过一系列处理以后,会生成对应的.obj文件,然后一个项目必然会有许多.obj文件,并且这些文件之间会有各种各样的联系,例如函数调用。链接器做的事就是把这些目标文件和所用的一些库链接在一起形成一个完整的可执行文件。
· 为什么会闪退
Objective-C的链接器并不会为每个方法建立符号表,而是仅仅为类建立了符号表。这样的话,如果静态库中定义了已存在的一个类的分类,链接器就会以为这个类已经存在,不会把分类和核心类的代码合起来。这样的话,在最后的可执行文件中,就会缺少分类里的代码,这样函数调用就失败了。
· 解决方法
解决方法在背景那块我就提到了,就是在Other Linker Flags里加上所需的参数,用到的参数一般有以下3个:
在编译Objective-C源文件到目标文件时,编译器并不知道方法的对应实现,只能在运行时才知道,所以编译器只会为类生成链接符号,对类中的方法不会生成链接符号。由于Category方法并不对应一个新类,所以不会生成链接符号,链接器也不会将Category方法合并到原始的类中,最终导致链接器忽略了Category方法,不会将其链接到可执行文件中。
当静态库中只有分类而没有类的时候,-ObjC参数就会失效了。这时候,就需要使用-all_load或者-force_load了。
-all_load会让链接器把所有找到的目标文件都加载到可执行文件中,但是千万不要随便使用这个参数!假如你使用了不止一个静态库文件,然后又使用了这个参数,那么你很有可能会遇到ld: duplicate symbol错误,因为不同的库文件里面可能会有相同的目标文件,所以建议在遇到-ObjC失效的情况下使用-force_load参数。
-force_load所做的事情跟-all_load其实是一样的,但是-force_load需要指定要进行全部加载的库文件的路径,这样的话,你就只是完全加载了一个库文件,不影响其余库文件的按需加载。
在能拿到静态库源码情况下,建议对.a库重新打包,删除部分重复的symbol。
在拿不到静态库源码情况下 ,只能采用-force_load+库文件路径方法设置Other Linker Flags,逐个加静态库,最终完美解决两个静态库存在同名文件冲突,发现那个静态库无法调用,就采用以下语句添加进去。
-force_load EightPartyCall/standaloneclass/BaiduSocialShare/WX/libWeChatSDK
(-force_load后面为静态库文件路径,根据自己项目对应路径)
也可以拆分静态库
lipo源于mac系统要制作兼容powerpc平台和intel平台的程序。
lipo 是一个在 Mac OS X 中处理通用程序(Universal Binaries)的工具。现在发售或者提供下载的许多(几乎所有)程序都打上了“Universal”标志,意味着它们同时具有 PowerPC 和 Intel 芯片能够处理的代码。不过既然你可能不在意其中的一个,你就能够使用 lipo 来给你的程序“瘦身”。