[iOS] Link Map File

1. Link Map File 简介

Link Map File 直译为 链接映射文件,在编译阶段,每个类会生成对应的.o文件,链接阶段,会把.o文件和动态库链接在一起。Link Map File就是这样一个记录链接相关信息的纯文本文件,里面记录了可执行文件的路径、CPU架构、目标文件、符号等信息。

2. Link Map File 作用

理解Link Map File,可以帮助我们:

  • 理解链接过程
  • 理解Crash时,通过Symbols还原出源码的机制
  • 理解内存分段、分区
  • 分析可执行文件中哪个类或库占用比较大,进行安装包瘦身

就是iOS安装包的一张地图,通过这张地图,你可以看到安装包里各个部分都是什么内容。通过分析各部分内容所占的内存空间,就可以知道各部分内容的体积大小了。

3. 生成 Link Map File

Xcode 在生成可执行文件的时候,默认不生成该文件。
Xcode的配置中 Target -> Build Setting -> LinkingWrite Link Map File设置为YES来生成Link Map File,运行代码即可生成Link Map File,是一个txt文件:

截屏2020-09-08 下午5.01.14.png

Write Link Map File上面就是生成文件的路径:

截屏2020-09-08 下午5.02.36.png

通过这个路径可以访问到:

~/Developer/Xcode/DerivedData/项目/Build/Intermediates.noindex/项目.build/Debug-iphonesimulator/项目.build/项目-LinkMap-normal-x86_64.txt

在 Products 下找到.app文件,返回上层根据路径找到Link Map文件:

截屏2020-09-08 下午5.06.24.png

4. 分析Link Map File

我们打开Link Map File 文件,然后从上往下去分析:

4.1 Path & Arch
Path: /Users/wangce/Library/Developer/Xcode/DerivedData/InjectTest-eqmjuuqdlkmitzgsjehvsiypbwoe/Build/Intermediates.noindex/InjectTest.build/Debug-iphoneos/InjectTest.build/Objects-normal/armv7/Binary/InjectTest
Arch: armv7

Path是可执行文件的路径,Arch是LinkMap对应的架构。

4.2 Object files
# Object files:
[  0] linker synthesized
[  1] /Users/wangce/Library/Developer/Xcode/DerivedData/InjectTest-eqmjuuqdlkmitzgsjehvsiypbwoe/Build/Intermediates.noindex/InjectTest.build/Debug-iphoneos/InjectTest.build/Objects-normal/armv7/GCDManager.o
[  2] /Users/wangce/Library/Developer/Xcode/DerivedData/InjectTest-eqmjuuqdlkmitzgsjehvsiypbwoe/Build/Intermediates.noindex/InjectTest.build/Debug-iphoneos/InjectTest.build/Objects-normal/armv7/ViewController.o
[  3] /Users/wangce/Library/Developer/Xcode/DerivedData/InjectTest-eqmjuuqdlkmitzgsjehvsiypbwoe/Build/Intermediates.noindex/InjectTest.build/Debug-iphoneos/InjectTest.build/Objects-normal/armv7/PresentTransition.o

Object files是编译后生成的文件列表,比如这个程序class都编译成了.o文件,像我们比较熟悉的AppDelegate.o文件等等。还有引进来的几个库,比如UIKit.tbd。
在编译成目标文件后,通过链接器进行链接,最终合成可执行文件。包括.o文件和dylib库。第一列的序号是类的编号,通过该编号可以对应到具体的类。

在后面的Symbols部分,我们会用到类的编号。

4.3 Section 区
# Sections:
# Address   Size        Segment Section
0x0000AF68  0x00002F48  __TEXT  __text
0x0000DEB0  0x00000270  __TEXT  __picsymbolstub4
0x0000E120  0x000001F8  __TEXT  __stub_helper
0x0000E318  0x00000014  __TEXT  __gcc_except_tab
0x0000E32C  0x00000207  __TEXT  __cstring
0x0000E533  0x0000105A  __TEXT  __objc_methname
0x0000F58D  0x00000094  __TEXT  __objc_classname
0x0000F621  0x000009D9  __TEXT  __objc_methtype
0x00010000  0x00000020  __DATA  __nl_symbol_ptr
0x00010020  0x0000009C  __DATA  __la_symbol_ptr
0x000100BC  0x0000009C  __DATA  __const
0x00010158  0x00000090  __DATA  __cfstring
0x000101E8  0x00000014  __DATA  __objc_classlist
0x000101FC  0x00000004  __DATA  __objc_nlclslist
0x00010200  0x0000000C  __DATA  __objc_protolist
0x0001020C  0x00000008  __DATA  __objc_imageinfo
0x00010214  0x00000B1C  __DATA  __objc_const
0x00010D30  0x0000011C  __DATA  __objc_selrefs
0x00010E4C  0x0000003C  __DATA  __objc_classrefs
0x00010E88  0x00000004  __DATA  __objc_superrefs
0x00010E8C  0x00000014  __DATA  __objc_ivar
0x00010EA0  0x000000C8  __DATA  __objc_data
0x00010F68  0x000000A0  __DATA  __data
0x00011008  0x00000008  __DATA  __bss

第一列Address是起始地址,第二列SizeSection占用内存大小,第三列是Segment类型,第四列是Section类型。

我们这里再次补充一点Mach-O的知识:
Mach-O文件中的虚拟地址最终会映射到物理地址上,这些地址被分成不同的Segment:
_Text段:包含Mach Header,被执行的代码和只读常量、只读可执行(r-x)
_DATA段:包含全局变量,静态变量,可读写(rw-)
_LINKEDIT:包含了加载程序的『元数据』,比如函数的名称和地址,只读(r–)

Segement划分成了不同的Section,不同的Section存储着不同的信息,下面是一些常用的Section的介绍:

//  __TEXT段中的一些Section
1. __text: 代码节,存放机器编译后的代码
2. __stubs: 用于辅助做动态链接代码(dyld).
3. __stub_helper:用于辅助做动态链接(dyld).
4. __objc_methname:objc的方法名称
5. __cstring:代码运行中包含的字符串常量,比如代码中定义`#define kGeTuiPushAESKey        @"DWE2#@e2!"`,那DWE2#@e2!会存在这个区里。
6. __objc_classname:objc类名
7. __objc_methtype:objc方法类型
8. __ustring:
9. __gcc_except_tab:
10. __const:存储const修饰的常量
11. __dof_RACSignal:
12. __dof_RACCompou:
13. __unwind_info:

// __DATA段中的一些Section
1. __got:存储引用符号的实际地址,类似于动态符号表
2. __la_symbol_ptr:lazy symbol pointers。懒加载的函数指针地址。和__stubs和stub_helper配合使用。具体原理暂留。
3. __mod_init_func:模块初始化的方法。
4. __const:存储constant常量的数据。比如使用extern导出的const修饰的常量。
5. __cfstring:使用Core Foundation字符串
6. __objc_classlist:objc类列表,保存类信息,映射了__objc_data的地址
7. __objc_nlclslist:Objective-C 的 +load 函数列表,比 __mod_init_func 更早执行。
8. __objc_catlist: categories
9. __objc_nlcatlist:Objective-C 的categories的 +load函数列表。
10. __objc_protolist:objc协议列表
11. __objc_imageinfo:objc镜像信息
12. __objc_const:objc常量。保存objc_classdata结构体数据。用于映射类相关数据的地址,比如类名,方法名等。
13. __objc_selrefs:引用到的objc方法
14. __objc_protorefs:引用到的objc协议
15. __objc_classrefs:引用到的objc类
16. __objc_superrefs:objc超类引用
17. __objc_ivar:objc ivar指针,存储属性。
18. __objc_data:objc的数据。用于保存类需要的数据。最主要的内容是映射__objc_const地址,用于找到类的相关数据。
19. __data:暂时没理解,从日志看存放了协议和一些固定了地址(已经初始化)的静态量。
20. __bss:存储未初始化的静态量。比如:`static NSThread *_networkRequestThread = nil;`其中这里面的size表示应用运行占用的内存,不是实际的占用空间。所以计算大小的时候应该去掉这部分数据。
21. __common:存储导出的全局的数据。类似于static,但是没有用static修饰。比如KSCrash里面`NSDictionary* g_registerOrders;`, g_registerOrders就存储在__common里面

虚拟内存最终会映射到物理内存,通过上面的介绍,我们就可以知道代码和数据在内存中是如何存储的。

4.4 Symbols
# Symbols:
// __text代码区
# Address   Size        File  Name
0x0000AF68  0x0000005E  [  1] +[GCDManager sharedInstance]
0x0000AFC6  0x00000052  [  1] ___28+[GCDManager sharedInstance]_block_invoke
0x0000B018  0x00000470  [  1] -[GCDManager scheduledDispatchTimerWithName:timeInterval:queue:repeats:action:]
0x0000B488  0x00000064  [  1] ___79-[GCDManager scheduledDispatchTimerWithName:timeInterval:queue:repeats:action:]_block_invoke
0x0000B4EC  0x00000064  [  1] ___copy_helper_block_e4_20b24s28w
0x0000B550  0x00000030  [  1] ___destroy_helper_block_e4_20s24s28w
0x0000B580  0x00000156  [  1] -[GCDManager cancelTimerWithName:]
0x0000B6D6  0x000000B0  [  1] -[GCDManager cancelAllTimer]
0x0000B786  0x000000B2  [  1] ___28-[GCDManager cancelAllTimer]_block_invoke
0x0000B838  0x00000026  [  1] ___copy_helper_block_e4_20s
0x0000B85E  0x00000016  [  1] ___destroy_helper_block_e4_20s
0x0000B874  0x0000005E  [  1] -[GCDManager timerContainer]
0x0000B8D2  0x0000005E  [  1] -[GCDManager actionBlockCache]
0x0000B930  0x000001FC  [  1] -[GCDManager cacheAction:forTimer:]
0x0000BB2C  0x00000102  [  1] -[GCDManager removeActionCacheForTimer:]
0x0000BC2E  0x00000020  [  1] -[GCDManager setTimerContainer:]
0x0000BC4E  0x00000020  [  1] -[GCDManager setActionBlockCache:]
0x0000BC6E  0x00000028  [  1] -[GCDManager .cxx_destruct]
0x0000BC98  0x000008C0  [  2] -[ViewController viewDidLoad]
0x0000C558  0x00000046  [  2] _CGRectMake
0x0000C59E  0x00000104  [  2] -[ViewController delNum]
0x0000C6A2  0x00000104  [  2] -[ViewController addNum]
0x0000C7A6  0x0000002A  [  2] -[ViewController delBtn]
0x0000C7D0  0x0000002A  [  2] -[ViewController addBtn]
0x0000C7FA  0x00000248  [  2] -[ViewController configNum:]
0x0000CA42  0x000000DE  [  2] -[ViewController undo]
...

// __objc_methname方法名区
0x0000E369  0x0000000F  [  1] literal string: timerContainer
0x0000E378  0x0000002D  [  1] literal string: T@"NSMutableDictionary",&,N,V_timerContainer
0x0000E3A5  0x00000011  [  1] literal string: actionBlockCache
0x0000E3B6  0x0000002F  [  1] literal string: T@"NSMutableDictionary",&,N,V_actionBlockCache
0x0000E3E5  0x00000005  [  2] literal string: undo
0x0000E3EA  0x00000005  [  2] literal string: redo
0x0000E3EF  0x00000003  [  2] literal string: +1
0x0000E3F2  0x00000003  [  2] literal string: -1
0x0000E3F5  0x00000004  [  2] literal string: %ld
0x0000E3F9  0x0000000D  [  2] literal string: can not undo
0x0000E406  0x0000000D  [  2] literal string: can not redo
0x0000E413  0x00000007  [  2] literal string: numLab
0x0000E41A  0x00000019  [  2] literal string: T@"UILabel",&,N,V_numLab
0x0000E433  0x00000006  [  2] literal string: index
0x0000E439  0x0000000D  [  2] literal string: Ti,N,V_index
0x0000E446  0x00000008  [  3] literal string: v8@?0c4
0x0000E44E  0x00000005  [  3] literal string: hash
...

Symbols 部分的 File 顺序是和Target -> Build Phase -> Compile Sources 的文件顺序一致的。

你可能感兴趣的:([iOS] Link Map File)