-
Symbol Table
:?來(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)表也是通過(guò)讀取
Load Command
找到符號(hào)表的具體位置
通過(guò)兩個(gè)Load Commands
,描述Symbol Table
的大小和位置棒拂,以及其他元數(shù)據(jù)
LC_SYMTAB
:當(dāng)前Mach-O
中的符號(hào)表信息LC_DYSYMTAB
:描述動(dòng)態(tài)鏈接器使用其他的Symbol Table
信息
LC_SYMTAB
用來(lái)描述該文件的符號(hào)表且预。不論是靜態(tài)鏈接器還是動(dòng)態(tài)鏈接器在鏈接此文件時(shí),都要使用該
Load Command
。調(diào)試器也可以使用該Load Command
找到調(diào)試信息
symtab_command
定義
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;
};
搭建測(cè)試項(xiàng)目
符號(hào)可以使用終端命令進(jìn)行查看坡椒,但每次手動(dòng)操作過(guò)于繁瑣汗唱。這里介紹一個(gè)更高效的方案:使用
Sellp
腳本,在項(xiàng)目編譯后丈攒,自動(dòng)將符號(hào)展示到終端創(chuàng)建
xcode_run_cmd.sh
文件哩罪,寫(xiě)入以下代碼,將文件放到項(xiàng)目根目錄該腳本的作用:執(zhí)行命令并將結(jié)果展示到終端
#!/bin/sh
RunCommand() {
if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
if [[ -n "$TTY" ]]; then
echo "? $@" 1>$TTY
else
echo "? $*"
fi
echo "------------------------------------------------------------------------------" 1>$TTY
fi
if [[ -n "$TTY" ]]; then
eval "$@" &>$TTY
else
"$@"
fi
return $?
}
EchoError() {
if [[ -n "$TTY" ]]; then
echo "$@" 1>&2>$TTY
else
echo "$@" 1>&2
fi
}
RunCMDToTTY() {
if [[ ! -e "$TTY" ]]; then
EchoError "=========================================="
EchoError "ERROR: Not Config tty to output."
exit -1
fi
if [[ -n "$CMD" ]]; then
RunCommand $CMD
else
EchoError "=========================================="
EchoError "ERROR:Failed to run CMD. THE CMD must not null"
fi
}
RunCMDToTTY
腳本內(nèi)定義了兩個(gè)變量:
CMD
:命令+參數(shù)TTY
:終端的標(biāo)識(shí)打開(kāi)終端巡验,使用
tty
命令际插,可獲取終端標(biāo)識(shí)
打開(kāi)項(xiàng)目,創(chuàng)建
xcconfig
文件显设,定義腳本中需要的兩個(gè)變量MACH_PATH=${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/${PRODUCT_NAME} CMD=nm -pa $MACHO_PATH TTY=/dev/ttys001
MACHO_PATH
:定義變量框弛,存儲(chǔ)Mach-O
文件的路徑${BUILD_DIR}
:當(dāng)前編譯路徑$(CONFIGURATION)
:構(gòu)建產(chǎn)品目錄$(EFFECTIVE_PLATFORM_NAME)
:Mach-O
所在目錄${PRODUCT_NAME}
:項(xiàng)目名稱,也是Mach-O
文件的名稱nm
:是names
的簡(jiǎn)稱捕捂,通過(guò)該命令可以列舉文件中的符號(hào)-p
:符號(hào)不進(jìn)行排序-a
:顯示所有符號(hào)瑟枫,包含調(diào)試符號(hào)
點(diǎn)擊
Target
,選擇Build Phases
指攒,在Run Script
中輸入:/bin/sh "$SRCROOT/xcode_run_cmd.sh"
- 通過(guò)
/bin/bash
命令運(yùn)行xcode_run_cmd.sh
腳本$SRCROOT
代表的是項(xiàng)目根目錄下
項(xiàng)目編譯后慷妙,自動(dòng)將
Mach-O
中的符號(hào)展示到終端,無(wú)需手動(dòng)操作
查看項(xiàng)目在
Build
時(shí)允悦,腳本的執(zhí)行時(shí)機(jī)
- 在編譯鏈接之后景殷,簽名之前
C語(yǔ)言符號(hào)
打開(kāi)
main.m
文件,寫(xiě)入以下代碼:
#import <Foundation/Foundation.h>
int global_uninit_value;
int global_init_value = 10;
double default_x __attribute__((visibility("hidden")));
static int static_init_value = 9;
static int static_uninit_value;
int main(int argc, char *argv[]) {
static_uninit_value = 10;
NSLog(@"%d", static_init_value);
return 0;
}
代碼里包含了全局變量和
static
關(guān)鍵字修飾的靜態(tài)變量澡屡,二者最大的差異在于作用域:
- 全局變量:對(duì)整個(gè)項(xiàng)目可見(jiàn)
- 靜態(tài)變量:僅對(duì)當(dāng)前文件可見(jiàn)
案例1:
使用
nm -pa ${MACH_PATH}
命令查看符號(hào)表
0000000100008008 d __dyld_private
0000000100008014 d _static_init_value
0000000100008018 b _static_uninit_value
0000000100008028 s _default_x
0000000000000000 - 00 0000 SO /Users/zang/Zang/Spark/MachOAndSymbol/
0000000000000000 - 00 0000 SO main.m
0000000060335e1d - 03 0001 OSO /Users/zang/Library/Developer/Xcode/DerivedData/MachOAndSymbol-bdzlylfoorwnhggerxdmwocpeyad/Build/Intermediates.noindex/MachOAndSymbol.build/Debug/MachOAndSymbol.build/Objects-normal/x86_64/main.o
0000000100003f50 - 01 0000 BNSYM
0000000100003f50 - 01 0000 FUN _main
000000000000003f - 00 0000 FUN
000000000000003f - 01 0000 ENSYM
0000000000000000 - 00 0000 GSYM _global_init_value
0000000100008014 - 0a 0000 STSYM _static_init_value
0000000100008018 - 0b 0000 STSYM _static_uninit_value
0000000000000000 - 00 0000 GSYM _global_uninit_value
0000000000000000 - 00 0000 GSYM _default_x
0000000000000000 - 01 0000 SO
0000000100000000 T __mh_execute_header
0000000100008010 D _global_init_value
0000000100008020 S _global_uninit_value
0000000100003f50 T _main
U _NSLog
U ___CFConstantStringClassReference
U dyld_stub_binder
第二列:按照符號(hào)種類劃分的標(biāo)識(shí),參見(jiàn)以下列表
按照符號(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ù)中的?寫(xiě) u
表示?個(gè)未定義引?對(duì)同?庫(kù)中另?個(gè)模塊中私有外部符號(hào)注:標(biāo)記
①
的Type
铣墨,?寫(xiě)代表本地符號(hào)(local symbol
)
案例2:
使用
objdump --macho --syms ${MACH_PATH}
命令查看符號(hào)表
SYMBOL TABLE:
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
0000000000000000 l d *UND* /Users/zang/Zang/Spark/study/iOS高級(jí)強(qiáng)化/20210118-iOS強(qiáng)化第二節(jié)課:符號(hào)與鏈接(下)/上課代碼/MachOAndSymbol/
0000000000000000 l d *UND* main.m
0000000060335b53 l d *UND* /Users/zang/Library/Developer/Xcode/DerivedData/MachOAndSymbol-bdzlylfoorwnhggerxdmwocpeyad/Build/Intermediates.noindex/MachOAndSymbol.build/Debug/MachOAndSymbol.build/Objects-normal/x86_64/main.o
0000000100003f50 l d *UND*
0000000100003f50 l d *UND* _main
000000000000003f l d *UND*
000000000000003f l d *UND*
0000000000000000 l d *UND* _global_init_value
0000000100008014 l d *UND* _static_init_value
0000000100008018 l d *UND* _static_uninit_value
0000000000000000 l d *UND* _global_uninit_value
0000000000000000 l d *UND* _default_x
0000000000000000 l d *UND*
0000000100000000 g F __TEXT,__text __mh_execute_header
0000000100008010 g O __DATA,__data _global_init_value
0000000100008020 g O __DATA,__common _global_uninit_value
0000000100003f50 g F __TEXT,__text _main
0000000000000000 *UND* _NSLog
0000000000000000 *UND* ___CFConstantStringClassReference
0000000000000000 *UND* dyld_stub_binder
- 第二列:
l
代表本地符號(hào)(local
)室埋,g
代表全局符號(hào)(global
)- 第三列:按照功能劃分的標(biāo)識(shí),參見(jiàn)以下列表
按照功能劃分:
Type 說(shuō)明 f File F Function O Data d Debug ABS Absolute COM Common UND 未定義
剝離調(diào)試符號(hào)
調(diào)試符號(hào):當(dāng)文件編譯成.o
文件時(shí),它會(huì)生成DWARF
格式的調(diào)試信息姚淆,放在__DWARF段
孕蝉。鏈接時(shí),將__DWARF段
變成符號(hào)放到符號(hào)表中腌逢。
上面的打印信息中降淮,包含大量調(diào)試符號(hào)。為了避免干擾搏讶,下面介紹如何剝離調(diào)試符號(hào)佳鳖。
方式一:
使用
Build Settings
里Strip
配置項(xiàng)
Deployment Postprocessing
在Debug
和Release
下均默認(rèn)為NO
,它相當(dāng)于是Deployment
的總開(kāi)關(guān)媒惕。將其改為YES
Strip Style
在Debug
和Release
下均默認(rèn)All Symbols
系吩,剝離除間接符號(hào)表以外的全部符號(hào)。將其改為Debugging Symbols
妒蔚,剝離調(diào)試符號(hào)- 當(dāng)
Deployment Postprocessing
為NO
穿挨,Strip Style
的配置是無(wú)法生效的編譯項(xiàng)目,
Strip
操作已經(jīng)執(zhí)行肴盏。但它是在腳本執(zhí)行后才觸發(fā)科盛,故此該方式在使用Sellp
腳本自動(dòng)化的場(chǎng)景下不可行
方式二:
在
xcconfig
中使用鏈接器配置鏈接器的作用就是將多個(gè)目標(biāo)文件合并到一起,此時(shí)可以對(duì)符號(hào)進(jìn)行指定處理
使用
man ld
命令查看鏈接器參數(shù):
使用
/-S
向下查找-S
關(guān)鍵字
-S
:不要將調(diào)試信息放置到輸出文件中叁鉴,本質(zhì)上和Strip
的Debugging Symbols
配置項(xiàng)是一樣的效果打開(kāi)
xcconfig
文件土涝,添加OTHER_LDFLAGS
配置項(xiàng)OTHER_LDFLAGS = -Xlinker -S
Xcode
使用Target
編譯時(shí),執(zhí)行的是clang
命令幌墓。但此時(shí)-S
需要傳遞給ld
鏈接器但壮,所以前面要加上-Xlinker
關(guān)鍵字
Build Settings
中的Other Linker Flags
配置已生效
來(lái)到終端,先
command+k
清空內(nèi)容常侣,再編譯項(xiàng)目蜡饵。此時(shí)打印出除調(diào)試符號(hào)以外的所有符號(hào)SYMBOL TABLE: 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 0000000100003f50 g F __TEXT,__text _main 0000000000000000 *UND* _NSLog 0000000000000000 *UND* ___CFConstantStringClassReference 0000000000000000 *UND* dyld_stub_binder
global_uninit_value
和global_init_value
為全局符號(hào)static_init_value
和static_uninit_value
為本地符號(hào)default_x
并沒(méi)有使用static
關(guān)鍵字修飾,本應(yīng)是全局符號(hào)胳施,但使用了__attribute__((visibility("hidden")))
溯祸,變?yōu)楸镜胤?hào)
__attribute__()
的作用:將編譯器支持的參數(shù)傳遞給編譯器。例如:對(duì)default_x
全局變量進(jìn)行visibility("hidden")
設(shè)置舞肆,就是隱藏其可見(jiàn)性
符號(hào)可見(jiàn)性
全局符號(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)
通過(guò)上述兩個(gè)案例卫袒,理解符號(hào)的可見(jiàn)性:
- 全局符號(hào)對(duì)整個(gè)項(xiàng)目可見(jiàn)
- 本地符號(hào)僅對(duì)當(dāng)前文件可見(jiàn)
案例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
案例4:
在
LGApp
的ViewController.m
中,實(shí)現(xiàn)global_object
函數(shù)
同樣在
LGApp
中仲吏,打開(kāi)AppDelegate.m
文件不铆,也實(shí)現(xiàn)global_object
函數(shù)
同一個(gè)
Project
中,實(shí)現(xiàn)兩個(gè)global_object
函數(shù)裹唆。此時(shí)編譯報(bào)錯(cuò)誓斥,提示出現(xiàn)重復(fù)符號(hào)
- 這說(shuō)明在同一作用域中,不能出現(xiàn)相同的全局符號(hào)
導(dǎo)入符號(hào)和導(dǎo)出符號(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
文件,寫(xiě)入以下代碼:
#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
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
文件戚炫,寫(xiě)入以下代碼:#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
文件媳纬,寫(xiě)入以下代碼: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
文件,寫(xiě)入以下代碼: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
文件,寫(xiě)入以下代碼: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
文件峦失,寫(xiě)入以下代碼:#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)
圖解
查看項(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
文件,寫(xiě)入以下代碼: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
不在工程目錄中。例如在工程的父目錄搓谆,可以寫(xiě)成:$(SRCROOT)/../Spark/libSDK
炒辉。其中/../
就是指向父目錄