May 15 2010
Symbolification: Shipping Symbols
by Brian Dunagan
nm -a path_to_executable
When Mac applications are compiled, Xcode has a setting to include or exclude debugging symbols: the “Strip Style” . When set to fully strip, the bundled executable (AppName.app/Contents/MacOS/AppName ) contains very few symbols, but when all the symbols are included, the executable is a wealth of information. Unfortunately, Xcode’s default Release configuration includes all the symbols, leading to shipping applications with lots of symbol information, including method names and even source files. These are accessible with nm . Let’s see what we find for Retrospect Client (from my company):
Method Names
...
0000431d t -[PrefController awakeFromNib]
0000431d - 01 001f FUN -[PrefController awakeFromNib]:f(0,1)
...
Source Files
...
0000276e - 01 0032 SO /Volumes/Leopard/projects/trunk/retrospect/client/mac/cpgui/AboutPanel.m
00004963 - 01 0032 SO /Volumes/Leopard/projects/trunk/retrospect/client/mac/cpgui/PasswordController.m
...
While interesting in isolation, the information is richer in aggregate. With a quick ruby script, we can rank the apps in /Applications by various metrics:
Top 10 by Methods
8857 Coda
8036 Evernote
7660 Cornerstone
6312 Pages
6054 Jing
5100 Papers
4805 Delicious Library 2
4589 Socialite
4563 Retrospect
4048 CSSEdit
Top 10 by Source Files
304 Delicious Library 2
295 LittleSnapper
257 Socialite
240 Evernote
192 NetNewsWire
158 Microsoft Document Connection
144 Tweetie
115 Retrospect
105 Clipstart
87 Transmit
Apple’s products are not in either list because they strip out the symbols for every major application. However, they apparently missed App Store’s Application Loader 1.1 (72), codenamed StarGazer:
...
00000000 - 00 0000 SO /Users/jfosback/jingle/iTMSTransporter/branches/iTMSTransporter-1.4/Applications/Stargazer/Source/ITunesPurpleSoftwareUploadDocumentController.m
...
Xcode Settings
Over half of the apps in my /Applications had no symbol information. The developers stripped it out, as described by Apple’s documentation on symbolizing crash dumps .
As I mentioned earlier, Apple’s default Release configuration does not strip out symbols. In fact, it leaves them all in, regardless of what “Strip Style” (STRIP_STYLE ) is set to. The culprit is “Deployment Postprocessing” (DEPLOYMENT_POSTPROCESSING ). When not enabled, Xcode ignores the setting for “Strip Style”. Apple describes this “Deployment Postprocessing” option in Xcode Build System Guide: Build Configuration and further in Xcode Project Management Guide: Building Products .
I wrote a quick sample application in Xcode and used the default Release configuration to generate the following dumps from nm :
No symbols are stripped (Xcode’s default Release configuration)
# Deployment Postprocessing: false
# Strip Style: any
[~/Desktop] $ nm -a SampleApp/build/Release/SampleApp.app/Contents/MacOS/SampleApp
0000000000000011 - 01 0000 ENSYM
0000000000000011 - 00 0000 FUN
0000000000000011 - 00 0000 FUN
00000001000018fd - 01 0000 BNSYM
0000000000000000 - 00 0000 SO
00000001000018dc - 01 0000 BNSYM
000000000000000a - 00 0000 FUN
000000000000000a - 01 0000 ENSYM
0000000000000000 - 01 0000 SO
0000000000000000 - 00 0000 SO
0000000000000000 - 01 0000 SO
0000000000000011 - 01 0000 ENSYM
00000001000018e6 - 01 0000 BNSYM
0000000000000006 - 00 0000 FUN
0000000000000006 - 01 0000 ENSYM
00000001000018ec - 01 0000 BNSYM
0000000100001e8b s stub helpers
00000001000018e6 t -[SampleAppAppDelegate applicationDidFinishLaunching:]
00000001000018e6 - 01 0000 FUN -[SampleAppAppDelegate applicationDidFinishLaunching:]
00000001000018fd t -[SampleAppAppDelegate setWindow:]
00000001000018fd - 01 0000 FUN -[SampleAppAppDelegate setWindow:]
00000001000018ec - 01 0000 FUN -[SampleAppAppDelegate window]
00000001000018ec t -[SampleAppAppDelegate window]
0000000000000000 - 00 0000 SO /Users/bdunagan/Desktop/SampleApp/SampleAppAppDelegate.m
000000004be395c1 - 00 0001 OSO /Users/bdunagan/Desktop/SampleApp/build/SampleApp.build/Release/SampleApp.build/Objects-normal/x86_64/SampleAppAppDelegate.o
000000004be395c1 - 00 0001 OSO /Users/bdunagan/Desktop/SampleApp/build/SampleApp.build/Release/SampleApp.build/Objects-normal/x86_64/main.o
0000000000000000 - 00 0000 SO /Users/bdunagan/Desktop/SampleApp/main.m
U _NSApplicationMain
0000000100002660 D _NXArgc
0000000100002668 D _NXArgv
U _OBJC_CLASS_$_NSObject
0000000100002070 - 0a 0000 STSYM _OBJC_CLASS_$_SampleAppAppDelegate
0000000100002070 s _OBJC_CLASS_$_SampleAppAppDelegate
00000001000021c8 s _OBJC_IVAR_$_SampleAppAppDelegate.window
00000001000021c8 - 0b 0000 STSYM _OBJC_IVAR_$_SampleAppAppDelegate.window
U _OBJC_METACLASS_$_NSObject
0000000100002048 s _OBJC_METACLASS_$_SampleAppAppDelegate
0000000100002048 - 0a 0000 STSYM _OBJC_METACLASS_$_SampleAppAppDelegate
0000000100002678 D ___progname
0000000100000000 A __mh_execute_header
U __objc_empty_cache
U __objc_empty_vtable
0000000100002670 D _environ
U _exit
00000001000018dc t _main
00000001000018dc - 01 0000 FUN _main
U dyld_stub_binder
00000001000018a0 T start
Debugging symbols are stripped
# Deployment Postprocessing: true
# Strip Style: Debugging Symbols (STRIP_STYLE=debugging)
[~/Desktop] $ nm -a SampleApp/build/Release/SampleApp.app/Contents/MacOS/SampleApp
0000000100001e8b s stub helpers
00000001000018e6 t -[SampleAppAppDelegate applicationDidFinishLaunching:]
00000001000018fd t -[SampleAppAppDelegate setWindow:]
00000001000018ec t -[SampleAppAppDelegate window]
U _NSApplicationMain
0000000100002660 D _NXArgc
0000000100002668 D _NXArgv
U _OBJC_CLASS_$_NSObject
0000000100002070 s _OBJC_CLASS_$_SampleAppAppDelegate
00000001000021c8 s _OBJC_IVAR_$_SampleAppAppDelegate.window
U _OBJC_METACLASS_$_NSObject
0000000100002048 s _OBJC_METACLASS_$_SampleAppAppDelegate
0000000100002678 D ___progname
0000000100000000 A __mh_execute_header
U __objc_empty_cache
U __objc_empty_vtable
0000000100002670 D _environ
U _exit
00000001000018dc t _main
U dyld_stub_binder
00000001000018a0 T start
Non-global symbols are stripped
# Deployment Postprocessing: true
# Strip Style: Non-Global Symbols (STRIP_STYLE=non-global)
[~/Desktop] $ nm -a SampleApp/build/Release/SampleApp.app/Contents/MacOS/SampleApp
U _NSApplicationMain
0000000100002660 D _NXArgc
0000000100002668 D _NXArgv
U _OBJC_CLASS_$_NSObject
U _OBJC_METACLASS_$_NSObject
0000000100002678 D ___progname
0000000100000000 A __mh_execute_header
U __objc_empty_cache
U __objc_empty_vtable
0000000100002670 D _environ
U _exit
U dyld_stub_binder
0000000005614542 - 00 0000 OPT radr://5614542
00000001000018a0 T start
All Symbols are stripped
# Deployment Postprocessing: true
# Strip Style: All Symbols (STRIP_STYLE=all) (default)
[~/Desktop] $ nm -a SampleApp/build/Release/SampleApp.app/Contents/MacOS/SampleApp
U _NSApplicationMain
U _OBJC_CLASS_$_NSObject
U _OBJC_METACLASS_$_NSObject
0000000100000000 A __mh_execute_header
U __objc_empty_cache
U __objc_empty_vtable
U _exit
U dyld_stub_binder
0000000005614542 - 00 0000 OPT radr://5614542
Ruby Script
To generate the statistics for the apps in /Applications , I wrote a short ruby script:
#!/usr/local/bin/ruby19
# analyze_apps.rb
# * Analyze dump from 'nm -a path_to_executable' to collect statistics: Name, Path, Lines, Archs, Files, Class Methods, Instance Methods
# Collect app paths.
apps = []
apps_folders = [ "/Applications" , "/Applications/iWork '08" , "/Applications/iWork '09" , "/Applications/Microsoft Office 2004" , "/Applications/Microsoft Office 2008" , "/Applications/Utilities" ]
apps_folders . each do | app_folder |
app_list = Dir . entries ( app_folder )
app_list . each do | app |
apps << " #{ app_folder } / #{ app } "
end
end
# Include source code?
PRINT_SOURCE_CODE = false
# Identify columns of data.
puts "Name, Path, Lines, Archs, Files, Class Methods, Instance Methods"
# Loop through apps.
apps . each do | app |
# Check that the app exists.
if Dir . exists? ( " #{ app } " ) && app . include? ( ".app" )
app_array = app . split ( "/" )
app_name = app_array [ app_array . count - 1 ]. split ( ".app" )[ 0 ]
exe_path = " #{ app } /Contents/MacOS/ #{ app_name } "
# Check that the executable exists.
if File . exists? ( exe_path )
# Use 'nm -a path_to_executable' to dump symbol information.
results = %x[nm -a " #{ exe_path } "]
# Get number of architectures supported. All other information is duplicated per architecture.
arch_count = results . scan ( "(for architecture " ). count
arch_count = 1 if arch_count == 0
# Get the number of lines, class methods, and instance methods per architecture.
line_count = results . scan ( " \n " ). count / arch_count
class_methods = results . scan ( " t +[" ). count / arch_count
instance_methods = results . scan ( " t -[" ). count / arch_count
# Loop through the results to more accurately count the source files (though not perfectly).
source_lookup = []
source_count = 0
results . split ( " \n " ). each do | line |
# Check that the line references "SO" or "SOL" but isn't repeated.
if line . include? ( "SO " ) && line . split ( "SO " ). count > 1 && line . split ( "SO " )[ 1 ]. include? ( ".m" ) && ! source_lookup . include? ( line . split ( "SO " )[ 1 ])
file = line . split ( "SO " )[ 1 ]
# Add file name to lookup.
source_lookup << file
# Increment counter.
source_count = source_count + 1
# Print out file name.
puts " #{ app_name } : #{ file } " if PRINT_SOURCE_CODE
elsif line . include? ( "SOL " ) && line . split ( "SOL " ). count > 1 && line . split ( "SOL " )[ 1 ]. include? ( ".m" ) && ! source_lookup . include? ( line . split ( "SOL " )[ 1 ])
file = line . split ( "SOL " )[ 1 ]
# Add file name to lookup.
source_lookup << file
# Increment counter.
source_count = source_count + 1
# Print out file name.
puts " #{ app_name } : #{ file } " if PRINT_SOURCE_CODE
end
end
# Print out the application's stats.
puts " #{ app_name } , #{ app } , #{ line_count } , #{ arch_count } , #{ source_count } , #{ class_methods } , #{ instance_methods } "
end
end
end