其實被這個問題困擾了好久,不過秉承著三分鐘熱度的新年新氣象,還是要多弄懂一點(⊙_⊙)ゞ
Symbols是什么東西呢?雖然我對它沒有深入的了解,但是大概知道它的作用。摘抄《深入理解計算機系統》里的一些描述:
一個典型的ELF可重定位目標文件包含下面幾個節:
... ...
.symtab:一個符號表,它存放在程序中定義和引用的函數和全局變量信息。一些程序員錯誤地認為必須通過-g選項來編譯程序才能得到符號表信息。實際上,每個可重定位目標文件在.symtab中都有一張符號表。然而,和編譯器中的符號表不同,.symtab符號表不包含局部變量的條目。
... ...
.debug:一個調試符號表,其條目是程序中定義的局部變量和類型定義,程序中定義和引用的全局變量,以及原始的C源文件。只有以-g選項調用編譯驅動程序時才會得到這張表。
... ...
為了構造可執行文件,鏈接器必須完成兩個主要任務:
- 符號解析(symbol resolution)。目標文件定義和引用符號。符號解析的目的是將每個符號引用剛好和一個符號定義聯系起來。
- 重定位(relocation)。編譯器和匯編器生成從地址0開始的代碼和數據節。鏈接器通過把每個符號定義與一個存儲器位置聯系起來,然后修改所有對這些符號的引用,使得它們指向這個存儲器位置,從而重定位這些節。
Objective-C有一些自己的生成符號的規則,比如文檔中有提到:
The dynamic nature of Objective-C complicates things slightly. Because the code that implements a method is not determined until the method is actually called, Objective-C does not define linker symbols for methods. Linker symbols are only defined for classes.
Objective-C不會為方法定義鏈接符號,只會為類定義鏈接符號。
可以在終端中用nm
命令查看一個可重定位文件或可執行文件的符號表,其中加上-a
參數可以顯示包括調試符號在內的所有符號。
合理的選擇與symbols有關的設置選項,可以縮減app的大小,一定程度上能阻礙與源代碼有關的信息被攻擊者獲得。Xcode的build setting中,有不少與symbols有關,現在我來依次試驗這幾個設置選項,了解一下它們的具體作用。
剛開始的時候,我使用Xcode7.2.1新建了一個工程,以下試驗均在run和DEBUG模式下進行。
Generate Debug Symbols [GCC_GENERATE_DEBUGGING_SYMBOLS]
在Xcode7.2.1中,Generate Debug Symbols
這個設置在DEBUG和RELEASE下均默認為YES
。
官方文檔對這個設置的說明:
Enables or disables generation of debug symbols. When debug symbols are enabled, the level of detail can be controlled by the build 'Level of Debug Symbols' setting.
調試符號是在編譯時生成的。在Xcode中查看構建過程,可以發現,當Generate Debug Symbols
選項設置為YES
時,每個源文件在編譯成.o文件時,編譯參數多了-g
和-gmodules
兩項。但鏈接等其他的過程沒有變化。
Clang文檔對-g
的描述是:
Generate complete debug info.
當Generate Debug Symbols
設置為YES
時,編譯產生的.o文件會大一些,當然最終生成的可執行文件也大一些。
當Generate Debug Symbols
設置為NO
的時候,在Xcode中設置的斷點不會中斷。但是在程序中打印[NSThread callStackSymbols]
,依然可以看到類名和方法名,比如:
** 0 XSQSymbolsDemo 0x00000001000667f4 -[ViewController viewDidLoad] + 100**
在程序崩潰時,也可以得到帶有類名和方法名的函數調用棧
現在把Generate Debug Symbols
設置回YES
,開始試驗下一個設置。
Debug Information Level [CLANG_DEBUG_INFORMATION_LEVEL]
在Xcode 7.2.1中,Debug Information Level
的默認值為Compiler default
,還有一個選項是Line tables only
。
官方文檔的描述是:
Toggles the amount of debug information emitted when debug symbols are enabled. This can impact the size of the generated debug information, which can matter in some cases for large projects (such as when using LTO).
當我把Debug Information Level
設置為Line tables only
的時候,然后構建app,每個源文件在編譯時,都多了一個編譯參數:-gline-tables-only
Clang的文檔中這樣解釋-gline-tables-only
:
Generate line number tables only.
This kind of debug info allows to obtain stack traces with function names, file names and line numbers (by such tools as gdb or addr2line). It doesn’t contain any other data (e.g. description of local variables or function parameters).
這種類型的調試信息允許獲得帶有函數名、文件名和行號的函數調用棧,但是不包含其他數據(比如局部變量和函數參數)。
所以當Debug Information Level
設置為Line tables only
的時候,斷點依然會中斷,但是無法在調試器中查看局部變量的值:
現在把Debug Information Level
設置回Compiler default
,然后試驗下一個設置。
Strip Linked Product [STRIP_INSTALLED_PRODUCT]
在Xcode7.2.1中,Strip Linked Product
在DEBUG和RELEASE下均默認為YES
。
這是一個讓我困惑了很久的設置選項。當我把這一設置選項改為NO
的時候,最終構建生成的app大小沒有任何變化,這讓我覺得很奇怪。
原來,Strip Linked Product
也受到Deployment Postprocessing
設置選項的影響。在Build Settings中,我們可以看到,Strip Linked Product
是在Deployment這欄中的,而Deployment Postprocessing
相當于是Deployment的總開關。
Xcode7.2.1中,Deployment Postprocessing
在DEBUG和RELEASE下均默認為NO
。
現在我們把Deployment Postprocessing
設置為YES
,對比Strip Linked Product
設為YES
和NO
的這兩種情況,發現當Strip Linked Product
設為YES
的時候,app的構建過程多了這樣兩步:
- 在app構建的開始,會生成一些.hmap輔助文件;(為什么會多出這一步我好像還不太清楚)
-
在app構建的末尾,會執行Strip操作。
當Strip Linked Product
設為YES
的時候,運行app,斷點不會中斷,在程序中打印[NSThread callStackSymbols]
也無法看到類名和方法名:
** 0 XSQSymbolsDemo 0x000000010001a7f4 XSQSymbolsDemo + 26612**
而在程序崩潰時,函數調用棧中也無法看到類名和方法名,注意右上角變成了unnamed_function:
繼續保持Strip Linked Product
和Deployment Postprocessing
為YES
,下面來看看Strip Style
設置選項。
Strip Style [STRIP_STYLE]
在Xcode7.2.1中,Strip Style
在DEBUG和RELEASE下均默認All Symbols
。
官方文檔中對Strip Style
的描述:
Defines the level of symbol stripping to be performed on the linked product of the build. The default value is defined by the target's product type. [STRIP_STYLE]
All Symbols - Completely strips the binary, removing the symbol table and relocation information. [all, -s]
Non-Global Symbols - Strips non-global symbols, but saves external symbols. [non-global, -x]
Debugging Symbols - Strips debugging symbols, but saves local and global symbols. [debugging, -S]
選擇不同的Strip Style
時,app構建末尾的Strip操作會被帶上對應的參數。
如果選擇debugging symbols
的話,函數調用棧中,類名和方法名還是可以看到的。
如果我們構建的不是一個app,而是一個靜態庫,需要注意,靜態庫是不可以strip all的。這時構建會失敗。想想符號在重定位時的作用,如果構建的靜態庫真的能剝離所有符號,那么它也就沒法被鏈接了。
現在我們保持Deployment Postprocessing
為YES
,Strip Linked Product
改回NO
,Strip Style
改回All Symbols
,接下來看下一個設置。
Strip Debug Symbols During Copy [COPY_PHASE_STRIP]
網上有很多文章,以為Strip Debug Symbols During Copy
開啟的時候,app中的調試符號會被剝離掉。我感覺他們混淆了Strip Linked Product
和Strip Debug Symbols During Copy
的用法。
文檔上的描述是:
Activating this setting causes binary files which are copied during the build (e.g., in a Copy Bundle Resources or Copy Files build phase) to be stripped of debugging symbols. It does not cause the linked product of a target to be stripped (use Strip Linked Product for that).
Strip Debug Symbols During Copy
中的During Copy
是什么意思呢?我覺得可能是app中引入的某些類型的庫,在app的構建過程中需要被復制一次。雖然我暫時沒找全究竟什么樣的“庫”需要在app構建時被復制,但是我發現,當app中包含extension或者watch app的時候,構建過程中會有Copy的步驟:
當我將app(而非extension)的Strip Debug Symbols During Copy
設置為YES
之后,在這句copy的命令中會多出-strip-debug-symbols
參數。
但是這里,strip并不能成功,并且出現了warning:
warning: skipping copy phase strip, binary is code signed: /Users/xsq/Library/Developer/Xcode/DerivedData/XSQSymbolsDemo-cysszdsykroyyddkvvyffgboglvo/Build/Products/Debug-iphoneos/Today.appex/Today
這似乎是由于app中的today extention已經經過了code sign,導致無法被篡改引起的警告。
那么如果略過code sign的過程,是否就能成功strip呢?我想使用模擬器調試可以略過code sign過程,于是便在模擬器上試了試。果然這個warning消失了。
Strip Debug Symbols During Copy
設置為YES
時,打開對應.app文件的“顯式包內容”,可以看到,/PlugIns/Today.appex
文件的大小變小了。(不過這些只能在使用模擬器時奏效)
Strip Debug Symbols During Copy
置為YES的時候,today extension中的斷點將不會中斷,但是打印[NSThread callStackSymbols]
時的類名和方法名還是可以看見的。
現在我們把Strip Debug Symbols During Copy
設置回NO
,來看看下一個設置。
Debug Information Format [DEBUG_INFORMATION_FORMAT]
Xcode7.2.1中,Debug Information Format
在DEBUG下默認為DWARF
,在RELEASE下默認為DWARF with dSYM File
。
官方文檔的解釋是:
This setting controls the format of debug information used by the developer tools. [DEBUG_INFORMATION_FORMAT]
DWARF - Object files and linked products will use DWARF as the debug information format. [dwarf]
DWARF with dSYM File - Object files and linked products will use DWARF as the debug information format, and Xcode will also produce a dSYM file containing the debug information from the individual object files (except that a dSYM file is not needed and will not be created for static library or object file products). [dwarf-with-dsym]
當Debug Information Format
為DWARF with dSYM File
的時候,構建過程中多了一步Generate dSYM File:
最終產出的文件也多了一個dSYM文件。
不過,既然這個設置叫做Debug Information Format
,所以首先得有調試信息。如果此時Generate Debug Symbols
選擇的是NO
的話,是沒法產出dSYM文件的。
dSYM文件的生成,是在Strip等命令執行之前。所以無論Strip Linked Product
是否開啟,生成的dSYM文件都不會受影響。
不過正如文檔中所說,無法為靜態庫生成dSYM文件。即便為給一個靜態庫的Debug Information Format
設置為DWARF with dSYM File
,構建過程中依然不會有生成dSYM文件的步驟。
一種配置方案
了解了每個設置的意思,個人覺得對于一個普通的app來說可以這樣配置這些設置:
-
Generate Debug Symbols
:DEBUG和RELEASE下均設為YES
(和Xcode默認一致); -
Debug Information Level
:DEBUG和RELEASE下均設為Compiler default
(和Xcode默認一致); -
Deployment Postprocessing
:DEBUG下設為NO,RELEASE下設為YES,這樣RELEASE模式下就可以去除符號縮減app的大小(但是似乎設置為YES后,會牽涉一些和bitcode有關的設置,對于bitcode暫時還不太了解(′?_?`)); -
Strip Linked Product
:DEBUG下設為NO,RELEASE下設為YES,用于RELEASE模式下縮減app的大小; -
Strip Style
:DEBUG和RELEASE下均設為All Symbols
(和Xcode默認一致); -
Strip Debug Symbols During Copy
:DEBUG下設為NO
,RELEASE下設為YES
; -
Debug Information Format
:DEBUG下設為DWARF
,RELEASE下設為DWARF with dSYM File
,dSYM文件需要用于符號化crash log(和Xcode默認一致);
參考
深入理解計算機系統
Build Setting Reference
Skipping Copy Phase Strip
xcode build settings for debug symbol
Clang Compiler User’s Manual
Symbolification: Shipping Symbols
Technical Q&A QA1490
2016.04.17更新
某天,我看了公司的項目的設置,發現Deployment Postprocessing
這項在DEBUG和RELEASE下均為NO
,讓我有些奇怪,難道公司的項目沒有濾去調試符號?
于是我archive了一下,發現,在archive的過程中,其實是跑了strip的命令的,讓我有點吃驚。這說明run和archive的構建過程是不同的。
查了官方文檔,暫時沒能查到run和archive到底有哪里不同,但是這里對ACTION的解釋讓我有些在意。對ACTION的設置會影響到Deployment Postprocessing
的默認值,即當$ACTION = install
的時候,Deployment Postprocessing
默認為YES
。