Today, we’ll talk about manually symbolicating iOS and OS X application “crash reports.” Why? When you hear about a crash in one of your apps from a customer, the first thing you should do is try to get a copy of the crash report. But there are times when you get crash reports that aren’t automatically symbolicated, or that you can’t symbolicate by dragging into Xcode, or are partially symbolicated. When not symbolicated, you’re reading numeric addresses when you want to be reading code, like your function/class names. There are workarounds and we’ll discuss one today. Download the sample Xcode 9 project written in Objective-C to follow along. What’s a crash report, anyway? According to Apple:
When an application crashes, a crash report is created and stored on the device. Crash reports describe the conditions under which the application terminated, in most cases including a complete backtrace for each executing thread, and are typically very useful for debugging issues in the application. You should look at these crash reports to understand what crashes your application is having, and then try to fix them.
Crash reports with backtraces need to be symbolicated before they can be analyzed. Symbolication replaces memory addresses with human-readable function names and line numbers. If you get crash logs off a device through Xcode’s Devices window, then they will be symbolicated for you automatically after a few seconds. Otherwise you will need to symbolicate the .crash file yourself by importing it to the Xcode Devices window. …
Er, ah, when it comes to believing the statement, “they will be symbolicated for you automatically after a few seconds. Otherwise you will need to symbolicate the .crash file yourself by importing it to the Xcode Devices window,” well, don’t bet your life on either automatic symbolication or symbolication upon dragging into Xcode. Often, dragging into Xcode does absolutely nothing.
Introduction
Suppose that someone sends you a copy of crash report through email or on a USB memory stick — a text file with .txt
or .crash
extension. It happens quite often, especially when you have an installed base of hundreds of thousands or millions. Some people don’t want Apple collecting all sorts of information about them on their Macs, MacBook Pros, iPhones, iPads, watches, etc., so they turn off the “Share analytics, diagnostics, and usage information with Apple” option. There are also people out there using OS X applications developed by third-party software boutiques and distributed outside of The Mac App Store.
Yes, Apple claims that “Xcode will automatically symbolicate [a] crash report and display the results” if you drag the report onto Xcode, and say you’ve kept copies of your “crashing application’s binary and dSYM file.” But guess what? I don’t know how many times I’ve seen this “automatic” symbolication fail. Have you ever tried to examine an unsymbolicated crash report? It’s often useless, especially when none of your code has been symbolicated. You’re just looking at a big list of numbers — memory addresses. That’s no help. What do you do if you’ve just received a crash report and it looks like this?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
... Thread 0 Crashed:: Dispatch queue: com.apple.main-thread 0 libsystem_kernel.dylib 0x00007fff60a1afce __pthread_kill + 10 1 libsystem_pthread.dylib 0x00007fff60b58150 pthread_kill + 333 2 libsystem_c.dylib 0x00007fff6097732a abort + 127 3 libsystem_c.dylib 0x00007fff6093f380 __assert_rtn + 320 4 com.domain.CrashLogDemo 0x0000000102c61270 0x102c60000 + 4720 5 com.domain.CrashLogDemo 0x0000000102c61062 0x102c60000 + 4194 6 com.domain.CrashLogDemo 0x0000000102c60fe0 0x102c60000 + 4064 7 com.apple.AppKit 0x00007fff369caa56 -[NSViewController _sendViewDidLoad] + 97 8 com.apple.AppKit 0x00007fff369c148d -[NSViewController _loadViewIfRequired] + 390 9 com.apple.AppKit 0x00007fff369c12bd -[NSViewController view] + 30 10 com.apple.AppKit 0x00007fff36b3ff7f -[NSWindow _contentViewControllerChanged] + 109 11 com.apple.Foundation 0x00007fff3b43535e -[NSObject(NSKeyValueCoding) setValue:forKey:] + 331 12 com.apple.AppKit 0x00007fff36b82c69 -[NSWindow setValue:forKey:] + 111 13 com.apple.AppKit 0x00007fff36b82bcd -[NSIBUserDefinedRuntimeAttributesConnector establishConnection] + 637 14 com.apple.AppKit 0x00007fff3694f0a5 -[NSIBObjectData nibInstantiateWithOwner:options:topLevelObjects:] + 1430 15 com.apple.AppKit 0x00007fff36a4a92e -[NSNib _instantiateNibWithExternalNameTable:options:] + 679 16 com.apple.AppKit 0x00007fff36a4a58a -[NSNib _instantiateWithOwner:options:topLevelObjects:] + 136 17 com.apple.AppKit 0x00007fff3719fa37 -[NSStoryboard instantiateControllerWithIdentifier:] + 236 18 com.apple.AppKit 0x00007fff369433b3 NSApplicationMain + 729 19 libdyld.dylib 0x00007fff608cb145 start + 1 ... |
Look at lines 7,8,9 in the code view above (lines 4,5,6 in the crash report’s backtrace, respectively). Those are just numbers (addresses). They’re not symbolicated. I want to see my code. This crash report was the result of a crash in a release version of an app, one in which the symbols (human-readable code), have been stripped out. What’s a release version? I’m not going to explain everything, but will give you clues.
One of the purposes of this blog is to inculcate developers with the skills, behaviors, and knowledge needed to be the best of the best. Doing research is one of those skills. Follow the links I provide and either discover what the terms I’ve highlighted mean or at least reinforce your understanding of those terms. Here’s a snippet from Microsoft on Visual Studio, but the same concepts apply to Xcode (my edits in strikeout and in brackets, like [ and ]):
Visual Studio [Xcode] projects have separate release and debug configurations for your program. As the names imply, you build the debug version for debugging and the release version for the final release distribution.
The debug configuration of your program is compiled with full symbolic debug information and no optimization. Optimization complicates debugging, because the relationship between source code and generated instructions is more complex.
The release configuration of your program contains no symbolic debug information and is fully optimized. Debug information can be generated in .pdb [.dSYM] files, depending on the compiler options that are used. Creating .pdb [.dSYM] files can be very useful if you later have to debug your release version. …
For Xcode, note how I substituted “.dSYM” for “.pdb” in the previous quote.
Definitions
Crash report
A “crash report” or “crash log” contains a wealth of information to help you debug a released app: the app name, the operating system version, the date/time of the crash, the threads running at the time of the crash, the stack trace, the libraries your app is dependent upon (e.g., .dylib), information on allocated memory, details about the device on which the crash occurred, the target machine architecture (i.e., x86_64
for a MacBook or arm64
for an iPhone), etc. Make use of crash reports. If someone reports a crash in one of your apps, the first thing you should do is try to get a copy of the crash report.
A crash report for a release version of an app is useful only as long as you keep the current app version’s .dSYM or “debug symbol file.” .dSYM files store the original symbols you used when writing your app in the Xcode IDE. Your pseudo-English source code files cannot be run on a device’s processor. When building (compiling and linking), your source code’s object names, variable names, function names, etc., are converted into a binary format that is executable on a device. But those names — symbols — are converted to addresses when Xcode produces a binary.
Call stack/stack trace/backtrace
Generally, the “call stack,” “backtrace,” or “stack trace” is what most developers want to look at to find out where in their code the bug/problem/exception occurred that caused their app to crash. According to Wikipedia:
A stack trace allows tracking the sequence of nested functions called – up to the point where the stack trace is generated. In a post-mortem scenario this extends up to the function where the failure occurred (but was not necessarily caused). Sibling calls do not appear in a stack trace.
If you’ve just started software development or are a seasoned developer, by now you should understand terms like “stack” and “stack trace.” You need to know what a “stack” is most definitely. If you don’t, you’re in big trouble.
Apple has a good article on “Examining the Call Stack.” Read it.
Symbolicating a crash report
After everything I’ve said so far, do you understand what “symbolicating a crash report” means? Think about it first before reading further. When I was a student, I learned that effective studying meant trying to rebuild the information conveyed to me in class in my mind before looking at my notes. To reinforce my discussion, read Apple’s definition (my emphasis added):
Symbolication is the process of resolving backtrace addresses to source code method or function names, known as symbols. Without first symbolicating a crash report it is difficult to determine where the crash occurred.
Manually symbolicating a crash report
If you find that you can’t symbolicate a crash report automatically, all is not lost. You can manually symbolicate each line in the backtrace. You’re going to use the OS X atos
command in Terminal.
The man
page for the atos
command on OS X states:
The atos command converts numeric addresses to their symbolic equivalents. If full debug symbol information is available, for example in a .app.dSYM sitting beside a .app, then the output of atos will include file name and source line number information.
Note that other Unix operating systems support atos
.
Apple even admits that you may have to manually symbolicate crash reports, and provides guidance for atos
usage:
1 |
atos -arch <Binary Architecture> -o <Path to dSYM file>/Contents/Resources/DWARF/<binary image name> -l <load address> <address to symbolicate> |
Here are the steps (we’ll perform a real symbolication below):
- Get the .dSYM for the currently released version of your app;
- Copy the .dSYM into a folder in which you stored things like your .app bundle, DistributionSummary.plist, ExportOptions.plist, and Packaging.log (you can copy the .dSYM anywhere, but I like to stay organized);
- Open Terminal and
cd
to the folder containing your .dSYM; - Note the hardware platform on which the crash occurred (Apple crash reports say things like “X86-64,” but
atos
wants “x86_64”); - Open your unsymbolicated crash report in a good text editor like TextWrangler;
- Run the
atos
command in Terminal for each line of unsymbolicated code, noting the corresponding crash report line number, andatos
will produce the fully symbolicated line of code; and, - Copy each symbolicated line of code, noting the line number in the backtrace, and paste into the corresponding unsymbolicated crash report, overwriting the corresponding unsymbolicated line in the backtrace.
Example
Refer to the excerpt of the unsymbolicated crash report at the beginning of this article. We’re going to symbolicate it now:
Line 4
1 2 |
$ atos -arch x86_64 -o CrashLogDemo.app.dSYM/Contents/Resources/DWARF/CrashLogDemo -l 0x102c60000 0x0000000102c61270 -[Test1 numbers] (in CrashLogDemo) (Test1.h:13) |
Line 5
1 2 |
$ atos -arch x86_64 -o CrashLogDemo.app.dSYM/Contents/Resources/DWARF/CrashLogDemo -l 0x102c60000 0x0000000102c61062 -[Test test] (in CrashLogDemo) (Test.m:19) |
Line 6
1 2 |
$ atos -arch x86_64 -o CrashLogDemo.app.dSYM/Contents/Resources/DWARF/CrashLogDemo -l 0x102c60000 0x0000000102c60fe0 -[ViewController viewDidLoad] (in CrashLogDemo) (ViewController.m:28) |
Now let’s copy the symbolicated lines into the unsymbolicated crash report and — voila! — Shama Lama Ding Dong! (see video below). Look at code view lines 7,8,9 (corresponding crash report lines 4,5,6, respectively). Thar’s code in that thar crash report!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
... Thread 0 Crashed:: Dispatch queue: com.apple.main-thread 0 libsystem_kernel.dylib 0x00007fff60a1afce __pthread_kill + 10 1 libsystem_pthread.dylib 0x00007fff60b58150 pthread_kill + 333 2 libsystem_c.dylib 0x00007fff6097732a abort + 127 3 libsystem_c.dylib 0x00007fff6093f380 __assert_rtn + 320 4 com.domain.CrashLogDemo 0x0000000102c61270 -[Test1 numbers] (in CrashLogDemo) (Test1.h:13) + 4720 5 com.domain.CrashLogDemo 0x0000000102c61062 -[Test test] (in CrashLogDemo) (Test.m:19) + 4194 6 com.domain.CrashLogDemo 0x0000000102c60fe0 -[ViewController viewDidLoad] (in CrashLogDemo) (ViewController.m:28) + 4064 7 com.apple.AppKit 0x00007fff369caa56 -[NSViewController _sendViewDidLoad] + 97 8 com.apple.AppKit 0x00007fff369c148d -[NSViewController _loadViewIfRequired] + 390 9 com.apple.AppKit 0x00007fff369c12bd -[NSViewController view] + 30 10 com.apple.AppKit 0x00007fff36b3ff7f -[NSWindow _contentViewControllerChanged] + 109 11 com.apple.Foundation 0x00007fff3b43535e -[NSObject(NSKeyValueCoding) setValue:forKey:] + 331 12 com.apple.AppKit 0x00007fff36b82c69 -[NSWindow setValue:forKey:] + 111 13 com.apple.AppKit 0x00007fff36b82bcd -[NSIBUserDefinedRuntimeAttributesConnector establishConnection] + 637 14 com.apple.AppKit 0x00007fff3694f0a5 -[NSIBObjectData nibInstantiateWithOwner:options:topLevelObjects:] + 1430 15 com.apple.AppKit 0x00007fff36a4a92e -[NSNib _instantiateNibWithExternalNameTable:options:] + 679 16 com.apple.AppKit 0x00007fff36a4a58a -[NSNib _instantiateWithOwner:options:topLevelObjects:] + 136 17 com.apple.AppKit 0x00007fff3719fa37 -[NSStoryboard instantiateControllerWithIdentifier:] + 236 18 com.apple.AppKit 0x00007fff369433b3 NSApplicationMain + 729 19 libdyld.dylib 0x00007fff608cb145 start + 1 ... |
Wrapping up
Take a look at the sample Xcode 9 project to see how I forced a crash. Look at the function calls that lead to the crash — the stack trace! I hope this helps you smash some ugly bugs.
Enjoy — and above all, celebrate with Otis Day And The Knights:
Awesome write up, helped me alot. Would like to add few steps which I took to symbolicate crash log received from App Store. Please check
https://mithaptechnologies.blogspot.com/2018/11/symbolicating-ios-crash-report-from.html
Rachna:
Glad I could help. Thanks for reading my blog — and thanks for the article on symbolicating crash reports. I hope together we can help all the folks trying to drag crash logs into Xcode and then nothing happens.
– Andrew
Brilliant, I’ve tried lots of methods that didn’t quite work. This one was perfect. Thanks.
Simon:
Glad I could help. Thanks for reading my blog!
– Andrew