Mach-O
Mach-O
(Mach Objec
t)是macOS
梨水、iOS
旋奢、iPadOS
存儲程序和庫的文件格式迄薄。對應(yīng)系統(tǒng)通過應(yīng)用二進(jìn)制接口(application binary interfac
e谍肤,縮寫為 ABI
)來運行該格式的文件啦租。
Mach-O
格式用來替代BSD
系統(tǒng)的a.out
格式。Mach-O
文件格式保存了在編譯過程和鏈接過程中產(chǎn)生的機器代碼和數(shù)據(jù)荒揣,從而為靜態(tài)鏈接和動態(tài)鏈接的代碼提供了單一文件格式篷角。
可執(zhí)行文件的調(diào)用過程:
- 調(diào)用
fork
函數(shù),創(chuàng)建一個process
(進(jìn)程)系任; - 調(diào)用
execve
或其衍生函數(shù)恳蹲,在該進(jìn)程上加載落追,執(zhí)行Mach-O
文件预皇。
當(dāng)調(diào)用execve
(程序加載器) 時,內(nèi)核實際上在執(zhí)行以下操作:
i.將文件加載到內(nèi)存金抡;
ii.開始分析Mach-O
中的mach_header
举农,以確認(rèn)它是有效的Mach-O
文件荆针。
Mach-o File Format
一個Mach-o
文件有兩部分組成:header
和data
。
一個簡單的
main
函數(shù)例子:
int main(int argc, const char * argv[]) {
return 0;
}
編譯好后查看下__TEXT
段:
objdump --macho -d ${MACHO_PATH}
main
函數(shù)對應(yīng)的機器碼如下颁糟,后面的匯編是給我們閱讀的航背,機器并不需要,可以理解為注釋棱貌。
'int main() { }' compiled for x86_64-apple-macosx with clang
0x55, // offset 0 -- pushq %rbp
0x48, 0x89, 0xe5, // offset 1 -- movq %rsp, %rbp
0x31, 0xc0, // offset 4 -- xorl %eax, %eax
0x5d, // offset 6 -- popq %rbp
0xc3 // offset 7 -- retq
這里的機器碼是固定的玖媚,也就是所謂的ABI
穩(wěn)定。
Big endian or little endian
iOS
和macOS
開發(fā)都是小端模式婚脱。
當(dāng)讀取一個地址的時候
- 小端:從右往左讀
- 大端:從左往右讀
header
代表了文件的映射今魔,描述了文件的內(nèi)容以及文件所有內(nèi)容所在的位置勺像。header
內(nèi)的section
描述了對應(yīng)的二進(jìn)制信息。
header
包含三種類型:
Mach header
segment
sections
Mach header
??:Mach header
屬于header
的一部分错森,它包含了整個文件的信息和segment
信息吟宦。
直接新建一個工程看下
Mach header
:
objdump --macho -private-header ${MACHO_PATH}
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
MH_MAGIC_64 X86_64 ALL 0x00 EXECUTE 19 1944 NOUNDEFS DYLDLINK TWOLEVEL WEAK_DEFINES BINDS_TO_WEAK PIE
也可以通過otool
查看:
otool -h ${MACHO_PATH}
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
0xfeedfacf 16777223 3 0x00 2 20 1960 0x00218085
查看mach header
objdump --macho -private-header ${MACHO_PATH}
otool -h ${MACHO_PATH}
相對來說objdump
更容易閱讀,otool
更接近原始數(shù)據(jù)涩维。
data
緊跟header
之后殃姓,由多個二進(jìn)制組成,one by one瓦阐。
Load Commands
二進(jìn)制文件加載進(jìn)內(nèi)存要執(zhí)行的一些指令蜗侈。
這里的指令主要在負(fù)責(zé)我們 APP
對應(yīng)進(jìn)程的創(chuàng)建和基本設(shè)置(分配虛擬內(nèi)存,創(chuàng)建主線程睡蟋,處理代碼簽名/加密的工作)踏幻,然后對動態(tài)鏈接庫(.dylib
系統(tǒng)庫和我們自己創(chuàng)建的動態(tài)庫)進(jìn)行庫加載和符號解析的工作。
直接在.app
目錄下dump
(objdump --macho --private-headers
)一下可執(zhí)行文件:
? TestMutableConfig.app objdump --macho --private-headers TestMutableConfig
可以看到load command
信息如下:
都知道程序主入口是
main
函數(shù)薄湿,看下對應(yīng)的main
信息:
objdump --macho --private-headers TestMutableConfig | grep 'LC_MAIN' -A 3
cmd LC_MAIN
cmdsize 24
entryoff 7856
stacksize 0
dyld
通過LC_MAIN
去找程序的主入口叫倍。當(dāng)然可以自己指定不為main
。
可以理解為Mach-O
就是:文件配置+二進(jìn)制代碼
Mach-O
是可讀可寫的
上面通過objdump
輸出的信息有點多豺瘤,如果只想看自己關(guān)注的信息吆倦,可以精簡下寫一個自定義讀取程序machoInfo
:
原理是根據(jù)Mach-O
格式讀取數(shù)據(jù)
1:直接終端命令查看:
2:直接
main
函數(shù)傳參進(jìn)去:machoInfo
地址:machoInfo
目標(biāo)文件生成過程:
- 鏈接器(
llvm-ld
)并沒有被執(zhí)行。 - 目標(biāo)文件不會包含
Unix
程序在被裝載和執(zhí)行時所必須包含的信息坐求。
修改main.m
文件:
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, const char * argv[]) {
static_uninit_value = 10;
NSLog(@"%d", static_init_value);
return 0;
}
對應(yīng)的代碼段:
(__TEXT,__text) section
_main:
100003f50: 55 pushq %rbp
100003f51: 48 89 e5 movq %rsp, %rbp
100003f54: 48 83 ec 10 subq $16, %rsp
100003f58: 48 8d 05 a9 00 00 00 leaq 169(%rip), %rax ## Objc cfstring ref: @"%d"
100003f5f: c7 45 fc 00 00 00 00 movl $0, -4(%rbp)
100003f66: 89 7d f8 movl %edi, -8(%rbp)
100003f69: 48 89 75 f0 movq %rsi, -16(%rbp)
100003f6d: c7 05 a1 40 00 00 0a 00 00 00 movl $10, _static_init_value(%rip)
100003f77: 8b 35 97 40 00 00 movl _static_init_value(%rip), %esi
100003f7d: 48 89 c7 movq %rax, %rdi
100003f80: b0 00 movb $0, %al
100003f82: e8 09 00 00 00 callq 0x100003f90 ## symbol stub for: _NSLog
100003f87: 31 c0 xorl %eax, %eax
100003f89: 48 83 c4 10 addq $16, %rsp
100003f8d: 5d popq %rbp
100003f8e: c3 retq
在生成.o
的過程中:
- 能變成匯編的代碼盡量變成匯編蚕泽。
- 符號歸類 -> 重定位符號表(放的是
.m/.o
文件用到的API
)。也就是保存了當(dāng)前文件用到的符號桥嗤。 -
.o
-> 鏈接 -> 合并到一張表 -> exec(可執(zhí)行文件)
查看重定位符號表
objdump --macho -reloc test.o
(目標(biāo)文件)
鏈接
鏈接的本質(zhì)就是把多個目標(biāo)文件組合成一個文件
將多個.o
文件合并成一個可執(zhí)行文件须妻,在鏈接的過程中可以進(jìn)行操作。
可以通過man ld
查看都有哪些功能泛领,比如:why-live
荒吏、why-load
等。
Symbol
-
Symbol Table
: 用來保存符號渊鞋。 -
String Table
: 用來保存符號的名稱绰更。 -
Indirect Symbol Table
: 間接符號表。保存使用的外部符號锡宋,更準(zhǔn)確一點就是使用外部動態(tài)庫的符號儡湾。是Symbol Table
的子集。
符號的種類
為了方便操作执俩,直接在Xcode
Run script
中添加命令編譯后直接將結(jié)果輸出到終端:
Run Script
:
#輸出 "HotpotCat" 重定位到 終端 /dev/ttys003
echo "HotpotCat" > /dev/ttys003
其中/dev/ttys003
為終端徐钠,可以在終端中輸入tty
查看獲取。
Run Script
也可以讀取XCConfig
中的內(nèi)容役首〕⒇ぃ可以配合起來完成显拜。所以我們可以分為3步:配置讀取符號腳本,XCConfig
傳遞參數(shù)摊崭,Run sctipt
執(zhí)行腳本
1.xcode_run_cmd.sh
定義一個xcode_run_cmd
腳本讼油,將符號輸出到終端杰赛,這個腳本需要2個參數(shù)呢簸,參數(shù)從XCConfig
配置。
#!/bin/sh
RunCommand() {
#判斷全局字符串VERBOSE_SCRIPT_LOGGING是否為空乏屯。-n string判斷字符串是否非空
#[[是 bash 程序語言的關(guān)鍵字根时。用于判斷
if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
#作為一個字符串輸出所有參數(shù)。使用時加引號"$*" 會將所有的參數(shù)作為一個整體辰晕,以"$1 $2 … $n"的形式輸出所有參數(shù)
if [[ -n "$TTY" ]]; then
echo "? $@" 1>$TTY
else
echo "? $*"
fi
echo "------------------------------------------------------------------------------" 1>$TTY
fi
#與$*相同蛤迎。但是使用時加引號,并在引號中返回每個參數(shù)含友。"$@" 會將各個參數(shù)分開替裆,以"$1" "$2" … "$n" 的形式輸出所有參數(shù)
if [[ -n "$TTY" ]]; then
eval "$@" &>$TTY
else
"$@"
fi
#顯示最后命令的退出狀態(tài)。0表示沒有錯誤窘问,其他任何值表明有錯誤辆童。
return $?
}
EchoError() {
#在shell腳本中,默認(rèn)情況下惠赫,總是有三個文件處于打開狀態(tài)把鉴,標(biāo)準(zhǔn)輸入(鍵盤輸入)、標(biāo)準(zhǔn)輸出(輸出到屏幕)儿咱、標(biāo)準(zhǔn)錯誤(也是輸出到屏幕)庭砍,它們分別對應(yīng)的文件描述符是0,1混埠,2
# > 默認(rèn)為標(biāo)準(zhǔn)輸出重定向怠缸,與 1> 相同
# 2>&1 意思是把 標(biāo)準(zhǔn)錯誤輸出 重定向到 標(biāo)準(zhǔn)輸出.
# &>file 意思是把標(biāo)準(zhǔn)輸出 和 標(biāo)準(zhǔn)錯誤輸出 都重定向到文件file中
# 1>&2 將標(biāo)準(zhǔn)輸出重定向到標(biāo)準(zhǔn)錯誤輸出。實際上就是打印所有參數(shù)已標(biāo)準(zhǔn)錯誤格式
if [[ -n "$TTY" ]]; then
echo "$@" 1>&2>$TTY
else
echo "$@" 1>&2
fi
}
RunCMDToTTY() {
# CMD = 運行到命令
# TTY = 終端
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
2.XCConfig
配置:
由于腳本中有3個參數(shù)钳宪,可以直接在XCConfig
中配置揭北,腳本中就可以讀取到了。因為腳本是在Run strip
執(zhí)行的使套,并且和工程在同一目錄罐呼。
// BUILD_DIR build路徑 *為可執(zhí)行文件名稱,這里為了適配寫為*通配符
MACHO_PATH=${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/*
//# CMD = 運行的命令
//# CMD_FLAG = 運行的命令參數(shù)
//# TTY = 終端
CMD = nm
// -p: 不排序
// -a: 顯示所有符號侦高,包含調(diào)試符號
CMD_FLAG = -pa ${MACHO_PATH}
TTY = /dev/ttys000
BUILD_DIR
:
由于不需要調(diào)試符號嫉柴,可以脫去(
strip
)調(diào)試符號我們知道
build setting
中有個strip
設(shè)置:Deployment Postprocessing
和Strip Style
:可以看到
Strip
是在Run Script
之后執(zhí)行的。所以在build setting
中設(shè)置行不通奉呛。那么直接
man ld
查看下有沒有可用的命令:可以看到有個
S
比較符合计螺。XCConfig
修改完后配置如下:
// BUILD_DIR build路徑 *為可執(zhí)行文件名稱夯尽,這里為了適配寫為*通配符
MACHO_PATH=${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/*
//# CMD = 運行的命令
//# CMD_FLAG = 運行的命令參數(shù)
//# TTY = 終端
CMD = nm
// -p: 不排序
// -a: 顯示所有符號,包含調(diào)試符號
CMD_FLAG = -pa ${MACHO_PATH}
TTY = /dev/ttys000
//-Xlinker 雖然執(zhí)行的ld登馒,但是命令傳給連接器
OTHER_LDFLAGS=-Xlinker -S
3.Run script
執(zhí)行腳本
# SRCROOT 當(dāng)前代碼路徑
/bin/sh "$SRCROOT/xcode_run_cmd.sh"
至此匙握,環(huán)境已經(jīng)配置好了。
接下來配置一些符號看下:
//全局變量
int global_uninit_value;
// 外部符號
int global_init_value = 10;
double default_x __attribute__((visibility("hidden"))) ;
//靜態(tài)變量
static int static_init_value = 9;
static int static_uninit_value;
int main(int argc, const char * argv[]) {
static_uninit_value = 10;
NSLog(@"%d", static_init_value);
return 0;
}
objdump --macho --syms ${MACHO_PATH}
查看下:
可以看到有好多
Debug
符號陈轿,在生成文件的時候會生成DWARF
調(diào)試文件圈纺,放在.o
專門的DWARF
段,當(dāng)連接的時候會放入符號表中麦射。也就是說調(diào)試符號在目標(biāo)文件中是放在DWARF
段中的蛾娶,當(dāng)鏈接后放在符號表中。去掉這些干擾信息潜秋,有兩種方式:
-
xcconfig
中配置OTHER_LDFLAGS=$(inherited) -Xlinker -S
-
xcode
中配置蛔琅。
image.png
- 從上面的分析可以看出,不論初始化的還是未初始化的全局變量都是全局符號峻呛。
- 靜態(tài)變量都變成了本地符號罗售。
全局和本地本質(zhì)上就是可見性。
全局變量和靜態(tài)變量區(qū)別
當(dāng)將一個全局變量定義為靜態(tài)變量后就變成了本地變量钩述,從全局可見變成了文件可見寨躁。 (靜態(tài)變量 -> 本地變量
)。
按功能劃分:
Type | 說明 | 備注 |
---|---|---|
f | File | 文件 |
F | Function | 方法 |
O | Data | 數(shù)據(jù) |
d | Debug | 調(diào)試符號 |
ABS | Absolute | |
COM | Common | |
UND | ? | 未定義符號 |
符號可見性
符號可見性在clang
下有兩種hidden
和default
(默認(rèn))切距。protected
在clang
下是沒有的朽缎。
-
default
:用它定義的符號將被導(dǎo)出。 -
hidden
:用它定義的符號將不被導(dǎo)出谜悟。
__attribute__
可以把編譯器支持的參數(shù)傳遞給編譯器话肖。我們經(jīng)常遇到就是__attribute__((deprecated))
。
visibility
控制文件導(dǎo)出符號葡幸,限制符號可見性最筒。
int hidden_y __attribute__((visibility("hidden"))) = 99;
double default_y __attribute__((visibility("default"))) = 100;
在上面例子中default_x
通過hidden
變成了l
本地符號,放在__DATA__common
未初始化區(qū)蔚叨。
所以可以通過:
static
__attribute__((visibility("hidden")))
控制符號可見性床蜘。
two_levelnamespace & flat_namespace
二級命名空間與一級命名空間。鏈接器默認(rèn)采用二級命名空間蔑水,也就是除了會記錄符號 名稱邢锯,還會記錄符號屬于哪個Mach-O
的,比如會記錄下來_NSLog
來自Foundation
搀别。
所以如果我們在自己主工程中有一個函數(shù)丹擎,另外一個framework
中也有同名函數(shù):
void global_object() {
NSLog(@"global_object");
}
在調(diào)用過程中由于存在二級命名空間并不會產(chǎn)生沖突。如果修改為一級命名空間就會沖突。
導(dǎo)入導(dǎo)出符號
export symbol
:導(dǎo)出符號意味著蒂培,告訴別的模塊再愈,我有一個這樣的符號,你可以將 其導(dǎo)入(Import
)护戳。
導(dǎo)出導(dǎo)入是相對的翎冲。比如NSLog
對于Foundation
來說是導(dǎo)出符號,對于調(diào)用方而言是導(dǎo)入符號媳荒。導(dǎo)出符號一定是全局符號抗悍。
查看導(dǎo)出符號:
objdump --macho --exports-trie ${MACHO_PATH}
Exports trie:
0x100000000 __mh_execute_header
0x100003F50 _main
0x100008010 _global_init_value
0x100008020 _global_uninit_value
可以看到就是上面例子中的全局符號。全局變量默認(rèn)會被導(dǎo)出肺樟。
對于動態(tài)庫而言是在運行的過程中被加載檐春,在編譯連接階段只要提供符號就可以了。這里就涉及到間接符號表
(保存著可執(zhí)行文件使用的其它動態(tài)庫的符號)了么伯。
查看間接符號表:
objdump --macho --indirect-symbols ${MACHO_PATH}
- 全局符號可以變成導(dǎo)出符號。
由于符號影響macho
的大小卡儒,而間接符號不能刪除田柔。也就意味著使用到的動態(tài)庫的全局符號不能刪除。所以在strip
動態(tài)庫的時候只能脫不是全局符號的符號骨望。
間接符號->動態(tài)庫的導(dǎo)出符號->全局符號->strip
只能拖不是全局符號的符號
定義一個OC
類:
@interface OCObject ()
- (void)testOCObject;
@end
@implementation OCObject
- (void)testOCObject {
NSLog(@"testOCObject");
}
@end
查看一下導(dǎo)出符號:
Exports trie:
0x100000000 __mh_execute_header
0x100003F20 _main
0x1000080B8 _OBJC_METACLASS_$_OCObject
0x1000080E0 _OBJC_CLASS_$_OCObject
0x100008110 _global_init_value
0x100008120 _global_uninit_value
可以看到OC
的類硬爆,默認(rèn)就是導(dǎo)出符號。所以對于OC
的動態(tài)庫要控制體積擎鸠,對于不想暴露的符號可以借助鏈接器設(shè)置-unexported_symbol
后面跟不想導(dǎo)出的符號:
OTHER_LDFLAGS=$(inherited) -Xlinker -unexported_symbol -Xlinker _OBJC_CLASS_$_OCObject
可以看到已經(jīng)沒有了
_OBJC_CLASS_$_OCObject
導(dǎo)出符號了缀磕。這個時候就可以strip
掉了。對于外界也不可見了劣光。
weak symbol
Weak Reference Symbol
: 表示此未定義符號是弱引用袜蚕。如果動態(tài)鏈接器找不到該符號的定義,則將其設(shè)置為0绢涡。鏈接器會將此符號設(shè)置弱鏈接標(biāo)志牲剃。
Weak defintion Symbol
: 表示此符號為弱定義符號。如果靜態(tài)鏈接器或動態(tài)鏈接器為此符號找到另一個(非弱)定義雄可,則弱定義將被忽略凿傅。只能將合并部分中的符號標(biāo)記為弱定義。
定義WeakSymbol.h
:
// 弱定義
void weak_function(void) __attribute__((weak));
//弱引用
void weak_import_function(void) __attribute__((weak_import));
實現(xiàn)WeakSymbol.m
:
void weak_function(void) {
NSLog(@"weak_function");
}
void weak_import_function(void) {
NSLog(@"weak_import_function");
}
如果在
main
文件中再實現(xiàn)一個weak_function
void weak_function(void) {
NSLog(@"main weak_function");
}
這個時候正常情況下應(yīng)該報符號沖突数苫,弱定義后并不會沖突聪舒。
- 弱定義符號并不影響符號的全局和導(dǎo)出。
- 聲明成弱定義的符號找到一個符號后虐急,其它的會被忽略箱残。
若把一個弱定義的全局符號隱藏掉,則會變成弱定義的本地符號戏仓。
void weak_hidden_function(void) __attribute__((weak, visibility("hidden")));
弱引用
如果沒有實現(xiàn)則不會報錯疚宇,調(diào)用的地方做判斷即可亡鼠。
if (weak_import_function) {
weak_import_function();
}
在編譯的時候需要告訴編譯器不要管-U
,這個符號是動態(tài)實現(xiàn)的敷待。
-U symbol_name
Specified that it is ok for symbol_name to have no definition. With -two_levelnamespace,the resulting symbol will be marked dynamic_lookup which means dyld will search all loaded images.
OTHER_LDFLAGS=$(inherited) -Xlinker -U -Xlinker _weak_import_function
這個時候即使weak_import_function
不實現(xiàn)间涵,編譯和運行也都不會報錯。
在這里如果我們將動態(tài)庫全部聲明為弱引用榜揖,則就不會報找不到符號的錯誤了勾哩。
重新導(dǎo)出符號
比如NSLog
對于我們來說是導(dǎo)入符號,對于可執(zhí)行文件是一個未定義符號(存在間接符號表中)举哟。
這里的
NSLog
只能在當(dāng)前文件中使用思劳,如果想給其它文件使用就需要重新導(dǎo)出。重新導(dǎo)出后就放在導(dǎo)出符號表中妨猩,外部就可以使用了潜叛。可以通過給一個符號起別名(只能給間接符號表中的符號起別名壶硅,起別名后會自動把間接符號表中的符號變成導(dǎo)出符號)的方式重新導(dǎo)出符號威兜。
OTHER_LDFLAGS=$(inherited) -Xlinker -alias -Xlinker _NSLog -Xlinker HOTPOT_NSLog
通過易于閱讀的
nm
方式輸出下:
nm -m ${MACHO_PATH} | grep 'HOTPOT_NSLog'
可以看到是間接的外部的
_NSLog
別名的符號。接著看下導(dǎo)出符號表的情況
objdump --macho --exports-trie ${MACHO_PATH}
可以看到
HOTPOT_NSLog
是一個re-export
的導(dǎo)出符號庐椒。所以我們已通過將自己庫依賴的動態(tài)庫重新導(dǎo)出讓動態(tài)庫可見椒舵。一般用在自己的庫依賴其它動態(tài)庫,讓其它動態(tài)庫可見(可以是庫也可以是符號)约谈。
-reexported_symbols_list file
The specified filename contains a list of symbol names that are implemented in a dependent dylib and should be re-exported through the dylib being created.
-unexported_symbol symbol
The specified symbol is added to the list of global symbols names that will not remain as global symbols in the output file. This option can be used multiple times. For short lists, this can be more convenient than creating a file and using -unexported_symbols_list.
reexported_symbols_list
后面跟文件笔宿,可以將需要重新導(dǎo)出的符號寫入到文件中。也可以通過unexported_symbol
隱藏起來棱诱。
swift symbol
internal enum SwiftEnumSymbol {
case maxHeap
case minHeap
internal func testSwiftEnumSymbol<T: Comparable>(type: T.Type) -> (T, T) -> Bool {
switch self {
case .maxHeap:
return (>)
case .minHeap:
return (<)
}
}
}
struct SwiftStructSymbol {
func testSwiftStructSymbol(o: Int) {
}
}
public protocol SwiftProtocolSymbol: class {
func testSwiftProtocolSymbol()
}
public class SwiftClassSymbol {
func testSwiftClassSymbol() {
}
}
查看全部符號
objdump --macho --syms ${MACHO_PATH}
可以看到添加swift
文件后多了很多符號:
只查看
SwiftClassSymbol
的符號:
objdump --macho --syms ${MACHO_PATH} | grep 'SwiftClassSymbol'
私有化SwiftClassSymbol
private class SwiftClassSymbol {
func testSwiftClassSymbol() {
}
}
可以看到都變成了local
泼橘。所以swift
是編譯型語言,編譯過程中就已經(jīng)確定了符號的類型军俊。
strip
- 動態(tài)庫不能脫全局符號侥加。
- App導(dǎo)出符號一般不提供給別人使用,對于間接符號(也就是導(dǎo)入符號)不能脫粪躬, 其它的都可以脫(本地+全局+弱定義都可以脫担败,只留下間接符號表中的符號)。
- 靜態(tài)庫是
.o
文件的合集 + 重定位符號表镰官。所以重定位符號不能脫提前,唯一能脫的就是調(diào)試符號。
strip style
- Debugging Symbols (.o 靜態(tài)庫 / 可執(zhí)行文件 動態(tài)庫)
- All Symbols(App)
- Non-Global Symbols(動態(tài)庫)
就只符號表來說
App使用靜態(tài)庫比使用動態(tài)庫文件大小要小
泳唠。由于靜態(tài)庫最終會合并到macho
狈网,對于App
來說會脫去所有符號,只留下間接符號。單純只說靜態(tài)庫和動態(tài)庫拓哺,那么靜態(tài)庫相對較小勇垛。引入App
后靜態(tài)庫小。(僅僅從符號的角度考慮)士鸥。
對于動態(tài)庫可以考慮從導(dǎo)出符號優(yōu)化體積闲孤,對于OC
默認(rèn)就是導(dǎo)出符號。
strip
命令:
-x: non_global
無參數(shù): 代表全部符號
-S
: 調(diào)試符號
dead code strip
在構(gòu)建完成之后如果是 C烤礁、C++ 等靜態(tài)的語言的代碼讼积、一些常量定義,如果發(fā)現(xiàn)沒有被使用到將會被標(biāo)記為 Dead code脚仔。開啟 DEAD_CODE_STRIP = YES 這些 Dead code 將不會被打包到安裝包中勤众。在 LinkMap 這些符號也會被標(biāo)記為 <<dead>>。
dead code strip
(死代碼剝離)是一個連接器參數(shù)鲤脏,可以掃描哪些代碼沒有用到并且刪掉的不是導(dǎo)出符號(本地符號)们颜。
-dead_strip
Remove functions and data that are unreachable by the entry point or exported symbols.
可以通過終端man ld
然后輸入/ + 關(guān)鍵字
搜索命令,n
往下查找,N
往上查找凑兰。
打開code dead strip
前:
打開
code dead strip
后可以查看下符號表:可以看到?jīng)]有使用的
weak_hidden_function
方法被脫去了掌桩。
strip原理
.o/靜態(tài)庫
.o/靜態(tài)庫
符號放在__DWARF
段,在脫符號的時候:
1.遍歷LoadCommands
找到Segname
為__DWARF
的LoadCommand
然后移除下面所有的Section
;
2.再移除符號表中的Symbol
;
3.將重新改寫后的macho
文件重新寫入;
動態(tài)庫/可執(zhí)行文件
調(diào)試符號
遍歷符號表判斷符號表中的符號
n_type
是否包含N_STAB(0xe0)
姑食,是調(diào)試符號就刪除。
All Symbols
遍歷間接符號表中所有的符號茅坛,只要不是間接符號表中的符號都可以刪除音半。
Non-Global Symbols
遍歷符號表,通過
n_type != N_EXT
判斷贡蓖。
dead code strip
和strip
主要是操作符號曹鸠。
包大小優(yōu)化方向
-
-O1
-Oz
生成目標(biāo)文件 -
dead code strip
死代碼剝離 鏈接的時候 -
strip
剝離符號 修改mach-o
LLVM調(diào)試
對于想要調(diào)試的命令直接添加scheme
,然后在Compile Sources
中查看文件:
2.找到對應(yīng)文件在main
函數(shù)中打斷點:
3.添加-pa
并將要調(diào)試的可執(zhí)行程序拖進(jìn)去
4.run without building
strip調(diào)試
1.在編譯好的LLVM
源碼工程中在TARGETS
中搜索strip
都時候斥铺,只有一個llvm-strip
:
可以看到是一個腳本:
這個腳本將
llvm-objcopy
可執(zhí)行文件鏈接成了llvm-strip
可執(zhí)行文件彻桃。類似快捷方式。2.添加
llvm-strip
腳本對應(yīng)的scheme
編譯晾蜘。3.編譯完成后在源碼對應(yīng)的
llvm-project/build/Debug/bin
目錄下可以看到llvm-strip
對應(yīng)的快捷方式邻眷。4.在
targets
中復(fù)制一個可執(zhí)行文件命名為strip
。由于
llvm-objcopy
的代碼中作樂判斷剔交,如果名稱為llvm-objcopy
句當(dāng)作llvm-objcopy
運行肆饶,如果是strip
就當(dāng)作strip
運行。搜
strip
Show In Finder
這個時候是找不到對應(yīng)的文件的岖常。5.優(yōu)化
復(fù)制一份
llvm-strip
:改名為
strip
:這個時候再重復(fù)4中的
Show In Finder
驯镊,就自動關(guān)聯(lián)到strip
了。6.添加
strip
scheme并且在llvm-objcopy.cpp
文件的main
函數(shù)打上斷點,然后run without building
就可以調(diào)試了-
**br read -f 文件路徑**
可以將一個文件的斷點信息導(dǎo)入板惑。導(dǎo)入后默認(rèn)沒有啟用橄镜。 -
br list strip
將所有斷點放入strip
組中。 -
br enable strip
啟用組里面的所有斷點冯乘。
Xcode
默認(rèn)斷點式根據(jù)絕對路徑來斷的洽胶,不通用。通過lldb
終端下的斷點式真正的符號斷點往湿,可以通用妖异。
我們當(dāng)然可以將自己的斷點寫入到文件中:
br write -f 文件
寫入文件。
7.拖入可執(zhí)行文件就可以開始調(diào)試了