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还原出源码的机制
- 理解内存分段、分区
- 分析可执行文件中哪个类或库占用比较大,进行安装包瘦身
3. 生成 Link Map File
Xcode 在生成可执行文件的时候,默认不生成该文件。
在Xcode的配置中 Target -> Build Setting -> Linking 将Write 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文件:
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是架构类型。
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
在编译成目标文件后,通过链接器进行链接,最终合成可执行文件。这里展示的信息是链接时用到的文件,包括.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是起始位置,第二列Size是Section占用内存大小,第三列是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 的文件顺序一致的。