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)都拥有一段虚拟地址映射到进程的地址空间。
Mach-O_Construction
  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位于

    1
    2
    3
    4
    ~/Library/Developer/Xcode/DerivedData/XXX-XXXXXXXXXXXX/Build/Intermediates/XXX.build/Debug-iphoneos/XXX.build/

    //example
    /Users/mxr/Library/Developer/Xcode/DerivedData/huashida_home.xcodeproj-fvbzvmahuzlfgqbzehannctanrbl/Build/Intermediates.noindex/huashida_home.build/Debug-iphoneos/4dBookCity.build/4dBookCity-LinkMap-normal-arm64.txt

这个LinkMap里展示了整个可执行文件的全貌,列出了编译后的每一个.o目标文件的信息(包括静态链接库.a里的),以及每一个目标文件的代码段,数据段存储详情。

LinkMap结构

  1. 首先列出来的是目标文件列表(中括号内为文件编号):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    # Path: /Users/mxr/Library/Developer/Xcode/DerivedData/huashida_home.xcodeproj-fvbzvmahuzlfgqbzehannctanrbl/Build/Products/Debug-iphoneos/4dBookCity.app/4dBookCity
    # Arch: arm64
    # Object files:
    [ 0] linker synthesized
    [ 1] /Users/mxr/Library/Developer/Xcode/DerivedData/huashida_home.xcodeproj-fvbzvmahuzlfgqbzehannctanrbl/Build/Intermediates.noindex/huashida_home.build/Debug-iphoneos/4dBookCity.build/Objects-normal/arm64/Bulk_Arrays_12.o
    [ 2] /Users/mxr/Library/Developer/Xcode/DerivedData/huashida_home.xcodeproj-fvbzvmahuzlfgqbzehannctanrbl/Build/Intermediates.noindex/huashida_home.build/Debug-iphoneos/4dBookCity.build/Objects-normal/arm64/MXRSnapLearnInviteView.o
    [ 3] /Users/mxr/Library/Developer/Xcode/DerivedData/huashida_home.xcodeproj-fvbzvmahuzlfgqbzehannctanrbl/Build/Intermediates.noindex/huashida_home.build/Debug-iphoneos/4dBookCity.build/Objects-normal/arm64/MXRPKHomeCellViewModel.o
    [ 4] /Users/mxr/Library/Developer/Xcode/DerivedData/huashida_home.xcodeproj-fvbzvmahuzlfgqbzehannctanrbl/Build/Intermediates.noindex/huashida_home.build/Debug-iphoneos/4dBookCity.build/Objects-normal/arm64/Bulk_Arrays_5.o
    [ 5] /Users/mxr/Library/Developer/Xcode/DerivedData/huashida_home.xcodeproj-fvbzvmahuzlfgqbzehannctanrbl/Build/Intermediates.noindex/huashida_home.build/Debug-iphoneos/4dBookCity.build/Objects-normal/arm64/MXRBookStoreItemScrollTemplateCell.o
    [ 6] /Users/mxr/Library/Developer/Xcode/DerivedData/huashida_home.xcodeproj-fvbzvmahuzlfgqbzehannctanrbl/Build/Intermediates.noindex/huashida_home.build/Debug-iphoneos/4dBookCity.build/Objects-normal/arm64/MXRAutoReadViewController.o
    [ 7] /Users/mxr/Library/Developer/Xcode/DerivedData/huashida_home.xcodeproj-fvbzvmahuzlfgqbzehannctanrbl/Build/Intermediates.noindex/huashida_home.build/Debug-iphoneos/4dBookCity.build/Objects-normal/arm64/MXRQAExerciseQestionTitleView.o
    [ 8] /Users/mxr/Library/Developer/Xcode/DerivedData/huashida_home.xcodeproj-fvbzvmahuzlfgqbzehannctanrbl/Build/Intermediates.noindex/huashida_home.build/Debug-iphoneos/4dBookCity.build/Objects-normal/arm64/MXRMyTaskController.o
    [ 9] /Users/mxr/Library/Developer/Xcode/DerivedData/huashida_home.xcodeproj-fvbzvmahuzlfgqbzehannctanrbl/Build/Intermediates.noindex/huashida_home.build/Debug-iphoneos/4dBookCity.build/Objects-normal/arm64/UnityView.o
    [ 10] /Users/mxr/Library/Developer/Xcode/DerivedData/huashida_home.xcodeproj-fvbzvmahuzlfgqbzehannctanrbl/Build/Intermediates.noindex/huashida_home.build/Debug-iphoneos/4dBookCity.build/Objects-normal/arm64/main.o
    ...
    [5229] /Users/mxr/Library/Developer/Xcode/DerivedData/huashida_home.xcodeproj-fvbzvmahuzlfgqbzehannctanrbl/Build/Products/Debug-iphoneos/libPods-4dBookCity.a(Pods-4dBookCity-dummy.o)
    [5230] /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/arc/libarclite_iphoneos.a(arclite.o)
    [5231] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.2.sdk/usr/lib/libobjc.tbd
    [5232] /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/9.0.0/lib/darwin/libclang_rt.ios.a(os_version_check.c.o)
    [5233] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.2.sdk/System/Library/Frameworks//AudioToolbox.framework/AudioToolbox.tbd
    [5234] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.2.sdk/System/Library/Frameworks//CoreVideo.framework/CoreVideo.tbd
  2. 接着是一个段表,描述各个段在最后编译成的可执行文件中的偏移位置及大小,包括了代码段(TEXT,保存程序代码段编译后的机器码)和数据段(DATA,保存变量值)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    # Sections:
    # Address Size Segment Section
    0x100005B00 0x0304A29C __TEXT __text
    0x10304FD9C 0x00004BC0 __TEXT __stubs
    0x10305495C 0x000044E8 __TEXT __stub_helper
    0x103058E50 0x0021563C __TEXT __cstring
    0x10326E48C 0x000AD400 __TEXT __objc_methname
    0x10331B88C 0x0000E6BA __TEXT __objc_classname
    0x103329F46 0x000166E3 __TEXT __objc_methtype
    0x103340640 0x002A0B60 __TEXT __const
    0x1035E11A0 0x001346D4 __TEXT __gcc_except_tab
    0x103715874 0x00008C78 __TEXT __ustring
    0x10371E4EC 0x0004D80C __TEXT __unwind_info
    0x10376BCF8 0x00000300 __TEXT __eh_frame
    0x10376C000 0x000015D8 __DATA __got
    0x10376D5D8 0x00003280 __DATA __la_symbol_ptr
    0x103770858 0x00001838 __DATA __mod_init_func
    0x103772090 0x000FF7F8 __DATA __const
    0x103871888 0x0006F9C0 __DATA __cfstring
    0x1038E1248 0x00004778 __DATA __objc_classlist
    0x1038E59C0 0x00000290 __DATA __objc_nlclslist
    0x1038E5C50 0x00000708 __DATA __objc_catlist
    0x1038E6358 0x00000038 __DATA __objc_nlcatlist
    0x1038E6390 0x00000910 __DATA __objc_protolist
    0x1038E6CA0 0x00000008 __DATA __objc_imageinfo
    0x1038E6CA8 0x00206C58 __DATA __objc_const
    0x103AED900 0x00027F28 __DATA __objc_selrefs
    0x103B15828 0x000000C0 __DATA __objc_protorefs
    0x103B158E8 0x000041B8 __DATA __objc_classrefs
    0x103B19AA0 0x000030C0 __DATA __objc_superrefs
    0x103B1CB60 0x0000BB54 __DATA __objc_ivar
    0x103B286B8 0x0002CB00 __DATA __objc_data
    0x103B551C0 0x01D52748 __DATA __data
    0x1058A7920 0x00714878 __DATA __bss
    0x105FBD000 0x0012B978 __DATA __common

首列是数据在文件的偏移位置,第二列是这一段占用大小,第三列是段类型,代码段和数据段,第四列是段名称。
每一行的数据都紧跟在上一行后面,如第二行stubs的地址0x10304FD9C就是第一行text的地址0x100005B00加上大小0x0304A29C,整个可执行文件大致数据分布就是这样。
这里可以清楚看到各种类型的数据在最终可执行文件里占的比例,例如text表示编译后的程序执行语句,data表示已初始化的全局变量和局部静态变量,bss表示未初始化的全局变量和局部静态变量,cstring表示代码里的字符串常量,等等。

  1. 接着就是按上表顺序,列出具体的按每个文件列出每个对应字段的位置和占用空间
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    # Symbols:
    # Address Size File Name
    0x100005B00 0x000000EC [ 2] -[MXRSnapLearnInviteView drawRect:]
    0x100005BEC 0x0000024C [ 2] -[MXRSnapLearnInviteView generatorlogoImageQRCode]
    0x100005E38 0x0000005C [ 2] _CGRectMake
    0x100005E94 0x00000034 [ 2] -[MXRSnapLearnInviteView inviteCode]
    0x100005EC8 0x00000050 [ 2] -[MXRSnapLearnInviteView setInviteCode:]
    0x100005F18 0x0000003C [ 2] -[MXRSnapLearnInviteView .cxx_destruct]
    0x100005F54 0x000001D8 [ 3] -[MXRPKHomeCellViewModel initWithModel:]
    0x10000612C 0x0000016C [ 3] -[MXRPKHomeCellViewModel encodeWithCoder:]
    0x100006298 0x00000268 [ 3] -[MXRPKHomeCellViewModel initWithCoder:]
    0x100006500 0x00000040 [ 3] -[MXRPKHomeCellViewModel desc]
    0x100006540 0x00000044 [ 3] -[MXRPKHomeCellViewModel setDesc:]
    0x100006584 0x00000040 [ 3] -[MXRPKHomeCellViewModel name]
    0x1000065C4 0x00000044 [ 3] -[MXRPKHomeCellViewModel setName:]
    0x100006608 0x00000040 [ 3] -[MXRPKHomeCellViewModel pic]
    0x100006648 0x00000044 [ 3] -[MXRPKHomeCellViewModel setPic:]
    0x10000668C 0x00000040 [ 3] -[MXRPKHomeCellViewModel classifyId]
    0x1000066CC 0x00000044 [ 3] -[MXRPKHomeCellViewModel setClassifyId:]
    0x100006710 0x000000B8 [ 3] -[MXRPKHomeCellViewModel .cxx_destruct]
    ...
    0x1060C82D0 0x000000C0 [3391] _jerrenv
    0x1060C8390 0x000204E0 [4793] _GC_arrays
    0x1060E8870 0x00000100 [4793] _GC_bm_table
    0x1060E8970 0x00000008 [4793] _GC_noop_sink

同样首列是数据在文件的偏移地址,第二列是占用大小,第三列是所属文件序号,对应上述Object files列表,最后是名字。

  1. 已废弃&多余重复的字段
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    # Dead Stripped Symbols:
    # Size File Name
    <<dead>> 0x00000001 [ 1] literal string:
    <<dead>> 0x00000005 [ 3] literal string: desc
    <<dead>> 0x00000005 [ 3] literal string: name
    <<dead>> 0x00000004 [ 3] literal string: pic
    <<dead>> 0x0000000B [ 3] literal string: classifyId
    <<dead>> 0x0000000E [ 3] literal string: .cxx_destruct
    <<dead>> 0x0000000B [ 3] literal string: v24@0:8@16
    <<dead>> 0x00000008 [ 3] literal string: v16@0:8
    <<dead>> 0x00000008 [ 3] literal string: @16@0:8
    <<dead>> 0x00000001 [ 4] literal string:
    <<dead>> 0x00000007 [ 4] literal string: System
    <<dead>> 0x0000000C [ 4] literal string: UnityEngine
    <<dead>> 0x0000000A [ 4] literal string: System.IO
    <<dead>> 0x00000008 [ 5] 8-byte-literal
    <<dead>> 0x0000000C [ 5] literal string: PRIMARY KEY
    <<dead>> 0x0000000C [ 5] literal string: FOREIGN KEY
    <<dead>> 0x00000001 [ 5] literal string:
    <<dead>> 0x00000020 [ 5] CFString
    <<dead>> 0x00000020 [ 5] CFString
    <<dead>> 0x00000008 [ 5] _LKSQL_Type_Text
    <<dead>> 0x00000008 [ 5] _LKSQL_Type_Int
    <<dead>> 0x00000008 [ 5] _LKSQL_Type_Double
    <<dead>> 0x00000008 [ 5] _LKSQL_Type_Blob
    <<dead>> 0x00000008 [ 5] _LKSQL_Attribute_NotNull
    <<dead>> 0x00000008 [ 5] _LKSQL_Attribute_PrimaryKey
    <<dead>> 0x00000008 [ 5] _LKSQL_Attribute_Default
    <<dead>> 0x00000008 [ 5] _LKSQL_Attribute_Unique
    <<dead>> 0x00000008 [ 5] _LKSQL_Attribute_Check
    <<dead>> 0x00000008 [ 5] _LKSQL_Attribute_ForeignKey
    <<dead>> 0x00000008 [ 5] _LKSQL_Convert_FloatType
    <<dead>> 0x00000008 [ 5] _LKSQL_Convert_IntType
    <<dead>> 0x00000008 [ 5] _LKSQL_Convert_BlobType
    <<dead>> 0x00000008 [ 5] _LKSQL_Mapping_Inherit
    <<dead>> 0x00000008 [ 5] _LKSQL_Mapping_Binding
    <<dead>> 0x00000008 [ 5] _LKSQL_Mapping_UserCalculate
    <<dead>> 0x00000008 [ 5] _LKDB_TypeKey
    <<dead>> 0x00000008 [ 5] _LKDB_TypeKey_Model
    <<dead>> 0x00000008 [ 5] _LKDB_TypeKey_JSON
    <<dead>> 0x00000008 [ 5] _LKDB_TypeKey_Combo
    ...
    <<dead>> 0x00000004 [4311] 4-byte-literal
    <<dead>> 0x00000004 [4311] 4-byte-literal
    <<dead>> 0x00000004 [4311] 4-byte-literal
    <<dead>> 0x00000004 [4311] 4-byte-literal
    <<dead>> 0x00000008 [4311] 8-byte-literal
    <<dead>> 0x00000008 [4312] 8-byte-literal
    <<dead>> 0x00000014 [4320] __ZN15PxcConvexMeshHLC2EP17PxConvexMeshData_
    <<dead>> 0x00000004 [4320] 4-byte-literal
    <<dead>> 0x00000004 [4320] 4-byte-literal
    <<dead>> 0x00000004 [4319] 4-byte-literal
    <<dead>> 0x00000076 [4319] literal string: /Applications/buildAgent/work/3d1e9e6e6eefaa7f/SDKs/compiler/iphone/../../../LowLevel/common/include/utils/PxcArray.h

这个文件可以让你了解整个APP编译后的情况,也许从中可以发现一些异常,还可以用这个文件计算静态链接库在项目里占的大小,有时候我们在项目里链了很多第三方库,导致APP体积变大很多,我们想确切知道每个库占用了多大空间,可以给我们优化提供方向。LinkMap里有了每个目标文件每个方法每个数据的占用大小数据,所以只要写个脚本,就可以统计出每个.o最后的大小,属于一个.a静态链接库的.o加起来,就是这个库在APP里占用的空间大小。

关于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查看。
如果有人不清楚链接器是什么东西的话,我可以作个简单的说明。
一个程序从简单易读的代码到可执行文件往往要经历以下步骤:

1
源代码 > 预处理器 > 编译器 > 汇编器 > 机器码 > 链接器 > 可执行文件

源文件经过一系列处理以后,会生成对应的.obj文件,然后一个项目必然会有许多.obj文件,并且这些文件之间会有各种各样的联系,例如函数调用。链接器做的事就是把这些目标文件和所用的一些库链接在一起形成一个完整的可执行文件。

  • 为什么会闪退

Objective-C的链接器并不会为每个方法建立符号表,而是仅仅为类建立了符号表。这样的话,如果静态库中定义了已存在的一个类的分类,链接器就会以为这个类已经存在,不会把分类和核心类的代码合起来。这样的话,在最后的可执行文件中,就会缺少分类里的代码,这样函数调用就失败了。

  • 解决方法

解决方法在背景那块我就提到了,就是在Other Linker Flags里加上所需的参数,用到的参数一般有以下3个:

1
2
3
4
`-ObjC`       : 链接器会把静态库中所有的类和分类都加载到最后的可执行文件中
`-force_load` : 需要指定要进行全部加载的库文件的路径,避免引用多个第三方库时会出现类名重叠的冲突
`-all_load` : 让链接器把所有找到的目标文件都加载到可执行文件中,不建议使用
`-dead_strip` : 删除多余的库符号,不建议使用

在编译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后面为静态库文件路径,根据自己项目对应路径)

也可以拆分静态库

1
2
3
4
5
6
7
8
9
10
11
12
$ cd /LibSDK 
$ ls
libiot.sdk.a
$ lipo -info libiot.sdk.a
Architectures in the fat file: libiot.sdk.a are: armv7 arm64
$ lipo libiot.sdk.a -thin armv7 -output tbv7.a
$ lipo libiot.sdk.a -thin arm64 -output tb64.a
$ ls
libiot.sdk.a tb64.a tbv7.a
$ ar -d tbv7.a AsyncSocket.o
$ ar -d tb64.a AsyncSocket.o
$ lipo -create tbv7.a tbv64.a -output libSun.a

lipo源于mac系统要制作兼容powerpc平台和intel平台的程序。
lipo 是一个在 Mac OS X 中处理通用程序(Universal Binaries)的工具。现在发售或者提供下载的许多(几乎所有)程序都打上了“Universal”标志,意味着它们同时具有 PowerPC 和 Intel 芯片能够处理的代码。不过既然你可能不在意其中的一个,你就能够使用 lipo 来给你的程序“瘦身”。

写了一堆乱七八糟的

参考app包优化
干货|今日头条iOS端安装包大小优化—思路与实践

希望对您有所帮助,您的支持将是我莫大的动力!