KeyMob
All articles

How to actually read an iOS crash log

4 min readCrashesDebugging

A crash log can look unreadable at first. It is dense, and most of it is irrelevant to your bug. This article covers the order in which to read one, and the few lines that tell you almost everything.

Get the log from the right device

Crash reports are written to the device that crashed, not to a similar device or the one on your desk. The person who hit the crash has the report on their phone.

There are three ways to get it:

  • The device’s own Settings → Privacy & Security → Analytics & Improvements → Analytics Data, a long list of cryptically named files that is hard for non-developers to navigate.
  • A crash-reporting SDK, which reports only what it was built to report.
  • A desktop tool that reads the reports directly from the device.

Whichever you use, get the actual file. A screenshot of the alert is not a crash log.

Symbolicate first

An unsymbolicated report contains addresses:

0 MyApp 0x00000001045a3b2c 0x1044f4000 + 719148

After symbolication, the same line shows a function name and a line number. This only works if the symbols match the exact binary that crashed: same version, same build number. Keep the dSYM for every build you release. Without it, your frames stay hexadecimal, and no tool can fix that afterwards.

System frames are symbolicated with the OS symbols for that iOS version. This is why a report from an iOS version that nobody on the team has installed is sometimes only partly symbolicated.

Read it in this order

1. Exception type

The most informative line in the file.

Type What happened
EXC_BAD_ACCESS (SIGSEGV) Accessed memory that was not valid, such as a released object or a bad pointer
EXC_CRASH (SIGABRT) Uncaught exception, failed assertion, or a deliberate abort()
EXC_BREAKPOINT (SIGTRAP) Swift runtime trap, such as force-unwrapping nil, an index out of range or a failed as!
EXC_RESOURCE Exceeded a resource limit, usually memory or sustained CPU
0x8badf00d Watchdog: too slow to launch or respond, so iOS terminated it
0xdead10cc Held a file lock or shared resource while suspended

The last two codes are hexadecimal wordplay (“ate bad food” and “dead lock”). They are also the two most commonly misdiagnosed entries, because neither is a crash in your code.

2. Termination description

When present, this line names the assertion or exception directly. Read it before you look at any stack. Many “impossible” crashes are fully explained by this one line.

3. The crashed thread

It is labeled in the report. Ignore the other threads; they were idle.

4. Your frames

Look down the crashed thread for your own binary. The system frames above it usually show only the path in, such as UIApplicationMain, the run loop and the delegate call. Start reading at the topmost frame in your own code.

Two entries that are not crashes

Jetsam reports. The process was terminated for using too much memory. The stack shows where it happened to be, not what caused it. The bug is not at the top of the trace; it is the memory trend over the preceding minutes. Cross-check with a memory capture. The typical pattern is a staircase that never comes back down, ending in a termination.

Watchdog terminations. The main thread was blocked long enough that iOS terminated the app. The backtrace shows the block, usually a synchronous file read, a lock or a network call on the main thread, but not always why it took so long. 0x8badf00d at launch almost always means too much work in application(_:didFinishLaunchingWithOptions:).

When the log does not explain it

Check these three things, in order:

  1. The log just before the crash. The app’s last few log lines are often more useful than the stack. What was it doing?
  2. Other activity at the time. Check usage history around that timestamp. Was something else putting heavy load on the device?
  3. The device itself. iOS version, free storage and battery health. “Only on that phone” often means “only when storage is nearly full”.

Habits that make this easier

  • Keep every dSYM. Archive them with each build. This is the one thing you cannot fix afterwards.
  • Log a breadcrumb for each screen. It is cheap, and it turns an unreadable stack into a clear sequence of events.
  • Do not catch and ignore errors. A crash with a report is better than a silently corrupted state.
  • Treat jetsam reports as memory bugs, not as crashes. They need a different investigation.