iOS二进制文件大小 - LinkMapFile分析

Anyhong 2021年06月01日 172次浏览

什么是Link Map File

什么是快乐星球? 什么是Link Map File?链接映射文件,是Xcode生成的一个链接过程记录文件。Xcode在构建可执行文件的过程中,先对每个文件进行单独编译生成目标文件(.o文件),然后再将目标文件链接生成最终可执行文件,Link Map File就是记录链接过程中的信息文件,它用来描述可执行文件的具体结构,包含可执行文件代码段、数据段等地址分布信息

有什么作用

用来分析二进制大小组成,分析二进制类大小,可以找出体积增长缩小趋势原因等

如何开启

Xcode默认配置下是不生成Link Map file的,需要手动开启。具体开启方式为:

在Xcode项目工程 Project -> Build Setting 中找到 Write Link Map File 选项,设置为Yes
linkmap_enable

然后在 Path to Link Map File 选项中配置生成的文件存放位置
linkmap_path
例如:$(SRCROOT)/$(PRODUCT_NAME)-LinkMap-$(CURRENT_VARIANT)-$(CURRENT_ARCH).txt

文件结构

生成的的Link Map File文件内容中依次有Path、Arch、Object files、Sections、Symbols、Dead Stripped Symbols几个段,示例格式如下:

# Path: /Users/anyhong/Library/Developer/Xcode/DerivedData/PodsDemo-audexdbzwtctzceuzpxrjkgumjrc/Build/Products/Release-iphonesimulator/PodsDemo.app/PodsDemo
# Arch: arm64
# Object files:
[  0] linker synthesized
[  1] /Users/anyhong/Library/Developer/Xcode/DerivedData/PodsDemo-audexdbzwtctzceuzpxrjkgumjrc/Build/Intermediates.noindex/PodsDemo.build/Release-iphoneos/PodsDemo.build/Objects-normal/arm64/ViewController.o
[  2] /Users/anyhong/Library/Developer/Xcode/DerivedData/PodsDemo-audexdbzwtctzceuzpxrjkgumjrc/Build/Intermediates.noindex/PodsDemo.build/Release-iphoneos/PodsDemo.build/Objects-normal/arm64/AppDelegate.o
[  3] /Users/anyhong/Library/Developer/Xcode/DerivedData/PodsDemo-audexdbzwtctzceuzpxrjkgumjrc/Build/Intermediates.noindex/PodsDemo.build/Release-iphoneos/PodsDemo.build/Objects-normal/arm64/main.o
···省略···

# Sections:
# Address	Size    	Segment	Section
0x100004B38	0x0002BB70	__TEXT	__text
0x1000306A8	0x00000774	__TEXT	__stubs
0x100030E1C	0x0000078C	__TEXT	__stub_helper
0x1000315A8	0x0000010F	__TEXT	__const
0x1000316B7	0x00009233	__TEXT	__objc_methname
0x10003A8F0	0x00001E15	__TEXT	__cstring
0x10003C705	0x00000818	__TEXT	__objc_classname
0x10003CF1D	0x00001CFE	__TEXT	__objc_methtype
0x10003EC1C	0x00000014	__TEXT	__swift5_typeref
0x10003EC30	0x00000010	__TEXT	__swift5_fieldmd
0x10003EC40	0x00000004	__TEXT	__swift5_types
0x10003EC44	0x00000514	__TEXT	__gcc_except_tab
0x10003F158	0x0000014A	__TEXT	__ustring
0x10003F2A4	0x00000CAC	__TEXT	__unwind_info
0x10003FF50	0x000000A8	__TEXT	__eh_frame
0x100040000	0x00000110	__DATA	__got
0x100040110	0x000004F8	__DATA	__la_symbol_ptr
0x100040608	0x00000008	__DATA	__mod_init_func
0x100040610	0x00001880	__DATA	__const
0x100041E90	0x00001320	__DATA	__cfstring
0x1000431B0	0x00000158	__DATA	__objc_classlist
0x100043308	0x00000010	__DATA	__objc_nlclslist
0x100043318	0x00000038	__DATA	__objc_catlist
0x100043350	0x00000088	__DATA	__objc_protolist
0x1000433D8	0x00000008	__DATA	__objc_imageinfo
0x1000433E0	0x0000C390	__DATA	__objc_const
0x10004F770	0x00001C98	__DATA	__objc_selrefs
0x100051408	0x00000010	__DATA	__objc_protorefs
0x100051418	0x000002C8	__DATA	__objc_classrefs
0x1000516E0	0x00000108	__DATA	__objc_superrefs
0x1000517E8	0x00000300	__DATA	__objc_ivar
0x100051AE8	0x00000DA8	__DATA	__objc_data
0x100052890	0x000006B5	__DATA	__data
0x100052F48	0x000000B8	__DATA	__swift_hooks
0x100053000	0x000000B8	__DATA	__swift51_hooks
0x1000530B8	0x000001B0	__DATA	__bss

# Symbols:
# Address	Size    	File  Name
0x100004B38	0x000000E8	[  1] -[ViewController viewDidLoad]
0x100004C20	0x00000060	[  1] -[ViewController traditionalLogicProcess]
0x100004C80	0x00000068	[  1] ___41-[ViewController traditionalLogicProcess]_block_invoke
0x100004CE8	0x00000068	[  1] ___41-[ViewController traditionalLogicProcess]_block_invoke_2
0x100004D50	0x00000028	[  1] ___41-[ViewController traditionalLogicProcess]_block_invoke_3
0x100004D78	0x0000003C	[  1] ___41-[ViewController traditionalLogicProcess]_block_invoke_4
···省略···

# Dead Stripped Symbols:
#        	Size    	File  Name
<<dead>> 	0x00000008	[  1] 8-byte-literal
<<dead>> 	0x00000008	[  1] 8-byte-literal
<<dead>> 	0x00000008	[  1] 8-byte-literal
<<dead>> 	0x00000005	[  2] literal string: hash
<<dead>> 	0x0000000B	[  2] literal string: superclass
<<dead>> 	0x0000000C	[  2] literal string: description
···省略···

Path

Path是二进制文件的所在路径

Arch

Arch是二进制文件架构指令集,常见的有arm64、armv7s、armv7、x86_64

Object files

# Object files:
[  0] linker synthesized
[  1] /Users/anyhong/Library/Developer/Xcode/DerivedData/PodsDemo-audexdbzwtctzceuzpxrjkgumjrc/Build/Intermediates.noindex/PodsDemo.build/Release-iphoneos/PodsDemo.build/Objects-normal/arm64/ViewController.o
[  2] /Users/anyhong/Library/Developer/Xcode/DerivedData/PodsDemo-audexdbzwtctzceuzpxrjkgumjrc/Build/Intermediates.noindex/PodsDemo.build/Release-iphoneos/PodsDemo.build/Objects-normal/arm64/AppDelegate.o
[  3] /Users/anyhong/Library/Developer/Xcode/DerivedData/PodsDemo-audexdbzwtctzceuzpxrjkgumjrc/Build/Intermediates.noindex/PodsDemo.build/Release-iphoneos/PodsDemo.build/Objects-normal/arm64/main.o
···省略···
[ 31] /Users/anyhong/Library/Developer/Xcode/DerivedData/PodsDemo-audexdbzwtctzceuzpxrjkgumjrc/Build/Products/Release-iphoneos/PromisesObjC/libPromisesObjC.a(FBLPromise+Do.o)
[ 32] /Users/anyhong/Library/Developer/Xcode/DerivedData/PodsDemo-audexdbzwtctzceuzpxrjkgumjrc/Build/Products/Release-iphoneos/PromisesObjC/libPromisesObjC.a(FBLPromise+Race.o)
[ 33] /Users/anyhong/Library/Developer/Xcode/DerivedData/PodsDemo-audexdbzwtctzceuzpxrjkgumjrc/Build/Products/Release-iphoneos/PromisesObjC/libPromisesObjC.a(FBLPromise+Recover.o)
···省略···
[ 53] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.5.sdk/System/Library/Frameworks//MobileCoreServices.framework/MobileCoreServices.tbd
[ 54] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.5.sdk/System/Library/Frameworks//UIKit.framework/UIKit.tbd
[ 55] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.5.sdk/usr/lib/swift/libswiftCore.tbd

目标文件列表,包括.o文件、.a静态库、.tbd库等文件路径,以文件编号和文件路径一一对应形式呈现,通过文件编号可以实现对应文件名的映射。上面示例中的信息按照一定规则可以解析出下面的数据,可以实现文件和模块之间的映射:

文件编号所属模块/静态库/动态库文件名
1appViewController.o
2appAppDelegate.o
3appmain.o
.........
31libPromisesObjC.aFBLPromise+Do.o
32libPromisesObjC.aFBLPromise+Race.o
33libPromisesObjC.aFBLPromise+Recover.o
.........
53MobileCoreServices.frameworkMobileCoreServices.tbd
54UIKit.frameworkUIKit.tbd
55swiftlibswiftCore.tbd

例如要统计libPromisesObjC.a的大小,就可以先通过Object files段中的数据先统计出libPromisesObjC.a包含的所有类。

Sections

# Sections:
# Address	Size    	Segment	Section
0x100004B38	0x0002BB70	__TEXT	__text
0x1000306A8	0x00000774	__TEXT	__stubs
0x100030E1C	0x0000078C	__TEXT	__stub_helper
0x1000315A8	0x0000010F	__TEXT	__const
0x1000316B7	0x00009233	__TEXT	__objc_methname
0x10003A8F0	0x00001E15	__TEXT	__cstring
0x10003C705	0x00000818	__TEXT	__objc_classname
0x10003CF1D	0x00001CFE	__TEXT	__objc_methtype
0x10003EC1C	0x00000014	__TEXT	__swift5_typeref
0x10003EC30	0x00000010	__TEXT	__swift5_fieldmd
0x10003EC40	0x00000004	__TEXT	__swift5_types
0x10003EC44	0x00000514	__TEXT	__gcc_except_tab
0x10003F158	0x0000014A	__TEXT	__ustring
0x10003F2A4	0x00000CAC	__TEXT	__unwind_info
0x10003FF50	0x000000A8	__TEXT	__eh_frame
0x100040000	0x00000110	__DATA	__got
0x100040110	0x000004F8	__DATA	__la_symbol_ptr
0x100040608	0x00000008	__DATA	__mod_init_func
0x100040610	0x00001880	__DATA	__const
0x100041E90	0x00001320	__DATA	__cfstring
0x1000431B0	0x00000158	__DATA	__objc_classlist
0x100043308	0x00000010	__DATA	__objc_nlclslist
0x100043318	0x00000038	__DATA	__objc_catlist
0x100043350	0x00000088	__DATA	__objc_protolist
0x1000433D8	0x00000008	__DATA	__objc_imageinfo
0x1000433E0	0x0000C390	__DATA	__objc_const
0x10004F770	0x00001C98	__DATA	__objc_selrefs
0x100051408	0x00000010	__DATA	__objc_protorefs
0x100051418	0x000002C8	__DATA	__objc_classrefs
0x1000516E0	0x00000108	__DATA	__objc_superrefs
0x1000517E8	0x00000300	__DATA	__objc_ivar
0x100051AE8	0x00000DA8	__DATA	__objc_data
0x100052890	0x000006B5	__DATA	__data
0x100052F48	0x000000B8	__DATA	__swift_hooks
0x100053000	0x000000B8	__DATA	__swift51_hooks
0x1000530B8	0x000001B0	__DATA	__bss

这个主要是Mach-O二进制文件中各个section的地址空间分布,每行4列依次表示Section起始偏移地址、Section占用内存大小、所属Segment类型、Section类型。

Segment类型

Segment作用
__PAGEZERO空指针段,用来捕捉NULL指针的引用,当一个指针变量的值为NULL时,实际就是将指针指向了__PAGEZERO段
__TEXT程序代码段,存放了所有函数代码
__DATA程序数据段,存放了所有全局变量信息
__LINKEDIT链接信息段,包含需要被动态链接器使用的信息,包括符号表、字符串表、重定位项表、签名等

__TEXT下的Section

Section作用
__text主程序代码
__stubs用于动态链接库的stub
__stub_helper用于动态链接库的stub
__constconst修饰的常量
__objc_methnameobjc的方法名称
__cstringc语言字符串
__objc_classnameobjc类方法
__objc_methtypeobjc方法类型
__gcc_except_tab用于异常处理
__ustring
__unwind_info用于确定异常发生时栈所对应的信息
__eh_frame用于异常处理

__DATA下的Section

Section作用
__got存储引用符号的实际地址
__la_symbol_ptr懒加载的函数指针地址
__mod_init_func模块初始化的方法
__constconst修饰的常量
__cfstring程序中使用的 Core Foundation 字符串
__objc_classlist类列表
__objc_nlclslistload函数列表
__objc_catlist分类列表
__objc_nlcatlist分类的load函数列表
__objc_protolist协议列表
__objc_imageinfo镜像信息
__objc_constOC常量
__objc_selrefs引用的OC方法
__objc_protorefs引用的OC协议
__objc_classrefs引用的OC类
__objc_superrefs引用的OC超类
__objc_ivar类的实例变量
__objc_data用于保存类需要的数据
__data存放了协议和已经初始化的静态量
__bss存储未初始化的静态量

关于Mach-O二进制格式可以简单看下Mach-O文件结构

Symbols

# Symbols:
# Address	Size    	File  Name
0x100004B38	0x000000E8	[  1] -[ViewController viewDidLoad]
0x100004C20	0x00000060	[  1] -[ViewController traditionalLogicProcess]
0x100004C80	0x00000068	[  1] ___41-[ViewController traditionalLogicProcess]_block_invoke
0x100004CE8	0x00000068	[  1] ___41-[ViewController traditionalLogicProcess]_block_invoke_2
0x100004D50	0x00000028	[  1] ___41-[ViewController traditionalLogicProcess]_block_invoke_3
0x100004D78	0x0000003C	[  1] ___41-[ViewController traditionalLogicProcess]_block_invoke_4

符号段,每行4列依次表示符号的起始地址、大小、所在的文件编号、符号名。将相同文件编号下的符号查询出来并将所有符号大小加起来就能得到这个文件类的整体大小了,也就是通过Link Map File来分析类大小的核心原理。

Dead Stripped Symbols

# Dead Stripped Symbols:
#        	Size    	File  Name
<<dead>> 	0x00000008	[  1] 8-byte-literal
<<dead>> 	0x00000008	[  1] 8-byte-literal
<<dead>> 	0x00000008	[  1] 8-byte-literal
<<dead>> 	0x00000005	[  2] literal string: hash
<<dead>> 	0x0000000B	[  2] literal string: superclass
<<dead>> 	0x0000000C	[  2] literal string: description

和Symbols的结构一致,但是没有起始偏移地址,这些符号链接器认为是没有用的,因此在链接过程中并不会链接进去。

二进制大小统计

如果要计算某个类的大小,通过通过Object files节下的数据,找到类对应的文件编号,然后再解析Symbols下面的数据,将相同文件编号下的符号查询出来再将所有符号大小加起来就到这个类的整体大小了。
如果要计算某个模块的大小(比如CocoaPods导入的某个库),先通过Object files节的数据解析统计出模块下所有的类文件,再统计每个类的大小,最后加起来就得到了整个模块的大小。