Investigating Crashes with Aardvark

Crashes can be one of the most straightforward types of issues to debug. There are many different categories of crashes that can happen on iOS - everything from bad memory access to preconditions that crash when the app enters an unexpected state - with a large variety of causes. But one thing these crashes all have in common is that they come with debugging data in the form of a crash report, which contains information about the cause of the crash and a backtrace of each thread in the process.

Incident Identifier: 5C6A7EC9-E763-4BE6-80B9-A0EC949A2B62
Hardware Model:      iPhone10,6
Process:         AardvarkCrashRep [6720]
Path:            /private/var/containers/Bundle/Application/F2DA0515-B31F-478A-BB77-DDB18765FFD0/
Identifier:      com.squareup.AardvarkCrashReporterDemo
Version:         1.0 (1)
Code Type:       ARM-64
Parent Process:   [1]

Date/Time:       2021-03-02 04:21:28 +0000
OS Version:      iPhone OS 13.3 (17C54)
Report Version:  104

Exception Type:  SIGTRAP
Exception Codes: #0 at 0x1a305f168
Crashed Thread:  0

Thread 0 Crashed:
0   libswiftCore.dylib                  0x00000001a305f168 <redacted> + 380
1   libswiftCore.dylib                  0x00000001a2e62210 $ss12_ArrayBufferV37_checkInoutAndNativeTypeCheckedBounds_03wasfgH0ySi_SbtF + 196
2   libswiftCore.dylib                  0x00000001a2e64fa4 $sSayxSicig + 84
3   AardvarkCrashReporterDemo           0x0000000104f45978 $s25AardvarkCrashReporterDemo14ViewControllerC07triggerB033_C7A9EC1A6962A633C365EE02C1845969LLyyF + 72
4   AardvarkCrashReporterDemo           0x0000000104f459ac $s25AardvarkCrashReporterDemo14ViewControllerC07triggerB033_C7A9EC1A6962A633C365EE02C1845969LLyyFTo + 32
5   UIKitCore                           0x0000000199c32e50 <redacted> + 96
19  CoreFoundation                      0x0000000195b3723c <redacted> + 1080
20  CoreFoundation                      0x0000000195b36adc CFRunLoopRunSpecific + 464
21  GraphicsServices                    0x000000019fabc328 GSEventRunModal + 104
22  UIKitCore                           0x0000000199c31ae0 UIApplicationMain + 1936
23  AardvarkCrashReporterDemo           0x0000000104f4b288 main + 88
24  libdyld.dylib                       0x00000001959c0360 <redacted> + 4

Thread 1:
0   libsystem_kernel.dylib              0x00000001959b6a7c __workq_kernreturn + 8
1   libsystem_pthread.dylib             0x00000001958dd760 start_wqthread + 8


A sample of a crash report on iOS. The report has been truncated for readability.

Many teams send these crash reports to a central source to be aggregated, making it easier to triage reports and determine which crashes are happening the most often. They can also be collected from devices directly, making it easier to debug crashes that you experience while testing the app locally, although sending the reports to a crash reporting service is often preferred since it can happen automatically on the next app launch.

Apple provides extensive documentation for how to interpret these crash reports. For many types of crashes, these reports can clearly highlight the problem area and guide engineers to a solution quickly. But sometimes the crash report alone doesn’t provide enough context to reproduce and debug the issue locally.

Connecting Crash Reports and Bug Reports

When crash reports aren’t enough, bug reports can save the day. A bug report that contains detailed reproduction steps and other information about the application state can make it clear how the app got to the point where it crashed. The combination of the bug report and the crash report paints a more complete picture of what happened.

Aardvark, our open source bug reporting framework for iOS, makes it dead simple to file actionable bug reports. By attaching additional metadata such as a device identifier and timestamps to both reports (which is supported by Aardvark and many popular crash reporting services), you can search for the crash report associated with a given bug report.

Sometimes, though, you get a bug report saying that the app crashed, but it’s unclear how these bug reports map to the crash reports you’ve collected. This is especially common if the crash isn’t a result of a direct user action, such as when the crash is caused by code executing on a background thread. You can often determine which crash report corresponds to the bug report through process of elimination - reading through each crash report and determining whether the root cause could be related to the described bug - but this can be a very time consuming process if there are a large number of crashes.

Now there’s a better way.

Bringing Crash Reports into Aardvark

Introducing AardvarkCrashReport, a new open source framework that provides utilities to embed a crash report directly in your Aardvark bug report. The time of manually tracking down the corresponding crash report is over. AardvarkCrashReport makes it straightforward to add a check when your app launches to determine whether the previous session terminated in a crash and, if so, prompt the user to file a bug report.

A screenshot from an internal Cash App build showing a prompt to file a bug report for a detected crash.

AardvarkCrashReport also supports attaching live reports to aid in debugging other types of bugs, for example those related to performance issues caused by background threads. Now you’ll have access to even more information about the state of your app when the bug report was filed, making the already-powerful bug reports provided by Aardvark even easier to investigate.

If you already use Aardvark in your app, it’s simple to integrate AardvarkCrashReport’s lightweight utilities into your existing bug reporting setup. And if you don’t already have a bug reporting tool built into your app, there’s never been a better time to adopt Aardvark.