什么是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
然后在 Path to Link Map File 选项中配置生成的文件存放位置
例如:$(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库等文件路径,以文件编号和文件路径一一对应形式呈现,通过文件编号可以实现对应文件名的映射。上面示例中的信息按照一定规则可以解析出下面的数据,可以实现文件和模块之间的映射:
文件编号 | 所属模块/静态库/动态库 | 文件名 |
---|---|---|
1 | app | ViewController.o |
2 | app | AppDelegate.o |
3 | app | main.o |
... | ... | ... |
31 | libPromisesObjC.a | FBLPromise+Do.o |
32 | libPromisesObjC.a | FBLPromise+Race.o |
33 | libPromisesObjC.a | FBLPromise+Recover.o |
... | ... | ... |
53 | MobileCoreServices.framework | MobileCoreServices.tbd |
54 | UIKit.framework | UIKit.tbd |
55 | swift | libswiftCore.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 |
__const | const修饰的常量 |
__objc_methname | objc的方法名称 |
__cstring | c语言字符串 |
__objc_classname | objc类方法 |
__objc_methtype | objc方法类型 |
__gcc_except_tab | 用于异常处理 |
__ustring | |
__unwind_info | 用于确定异常发生时栈所对应的信息 |
__eh_frame | 用于异常处理 |
__DATA下的Section
Section | 作用 |
---|---|
__got | 存储引用符号的实际地址 |
__la_symbol_ptr | 懒加载的函数指针地址 |
__mod_init_func | 模块初始化的方法 |
__const | const修饰的常量 |
__cfstring | 程序中使用的 Core Foundation 字符串 |
__objc_classlist | 类列表 |
__objc_nlclslist | load函数列表 |
__objc_catlist | 分类列表 |
__objc_nlcatlist | 分类的load函数列表 |
__objc_protolist | 协议列表 |
__objc_imageinfo | 镜像信息 |
__objc_const | OC常量 |
__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节的数据解析统计出模块下所有的类文件,再统计每个类的大小,最后加起来就得到了整个模块的大小。