When a user's iPhone crashes, Apple captures a .ips crash log and stores it on the device. That log is the only post-mortem evidence you get—no debugger attached, no console output, just a snapshot of the moment it failed. How to read iOS crash logs is not optional: it's your lifeline for debugging production crashes you can't reproduce locally. The format looks intimidating at first, but once you know what each section means and why it's there, a crash log tells a complete story: what failed, where it failed, and why.
This guide walks you through a real iOS crash log from header to footer, decodes every field, explains the hex codes and exception types, and shows you how to symbicate the raw addresses back into readable function names. By the end you'll transform an unreadable blob of memory addresses into a actionable stack trace.
The header: Incident ID and process info
Every iOS crash log starts with metadata that identifies the crash and the app that crashed. Here's a typical header:
Incident Identifier: E621E8F0-C1C5-48F3-9B32-C0D44C0F7A9E
CrashReporter Key: D7C4C2A1F9B3E2D5C8A7F6E5D4C3B2A1
Hardware Model: iPhone14,2
Process: MyApp [12345]
Path: /private/var/containers/Bundle/Application/ABC123.../MyApp.app/MyApp
Identifier: com.example.MyApp
Version: 2.1.0 (47)
Code Type: ARM64
OS Version: iOS 17.2 (21C62)
Build Version: MyApp 2.1.0-build.47
Let's decode each:
- Incident Identifier — a unique UUID for this crash event. File this away if you need to search your crash database later.
- Hardware Model —
iPhone14,2is an iPhone 14 Pro;iPhone15,3is an iPhone 15 Pro Max. Useful to know if crashes are device-specific. - Process — the app name and its process ID.
[12345]is the kernel's PID for this run. - Version — the build number (
2.1.0marketing,47build number). Critical: crashes from different builds may have different symbol locations. - Code Type: ARM64 — the CPU architecture. Modern iPhones are all ARM64. This matters for symbolication: your dSYM must match.
- OS Version — the iOS version. Crashes sometimes occur on specific iOS versions, not others.
Exception Type and Exception Code
Below the header comes the moment of truth: what actually broke?
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000018
Exception Codes: 0x0000000000000001, 0x0000000000000018
Termination Reason: Namespace SIGNAL, Code 11
This is where you learn what went wrong. iOS exception types follow a Unix pattern. Here are the most common:
EXC_BAD_ACCESS (SIGSEGV / SIGBUS)
The app tried to read or write memory it doesn't own. Usually an over-released object (you called release or dealloc too many times) or a dangling pointer. The exception code tells you the address: 0x0000000000000018 is a low address, often a null pointer dereference.
EXC_BREAKPOINT (SIGTRAP)
A runtime assertion or unwrap failure in Swift. Common causes: force-unwrapped nil (try! or !), failed force cast (as!), array index out of bounds, integer overflow on a trap. This one often looks intentional because it is—Swift's runtime is stopping you before undefined behavior happens.
EXC_CRASH (SIGKILL)
The system killed your app. This is the worst one to debug because the app didn't crash on its own—the system terminated it. Causes include:
- Watchdog timeout (Exception Code:
0x8badf00d, pronounced "ate bad food") — the main thread was blocked for over 20 seconds. The app was unresponsive. - Jetsam / Memory pressure (Exception Code:
0xd00dfeed) — the system ran out of memory and killed the app to free up RAM. - File lock timeout (Exception Code:
0xdead10cc, "dead lock") — the app was holding a file lock in the background. - Daylight Saving Time issue (Exception Code:
0xbaaaaaad, "bad bad bad") — less common now, but old codebases sometimes hit this on DST transitions.
EXC_BAD_INSTRUCTION (SIGILL)
An illegal instruction or an unhandled exception. Often triggered by fatalError() or an assertion in debug code. The app called a method that doesn't exist or a jump landed in the middle of nowhere.
The exception code is a key hint. Memorable hex codes like 0x8badf00d are intentional—Apple engineers use them so you can spot the root cause at a glance even in a wall of logs.
Triggered by thread
After the exception info, the log tells you which thread crashed:
Triggered by Thread: 0
Always start here. Thread 0 is typically the main thread. If the crash is on Thread 0, the UI was affected. If it's on Thread 5, it's a background operation. The log will then show the backtraces for all threads, but the triggered thread is the one you focus on first.
The backtrace: which function called which?
Below the thread triggers comes the stack trace. Here's a realistic example:
Thread 0 Crashed:
0 libsystem_c.dylib 0x0000000182514ab8 __memcpy_chk + 204
1 MyApp 0x0000000104c2a1f4 MyApp + 2765812 (DetailViewController.swift:42)
2 MyApp 0x0000000104c29e10 MyApp + 2761232 (DetailViewController.swift:39)
3 UIKit 0x00000001841c5c34 -[UIViewController viewDidLoad] + 124
4 UIKit 0x00000001841c6110 -[UIViewController loadViewIfRequired] + 200
5 UIKit 0x00000001841c6234 -[UIViewController view] + 44
6 UIKit 0x0000000184234a88 -[UIWindow makeKeyAndVisible] + 44
7 UIKitCore 0x0000000187654ab4 UIApplicationMain + 2048
8 MyApp 0x0000000104c21200 MyApp + 2727424 (main.swift:5)
9 libdyld.dylib 0x00000001824fb050 start + 16
Each line is a frame. Read from top to bottom:
- Frame 0 is where the crash happened: inside
__memcpy_chk(a memory copy safety check). - Frame 1 shows MyApp crashed in
DetailViewController.swiftline 42. - Frames 2–6 are UIKit machinery that led to your code.
- Frame 8 is your
main()entry point. - Frame 9 is the OS's process starter.
The key insight: your crash is in Frame 1, DetailViewController.swift:42, even though the deepest frame is in a system library. The system library is just the mechanism; your code provided the bad input.
Notice the format: MyApp + 2765812. That 2765812 is an offset from the load address of the binary. This is where symbolication comes in.
Focus on the first frame that is your code—the one with your app name, not system frameworks. That's the culprit. Frames below it in system code are just the path your bad data took.
Binary Images: where the symbols live
At the bottom of the crash log is the binary images section. This maps memory addresses to loaded frameworks:
Binary Images:
0x104c20000 - 0x104e7ffff MyApp arm64 <40f3c89d201c3e3d98f1d2a4b5c6d7e8> /private/var/containers/Bundle/Application/ABC123.../MyApp.app/MyApp
0x182400000 - 0x1824fffff libobjc.A.dylib arm64 <7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d> /usr/lib/libobjc.A.dylib
0x1840c0000 - 0x1856fffff UIKit arm64 <c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6> /System/Library/Frameworks/UIKit.framework/UIKit
...
Each line tells you:
- Address range: where the library is loaded in memory (e.g.,
0x104c20000 - 0x104e7fffffor MyApp). - Library name:
MyApp,UIKit,libobjc.A.dylib. - Architecture:
arm64for all modern iPhones. - UUID: the long hex string like
<40f3c89d201c3e3d98f1d2a4b5c6d7e8>. This UUID must match your dSYM file.
The load address 0x104c20000 is critical for symbolication. When you see MyApp + 2765812, you add the offset to the load address: 0x104c20000 + 2765812 = 0x104c2a1f4. That's the actual memory address where the crash occurred.
Symbolication: turning addresses into names
Raw crash logs show memory addresses. MyApp + 2765812 means nothing. Symbolication translates those addresses into function names, file paths, and line numbers—the stuff that's actually useful for debugging.
Your app's symbols live in a dSYM bundle. When you build your app in Xcode, it generates MyApp.dSYM, a directory structure that maps every address in your binary to the original source code. The dSYM contains debugging symbols (hence the name). It's critical and often forgotten.
Why symbols disappear
When you archive your app for release, Xcode strips symbols from the app binary to reduce size. This is good for end users (smaller download), bad for debugging (you can't read the crash log). The symbols are kept in the dSYM, which you must save for every release. Lose the dSYM, and that crash log is permanently unreadable.
Matching dSYM to crash log
The dSYM UUID must match the Binary Images UUID. Check your dSYM's UUID with dwarfdump:
dwarfdump --uuid MyApp.dSYM/Contents/Resources/DWARF/MyApp
You'll see:
UUID: 40f3c89d-201c-3e3d-98f1-d2a4b5c6d7e8 (arm64)
Compare this to the crash log's Binary Images:
0x104c20000 - 0x104e7ffff MyApp arm64 <40f3c89d201c3e3d98f1d2a4b5c6d7e8>
They must match (note: dwarfdump adds dashes; the crash log uses continuous hex). If they don't, the dSYM is from a different build and symbolication will fail.
Symbolicating via Xcode
The easiest way: drag the .ips crash log into Xcode's Organizer (Window → Organizer → Crashes). Xcode matches it to your archived builds, finds the dSYM, and symbolicates automatically. If you see function names and line numbers in the backtrace, Xcode already did the work.
Manual symbolication with atos
If you have the dSYM and know the load address and target address, you can symbolicate manually using atos:
atos -o MyApp.dSYM/Contents/Resources/DWARF/MyApp \
-arch arm64 \
-l 0x104c20000 \
0x104c2a1f4
Output:
-[DetailViewController loadData] (DetailViewController.swift:42)
The -l flag is the load address from Binary Images. The final argument is the target address from the backtrace. atos prints the function name, file, and line.
For multiple addresses, script it:
#!/bin/bash
LOAD_ADDR=0x104c20000
DSYM="MyApp.dSYM/Contents/Resources/DWARF/MyApp"
while read addr; do
atos -o "$DSYM" -arch arm64 -l "$LOAD_ADDR" "$addr"
done
Then pipe crash addresses one per line.
Where to get crash logs
Xcode Organizer
When a user runs your beta or a TestFlight build, Xcode collects crashes in the Organizer. Go to Window → Organizer → Crashes, select your app, and you'll see a list. Click a crash to view the symbicated backtrace (if Xcode found the dSYM).
Device Settings
A user can export crash logs from their device: Settings → Privacy & Security → Analytics & Improvements → Analytics Data. They'll see a list of crashes. They can share a .ips file via email or AirDrop. Then you open it in Xcode.
Distribute via App Store or TestFlight
Crashes from App Store and TestFlight users appear in Xcode Organizer automatically (if they've opted into analytics sharing). This is the most reliable channel for production data.
Store-distributed builds and dSYM management
If your app is built by App Store Connect (using bitcode or standard archiving), the binary may be recompiled or re-signed after upload. This can change the UUID, meaning your local dSYM won't match. Apple provides the correct dSYM in Xcode when you download the crash log. Always download the dSYM from App Store Connect for store-built releases, not your local build archive.
Losing dSYM files is a common mistake. Store them alongside your release builds, versioned by release number. If you can't match a dSYM to a crash, that crash log stays unreadable forever.
Using an error tracker for iOS crashes
Reading crash logs manually works, but it doesn't scale. If you ship to millions of users, you'll get thousands of crashes. Manual triage is impossible. That's where crash reporting comes in: automated tools capture crashes and group identical ones together.
LightTrace's iOS support uses the Sentry Cocoa SDK (sentry-cocoa). When your app crashes, Sentry captures the exception, symbolication details, and context—then sends it to LightTrace. LightTrace groups crashes by fingerprint, so you see MyApp crashed 1,247 times this week instead of 1,247 separate log files. You can filter by iOS version, device model, or user, and every crash shows a symbicated stack trace with links to the exact line on GitHub.
LightTrace also shows you crash-free rate and release health—how stable each version is—so you know when a release is regressing. You stay on top of exceptions as they happen, not weeks later.
Common iOS crash patterns
Knowing what to look for speeds up debugging. EXC_BAD_ACCESS with address 0x0 is almost always a nil dereference or use-after-free in Objective-C. EXC_BREAKPOINT in Swift almost always means a force-unwrap of nil—add a guard. EXC_CRASH with 0x8badf00d means the main thread hung; profile your view controller's viewDidLoad and network calls to find the bottleneck.
Learn NSRangeException—a common crash when accessing an array out of bounds. Understand Swift's error handling—proper error handling prevents crashes that trap or crash hard. If you ship SwiftUI, watch for retain cycle issues in closures, which can cause memory pressure crashes.
Start tracking errors in minutes
Capture iOS crashes automatically: add LightTrace's Sentry SDK to your app, point it at LightTrace, and every crash lands in a fast dashboard with symbicated stacks, grouped by issue, and filtered by version and device.
Reading an iOS crash log is detective work, but the clues are all there. Master the format, learn to symbolicate, and keep your dSYM files safe. Your users will thank you when you fix their crashes fast.