前言
我們編寫的源碼需要經(jīng)過編譯、鏈接惨奕,最終生成一個(gè)可執(zhí)行文件雪位。在編譯階段,每個(gè)類會生成對應(yīng)的 .o
文件(目標(biāo)文件)梨撞。在鏈接階段雹洗,會把 .o
文件和動(dòng)態(tài)庫鏈接在一起。Link Map File
就是這樣一個(gè)記錄鏈接相關(guān)信息的純文本文件卧波,里面記錄了可執(zhí)行文件的路徑队伟、CPU
架構(gòu)、目標(biāo)文件幽勒、符號等信息嗜侮。
為什么要理解 Link Map File ?
理解 Link Map File
啥容,可以幫助我們:
- 理解鏈接過程
- 理解
Crash
時(shí)锈颗,通過Symbols
還原出源碼的機(jī)制 - 理解內(nèi)存分段、分區(qū)
- 分析可執(zhí)行文件中哪個(gè)類或庫占用比較大咪惠,進(jìn)行安裝包瘦身
Link Map File配置
點(diǎn)擊工程击吱,選擇 Build Setting
選項(xiàng),搜索 map
遥昧,可以看到如下界面覆醇。將 Write Link Map File
設(shè)置為 Yes
后,Build
結(jié)束后炭臭,會在默認(rèn)路徑下生成一個(gè) Link Map File
文件永脓,該文件是 txt
格式的。點(diǎn)擊 Path to Link Map File
鞋仍,可以設(shè)置 Debug
或 Release
模式下的生成路徑常摧。
Path & Arch
# Path: /Users/shanggaolin/Library/Developer/Xcode/DerivedData/xxxxxx.app-hjsjojqpqxstlzceepeqbvxqzcrh/Build/Products/Debug-iphonesimulator/xxxxxx.app/xxxxxx
# Arch: x86_64
注
:Path
是可執(zhí)行文件的路徑,Arch
是架構(gòu)類型威创。
Object files
# Object files:
[ 0] linker synthesized
[ 1] /Users/shanggaolin/Library/Developer/Xcode/DerivedData/NewMissFresh-hjsjojqpqxstlzceepeqbvxqzcrh/Build/Intermediates.noindex/NewMissFresh.build/Debug-iphonesimulator/NewMissFresh.build/NewMissFresh.app-Simulated.xcent
[ 2] /Users/shanggaolin/Library/Developer/Xcode/DerivedData/NewMissFresh-hjsjojqpqxstlzceepeqbvxqzcrh/Build/Intermediates.noindex/NewMissFresh.build/Debug-iphonesimulator/NewMissFresh.build/Objects-normal/x86_64/UIButton+SSEdgeInsets.o
[ 3] /Users/shanggaolin/Library/Developer/Xcode/DerivedData/NewMissFresh-hjsjojqpqxstlzceepeqbvxqzcrh/Build/Intermediates.noindex/NewMissFresh.build/Debug-iphonesimulator/NewMissFresh.build/Objects-normal/x86_64/MFRankListTableViewDelegate.o
在編譯成目標(biāo)文件后落午,通過鏈接器進(jìn)行鏈接,最終合成可執(zhí)行文件肚豺。這里展示的信息是鏈接時(shí)用到的文件溃斋,包括 .o
文件和 dylib
庫。第一列的序號是類的編號吸申,通過該編號可以對應(yīng)到具體的類梗劫。
在后面的 Symbols
部分寞奸,我們會用到類的編號。
Section區(qū)
# Sections:
# Address Size Segment Section
0x100002780 0x0129617D __TEXT __text
0x1012988FE 0x000015E4 __TEXT __stubs
0x101299EE4 0x0000207C __TEXT __stub_helper
0x10129BF60 0x0002BE10 __TEXT __const
0x1012C7D70 0x00097A6D __TEXT __objc_methname
0x10135F7DD 0x00010CD5 __TEXT __objc_classname
0x1013704B2 0x00015F47 __TEXT __objc_methtype
0x101386400 0x000B6CB2 __TEXT __cstring
0x10143D0B2 0x00007D60 __TEXT __ustring
0x101444E14 0x00054EA8 __TEXT __gcc_except_tab
0x101499CBC 0x000002B0 __TEXT __entitlements
0x101499F6C 0x00014CA8 __TEXT __unwind_info
0x1014AEC18 0x0003B3E8 __TEXT __eh_frame
0x1014EA000 0x00000010 __DATA __nl_symbol_ptr
0x1014EA010 0x00000BD8 __DATA __got
0x1014EABE8 0x00001D30 __DATA __la_symbol_ptr
0x1014EC918 0x00000028 __DATA __mod_init_func
0x1014EC940 0x00073BB0 __DATA __const
0x1015604F0 0x00062700 __DATA __cfstring
0x1015C2BF0 0x00004BD0 __DATA __objc_classlist
0x1015C77C0 0x00000058 __DATA __objc_nlclslist
0x1015C7818 0x00000640 __DATA __objc_catlist
0x1015C7E58 0x00000088 __DATA __objc_nlcatlist
0x1015C7EE0 0x00001018 __DATA __objc_protolist
0x1015C8EF8 0x00000008 __DATA __objc_imageinfo
0x1015C8F00 0x001EE0A0 __DATA __objc_const
0x1017B6FA0 0x00024A08 __DATA __objc_selrefs
0x1017DB9A8 0x000000B8 __DATA __objc_protorefs
0x1017DBA60 0x00004AD8 __DATA __objc_classrefs
0x1017E0538 0x00002F08 __DATA __objc_superrefs
0x1017E3440 0x00015A38 __DATA __objc_ivar
0x1017F8E78 0x0002F670 __DATA __objc_data
0x1018284F0 0x00024D18 __DATA __data
0x10184D210 0x00006C74 __DATA __bss
0x101853E90 0x00000E90 __DATA __common
第一列是起始位置在跳,第二列是 Section
占用內(nèi)存大小枪萄,第三列是 Segment
類型,第四列是 Section
類型猫妙。
為了理解上面的信息瓷翻,我們需要先補(bǔ)充一點(diǎn) Mach-O
知識。
Mach-O
文件中的虛擬地址最終會映射到物理地址上割坠。這些地址被分成不同的 Segement
: __TEXT
段齐帚、__DATA
段 和 __LINKEDIT
段。
(1)__TEXT
包含 Mach header
彼哼,被執(zhí)行的代碼和只讀常量(如 C
字符串)对妄,只讀可執(zhí)行(r-x
)。
(2)__DATA
包含全局變量敢朱,靜態(tài)變量等剪菱,可讀寫(rw-
)。
(3)__LINKEDIT
包含了加載程序的『元數(shù)據(jù)』拴签,比如函數(shù)的名稱和地址孝常,只讀(r–
)。
Segement
劃分成了不同的 Section
蚓哩,不同的 Section
存儲著不同的信息构灸,下面是一些常用的 Section
的介紹。
// __TEXT段中的一些Section
1. __text: 代碼節(jié)岸梨,存放機(jī)器編譯后的代碼
2. __stubs: 用于輔助做動(dòng)態(tài)鏈接代碼(dyld).
3. __stub_helper:用于輔助做動(dòng)態(tài)鏈接(dyld).
4. __objc_methname:objc的方法名稱
5. __cstring:代碼運(yùn)行中包含的字符串常量,比如代碼中定義`#define kGeTuiPushAESKey @"DWE2#@e2!"`,那DWE2#@e2!會存在這個(gè)區(qū)里喜颁。
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:存儲引用符號的實(shí)際地址,類似于動(dòng)態(tài)符號表
2. __la_symbol_ptr:lazy symbol pointers曹阔。懶加載的函數(shù)指針地址半开。和__stubs和stub_helper配合使用。具體原理暫留次兆。
3. __mod_init_func:模塊初始化的方法稿茉。
4. __const:存儲constant常量的數(shù)據(jù)。比如使用extern導(dǎo)出的const修飾的常量芥炭。
5. __cfstring:使用Core Foundation字符串
6. __objc_classlist:objc類列表,保存類信息,映射了__objc_data的地址
7. __objc_nlclslist:Objective-C 的 +load 函數(shù)列表恃慧,比 __mod_init_func 更早執(zhí)行园蝠。
8. __objc_catlist: categories
9. __objc_nlcatlist:Objective-C 的categories的 +load函數(shù)列表。
10. __objc_protolist:objc協(xié)議列表
11. __objc_imageinfo:objc鏡像信息
12. __objc_const:objc常量痢士。保存objc_classdata結(jié)構(gòu)體數(shù)據(jù)彪薛。用于映射類相關(guān)數(shù)據(jù)的地址茂装,比如類名,方法名等善延。
13. __objc_selrefs:引用到的objc方法
14. __objc_protorefs:引用到的objc協(xié)議
15. __objc_classrefs:引用到的objc類
16. __objc_superrefs:objc超類引用
17. __objc_ivar:objc ivar指針,存儲屬性少态。
18. __objc_data:objc的數(shù)據(jù)。用于保存類需要的數(shù)據(jù)易遣。最主要的內(nèi)容是映射__objc_const地址彼妻,用于找到類的相關(guān)數(shù)據(jù)。
19. __data:暫時(shí)沒理解豆茫,從日志看存放了協(xié)議和一些固定了地址(已經(jīng)初始化)的靜態(tài)量侨歉。
20. __bss:存儲未初始化的靜態(tài)量。比如:`static NSThread *_networkRequestThread = nil;`其中這里面的size表示應(yīng)用運(yùn)行占用的內(nèi)存揩魂,不是實(shí)際的占用空間幽邓。所以計(jì)算大小的時(shí)候應(yīng)該去掉這部分?jǐn)?shù)據(jù)。
21. __common:存儲導(dǎo)出的全局的數(shù)據(jù)火脉。類似于static牵舵,但是沒有用static修飾。比如KSCrash里面`NSDictionary* g_registerOrders;`, g_registerOrders就存儲在__common里面
虛擬內(nèi)存最終會映射到物理內(nèi)存倦挂,通過上面的介紹棋枕,我們就可以知道代碼和數(shù)據(jù)在內(nèi)存中是如何存儲的。
Symbols
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
# Symbols:
// __text代碼區(qū)
# Address Size File Name
0x100002780 0x00000450 [ 2] -[UIButton(SSEdgeInsets) setImageUpTitleDownWithSpacing:]
0x100002BD0 0x00000070 [ 2] _UIEdgeInsetsMake
0x100002C40 0x000004B0 [ 2] -[UIButton(SSEdgeInsets) setImageRightTitleLeftWithSpacing:]
0x1000030F0 0x000001B0 [ 2] -[UIButton(SSEdgeInsets) setDefaultImageTitleStyleWithSpacing:]
0x1000032A0 0x000006F0 [ 2] -[UIButton(SSEdgeInsets) setEdgeInsetsWithType:marginType:margin:]
0x100003990 0x000000B0 [ 3] -[MFRankListTableViewDelegate removeAllCellModels]
0x100003A40 0x000002A0 [ 3] -[MFRankListTableViewDelegate appendCellModels:]
0x100003CE0 0x00000050 [ 3] -[MFRankListTableViewDelegate numberOfSectionsInTableView:]
0x100003D30 0x000000C0 [ 3] -[MFRankListTableViewDelegate tableView:numberOfRowsInSection:]
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// __objc_methname方法名區(qū)
0x1012C7D70 0x0000000F [ 2] literal string: imageForState:
0x1012C7D7F 0x00000005 [ 2] literal string: size
0x1012C7D84 0x00000014 [ 2] literal string: setTitleEdgeInsets:
0x1012C7D98 0x0000000F [ 2] literal string: titleForState:
0x1012C7DA7 0x00000007 [ 2] literal string: length
0x1012C7DAE 0x0000000B [ 2] literal string: titleLabel
0x1012C7DB9 0x00000005 [ 2] literal string: font
0x1012C7DBE 0x00000025 [ 2] literal string: dictionaryWithObjects:forKeys:count:
0x1012C7DE3 0x00000014 [ 2] literal string: sizeWithAttributes:
0x1012C7DF7 0x00000014 [ 2] literal string: setImageEdgeInsets:
0x1012C7E0B 0x00000019 [ 2] literal string: attributedTitleForState:
0x1012C7E24 0x00000006 [ 2] literal string: frame
0x1012C7E2A 0x00000020 [ 2] literal string: setImageUpTitleDownWithSpacing:
0x1012C7E4A 0x00000023 [ 2] literal string: setImageRightTitleLeftWithSpacing:
0x1012C7E6D 0x00000026 [ 2] literal string: setDefaultImageTitleStyleWithSpacing:
0x1012C7E93 0x00000029 [ 2] literal string: setEdgeInsetsWithType:marginType:margin:
0x1012C7EBC 0x0000000E [ 3] literal string: setIsLoading:
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// __objc_classlist類列表區(qū)
0x1015C2BF0 0x00000008 [ 3] anon
0x1015C2BF8 0x00000008 [ 4] anon
0x1015C2C00 0x00000008 [ 5] anon
0x1015C2C08 0x00000008 [ 6] anon
0x1015C2C10 0x00000008 [ 7] anon
1妒峦、__text代碼區(qū)
通過地址 0x100002780
重斑,可以知道它位于 __TEXT
段的 __text
區(qū),這段區(qū)域存儲著代碼肯骇,通過符號表窥浪,根據(jù)地址可以對應(yīng)出源代碼,如 -[UIButton(SSEdgeInsets) setImageUpTitleDownWithSpacing:]
笛丙。通過第三列的類編號漾脂,可以知道該代碼屬于 UIButton+SSEdgeInsets
分類。
2胚鸯、__objc_methname方法名區(qū)
通過地址 0x1012C7D70
骨稿,可以知道它位于 __TEXT
段的 __objc_methname
區(qū),這段區(qū)域存儲著方法名姜钳,通過符號表坦冠,根據(jù)地址可以對應(yīng)出具體的方法名,如 imageForState
哥桥。
由上面的信息辙浑,可以看出方法名越長,最終占用的內(nèi)存也越大拟糕。
3判呕、__objc_classlist類列表區(qū)
__objc_classlist
區(qū)的 size
值都是 8
倦踢,區(qū)域里存儲的值都是一個(gè)指針,指向了類的虛擬地址(__objc_data
區(qū)中地址)侠草。
objc_class
的數(shù)據(jù)結(jié)構(gòu)為:
typedef struct objc_class {
unsigned long long isa;
unsigned long long wuperclass;
unsigned long long cache;
unsigned long long vtable;
unsigned long long data;
unsigned long long reserved1;
unsigned long long reserved2;
unsigned long long reserved3;
}objc_class;
上面的 data
字段保存了 _objc_const
區(qū)對應(yīng)的數(shù)據(jù)地址(objc_classdata
地址)辱挥。
objc_classdata
數(shù)據(jù)結(jié)構(gòu)為:
typedef struct objc_classdata {
long long flags;
long long instanceStart;
long long instanceSize;
long long reserved;
unsigned long long ivarlayout;
unsigned long long name;
unsigned long long baseMethod;
unsigned long long baseProtocol;
unsigned long long ivars;
unsigned long long weakIvarLayout;
unsigned long long baseProperties;
}
objc_classdata
結(jié)構(gòu)體中保存了類名,方法名边涕,協(xié)議名晤碘,ivar
指針和屬性對應(yīng)的地址。通過地址奥吩,可以找到對應(yīng)的段和分區(qū)哼蛆,進(jìn)而找到對應(yīng)類的各種信息。
查找未使用的類和方法
__objc_classrefs
是引用到的類霞赫,_objc_classname
是所有類名腮介,通過分析兩者之間的差別,就可以知道哪些類沒有用到端衰。
同理叠洗,分析 __objc_selrefs
和 _objc_methname
的差別,也可知道哪些方法沒用到旅东。
由于 OC
是動(dòng)態(tài)語言灭抑,中間可能會出現(xiàn)判斷失誤的情況。
分析大文件
在 Symbols
部分抵代,我們可以把類編號相同的 size
加起來腾节,算出每個(gè)類或庫占用的大小。在 Object files
部分根據(jù)類的編號可以查出對應(yīng)的類荤牍。分析的結(jié)果對 App
安裝包瘦身會有一些幫助案腺。github
上有很多實(shí)現(xiàn)了這樣功能的開源工具,實(shí)現(xiàn)原理很簡單康吵,如有需要可自行查找劈榨。
Author
如果你有什么建議,可以關(guān)注我晦嵌,直接留言同辣,留言必回。