Accessing and Analyzing crash reports in iOS

Shashank Mishra
Mac O’Clock
Published in
6 min readMay 27, 2020

Crashes give a very bad user experience and lead to more negative reviews. Do you wait for users to provide crash information or you use some tools like Xcode organizer or Firebase Crashlytics to track it? Even after getting the crash information(reports), is it easy to read and fix the cause? Let’s discuss and make it a little bit easier to understand.

Crash reports get generated due to the following reasons -

  1. App violating OS policies like Watchdog Timeout*, overconsumption of memory
  2. Impossible for CPU to execute the code like divide by zero calculation
  3. Bugs in the app like setting nil value to a non-optional variable(link), accessing a property of a nil object, index out of bounds for an array, etc.
  4. If a developer is preventing failure by using assert and precondition, then fatal error caused by it.

*Watchdog Timeout — iOS kills the app because it is taking too much time to launch, terminate and respond to the system events.

Accessing Crash reports(logs)

OS captures the backtrace and saves it in the disk. Release build crash logs are unsymbolicated which means a list of binary names and addresses. You can’t read it. Xcode will automatically attempt to locate the appropriate .dSYM and symbolicate the crash.

Symbolicated logs are a human-readable format of crash logs. The pretty function names, file names, and line numbers that you are familiar with.

dSYM stands for debug symbols, i.e. symbols in the crash logs with the names of the appropriate method so that it is readable and makes sense.

You can access crash logs -

Using Device window

Connect your device to mac

Open Xcode > go to Window > Devices and Simulators > View device Logs under Device Information > Select your app and you can see crash logs.

These are “symbolicated” using the local symbol information on your Mac.

On the device under Settings > Privacy > Analytics > Analytics Data

You can see all of the logs that are saved to disk and if you want, you can ask your user to provide the logs.

Using Xcode organiser window

You can download crash logs using the Xcode Organizer for the apps distributed over TestFlight and the App Store. In the right section, for a given crash point you can see the number of unique devices affected and other information.

Open in a project opens the crash log in your project in the debug navigator, and it can be seen alongside your source code.

Third party tools

Firebase, Instabug, Bugsee — For all you need to upload dSYMs to get “symbolicated” logs

Analyzing the crash reports

When you open a crash log file(xyz.crash) in a text editor, you can see a pattern of information. You need to understand each section properly to find out the bug’s cause easily.

Header

It is comprised of two types of information — process information & basic details of time and date. All fields are self-explanatory like used version, app’s path, the model of device, date and time, app launch time, etc.

It is helpful in diagnosing the problem in the right direction, it may be for a specific version and device-related issue.

Process Information
Basic Information

Exception Information

This provides information about nature of the crash. Not all fields will be present in every crash report. Most of the time, after seeing this section, you can start guessing the reason for the crash in a broad way. Although, it won’t be the exact line for the crash.

Exception

Some common types of exceptions are — EXC_BAD_ACCESS, SIGABRT, SIGTRAP. I’ll cover some later, in a section.

Thread Backtrace

Yes, this will be the exact thread trace that you can see during debug mode in Xcode. You have also seen it many times while fixing the crashes in debug mode.

It can be sometimes very informative and you can easily find out the exact line of cause and fix your code. Sometimes, it is not easy because of only internal library class information.

A crash log with the Last Exception Backtrace containing only hexadecimal addresses must be “symbolicated” to produce a usable backtrace.

Thread Backtrace

Binary Images

It contains process binary images that were loaded during the time of termination.

Also, it has a UUID that uniquely identifies the binary image. This value changes with each build of the binary and is used to locate the corresponding dSYM file when “symbolicating” the crash report.

Binary images

Some common Exception types

#1 EXC_BAD_ACCESS (SIGSEGV) or (SIGBUS) It occurs when there is an attempt to access invalid memory. Few known pointers -

  • If objc_msgSend or objc_release are near the top of the backtrace, the process may have attempted to message a deallocated object. You should profile the app with the Zombies to get an idea.
  • If gpus_ReturnNotPermittedKillClient is near the top of the backtrace, the process was killed because it attempted to do rendering with OpenGL ES or Metal while in the background.

#2 EXC_CRASH (SIGABRT) It is an exit of the process.

  • Commonly, some of the exceptions that are not handled by Objective C/C++
  • App Extensions will be terminated with this exception type if they take too much time to initialize (a watchdog situation).

#3 EXC_BREAKPOINT (SIGTRAP) Similar to the previous one. However, it will occur in the absence of any attached debugger(Internal classes).

  • Swift code will also terminate with this exception type if an unexpected condition is encountered at runtimes such as a non-optional type with a nil value or a failed forced type conversion

#4 EXC_BAD_INSTRUCTION (SIGILL) The process attempted to execute illegal or undefined instruction.

#5 SIGQUIT The process was terminated at the request of another process with privileges to manage its lifetime.

  • On iOS, keyboard extensions will get quit by the host app if they take too long to load.

#6 SIGKILL The process was terminated at the request of the system. There are different termination codes(detail), on the basis of which, you can find out the reason.

Low memory crash reports are different from other crash as there is no backtrace for the app threads. There are different ways to identify them.

Important pointers to remember

If you want to minimize the crash on production build,

  • Avoiding launch timeouts
  • Test your app without debugger on a real device and older hardware
  • Do instrument testing(link) of your app before publishing it on production.

Tips

  • Look at code around the crash line. Sometimes, the cause can be in the parent class or some other method.
  • Add Address Sanitizer(will be covered in my next blog) and Zombies to reproduce memory issues

Any queries or suggestions are welcome.

Thanks :)

Reference —

https://developer.apple.com/library/archive/technotes/tn2151/_index.html

https://developer.apple.com/videos/play/wwdc2018/414/

--

--