其实被这个问题困扰了好久,不过秉承着三分钟热度的新年新气象,还是要多弄懂一点(⊙_⊙)ゞ
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
。