前言 :
之前接觸過(guò)Bugly幕屹,在Bugly文檔中心 這里接觸了一些iOS符號(hào)表的內(nèi)容浅碾,符號(hào)(Symbol)是日常開(kāi)發(fā)中經(jīng)常接觸的一個(gè)概念粤咪,雖然日常開(kāi)發(fā)中直接應(yīng)用的場(chǎng)景比較少才漆,但符號(hào)編譯期和運(yùn)行時(shí)都扮演了重要的角色。今天就來(lái)好好學(xué)習(xí)下符號(hào) Symbol,并為后面學(xué)習(xí)動(dòng)態(tài)庫(kù)靜態(tài)庫(kù)學(xué)習(xí)做準(zhǔn)備。
參考:帥駝駝
目錄 :
- 符號(hào) Symbol的定義
- 符號(hào)表的種類
- 符號(hào)的實(shí)際探究
- 符號(hào)的擴(kuò)展
- 5 .符號(hào)的剝離Strip命令
一双揪、 符號(hào) Symbol的定義
《深入理解計(jì)算機(jī)系統(tǒng)》一書中有一段Linux編譯系統(tǒng)采用的方法:
在編譯時(shí),編譯器向匯編器輸出每個(gè)
全局符號(hào)
包帚,或者是強(qiáng)(strong)或者是弱(weak)渔期,而匯編器把這個(gè)信息隱含地編碼在可重定位目標(biāo)文件的符號(hào)表
里。函數(shù)和已初始化的全局變量是強(qiáng)符號(hào)渴邦,未初始化的全局變量是弱符號(hào)疯趟。
- 符號(hào) Symbol
符號(hào)是一個(gè)
數(shù)據(jù)結(jié)構(gòu)
,包含了名稱(String)和類型等元數(shù)據(jù)谋梭,符號(hào)對(duì)應(yīng)一個(gè)函數(shù)或者數(shù)據(jù)的地址
信峻。符號(hào)直接影響api的體積
- 符號(hào)表Symbol Table
什么是符號(hào)表?
符號(hào)表是內(nèi)存地址與函數(shù)名章蚣、文件名站欺、行號(hào)的映射表姨夹。符號(hào)表元素如下所示:
<起始地址
> <結(jié)束地址
> <函數(shù)
> [<文件名:行號(hào)
>]
Symbol Table
:符號(hào)表存儲(chǔ)了當(dāng)前文件的符號(hào)信息纤垂,靜態(tài)鏈接器(ld)和動(dòng)態(tài)鏈接器(dyld)在鏈接的過(guò)程中都會(huì)讀取符號(hào)表,另外調(diào)試器也會(huì)用符號(hào)表來(lái)把符號(hào)映射到源文件, 就是?來(lái)保存符號(hào)磷账。
String Table
:就是?來(lái)保存符號(hào)的名稱峭沦。
Indirect Symbol Table
:間接符號(hào)表
。保存使?的外部符號(hào)逃糟。更準(zhǔn)確?點(diǎn)就是使?的外部動(dòng)態(tài)庫(kù)的符號(hào)吼鱼。是Symbol Table
的?集。
符號(hào)表中存儲(chǔ)符號(hào)的數(shù)據(jù)結(jié)構(gòu)如下:
struct nlist_64 {
union {
uint32_t n_strx; /* index into the string table */
} n_un;
uint8_t n_type; /* type flag, see below */
uint8_t n_sect; /* p number or NO_SECT */
uint16_t n_desc; /* see <mach-o/stab.h> */
uint64_t n_value; /* value of this symbol (or stab offset) */
};
符號(hào)表 在Mach-O中的位置
通過(guò)兩個(gè)Load Commands
绰咽,描述Symbol Table
的大小和位置菇肃,以及其他元數(shù)據(jù)
LC_SYMTAB
:用來(lái)描述該文件的符號(hào)表。不論是靜態(tài)鏈接器還是動(dòng)態(tài)鏈接器在鏈接此文件時(shí)取募,都要使用該Load Command琐谤。調(diào)試器也可以使用該Load Command找到調(diào)試信息LC_DYSYMTAB
:描述動(dòng)態(tài)鏈接器使用其他的Symbol Table
信息,
定義
LC_SYMTAB
加載命令具體屬性。在/usr/include/mach-o/loader.h
中定義:
struct symtab_command {
// 共有屬性玩敏。指明當(dāng)前描述的加載命令斗忌,當(dāng)前被設(shè)置為L(zhǎng)C_SYMTAB
uint32_t cmd ;
// 共有屬性质礼。指明加載命令的大小,當(dāng)前被設(shè)置為sizeof(symtab_command)
uint32_t cmdsize;
// 表示從文件開(kāi)始到symbol table所在位置的偏移量织阳。symbol table用[nlist]來(lái)表示
uint32_t symoff;
// 符號(hào)表內(nèi)符號(hào)的數(shù)量
uint32_t nsyms;
// 表示從文件開(kāi)始到string table所在位置的偏移量眶蕉。
uint32_t stroff;
// 表示string table大小(以byteカ單位)
uint32_t strsize;
};
二、符號(hào)表的種類
2.1 分類
按照模塊區(qū)分:
- 全局符號(hào)(Global Symbol) : 整個(gè)項(xiàng)目可見(jiàn)
- 本地符號(hào)(Local Symbol) : 當(dāng)前類可見(jiàn)
按照位置劃分:
- 外部符號(hào) : 符號(hào)不在當(dāng)前文件唧躲,需要ld或者dyld在鏈接的時(shí)候解決
- 非外部符號(hào) : 即當(dāng)前文件內(nèi)的符號(hào)
按照功能分:
Type | 說(shuō)明 |
---|---|
f | File |
F | Function |
O | Data |
d | Debug |
ABS | Absolute |
COM | Common |
UND | 未定義 |
按照符號(hào)種類劃分:
Type | 說(shuō)明 |
---|---|
U |
undefined (未定義) |
A |
absolute (絕對(duì)符號(hào)) |
T①
|
text section symbol (__TEXT.__text ) |
D①
|
data section symbol (__DATA.__data ) |
B①
|
bss section symbol (__DATA.__bss ) |
C |
common symbol (只能出現(xiàn)在MH_OBJECT 類型的Mach-O ?件中) |
- | debugger symbol table |
S①
|
除了上?所述的造挽,存放在其他section 的內(nèi)容,例如未初始化的全局變量存放在(__DATA,__common )中 |
I |
indirect symbol (符號(hào)信息相同弄痹,代表同?符號(hào)) |
u | 動(dòng)態(tài)共享庫(kù)中的?寫u 表示?個(gè)未定義引?對(duì)同?庫(kù)中另?個(gè)模塊中私有外部符號(hào) |
注:標(biāo)記①
的Type
刽宪,?寫代表本地符號(hào)(local symbol
)
nm命令里的小寫字母對(duì)應(yīng)著本地符號(hào),大寫字母表示全局符號(hào)界酒;U表示undefined圣拄,即未定義的外部符號(hào)
三、符號(hào)的實(shí)際探究
3.1 可見(jiàn)性
有個(gè)很常見(jiàn)的case毁欣,就是你有1000個(gè)函數(shù)庇谆,但只有10個(gè)函數(shù)是公開(kāi)的,希望最后生成的動(dòng)態(tài)庫(kù)里不包含其他990個(gè)函數(shù)的符號(hào)凭疮,這時(shí)候就可以用clang的attribute來(lái)實(shí)現(xiàn):
//符號(hào)可被外部鏈接__attribute__((visibility("default")))//符號(hào)不會(huì)被放到Dynamic Symbol Table里饭耳,意味著不可以再被其他編譯單元鏈接__attribute__((visibility("hidden")))
clang來(lái)提供了一個(gè)全局的開(kāi)關(guān),用來(lái)設(shè)置符號(hào)的默認(rèn)可見(jiàn)性:
如果動(dòng)態(tài)庫(kù)的Target把這個(gè)開(kāi)關(guān)打開(kāi)执解,會(huì)發(fā)現(xiàn)動(dòng)態(tài)庫(kù)仍然能編譯通過(guò)寞肖,但是App會(huì)報(bào)一堆鏈接錯(cuò)誤,因?yàn)榉?hào)變成了hidden衰腌。
但這是一種常見(jiàn)的編譯方式:讓符號(hào)默認(rèn)是Hidden的新蟆,即-fvisibility=hidden
,然后手動(dòng)為每個(gè)接口加上__attribute__((visibility("default")))
右蕊。
//頭文件#define LH_EXPORT __attribute__((visibility("default")))LH_EXPORT void method_1(void);//實(shí)現(xiàn)文件LH_EXPORT void method_1(){ NSLog(@"1");}
全局符號(hào)
和本地符號(hào)
琼稻,它們本質(zhì)上的區(qū)別就是可見(jiàn)性
一個(gè)符號(hào)的可見(jiàn)性有兩種:
default
:默認(rèn)值,定義的符號(hào)類型是全局即為全局饶囚,是本地即為本地hidden
:將全局符號(hào)隱藏帕翻,變?yōu)楸镜胤?hào)
故此隱藏全局符號(hào)的方式有兩種:
- 使用static關(guān)鍵字修飾
- 使用attribute((visibility("hidden")))
舉例 :
案例1:
打開(kāi)項(xiàng)目,里面包含兩個(gè)
Project
來(lái)到
LGOneFramework
萝风,打開(kāi)LGOneObject.m
文件嘀掸,實(shí)現(xiàn)global_object
函數(shù)在
LGOneObject.h
文件中,并沒(méi)有暴露global_object
函數(shù)來(lái)到
LGApp
(另一個(gè)Project
)规惰,打開(kāi)ViewController.m
文件睬塌,定義global_object
函數(shù),但并不實(shí)現(xiàn)。在viewDidLoad
方法中調(diào)用global_object
函數(shù)運(yùn)行項(xiàng)目衫仑,
LGOneFramework
中實(shí)現(xiàn)的global_object
函數(shù)被正常調(diào)用
案例2:
如果將
LGOneFramework
中的global_object
函數(shù)梨与,使用static
關(guān)鍵字修飾在
LGApp
中,global_object
函數(shù)的調(diào)用代碼不變此時(shí)
global_object
函數(shù)變成本地符號(hào)文狱,僅對(duì)當(dāng)前文件可見(jiàn)粥鞋。所以在LGApp
中調(diào)用該函數(shù),編譯報(bào)錯(cuò)瞄崇,并提示未定義符號(hào)
案例3:
在
LGOneFramework
中呻粹,實(shí)現(xiàn)global_object
函數(shù),不使用static
關(guān)鍵字修飾在
LGApp
的ViewController.m
中苏研,也實(shí)現(xiàn)global_object
函數(shù)運(yùn)行項(xiàng)目等浊,調(diào)用的是
LGApp
中的global_object
函數(shù)
案例3
中,為什么不會(huì)發(fā)生沖突摹蘑?
在LGApp
和LGOneFramework
兩個(gè)Project
中筹燕,都定義了global_object
函數(shù),對(duì)于global_object
函數(shù)來(lái)說(shuō)衅鹿,它們其實(shí)存儲(chǔ)在兩個(gè)Mach-O
中撒踪。由于編譯器有?級(jí)命名空間的概念,所以兩個(gè)global_object
函數(shù)的符號(hào)其實(shí)是不一樣的
two_levelnamespace & flat_namespace
:
?級(jí)命名空間與?級(jí)命名空間大渤。鏈接器默認(rèn)采??級(jí)命名空間制妄,也就是除了會(huì)記錄符號(hào)名稱,還會(huì)記錄符號(hào)屬于哪個(gè)Mach-O
的泵三,?如會(huì)記錄下來(lái)_NSLog
來(lái)?Foundation
3.2 作用域
導(dǎo)入符號(hào)
和導(dǎo)出符號(hào)
- 動(dòng)態(tài)庫(kù)因?yàn)椴恢劳饷媸侨绾问褂玫母蹋宰詈玫姆绞绞撬蓄^文件暴露出的符號(hào)全部導(dǎo)出來(lái)。從包大小的角度考慮烫幕,肯定是用到哪些符號(hào)俺抽,保留哪些符號(hào)對(duì)應(yīng)的代碼,ld提供了這樣一個(gè)方案纬霞,通過(guò)
exported_symbol
來(lái)只保留特定的符號(hào)
export symbol
:導(dǎo)出符號(hào)意味著凌埂,告訴別的模塊,我有?個(gè)這樣的符號(hào)诗芜,你可以將其導(dǎo)?(Import
)。以
NSLog
為例:NSLog(@"%d", static_init_value);
NSLog
存儲(chǔ)在Foundation
庫(kù)中
- 對(duì)于
Foundation
庫(kù)來(lái)說(shuō)埃疫,NSLog
屬于供外部使用的導(dǎo)出符號(hào)- 對(duì)于當(dāng)前程序來(lái)說(shuō)伏恐,
NSLog
屬于從Foundation
庫(kù)中導(dǎo)入的符號(hào)
導(dǎo)出符號(hào)
就是全局符號(hào)
項(xiàng)目中定義的全局變量生成為全局符號(hào),默認(rèn)就會(huì)被導(dǎo)出栓霜,這些符號(hào)可以被外界查看并使用
使用
objdump --macho --exports-trie ${MACH_PATH}
命令查看導(dǎo)出符號(hào)Exports trie: 0x100000000 __mh_execute_header 0x100003F50 _main 0x100008010 _global_init_value 0x100008020 _global_uninit_value
- 只有上述四個(gè)導(dǎo)出符號(hào)翠桦,對(duì)應(yīng)符號(hào)表中的四個(gè)全局符號(hào)
動(dòng)態(tài)庫(kù)在運(yùn)行時(shí)才會(huì)加載,在編譯鏈接階段只提供符號(hào)即可。
Mach-O
中使用的動(dòng)態(tài)庫(kù)符號(hào)保存在間接符號(hào)表里使用
objdump --macho --indirect-symbols ${MACH_PATH}
命令查看間接符號(hào)表Indirect symbols for (__TEXT,__stubs) 1 entries address index name 0x0000000100003f90 8 _NSLog Indirect symbols for (__DATA_CONST,__got) 1 entries address index name 0x0000000100004000 10 dyld_stub_binder Indirect symbols for (__DATA,__la_symbol_ptr) 1 entries address index name 0x0000000100008000 8 _NSLog
- 符號(hào)在
Mach-O
中占有一定體積销凑,剝離符號(hào)時(shí)丛晌,間接符號(hào)表是不能被刪除的Mach-O
所使用的動(dòng)態(tài)庫(kù)的全局符號(hào)都不能被刪除- 動(dòng)態(tài)庫(kù)剝離符號(hào),只能剝離非全局符號(hào)的所有符號(hào)
查看
OC
中的符號(hào)打開(kāi)
LGOneObject.m
文件斗幼,寫入以下代碼:
#import "LGOneObject.h"
@interface LGOneObject : NSObject
- (void)testOneObject;
@end
@implementation LGOneObject
- (void)testOneObject {
NSLog(@"testOneObject");
}
@end
使用
objdump --macho --exports-trie ${MACH_PATH}
命令查看導(dǎo)出符號(hào)Exports trie: 0x100000000 __mh_execute_header 0x100003F20 _main 0x1000080B8 _OBJC_METACLASS_$_LGOneObject 0x1000080E0 _OBJC_CLASS_$_LGOneObject 0x100008110 _global_init_value 0x100008120 _global_uninit_value
OC
默認(rèn)都是全局符號(hào)
澎蛛,同時(shí)也是導(dǎo)出符號(hào)
。它們可以被外界查看并使用蜕窿,會(huì)增加Mach-O
的體積
開(kāi)發(fā)
OC
動(dòng)態(tài)庫(kù)時(shí)谋逻,想要減小Mach-O
的體積,就要將外部無(wú)需使用的符號(hào)剝離桐经。此時(shí)可以借助鏈接器毁兆,將不想暴露的符號(hào)
聲明為不導(dǎo)出符號(hào)
打開(kāi)
xcconfig
文件,添加OTHER_LDFLAGS
配置項(xiàng)OTHER_LDFLAGS=$(inherited) -Xlinker -unexported_symbol -Xlinker _OBJC_CLASS_$_LGOneObject OTHER_LDFLAGS=$(inherited) -Xlinker -unexported_symbol -Xlinker _OBJC_METACLASS_$_LGOneObject
- 將
_OBJC_CLASS_$_LGOneObject
聲明為不導(dǎo)出符號(hào)- 將
_OBJC_METACLASS_$_LGOneObject
聲明為不導(dǎo)出符號(hào)編譯項(xiàng)目阴挣,此時(shí)
OC
的兩個(gè)導(dǎo)出符號(hào)已經(jīng)被隱藏Exports trie: 0x100000000 __mh_execute_header 0x100003F20 _main 0x100008110 _global_init_value 0x100008120 _global_uninit_value
- 隱藏
OC
不想暴露的符號(hào)气堕,需要借助鏈接器,將符號(hào)聲明為不導(dǎo)出符號(hào)- 由于
OC
是運(yùn)行時(shí)語(yǔ)言畔咧,不能直接使用visibility("hidden")
- 不導(dǎo)出符號(hào)送巡,將全局符號(hào)變?yōu)楸镜胤?hào),這些符號(hào)可以被剝離盒卸,從而減小
Mach-O
的體積- 隱藏不需要暴露的符號(hào)骗爆,從而避免被外界查看并使用,解決安全隱患
鏈接器提供的另一種方式:指定一個(gè)文件蔽介,將文件內(nèi)的符號(hào)全部聲明為不導(dǎo)出符號(hào)
創(chuàng)建
symbol.txt
文件摘投,放到工程目錄中,里面定義不想暴露的符號(hào)_OBJC_CLASS_$_LGOneObject _OBJC_METACLASS_$_LGOneObject _global_init_value _global_uninit_value
打開(kāi)
xcconfig
文件虹蓄,添加OTHER_LDFLAGS
配置項(xiàng)OTHER_LDFLAGS=$(inherited) -Xlinker -unexported_symbols_list >$(PROJECT_DIR)/symbol.txt
編譯項(xiàng)目犀呼,此時(shí)
symbol.txt
文件中定義的四個(gè)符號(hào)已經(jīng)被隱藏Exports trie: 0x100000000 __mh_execute_header 0x100003F20 _main
3.3 Weak Symbol
弱定義符號(hào)
Weak Defintion Symbol
:表示此符號(hào)為弱定義符號(hào)。如果靜態(tài)鏈接器或動(dòng)態(tài)鏈接器為此符號(hào)找到另?個(gè)(?弱)定義薇组,則弱定義將被忽略外臂。只能將合并部分中的符號(hào)標(biāo)記為弱定義打開(kāi)
WeakSymbol.m
文件,寫入以下代碼:#import "WeakSymbol.h" #import <Foundation/Foundation.h> void weak_function(void) { NSLog(@"weak_function"); }
- 此時(shí)
weak_function
是一個(gè)全局符號(hào)律胀,同樣也是導(dǎo)出符號(hào)打開(kāi)
WeakSymbol.h
文件宋光,寫入以下代碼:void weak_function(void) __attribute__((weak));
- 使用
__attribute__((weak))
將weak_function
聲明為弱定義符號(hào)使用
objdump --macho --exports-trie ${MACH_PATH}
命令查看導(dǎo)出符號(hào)Exports trie: 0x100000000 __mh_execute_header 0x100003EF0 _weak_function [weak_def] 0x100003F30 _main 0x100008010 _global_init_value 0x100008020 _global_uninit_value
weak_function
還是導(dǎo)出符號(hào),這也證明它依然是全局符號(hào)炭菌,其后增加了[weak_def]
的標(biāo)記
弱定義符號(hào)的作用
在
WeakSymbol.m
和main.m
中罪佳,都實(shí)現(xiàn)一個(gè)weak_function
函數(shù)void weak_function(void) { NSLog(@"weak_function"); }
同一個(gè)
Project
中,出現(xiàn)兩個(gè)相同的全局符號(hào)黑低,此時(shí)編譯報(bào)錯(cuò)赘艳,提示出現(xiàn)重復(fù)符號(hào)將其中一個(gè)
weak_function
函數(shù)聲明為弱定義符號(hào),此時(shí)編譯成功void weak_function(void) __attribute__((weak));
- 弱定義符號(hào)的作用:可以解決同名符號(hào)的沖突;鏈接器按照符號(hào)上下順序蕾管,找到一處符號(hào)的實(shí)現(xiàn)后枷踏,其他地方的同名符號(hào)將被忽略
如果同時(shí)使用
weak
和visibility("hidden")
,符號(hào)會(huì)變成一個(gè)弱定義的本地符號(hào)打開(kāi)
WeakSymbol.m
文件掰曾,寫入以下代碼:void weak_hidden_function(void) { NSLog(@"weak_hidden_function"); }
打開(kāi)
WeakSymbol.h
文件旭蠕,將weak_hidden_function
函數(shù)同時(shí)使用weak
和visibility("hidden")
修飾void weak_hidden_function(void) __attribute__((weak, visibility("hidden")));
使用
objdump --macho --syms ${MACH_PATH}
命令查看符號(hào)表SYMBOL TABLE: 0000000100003f10 lw F __TEXT,__text _weak_hidden_function 0000000100008008 l O __DATA,__data __dyld_private 0000000100008014 l O __DATA,__data _static_init_value 0000000100008018 l O __DATA,__bss _static_uninit_value 0000000100008028 l O __DATA,__common _default_x 0000000100000000 g F __TEXT,__text __mh_execute_header 0000000100008010 g O __DATA,__data _global_init_value 0000000100008020 g O __DATA,__common _global_uninit_value 0000000100003f30 g F __TEXT,__text _main 0000000100003ef0 gw F __TEXT,__text _weak_function 0000000000000000 *UND* _NSLog 0000000000000000 *UND* ___CFConstantStringClassReference 0000000000000000 *UND* dyld_stub_binder
- 此時(shí)
_weak_hidden_function
被標(biāo)記為lw
,變?yōu)槿醵x本地符號(hào)
弱引用符號(hào)
Weak Reference Symbol
:表示此未定義符號(hào)是弱引?婴梧。如果動(dòng)態(tài)鏈接器找不到該符號(hào)的定義下梢,則將其設(shè)置為0
。鏈接器會(huì)將此符號(hào)設(shè)置弱鏈接標(biāo)志打開(kāi)
WeakImportSymbol.h
文件塞蹭,寫入以下代碼:void weak_import_function(void) __attribute__((weak_import));
- 使用
__attribute__((weak_import))
將weak_import_function
聲明為若引用符號(hào)- 此時(shí)項(xiàng)目中沒(méi)有
weak_import_function
函數(shù)的實(shí)現(xiàn)打開(kāi)
main.m
文件孽江,寫入以下代碼:#import <Foundation/Foundation.h> #import "WeakImportSymbol.h" int main(int argc, char *argv[]) { if (weak_import_function) { weak_import_function(); } return 0; }
由于
weak_import_function
函數(shù)沒(méi)有實(shí)現(xiàn),但在main.m
中被使用番电,此時(shí)編譯報(bào)錯(cuò)岗屏,提示未定義符號(hào)
- 當(dāng)導(dǎo)入
.h
頭文件并使用符號(hào)時(shí),類似于API
的使用漱办,只要找到符號(hào)的聲明即可这刷。即使函數(shù)沒(méi)有被實(shí)現(xiàn),也可以生成目標(biāo)文件娩井。但鏈接生成可執(zhí)行文件時(shí)暇屋,需要知道符號(hào)的具體位置,如果函數(shù)沒(méi)有被實(shí)現(xiàn)洞辣,會(huì)出現(xiàn)錯(cuò)誤提示:未定義符號(hào)
解決弱引用符號(hào)的使用問(wèn)題咐刨,可以通過(guò)鏈接器,將符號(hào)聲明為動(dòng)態(tài)鏈接
使用
man ld
命令查看鏈接器參數(shù):
-U
:指明該符號(hào)未定義扬霜,需要運(yùn)行時(shí)動(dòng)態(tài)查找打開(kāi)
xcconfig
文件定鸟,添加OTHER_LDFLAGS
配置項(xiàng)OTHER_LDFLAGS=$(inherited) -Xlinker -U -Xlinker _weak_import_function
- 此時(shí)項(xiàng)目可以正常編譯成功
- 通過(guò)
-U
參數(shù),告訴鏈接器此符號(hào)是動(dòng)態(tài)鏈接的著瓶,所以在鏈接階段联予,即使它是未定義符號(hào),忽略材原,不用管它沸久。因?yàn)樵谶\(yùn)行時(shí),動(dòng)態(tài)鏈接器會(huì)自動(dòng)找到它運(yùn)行項(xiàng)目华糖,雖然
weak_import_function
函數(shù)沒(méi)有被實(shí)現(xiàn)麦向,但運(yùn)行并不會(huì)報(bào)錯(cuò)
- 因?yàn)?code>main函數(shù)中調(diào)用
weak_import_function
函數(shù)之前有if (weak_import_function)
的判斷- 當(dāng)動(dòng)態(tài)鏈接器找不到該符號(hào)的定義,則將其設(shè)置為
0
客叉。所以weak_import_function
函數(shù)并不會(huì)被調(diào)用
弱引用符號(hào)的作用
- 將一個(gè)符號(hào)聲明為弱引用符號(hào),可以避免編譯鏈接時(shí)報(bào)錯(cuò)。在調(diào)用之前增加條件判斷兼搏,運(yùn)行時(shí)也不會(huì)報(bào)錯(cuò)
- 使用動(dòng)態(tài)庫(kù)的時(shí)候卵慰,可以將整個(gè)動(dòng)態(tài)庫(kù)聲明為弱引用,此時(shí)動(dòng)態(tài)庫(kù)即使沒(méi)有被導(dǎo)入佛呻,也不會(huì)出現(xiàn)未找到動(dòng)態(tài)庫(kù)的錯(cuò)誤
Common Symbol
在定義時(shí)裳朋,未初始化的全局符號(hào)
例如:
main.m
文件中,未初始化的global_uninit_value
全局變量吓著,它就屬于Common Symbol
int global_uninit_value;
打開(kāi)
main.m
文件鲤嫡,定義兩個(gè)同名的全局變量,一個(gè)初始化绑莺,另一個(gè)不進(jìn)行初始化暖眼,這種操作并不會(huì)報(bào)錯(cuò)int global_init_value = 10; int global_init_value;
Common Symbol
的作用- 在編譯和鏈接的過(guò)程中,如果找到定義的符號(hào)纺裁,會(huì)自動(dòng)將未定義符號(hào)刪掉
- 在鏈接過(guò)程中诫肠,鏈接器默認(rèn)會(huì)把未定義符號(hào)變成強(qiáng)制定義的符號(hào)
鏈接器設(shè)置:
-d
:強(qiáng)制定義Common Symbol
-commons
:指定對(duì)待Common Symbol
如何響應(yīng)
重新導(dǎo)出符號(hào)
以
NSLog
為例:
- 對(duì)于當(dāng)前程序來(lái)說(shuō),
NSLog
屬于存儲(chǔ)在間接符號(hào)表中的未定義符號(hào)
NSLog
可以在當(dāng)前程序使用欺缘,如果想讓使用此程序的其他程序也能使用栋豫,就要將此符號(hào)重新導(dǎo)出。重新導(dǎo)出之后的符號(hào)會(huì)放在導(dǎo)出符號(hào)表中谚殊,此時(shí)才能被外界查看并使用使用
man ld
命令查看鏈接器參數(shù):
-alias
:只能給間接符號(hào)表中的符號(hào)創(chuàng)建別名丧鸯,別名符號(hào)具有全局可見(jiàn)性打開(kāi)
xcconfig
文件,添加OTHER_LDFLAGS
配置項(xiàng)OTHER_LDFLAGS=$(inherited) -Xlinker -alias -Xlinker _NSLog -Xlinker Cat_NSLog
- 給
_NSLog
符號(hào)創(chuàng)建Cat_NSLog
別名使用
nm -m ${MACH_PATH} | grep "Cat_NSLog"
命令查看符號(hào)表嫩絮,指定"Cat_NSLog"
關(guān)鍵字(indirect) external Cat_NSLog (for _NSLog)
- 此時(shí)
Cat_NSLog
是一個(gè)間接外部符號(hào)丛肢,是_NSLog
符號(hào)的別名使用
objdump --macho --exports-trie ${MACH_PATH}
命令查看導(dǎo)出符號(hào)Exports trie: 0x100000000 __mh_execute_header 0x100003F20 _main 0x100008018 _global_init_value 0x100008028 _global_uninit_value [re-export] Cat_NSLog (_NSLog from Foundation)
Cat_NSLog
為導(dǎo)出符號(hào),并且標(biāo)記為[re-export]
絮记,代表重新導(dǎo)出符號(hào)
重新導(dǎo)出符號(hào)的作用
- 將一個(gè)間接符號(hào)表中的符號(hào)聲明為重新導(dǎo)出符號(hào)摔踱,可以讓使用此程序的其他程序也能使用
- 當(dāng)程序鏈接
A動(dòng)態(tài)庫(kù)
,而A動(dòng)態(tài)庫(kù)
又鏈接B動(dòng)態(tài)庫(kù)
時(shí)怨愤,B動(dòng)態(tài)庫(kù)
對(duì)于程序來(lái)說(shuō)是不可見(jiàn)的派敷。此時(shí)可以使用重新導(dǎo)出的方式,讓B動(dòng)態(tài)庫(kù)
對(duì)程序可見(jiàn)
四撰洗、符號(hào)的擴(kuò)展
查看項(xiàng)目使用的三方庫(kù)和符號(hào)等信息
通過(guò)鏈接器篮愉,可以查看當(dāng)前項(xiàng)目中使用的三方庫(kù)和符號(hào)等信息
使用
man ld
命令查看鏈接器參數(shù):
-map
:將所有符號(hào)詳細(xì)信息導(dǎo)出到指定文件打開(kāi)
xcconfig
文件,添加OTHER_LDFLAGS
配置項(xiàng)OTHER_LDFLAGS=$(inherited) -Xlinker -map -Xlinker $(PROJECT_DIR)/export.txt
編譯項(xiàng)目差导,此時(shí)項(xiàng)目目錄下多出
export.txt
文件
打開(kāi)
export.txt
文件
# Path: /Users/zang/Library/Developer/Xcode/DerivedData/MachOAndSymbol-bdzlylfoorwnhggerxdmwocpeyad/Build/Products/Debug/MachOAndSymbol
# Arch: x86_64
# Object files:
[ 0] linker synthesized
[ 1] /Users/zang/Library/Developer/Xcode/DerivedData/MachOAndSymbol-bdzlylfoorwnhggerxdmwocpeyad/Build/Intermediates.noindex/MachOAndSymbol.build/Debug/MachOAndSymbol.build/Objects-normal/x86_64/LGOneObject.o
[ 2] /Users/zang/Library/Developer/Xcode/DerivedData/MachOAndSymbol-bdzlylfoorwnhggerxdmwocpeyad/Build/Intermediates.noindex/MachOAndSymbol.build/Debug/MachOAndSymbol.build/Objects-normal/x86_64/main.o
[ 3] /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk/System/Library/Frameworks//Foundation.framework/Foundation.tbd
# Sections:
# Address Size Segment Section
0x100003EF0 0x0000006F __TEXT __text
0x100003F60 0x00000006 __TEXT __stubs
0x100003F68 0x0000001A __TEXT __stub_helper
0x100003F82 0x00000011 __TEXT __cstring
0x100003F93 0x0000000C __TEXT __objc_classname
0x100003F9F 0x0000000E __TEXT __objc_methname
0x100003FAD 0x00000008 __TEXT __objc_methtype
0x100003FB8 0x00000048 __TEXT __unwind_info
0x100004000 0x00000008 __DATA_CONST __got
0x100004008 0x00000040 __DATA_CONST __cfstring
0x100004048 0x00000008 __DATA_CONST __objc_classlist
0x100004050 0x00000008 __DATA_CONST __objc_imageinfo
0x100008000 0x00000008 __DATA __la_symbol_ptr
0x100008008 0x000000B0 __DATA __objc_const
0x1000080B8 0x00000050 __DATA __objc_data
0x100008108 0x00000010 __DATA __data
0x100008118 0x00000004 __DATA __bss
0x100008120 0x00000010 __DATA __common
# Symbols:
# Address Size File Name
0x100003EF0 0x00000027 [ 1] -[LGOneObject testOneObject]
0x100003F20 0x0000003F [ 2] _main
0x100003F60 0x00000006 [ 3] _NSLog
0x100003F68 0x00000010 [ 0] helper helper
0x100003F78 0x0000000A [ 3] _NSLog
0x100003F82 0x0000000E [ 1] literal string: testOneObject
0x100003F90 0x00000003 [ 2] literal string: %d
0x100003F93 0x0000000C [ 1] literal string: LGOneObject
0x100003F9F 0x0000000E [ 1] literal string: testOneObject
0x100003FAD 0x00000008 [ 1] literal string: v16@0:8
0x100003FB8 0x00000048 [ 0] compact unwind info
0x100004000 0x00000008 [ 0] non-lazy-pointer-to-local: dyld_stub_binder
0x100004008 0x00000020 [ 1] CFString
0x100004028 0x00000020 [ 2] CFString
0x100004048 0x00000008 [ 1] objc-cat-list
0x100004050 0x00000008 [ 0] objc image info
0x100008000 0x00000008 [ 3] _NSLog
0x100008008 0x00000048 [ 1] __OBJC_METACLASS_RO_$_LGOneObject
0x100008050 0x00000020 [ 1] __OBJC_$_INSTANCE_METHODS_LGOneObject
0x100008070 0x00000048 [ 1] __OBJC_CLASS_RO_$_LGOneObject
0x1000080B8 0x00000028 [ 1] _OBJC_METACLASS_$_LGOneObject
0x1000080E0 0x00000028 [ 1] _OBJC_CLASS_$_LGOneObject
0x100008108 0x00000008 [ 0] __dyld_private
0x100008110 0x00000004 [ 2] _global_init_value
0x100008114 0x00000004 [ 2] _static_init_value
0x100008118 0x00000004 [ 2] _static_uninit_value
0x100008120 0x00000008 [ 2] _global_uninit_value
0x100008128 0x00000008 [ 2] _default_x
- 文件內(nèi)包含了編譯鏈接時(shí)生成的目標(biāo)文件试躏,項(xiàng)目中使用的三方庫(kù),還包含項(xiàng)目中的
Sections
和Symbols
等信息
Section的名稱與作用
名稱 | 作用 |
---|---|
TEXT.text | 可執(zhí)行的機(jī)器碼 |
TEXT.cstring | 去重后的C 字符串 |
TEXT.const | 初始化過(guò)的常量 |
TEXT.stubs | 符號(hào)樁设褐。lazybinding 的表對(duì) 應(yīng)項(xiàng)指針指向的地址的代碼 |
TEXT.stub_ helper | 輔助函數(shù)颠蕴。當(dāng)在lazybinding 的表中沒(méi)有找到對(duì)應(yīng)項(xiàng)的指針表示的真正的符號(hào)地址的時(shí)候泣刹,指向這 |
TEXT.unwind_info | 存儲(chǔ)處理異常情況信息 |
TEXT.eh_frame | 調(diào)試輔助信息 |
DATA.data | 初始化過(guò)的可變的數(shù)據(jù) |
DATA.nI_symbol_ptr | 非lazy-binding 的指針表,每個(gè)表中的指針指向一個(gè)在裝載過(guò)程中犀被,被動(dòng)態(tài)鏈接器搜索完成的符號(hào) |
DATA.Ia_symbol_ptr |
lazy-binding 的指針表椅您,每個(gè)表中的指針一開(kāi)始指向stub_helper
|
DATA.const | 沒(méi)有初始化過(guò)的常量 |
DATA.mod_init_func | 初始化函數(shù),在main 之前調(diào)用 |
DATA.mod_term_func | 終止函數(shù)寡键,在main 返回之后調(diào)用 |
DATA.bss | 沒(méi)有初始化的靜態(tài)變量 |
DATA.common | 沒(méi)有初始化過(guò)的符號(hào)聲明(for example, int I; ) |
Swift符號(hào)表
打開(kāi)
SwiftSymbol.swift
文件掀泳,寫入以下代碼:public class LGSwiftClassSymbol { func testSwiftSymbol() { } }
使用
objdump --macho --syms ${MACH_PATH} | grep "Swift"
命令查看符號(hào)表,指定Swift
關(guān)鍵字
0000000100003f8a lw O __TEXT,__swift5_typeref _symbolic _____ 14MachOAndSymbol012LGSwiftClassC0C
0000000100003f90 l O __TEXT,__swift5_fieldmd _$s14MachOAndSymbol012LGSwiftClassC0CMF
0000000100008020 l O __DATA,__objc_const __METACLASS_DATA__TtC14MachOAndSymbol18LGSwiftClassSymbol
0000000100008068 l O __DATA,__objc_const __DATA__TtC14MachOAndSymbol18LGSwiftClassSymbol
00000001000080e8 l O __DATA,__data _$s14MachOAndSymbol012LGSwiftClassC0CMf
0000000100003d90 g F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC0C09testSwiftC0yyF
0000000100003f78 g O __TEXT,__const _$s14MachOAndSymbol012LGSwiftClassC0C09testSwiftC0yyFTq
0000000100003e10 g F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC0CACycfC
0000000100003f80 g O __TEXT,__const _$s14MachOAndSymbol012LGSwiftClassC0CACycfCTq
0000000100003e40 g F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC0CACycfc
0000000100003e60 g F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC0CMa
00000001000080c0 g O __DATA,__data _$s14MachOAndSymbol012LGSwiftClassC0CMm
0000000100003f44 g O __TEXT,__const _$s14MachOAndSymbol012LGSwiftClassC0CMn
00000001000080f8 g O __DATA,__data _$s14MachOAndSymbol012LGSwiftClassC0CN
0000000100003dd0 g F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC0CfD
0000000100003db0 g F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC0Cfd
0000000000000000 *UND* _OBJC_CLASS_$__TtCs12_SwiftObject
0000000000000000 *UND* _OBJC_METACLASS_$__TtCs12_SwiftObject
- 查找出所有包含
Swift
關(guān)鍵字的符號(hào)西轩,其中有很多標(biāo)記為g
的全局符號(hào)
打開(kāi)
SwiftSymbol.swift
文件员舵,修改LGSwiftClassSymbol
類的訪問(wèn)控制,改為private
修飾private class LGSwiftClassSymbol { func testSwiftSymbol() { } }
使用
objdump --macho --syms ${MACH_PATH} | grep "Swift"
命令查看符號(hào)表藕畔,指定Swift
關(guān)鍵字
0000000100003d10 l F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLC09testSwiftC0yyF
0000000100003d30 l F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCfd
0000000100003d50 l F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCfD
0000000100003d90 l F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCMa
0000000100003db0 l F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCADycfC
0000000100003de0 l F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCADycfc
0000000100003f1c lw O __TEXT,__const _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCMXX
0000000100003f44 l O __TEXT,__const _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCMn
0000000100003f78 l O __TEXT,__const _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLC09testSwiftC0yyFTq
0000000100003f80 l O __TEXT,__const _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCADycfCTq
0000000100003f8a lw O __TEXT,__swift5_typeref _symbolic _____ 14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLC
0000000100003f90 l O __TEXT,__swift5_fieldmd _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCMF
0000000100008020 l O __DATA,__objc_const __METACLASS_DATA__TtC14MachOAndSymbolP33_66093EBE10D00815F1A5CBD65FFF466118LGSwiftClassSymbol
0000000100008068 l O __DATA,__objc_const __DATA__TtC14MachOAndSymbolP33_66093EBE10D00815F1A5CBD65FFF466118LGSwiftClassSymbol
00000001000080c0 l O __DATA,__data _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCMm
00000001000080e8 l O __DATA,__data _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCMf
00000001000080f8 l O __DATA,__data _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCN
0000000000000000 *UND* _OBJC_CLASS_$__TtCs12_SwiftObject
0000000000000000 *UND* _OBJC_METACLASS_$__TtCs12_SwiftObject
- 之前那些標(biāo)記為
g
的全局符號(hào)马僻,全部變?yōu)楸镜胤?hào)
$(SRCROOT)
和$(PROJECT_DIR)
的區(qū)別
$(SRCROOT)
代表的是項(xiàng)目根目錄下$(PROJECT_DIR)
代表的是整個(gè)項(xiàng)目往項(xiàng)目添加文件時(shí),例如
.a
文件劫流,要先Show in Finder
巫玻,復(fù)制到工程目錄中,然后再拖到xcode
項(xiàng)目中而有時(shí)
.a
不在工程目錄中祠汇。例如在工程的父目錄仍秤,可以寫成:$(SRCROOT)/../Spark/libSDK
。其中/../
就是指向父目錄
五可很、 符號(hào)的剝離Strip命令
- 符號(hào)表中有些符號(hào)是必須的诗力,但是很多符號(hào)都是去掉的 , strip經(jīng)常用來(lái)去除目標(biāo)文件中的一些符號(hào)表、調(diào)試符號(hào)表信息我抠,以減小程序的大小;
-
strip命令
:移除或修改符號(hào)表中的符號(hào)苇本。App 進(jìn)行脫符號(hào),可以將本地符號(hào)菜拓,全局符號(hào)全部脫去(All symbols),只留下間接符號(hào)表中的符號(hào)瓣窄。
5.1 如何脫去調(diào)試符號(hào)
5.2 如何脫去All Symbols
- 靜態(tài)庫(kù)進(jìn)行脫符號(hào),放置在重定位符號(hào)表中的符號(hào)不能脫去纳鼎,能脫去的只要調(diào)試符號(hào)(Debugging Symbols)
調(diào)試符號(hào):由匯編器生成.o文件時(shí)俺夕,會(huì)生成一個(gè)DWARF格式的調(diào)試信息,它會(huì)被放到__DWARF段贱鄙,在鏈接是劝贸,會(huì)將__DWARF段放到符號(hào)表中,鏈接后所有的符號(hào)都放在符號(hào)表中
- 動(dòng)態(tài)庫(kù)進(jìn)行脫符號(hào)逗宁,只要不是全局符號(hào)都可以被干掉(Non-Global Symbols)
如何脫去Non-Global Symbols
鏈接一個(gè)靜態(tài)庫(kù)映九,靜態(tài)庫(kù)中的符號(hào)會(huì)合并到APP中,但不會(huì)存入間接符號(hào)表瞎颗,然后進(jìn)行APP脫符號(hào)時(shí)件甥,可以體積縮小
鏈接一個(gè)動(dòng)態(tài)庫(kù)捌议,動(dòng)態(tài)庫(kù)中的符號(hào)表會(huì)放到APP的間接符號(hào)表,然后進(jìn)行APP脫符號(hào)時(shí)嚼蚀,間接符號(hào)表不能被脫去
5.3 Build Setting的Strip Style選擇
- Debugging Symbols
- All Symbols
- Non-Global Symbols
5.4 Strip執(zhí)行的時(shí)機(jī)
編譯完成執(zhí)行腳本后 禁灼,簽名之前管挟,所以這是后剝離符號(hào) 已經(jīng)不起作用了
5.5 Strip剝離符號(hào)
- -x : non_global
- 無(wú)參數(shù): 代表全部符號(hào)
- -S : 剝離調(diào)試符號(hào)
app瘦身:
- 在編譯的時(shí)生產(chǎn)目標(biāo)文件時(shí)優(yōu)化 -O1轿曙,-Oz;
- dead code strip 在鏈接的時(shí)死代碼剝離 僻孝;
- strip 生產(chǎn)Macho文件后剝離符號(hào) 导帝;
給鏈接器傳參 -S (去除調(diào)試符號(hào))