App crashes are unavoidable, and they happen all the time. When a published app crashes on a user's iOS device, Apple sends a crash log to the developer. Because of that, the developer should know how to read and analyze the log in order to fix the bug.
In this post, we are first going to learn about app crashes. After that, we'll learn about the ways to access crash logs. Next, we'll learn to analyze the crash logs, followed by learning to symbolicate the crash report. Lastly, we'll learn about different types of exceptions.
Apps can crash for a variety of reasons. Some common reasons include:
If the app is on App Store and crashes on a user's iOS device, then Apple will send the crash log to the developer. The developer can also get the crash logs directly from a physical device. For this to happen in a controlled environment, the developer can crash the app on their device. Sometimes for bigger bugs like Watchdog timeouts, the app will then crash on all devices.
To get the log from a physical device, first connect your iPhone/iPad to a Mac machine. After that, open Xcode and then click on Window. Then click on Devices and Simulators. Now you can see all the logs.
You can also see the crash log on an iPhone/iPad by going to Settings -> Privacy -> Analytics -> Analytics Data. You can also see the crash log on a simulator if the app crashes during building through a simulator.
To see the simulator log, first open Finder. After that, click on Go and then click on Go to Folder...
It will open a box, in which you can type ~/Library/Logs/CoreSimulator and press enter.
Now we can see all the logs, including the crash logs for different applications. These logs can be easily opened with any text editor.
There is a pattern in the crash log, and we can understand it easily. By doing so, we can quickly find the root cause of the error and fix it. There are three sections in a crash report, and we'll will look into them below.
The header contains the process information and other information related to the crash. It looks like the example below, containing the Incident Identifier first, followed by the CrashReporter Key. Both of these are unique keys. After that, Hardware Model, Process, Path, Identifier, and Version are given. These fields are self-explanatory. Then we have the Code Type field, which is ARM, because all iPhones/iPads use ARM processors.
Next, the Date/Time of the crash is given, followed by OS Version and Report Version.
Incident Identifier: 14FFD847-61CB-435B-9E98-C06B3B661429
CrashReporter Key: 7c5fd78cf04b38cfd2aa153f61eb1655ed671274
Hardware Model: iPhone4,1
Process: My iPhone App 
Path: /var/mobile/Applications/ABAB96ED-A203-48A5-8B50-B34BA3A8E4A4/My iPhone App.app/My iPhone App
Identifier: My iPhone App
Version: ??? (???)
Code Type: ARM (Native)
Parent Process: launchd 
Date/Time: 2012-07-01 22:17:43.458 -0600
OS Version: iPhone OS 5.1 (9B179)
Report Version: 104
The next thing in the crash log is the exception information. This contains the most obvious reason for the crash. Here, the Exception Type can mean different things, and we'll look into those in detail in a later section.
Exception Type: EXC_CRASH (SIGKILL)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note: EXC_CORPSE_NOTIFY
Termination Reason: Namespace SPRINGBOARD, Code 0x8badf00d
Next, we have the exact exception backtrace. This is very useful, and we can trace the exact root cause of the crash from it. It looks like the example below.
Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0 libobjc.A.dylib 0x35a65f7e objc_msgSend + 22
1 UIKit 0x33c31042 -[UIImageView isAnimating] + 130
2 UIKit 0x33c3b100 -[UIImageView stopAnimating] + 96
3 UIKit 0x33d5d1de -[UIActivityIndicatorView _tearDownAnimation] + 30
4 UIKit 0x33cdb972 -[UIActivityIndicatorView _applicationDidEnterBackground:] + 34
5 Foundation 0x37d8f4f8 __57-[NSNotificationCenter addObserver:selector:name:object:]_block_invoke_0 + 12
6 CoreFoundation 0x37531540 ___CFXNotificationPost_block_invoke_0 + 64
7 CoreFoundation 0x374bd090 _CFXNotificationPost + 1400
8 Foundation 0x37d033e4 -[NSNotificationCenter postNotificationName:object:userInfo:] + 60
9 UIKit 0x33c813f6 -[UIApplication _handleApplicationSuspend:eventInfo:] + 786
10 UIKit 0x33c120a0 -[UIApplication handleEvent:withNewEvent:] + 2088
11 UIKit 0x33c11708 -[UIApplication sendEvent:] + 48
12 UIKit 0x33c110dc _UIApplicationHandleEvent + 5820
13 GraphicsServices 0x323c9224 PurpleEventCallback + 876
14 CoreFoundation 0x3753951c __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 32
15 CoreFoundation 0x375394be __CFRunLoopDoSource1 + 134
16 CoreFoundation 0x3753830c __CFRunLoopRun + 1364
17 CoreFoundation 0x374bb49e CFRunLoopRunSpecific + 294
18 CoreFoundation 0x374bb366 CFRunLoopRunInMode + 98
19 GraphicsServices 0x323c8432 GSEventRunModal + 130
20 UIKit 0x33c3fe76 UIApplicationMain + 1074
21 My iPhone App 0x000f7ec2 0xdc000 + 114370
22 My iPhone App 0x000ddc50 0xdc000 + 7248
But it contains all hexadecimal addresses, and these needed to be changed to human readable addresses before we can use them. We use the process of symbolication to do that.
Connect you iPhone/iPad to a MacOS system and open Xcode. After that, click on Window, then click on Devices and Simulators. Now you can see all the logs. The logs which we see here are symbolicated and contains proper information.
Here, all the hexadecimal and other system-related things have been changed to a human-readable form automatically by Xcode.
If you are not able to find the crash file here, you have likely received the crash file from other sources. Suppose we got the crash file from Apple, as our app was in App Store from some user's device, and we cannot see the crash file in our device. In that case, we need to manually symbolicate the crash report. This process is well documented on the Apple site here.
Earlier, we looked at exception types, each of which contains exception information. Each exception type means a different thing, and we'll look into them below.
EXC_CRASH (SIGKILL): This one means that the process was killed by the system. The most common reason is Watchdog termination. Apple expects the app to launch quickly, but if it takes too much time to launch, the Watchdog terminates it.
EXC_CRASH (SIGQUIT): In this one, the process is terminated by the main process. Suppose the keyboard is taking a lot of time to load, then the app can request to kill the keyboard process.
EXC_CRASH (SIGABRT): This one means that the process was terminated because of an exception in the Objective-C/C++ code. It also occurs when the Objective-C/C++ code takes more time to initialize.
EXC_BREAKPOINT (SIGTRAP): This one means that the process was terminated because of an exception in the Swift code. The most common errors are a non-optional type with a nil value, or a type conversion which was forced and failed.
EXC_BAD_ACCESS (SIGSEGV): This one occurs when the app tries to access an invalid memory location. This issue generally happens in Objective-C code, which is not good with memory management. Modern iOS apps with Swift are better at memory management.
EXC_BAD_INSTRUCTION (SIGILL): The one occurs when a process in the app tries to run some illegal statement that's not allowed in iOS.
In this post, we've learned to read iOS crash logs. We first learned about app crashes in iOS devices, then we learned about the different methods to access the crash logs. After that, we learned to analyze the crash logs and about the different parts in a crash log.
Next, we learned to symbolicate the crash report. Lastly, we learned about the different exception types in the exception information.
You should now be able to take this information and use it to find and fix issues in your own iOS apps. This should help you more easily develop and maintain iOS apps.