Before you submit your app, you perform a lot of testing to make sure your app runs flawlessly. It works fine on your device, but after the app is in the App Store, some users report crashes!

If you’re anything like me, you want your app to be A+. So you go back to your code to fix the crashes… but where do you look?

This is when iOS crash logs come in handy. In most cases, you’ll get very detailed and useful information about the cause of the crash, like feedback from a good teacher.

In this tutorial, you’ll learn about some common crash log scenarios, as well as how to acquire crash logs from development devices and iTunes Connect. You will learn about symbolication, and tracing back from log to code. You will also debug an application that can crash in certain situations.

OK, let’s get crashing!

What Is A Crash Log, and Where Do I Get One?

When an application crashes on an iOS device, the operating system creates a crash report or a crash log. The report is stored on the device.

You can find a lot of useful information in a crash log, including the conditions under which the application terminated. Usually, there is a complete stack trace of each executing thread, so you can see what was happening in every thread at the time of the crash, and identify the thread where the crash occurred.

There are many ways to get a crash log from a device.

A device that is synced with iTunes stores its crash logs on the machine. Depending on the OS, here are the locations where you can find them:

When your users experience a crash, you can instruct them to sync their device with iTunes, go to one of the above locations (depending on their OS), download the crash logs and email them to you.

You want any crash logs your users generate. The more crash logs you have, the better equipped you are to find and diagnose weaknesses in your app!

Also, you can easily get the crash reports for your devices via Xcode, if you have it installed. To do this, connect the iOS device to your computer and open Xcode. From the menu bar, select Window, then Organizer (the shortcut is Shift-CMD-2).

In the Organizer window, go to the Devices tab. On the left-hand side navigation pane, look for Device Logs, as shown in the image below:

As you can see in the screenshot, there are multiple Device Logs items. The Device Logs under LIBRARY contain all the logs from all devices you own (or have connected to Xcode). The Device Logs entries under each device are for that particular device.

Once your app is submitted, you can also get crash logs from your users using iTunes Connect. To do this, simply sign in to your iTunes Connect account, navigate to Manage Your Applications, click on the app you want crash logs for, click on the View Details button below the icon, and click on Crash Reports in the right-hand side pane in the Links section.

If there are no crash logs, try clicking on the Refresh button. If you haven’t sold many copies of your app, or if it has been available only for a short time, chances are that you don’t have any crash logs in your iTunes Connect account.

If you did have any crash logs in iTunes Connect, you would see something like this:

Sometimes you won’t see anything in here despite your users reporting a crash. In that case, it’s best to ask them to send you the crash logs if possible.

What Generates a Crash Log?

There are two main situations that can result in a crash log:

Your app violates OS policies.

There are bugs in your app.

iOS policy violations include things such as watchdog timeout at the time of launch, resume, suspend and quit; user force-quit; and low memory termination. Let’s go over these in more detail.

Watchdog Timeout

As you’re probably aware, since iOS 4.x, most of the time when you quit an iOS app, the app isn’t terminated – instead, it’s sent to the background.

However, there are times when the OS will terminate your app and generate a crash log if the app didn’t respond fast enough. These events correspond with the implementation of the following UIApplicationDelegate methods:

application:didFinishLaunchingWithOptions:

applicationWillResignActive:

applicationDidEnterBackground:

applicationWillEnterForeground:

applicationDidBecomeActive:

applicationWillTerminate:

In all of the above methods, the app gets a limited amount of time to finish its processing. If the app takes too long, the OS will terminate the app.

Note: This can easily happen to you if you aren’t performing long running operations (like network access) on background threads. For information on how to avoid this, check out our Grand Central Dispatch and NSOperations tutorials.

User Force-Quit

iOS 4.x supports multitasking. If an app blocks the UI and stops responding, the user can double-tap the Home button from the Home screen and terminate the app. In this case, the OS generates a crash log.

Note: You may have noticed that when you double-tap the Home button, you also get a list of all the applications you’ve run in the past. Those apps are not necessarily running, nor are they necessarily suspended.

Usually an app gets about 10 minutes to stay in the background once the user hits the Home button, and then it gets terminated automatically by the OS. So the list of apps that you see by double-tapping the Home button is only a list of past app runs. Deleting the icons for those apps does not generate any crash logs.

Low Memory Termination

When subclassing UIViewController, you may have noticed the didReceiveMemoryWarning method.

Any app that is running in the foreground has the highest priority in terms of accessing and using memory. However, that does not mean the app gets all the available memory on the device – each app gets a portion of the available memory.

When total memory consumption hits a certain level, the OS sends out a UIApplicationDidReceiveMemoryWarningNotification notification. At the same time, didReceiveMemoryWarning is invoked for the app.

At this point, so that your app continues to run properly, the OS begins terminating apps in the background to free some memory. Once all background apps are terminated, if your app still needs more memory, the OS terminates your app and generates a crash log.

The OS does not generate a crash log for the background apps that were terminated under this circumstance.

Note: Per Apple documentation, Xcode does not add low memory logs automatically. You have to obtain them manually, as described above. However, in my personal experience using Xcode 4.5.2, low memory crash logs were imported automatically with “Process” and “Type” listed as unknown.

It is also worth mentioning that allocating a big chunk of memory in a very short period of time puts a strain on the system memory — even if for a fraction of a second. This, too, can result in memory warning notifications.

Bugs in the Application

As you can imagine, these cause the majority of app crashes, and hence, are behind the generation of most crash logs. Bugs come in seemingly endless varieties.

Later on in this tutorial, you will confront some actual crash logs from a buggy application, and use your powers of deduction to find the culprits and apply some fixes!

A Sample Crash Log

Let’s start by taking a look at a sample crash log, so that you have an idea of what to expect before tackling some real scenarios.

There is a lot of mysterious stuff in this report. :] Let’s go through it section-by-section:

(1) Process Information

This first section gives you some information about the process that crashed.

Incident Identifier is a unique identifier for the crash report.

CrashReporter Key is also a unique key that is mapped to the device identifier. Although it is anonymized, it gives you a very useful piece of information: if you see 100 crash logs from the same CrashReporter Key or a few CrashReporter Keys, it means that the issue might not be a widespread problem, limited to only one or a few devices.

The Hardware Model identifies the device type. If you get a lot of crashes from the same device model, it might mean that your app is not working properly on a specific model. In the log above, the device is an iPhone 4s.

Process is the name of the application. The number in the brackets is the process ID of the application at the time of crash.

The next few lines should be self-explanatory.

(2) Basic Information

This section gives you some basic information about the date and time of the crash, and the version of iOS running on the device. If you have a lot of crash logs coming from iOS 6.0, it might mean that your problem is specific to iOS 6.

(3) Exception

In this section, you can see the type of exception that was thrown at the time of the crash. You also get the exception code and the thread that threw the exception. Depending on the type of crash report, you may get some extra information in this section as well.

(4) Threads backtraces

This section provides the backtrace log for all threads in the app. Backtrace is a list of all active frames at the time of the crash. It gives you a list of function calls when the crash happened. Consider the line below:

2 XYZLib 0x34648e88 0x83000 +8740

It is basically four columns:

The frame number – in this case, 2.

The name of the binary – in this case, XYZLib.

The address of the function that was called – in this case, 0x34648e88.

The fourth column is divided into two sub-columns, a base address and an offset. Here it is 0x83000 + 8740, where the first number points to the file, and the second points to the line of code in that file.

(5) Thread state

This section gives you the values in the registers at the time of crash. You don’t usually need this section, because the backtrace has already given you the information you need to find your problem.

(6) Binary images

This section lists all the binaries that were loaded at the time of the crash.

Demystification with Symbolication

When you first look at the backtrace in a crash log, it doesn’t make sense. You’re used to working with function names and line numbers, not a cryptic location like this:

6 Rage Masters 0x0001625c 0x2a000 +30034

The process of converting from these hexidecimal addresses in the executable code to method names and line numbers is called symbolification.

When you get crash logs off of a device through Xcode’s Organizer window, they are automatically symbolicated after a few seconds. The symbolicated version of the above line is this:

For Xcode to symbolicate a crash log, it needs to have access to the matching application binary that was uploaded to the App Store, and the .dSYM file that was generated when that binary was built. This must be an exact match; otherwise, the report cannot be fully symbolicated.

So, it is essential that you keep each build distributed to users. When you archive your app before submission, Xcode stores your binary. You can find all of your archived applications in the Xcode Organizer under the Archives tab.

Xcode will automatically symbolicate all crash reports that it encounters, if it has the matching .dSYM and application binary that produced the crash report. If you are switching computers or creating a new account, make sure you move over all of those binaries and put them in the right place, where Xcode can find them.

Note: You must keep both the application binary and the .dSYM file to be able to fully symbolicate crash reports. You should archive these files for every build that you submit to iTunes Connect.

The .dSYM and application binary are specifically tied together on a per-build-basis, and subsequent builds, even from the same source files, will not interoperate with files from other builds.

If you use the Build and Archive command, the files will be placed in a suitable location automatically. Otherwise, any location searchable by Spotlight (such as your home directory) is fine.

Low Memory Crashes

Since low memory crash logs are slightly different than normal crash logs, this tutorial will address them separately. :]

When a low memory condition is detected on an iOS device, the virtual memory system sends out notifications asking applications to release memory. These notifications are sent to all running applications and processes, in an effort to reduce the total amount of memory in use.

If memory usage remains high, the system may terminate background processes to ease memory pressure. If enough memory can be freed, your application will continue to run and no crash report will be generated. Otherwise, your app will be terminated by iOS, and a low memory report will be generated.

In low memory crash logs, there are no stack traces for the application threads. Instead, the memory usage of each process is reported in terms of the number of memory pages. (At the time of writing, a memory page was 4KB in size.)

You will see jettisoned next to the name of any process terminated by iOS to free up memory. If you see it next to your application’s name, it means the application was terminated for using too much memory.

When you experience a low memory crash in your app, you should investigate memory usage patterns and how your app responds to low memory warnings. You can use the Allocations and Leaks Instruments to discover memory allocation issues and leaks. If you don’t know how to use Instruments to check for memory issues, you might want to check out this tutorial as a starting point.

And don’t forget virtual memory! The Leaks and Allocations Instruments do not track graphics memory. You need to run your application with the VM Tracker Instrument to see your graphics memory usage.

VM Tracker is disabled by default. To profile your application with VM Tracker, click the Instrument, and check the Automatic Snapshotting flag or press the Snapshot Now button manually.

You will investigate a low memory crash log later on in this tutorial.

Exception Codes

Before you dive into some real-life crash scenarios, there’s one more bit about crash log content that warrants a brief introduction: those funny exception codes.

You can find the exception code in the Exception section of the report – section #3 in the above example. There are a few well-known exception codes that you might encounter often.

Usually, an exception code starts with some text, followed by one or more hexadecimal values, which are processor-specific codes that may give you more information on the nature of the crash. From these codes, you can tell if the application crashed due to a programming error, a bad memory access, or if the application was terminated for some other reason.

Here are some of the more common exception codes:

0x8badf00d: Reads as “ate bad food”! (If you squint your eyes and replace the digits with alphabetic characters. :p) This code indicates that an application was terminated by iOS because a watchdog timeout occurred. Basically, the application took too long to launch, terminate, or respond to system events.

0xbad22222: This code indicates that a VoIP application was terminated by iOS because it resumed too frequently.

0xdead10cc: Read this one as “dead lock”! It indicates that an application was terminated by iOS because it held onto a system resource, like the address book database, while running in the background.

0xdeadfa11: Another cute code – read it as “dead fall”! It indicates that an application was force-quit by the user. Per Apple, force quits happen when the user holds down the On/Off button until “slide to power off” appears, then holds down the Home button. As per Apple Documentation, a force quit event results in a 0xdeadfa11 exception code, presumably because the app became unresponsive.

Note: Remember that terminating a suspended app by removing it from the background task list does not generate a crash log. Once an app is suspended, it is eligible for termination by iOS at any time. So no crash log will be generated.

Swimming Time!

All right! You’ve got all the basic background information you need to dive and swim in the pool of crash logs, and fix some nasty bugs! Here is the basic scenario:

You have just started a new job at Rage-O-Rage LLC. The company has a top-selling app in the App Store, Rage Masters.

Your boss, Andy, comes to you and says that to get you started, he has decided to give you a list of different scenarios under which users have claimed the app crashes. Your job is to go through the scenarios and the symbolicated crash logs provided by users, find the bugs in the app, and fix them.

This means that the app failed to launch in time, and the iOS watchdog terminated the app. Cool! You found the reason, but why (and more importantly, where) is it happening?

Look further down in the log. The convention is to read the backtrace log from the bottom up. The lowest frame (frame 25: libdyld.dylib) is the first call, then frame 24, Rage Masters, main (main.m:16) is called from there, and so on.

You are interested in frames that are related to your app’s code. So ignore system libraries and frameworks. The next line relevant to your code is:

Yep, there it is! A synchronous call to a web service?! On the main thread?! In application:didFinishLaunchingWithOptions:?!! Who wrote this code?!

Network calls on the main thread makes kittens sad.

Anyway, it’s your job to fix it. This call should be asynchronous and, even better, executed from another part of the application, after application:didFinishLaunchingWithOptions: has returned YES.

It might require more extensive changes to move the call elsewhere. So for the moment, just take care of the code so that it doesn’t crash. You can always go back and implement a better design later. Replace the line of offending code from above (and the three lines following it) with an asynchronous version:

The exception code is a SIGABRT. Usually, a SIGABRT exception is raised when an object receives an unimplemented message. Or to put it in simpler terms, there’s a call for a nonexistent method on an object.

Usually this won’t happen, since if you call method “foo” on object “bar”, the compiler will throw an error if the method “foo” doesn’t exist. But when you indirectly call a method using a selector, the compiler might not be able to determine whether or not the method exists on the object.

Back to the crash log. It indicates that the crashed thread is 0. This means that you’re probably looking at a situation where a method was called on an object on the main thread, where the object did not implement the method.

If you continue reading the backtrace log, you see that the only call related to your code is frame 22, main.m:16. That doesn’t help much. :[

That looks OK, so check the storyboard (XIB file) and make sure that the button is hooked up correctly.

There it is! In MainStoryboard.storyboard, the button refers to bookmarkButtonPressed: instead of bookmarkButtonPressed (note the final colon indicating that the method expects a parameter). To fix this, replace the signature of the method above with this:

-(IBAction)bookmarkButtonPressed:(id)sender {// Remain unchanged...}

Of course, you could also simply remove the incorrect connection in the XIB file and reconnect to the method, so that the method signature is correct in the XIB file. Either way works.

And that’s another crash fixed. You’re getting pretty good at this. :]

Scenario 3: Another Bug On the Table

Another user complains saying, “I can’t delete bookmarked masters from the bookmarks view…” And another email about the same issue reads, “If I try to delete a master in bookmarks, the app crashes…”

By now, you are used to these emails not being very helpful. To the crash logs!

Well, this is a UITableViewDataSource method. Huh?! You are pretty sure Apple has implemented this method – you can override it, but it is not like it hasn’t been implemented. Plus, it is an optional delegate method. So the problem isn’t a call to an unimplemented method.

In frame 5, UITableView is calling another method of its own, deleteRowsAtIndexPaths:withRowAnimation: and then _endCellAnimationsWithContext: is called, which looks like an internal Apple method. Then the Foundation framework raises an exception, handleFailureInMethod:object:file:lineNumber:description:.

Putting the above together with the user complaints, it looks as if you are dealing with a buggy deletion procedure in UITableView. Go to Xcode. Do you know where to go? Can you tell from the crash log? It’s line 68 in RMBookmarksViewController.m:

This is a low memory crash log from iOS 6. As discussed earlier, low memory crash logs are different from other types of crash logs because they don’t point to a specific file or line of code. Instead, they paint a picture of the memory situation on the device at the time of the crash.

The header, at least, is similar to that of other crash logs: it provides the Incident Identifier, CrashReporter Key, Hardware Model, OS Version, and some other information.

The next section is specific to low memory crash logs:

Free pages refers to available memory. Each page is approximately 4KB, so in the log above, available memory is about 3,872 KB (or 3.9 MB).

Purgeable pages are those parts of memory that can be purged and reused. In the log above, it is 0KB.

Largest process refers to the application that was using most of the memory at the time of the crash, which in this case is your app!

Processes gives you a list of processes, along with their memory usage at the time of the crash. You are given the name of the process (first column), the unique identifier of the process (second column), and the number of pages being used by the process (third column). In the last column, State, you can see the state of each app. Usually, the app that caused the crash is the app with the frontmost state. Here it is Rage Masters, which was using 28591 pages (or 114.364 MB) – that’s a lot of memory!

Usually, the largest process and the frontmost app are the same, and are also the process that has caused the low memory crash. But you may see some instances where the largest process and the frontmost app are not the same. For example, if the largest process is SpringBoard, ignore it, because SpringBoard is the process that shows the apps on the home screen, the popups that appear when you double tap the home button, etc. and is always active.

When low memory situations happen, iOS sends a low memory warning to the active application and terminates background processes. If the frontmost app still continues to grow in memory, iOS jettisons it.

To find the reason for the low memory issues, you need to profile your app using Instruments. You won’t be doing that here, since we already have a tutorial for that. :] Instead, you’ll take a shortcut and just respond to the low memory warning notification that the app receives to solve this particular crash.

Switch to Xcode and go to RMLollipopLicker.m. This is where the lollipop licker view controller is implemented. Take a look at the source code:

When the user taps the run button, the app starts a background operation, calls lickLollipop a number of times, and then updates the UI to reflect the number of licks. lickLollipop reads a big NSString from a property list (PLIST) and adds it to an array. This data is not crucial, and can be recreated without affecting the user experience.

It’s a good habit to take advantage of every situation where you can purge data and recreate it without adversely affecting the user experience. This frees up memory, making low memory warnings less likely.

So how can you improve the code here? Implement didReceiveMemoryWarning and get rid of the data in lollipops as follows:

Soheil Moayedi Azarpour is an independent iOS developer. He’s worked on iOS applications for clients as well as his own personal apps. You can find him on Twitter, GitHub, Stack Overflow and connect on LinkedIn.