目錄
一、Mach-O與鏈接器
Mach-O
Mach-O(Mach Object)
是macOS、iOS恶迈、 iPadOS
存儲程序和庫的文件格式。對應(yīng)系統(tǒng)通過應(yīng)用二進(jìn)制接口(application binary interface
,縮寫為ABI
)來運(yùn)行該格式的文件谱醇。Mach-O
格式用來替代BSD
系統(tǒng)的a.out
格式暇仲。Mach-O
文件格式保存了在編譯過程和鏈接過程中產(chǎn)生的機(jī)器代碼和數(shù)據(jù),從而為靜態(tài)鏈接和動態(tài)鏈接的代碼提供了單一文件格式
副渴。
Mach-O
文件就是一個可讀可寫的二進(jìn)制文件
可執(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)核實(shí)際上在執(zhí)行以下操作:
- 將文件加載到內(nèi)存
- 開始分析
Mach-O
中的mach header
,以確認(rèn)它是有效的Mach-O
文件
通過終端命令查看Mach-O
文件:objdump --macho --private-headers 可執(zhí)行文件地址
通過自己的項目查看Mach-O
文件
machoinfo項目是用來查看Mach-O
文件的,編譯后生成machoinfo
的可執(zhí)行文件可作為命令使用:
拷貝machoinfo
可執(zhí)行文件到桌面
使用machoinfo項目調(diào)試Mach-O
文件讀取過程
通過以下操作將TestCode
項目編譯的可執(zhí)行文件路徑拖入到machoinfo
項目啟動參數(shù)中勉盅,這樣TestCode
可執(zhí)行文件路徑就會傳入到machoinfo
項目main
函數(shù)的argv
參數(shù)中
main
函數(shù)中添加斷點(diǎn)后運(yùn)行machoinfo
項目就能看到參數(shù)傳入進(jìn)來了佑颇,然后通過這個路徑就能讀取到TestCode
可執(zhí)行文件并進(jìn)行Mach-O
文件讀取過程
二、符號的種類與作用
Symbol Table
- Symbol Table:就是用來保存符號草娜。
- String Table:就是用來保存符號的名稱错蝴。
- Indirect Symbol Table:間接符號表盐须。保存使用的外部符號敛惊,也就是使用的外部動態(tài)庫的符號(比如
NSLog
)精堕。是Symbol Table
的子集。
通過編譯項目生成可執(zhí)行文件-->然后在終端使用命令查看Mach-O
的中的符號操作比較繁瑣议蟆,現(xiàn)在通過配置Shell
腳本闷沥,當(dāng)項目編譯成功后直接在終端執(zhí)行命令并展示命令的結(jié)果。
首先通過重定向在Xcode中讓當(dāng)前終端顯示特定內(nèi)容:
Xcode讓終端顯示xcconfig
文件中的變量:
現(xiàn)在通過xcode_run_cmd.sh
腳本執(zhí)行相關(guān)的命令:
#!/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
echo `$@ &>$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í)際上就是打印所有參數(shù)已標(biāo)準(zhǔn)錯誤格式
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
# CMD:終端需要運(yùn)行的命令
# CMD_FLAG:運(yùn)行的命令的參數(shù)
# TTY:終端標(biāo)志
if [[ -n "$CMD" ]]; then
RunCommand "$CMD" ${CMD_FLAG}
else
EchoError "=========================================="
EchoError "ERROR:Failed to run CMD. THE CMD must not null"
fi
}
RunCMDToTTY
該xcode_run_cmd.sh
腳本需需要三個參數(shù)CMD 、CMD_FLAG 杯聚、TTY
臼婆,這三個參數(shù)在xcconfig
文件中定義就能獲取到
// -p:不排序
// -a: 顯示除了調(diào)試符號的其他所有符號
MACHO_PATH = ${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/$(FULL_PRODUCT_NAME)/$(PRODUCT_NAME)
CMD = nm
CMD_FLAG = -pa ${MACHO_PATH}
TTY = /dev/ttys000
添加shell
腳本執(zhí)行命令/bin/sh "$SRCROOT/xcode_run_cmd.sh"
并編譯即可在終端看到nm
命令執(zhí)行的結(jié)果
在
Xcode
編譯日志中可以看到項目編譯后-簽名前執(zhí)行的shell
腳本,因此執(zhí)行shell
腳本時Mach-O
文件已經(jīng)生成了
三幌绍、strip命令
strip
命令可以用來剝離Mach-O
文件中的符號,比如調(diào)試符號等故响。strip
命令修改的是Symbol Table
符號表傀广、不能修改Indirect Symbol Table
間接符號表。
Xcode
默認(rèn)會在Release
編譯情況下剝離所有符號彩届,但是Debug
編譯情況下不會剝離符號伪冰。
設(shè)置Debug
編譯情況下剝離調(diào)試符號:
- 從編譯日志中也可以看到,
Xcode
的strip
命令是在shell
腳本之后執(zhí)行的- 在實(shí)際開發(fā)項目中測試
Xcode
的strip
樟蠕,沒有剝離符號的Mach-O
大小為34M贮聂,剝離符號后大小為20.8M,可見剝離符號對于瘦包還是非常有用的
現(xiàn)在不使用Xcode
的strip
命令寨辩,改在shell
腳本中執(zhí)行strip
命令
因?yàn)?code>Xcode的strip
使用的是clang
的命令吓懈,shell
腳本使用的是ld
鏈接器的命令,所以可以在終端查看ld
鏈接器的參數(shù)靡狞,終端輸入命令man ld
回車后輸入/-S
進(jìn)行搜索:
xcconfig
文件添加ld
的參數(shù)
OTHER_LDFLAGS = -Xlinker -S
在Xcode
的Build Sttings
中可以看到添加成功了:
編譯后可發(fā)現(xiàn)終端輸出中少了很多調(diào)試符號耻警。
strip 參數(shù)如下:
- -x: Non-Global
- 無參數(shù): All Symbol
- -S: 調(diào)試符號
四、在LLVM項目中調(diào)試nm命令
LLVM
項目下載安裝參考:iOS-底層探索29:自定義Clang插件
填入啟動參數(shù)和machoinfo項目查看Mach-O
文件添加啟動參數(shù)的方法相同:
運(yùn)行LLVM
項目(也就是運(yùn)行LLVM
項目中llvm-nm Scheme
的 llvm-nm Target
)甸怕,控制臺打印如下甘穿,可以看到和shell
中使用nm
命令輸出到終端的信息相同:
此外在llvm-nm.cpp
源碼的main
函數(shù)中添加斷點(diǎn)后運(yùn)行項目即可斷點(diǎn)調(diào)試llvm-nm
命令的源碼,從llvm-nm
的源碼中我們就能看到nm
命令是如何讀取Mach-O
文件的梢杭。
五温兼、總結(jié)
通過對符號的
strip
不僅可以減少ipa
包體積還可以減少動態(tài)庫、靜態(tài)庫的體積對
ipa
包瘦身主要有以下操作:
- 編譯時期:
-O0武契、-Os
生成目標(biāo)文件- 鏈接時期:
dead code strip
死代碼剝離(也是剝離符號)- 生成
Mach-O
后:strip
剝離符號募判,對Mach-O
文件進(jìn)行修改
快捷鍵
Command + K
清空終端顯示內(nèi)容