Analyzing a crash report
Identify clues in a crash report that help you diagnose problems.
Overview
A crash report is a detailed log of an app’s state when it crashed, making it a crucial resource for identifying a problem before attempting to fix it. If you’re investigating a crash that isn’t resolved by the techniques discussed in Identifying the cause of common crashes, you need to do a careful analysis of the complete crash report.
When analyzing a crash report, read the information in all sections. As you formulate the hypothesis about the cause of a crash, ask questions about what the data in each section of the crash report says to refine or disprove the hypothesis. Some clues are explicitly captured by fields in the crash report, but other clues are subtle, and require you to uncover them by noticing small details. Performing a thorough analysis of a crash report and formulating a hypothesis takes time and practice to develop, but is a critical tool for making your app more robust.
Start from the user’s perspective
Find a starting point from the crash report’s information to think about the crash from the user’s perspective to refine the hypothesis. For example, a frame in the backtrace may indicate a particular feature of the app is in use, and you can think about how other information in the crash report relates to that feature.
Group multiple crash reports according to similar and unique details
If you have many crash reports, try organizing them into groups to clarify the source of a crash. If many crash reports contain the exact same information, the problem likely is consistently reproducible, and the common details in the crash reports help you isolate the issue. If you have crash reports that all appear different, but you suspect the underlying cause is the same, pay attention to any detail that looks unusual. Place any crash reports with unusual details into their own group. By grouping crash reports based on similar and dissimilar details, you sometimes uncover insights into the cause of a crash that aren’t visible when looking at crash reports individually.
Check the header to identify the crash environment
If you have multiple crash reports that are similar, use the header information to help understand the scope of the problem, and to target the specific operating system versions and devices you need to reproduce the problem. Some questions that can help refine your hypothesis about the crash are:
Does the crash occur in multiple versions of your app, or just one?
Does the crash occur on multiple versions of the operating system?
Is the crash from only one type of device, such as an iPad but not an iPhone?
Did the crash originate from your main app, or from one of your app extensions?
Is the crash from a TestFlight beta of your app?
Are all of the crashes from the same app thinning variant? Are you able to reproduce the crash if you export the specific app variant? To export a specific app variant, see Distributing your app for beta testing and releases.
What device model did the app crash on? How much testing did you do on a device with similar capabilities?
Do multiple users experience the crash, or only a small number of unique users? Use the
CrashReporter Keyor theBeta Identifierfields to determine this.How long was the app running before it crashed? Use the
Date/TimeandLaunch Timefields to determine this.
If you’re unfamiliar with a specific field or its value in this section of crash report, consult Header.
Identify the exception information
Every crash report records exception information that shows the exact mechanism by which the app’s process terminated. Termination is always the last step in error handling, but it starts when an unrecoverable condition occurs in the app or the frameworks it uses. For example, the app may request termination directly, such as by calling abort(). As a different example, the operating system may terminate the process to enforce a system policy, such as through a watchdog that ensures app responsiveness.
Exception information narrows the sources for the crash you’re analyzing, and helps identify the clues you look for in other sections of the crash report. See Understanding the exception types in a crash report for details on the specific exception type in the crash report you’re analyzing, and then answer the following questions:
What is the exception type? What category of error does the exception define?
What thread triggered the crash? What relationship do you see between the frames in the crashed thread’s backtrace and the information communicated by the exception type?
What types of underlying issues does the exception type rule out? For example, the
EXC_BAD_ACCESSexception rules out that a crash is due to an uncaught language exception.Are there any additional codes in the
Termination Reasonfield? What does the code mean?Does the exception type indicate specific diagnostic tools are useful for discovering the issue?
Is the exception related to a specific type of system resource?
If you’re unfamiliar with a field in this section, see Exception information.
Look for diagnostic messages
For certain types of problems, a crash report may contain additional diagnostic information between the Exception Information section and the Backtraces section. This information is directly related to the exception type.
Based on the exception type, is the crash due to an uncaught language exception? If so, what additional information about the API throwing the exception is in the message? See Addressing language exception crashes for additional information.
Based on the exception type, is the crash due to a memory access issue? See Investigating memory access crashes for how to decode the provided
VM Region Info.Is there a
Termination Descriptionfield indicating the involvement of a specific part of the operating system? Is there an additional code in theTermination Reasonfield? What hints to the source of the problem does the message provide?Is there an
Application Specific Informationfield? Is there a specific API named in that message? Where do you use that API in your code?
If you’re unfamiliar with a field in this section, see Diagnostic messages.
Read the backtraces
The backtraces in a crash report show the exact methods executing at the time of the crash—see Backtraces for a breakdown of what each column in this section means. As a starting point, look at the crashed thread, as well as the Last Exception Backtrace if one is present. Answer these questions about the backtrace:
What function is this thread serving in the app? Is it the main thread or another thread with a specific purpose?
Was a language exception thrown? What does the
Last Exception Backtraceshow?What parts of the app use this thread and the functions that appear in this thread’s backtrace?
What mix of binaries in your app and Apple’s system frameworks are in the backtrace?
Even if the functions in the backtrace aren’t ones you directly call, they contain key clues. For example, this backtrace contains only system frameworks except for the app’s main function, but the crash is due to an invalid popover configuration in an iPadOS app:
Last Exception Backtrace:
0 CoreFoundation 0x1a1801190 __exceptionPreprocess + 228
1 libobjc.A.dylib 0x1a09d69f8 objc_exception_throw + 55
2 UIKitCore 0x1cd5d0af0 -[UIPopoverPresentationController presentationTransitionWillBegin] + 2739
3 UIKitCore 0x1cd5d9358 __71-[UIPresentationController _initViewHierarchyForPresentationSuperview:]_block_invoke + 2175
4 UIKitCore 0x1cd5d6ea4 __56-[UIPresentationController runTransitionForCurrentState]_block_invoke + 463
5 UIKitCore 0x1cdc5c0ac _runAfterCACommitDeferredBlocks + 295
6 UIKitCore 0x1cdc4abfc _cleanUpAfterCAFlushAndRunDeferredBlocks + 351
7 UIKitCore 0x1cdc77a6c _afterCACommitHandler + 115
8 CoreFoundation 0x1a179250c __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 31
9 CoreFoundation 0x1a178d234 __CFRunLoopDoObservers + 411
10 CoreFoundation 0x1a178d7b0 __CFRunLoopRun + 1227
11 CoreFoundation 0x1a178cfc4 CFRunLoopRunSpecific + 435
12 GraphicsServices 0x1a398e79c GSEventRunModal + 103
13 UIKitCore 0x1cdc50c38 UIApplicationMain + 211
14 MyGreatApp 0x10079600c main (in MyGreatApp) (AppDelegate.swift:12)
15 libdyld.dylib 0x1a124d8e0 start + 3Frames 3 and 4 provide a clue that this crash relates to presenting a view controller, and frame 2 is a clue that the app is presenting a popover. This information narrows down what parts of your app’s code to focus on, even when no code from the app is in the backtrace.
You can frequently determine the purpose of a thread based on the bottom frames in the thread’s backtrace. An app’s main thread has NSApplicationMain(_:_:) or UIApplicationMain(_:_:_:_:) in the bottom frames. Threads created through the Dispatch framework have start_wqthread in the bottom frames. As you look closer at the crashed thread’s backtrace, consider whether your app appears in a coherent state that matches your expectations about how your app functions:
Should the code from the app be running on this specific thread?
Is the crashed thread a background thread?
Do any of the backtraces show the app manipulating UI elements on any thread except the main thread? Have you tested your app with the
Main Thread Checkerenabled?If your code uses an API that takes a completion handler, does that API guarantee the specific queue the completion handler uses? Is your code expecting that queue?
If your code uses an API where you provide a DispatchQueue to the API, does the crash report show you’re using the queue you expect?
In addition to the backtrace for the crashed thread or language exception, other thread backtraces provide additional clues about what state the app is in. These clues are subtle:
Do any of the other threads help indicate what state the app is in? For example, if you see threads with frames from the Contacts framework and you only access contacts in one part of your app, you can focus your investigation on the crash to that part of the app.
Do any of the other thread backtraces contain frames related to the frames in the crashed thread’s backtrace? What does that say about the state of the app?
Are there many threads that have similar state, such as the same set of functions from your app before frames that are waiting on system resources?
In some types of crashes, the crashed thread’s backtrace doesn’t consistently contain the source of the issue. Addressing watchdog terminations describes situations where this happens for watchdog terminations, and Investigating memory access crashes describes this scenario for memory corruption crashes.
Understand the crashed thread’s registers
Analysis of most crash reports don’t need to consider the register state. However, if you’re investigating a difficult memory access issue, the registers provide information not found elsewhere in the crash report.
Is the memory access a memory fetch, or an instruction fetch?
Do the program counter, link registers, and stack pointer register contain valid addresses in your program’s address space?
If you use
atosto symbolicate the address in the link register, what function is it? Does that function jump to other code through function pointers? Symbolicate the crash report with the command line describes how to useatos.
Identify the type of memory access that caused the issue describes how to use these questions to diagnose a memory access crash.
Verify your frameworks are present in the binary images
Use the Binary Images section of the crash report to take stock of the frameworks your app loads. You can identify the frameworks in your app by the file paths.
What frameworks had the app loaded at the time of the crash? Are any frameworks provided by your app missing?
If a framework is missing, were you expecting the system to automatically load the framework when the app launched, or do you manually load it by calling
dlopen(_:_:)?How many frameworks are from your app? If you’re investigating a watchdog termination, a high number of frameworks inside your app can consume a significant part of the app’s launch time budget.
For the meaning of each column in this section, see Binary images.