Own Your Crash Logs with PLCrashReporter: Part 1
Welcome to the PLCrashReporter blog post series. For those unfamiliar with this crash reporting library, it is defined as follows:
PLCrashReporter is a reliable open source library that provides an in-process live crash reporting framework for use on iOS, macOS, and tvOS. The library detects crashes and generates reports to help your investigation and troubleshooting with the information of application, system, process, thread, etc. as well as stack traces.
In the upcoming posts we will first dive deep into crashes by covering:
- Crash fundamentals
- Obtaining crash logs
- Creating crash logs
- POSIX signals
- Mach exceptions
- Async safety
Then, we will provide you with a step-by-step tutorial on how to get PLCrashReporter up and running.
Who This Series is For
You’re curious about crashes – What even is a crash? Obvious questions can be difficult to answer. If there’s a chance you might get asked this in an interview, you’ll be better prepared to answer it after reading.
You want to know how crash logs are created – You’re equally horrified and mesmerized by whatever dark art must be summoned to intercept a crash and finagle it into some useful output.
You’re looking for an alternative to third-party crash reporting services – There are a number of reasons why you wouldn’t want to use a paid crash reporting service. Your institution might not permit closed-source libraries, which most (perhaps all?) third-party crash reporters require. Third-party services cost money, which is a deal-breaker for some small projects. Some have dubious privacy policies that might permit them to gather more than just crash logs. Or a Facebook bug might bring down their infrastructure, leaving your app in the lurch.
You need an alternative to TestFlight and App Store crash reporting for internal distributions – TestFlight gathers crash logs for you, which appear in the Xcode Organizer, but these are, naturally, only available for TestFlight builds. If you’re using Enterprise or Ad Hoc distribution, you need another way to gather development-build crash logs.
If none of these describe you, well, you should keep reading anyway, because what have you got to lose?
It seems obvious, but let’s ask the question: what is a crash? The video for WWDC 2008 session 414 says it well:
A crash is a sudden termination of your app when it attempts to do something that is not allowed.
There are a number of types of crashes. Let’s take a look at them.
First, there are assertions and preconditions. These occur when code, either in your own source or in libraries that your source is using, deliberately stops the process. For example:
- Force unwrapping a Swift.Optional – The Swift compiler’s implementation of the
!postfix operator will terminate the process if code attempts to unwrap a
- Out-of-bounds Swift.Array access – The standard library will trigger a precondition if the code attempts to access an array using an invalid index.
- Swift arithmetic errors – Some arithmetic errors, like integer overflow, will cause the app to terminate.
- Uncaught NSExceptions – Exceptions raised by Objective-C code will cause the app to terminate unless they are explicitly caught. This is particularly pernicious for Swift code since it is unable to catch NSExceptions.
- Your assertions – Your own assertions and preconditions can cause the app to crash, too, if you’re using any.
Next, there are terminations by the operating system, i.e. something that results in your process receiving the
SIGKILL signal. We’ll delve into signals more in the next post. Some examples of these terminations:
- Watchdog timeout – If your app occupies the main thread without returning for too long, typically for ten seconds or more, then the OS will terminate your application.
- Device overheating – If the device temperature is climbing too high, the OS will start lopping off the greediest processes to cool down the CPU.
- Out of memory – If the process attempts to consume more memory than the OS is willing to provide it, the app will be terminated.
- Invalid code signature – Ever tried launching an app with an expired provisioning profile? Anyone? Anyone? It is terminated the moment you launch it.
Last, but certainly not least, there are memory errors. Our old pal
EXC_BAD_ACCESS. Memory errors are some of the most troublesome issues to debug, particularly when multi-threading or manual reference-counting is involved. Some examples:
- Over-releasing a reference-counted object – Sending the
objc_releasemessage to an object that already has a zero reference count is not allowed.
- Dereferencing a null pointer – Doing so has undefined behavior according to C standards, and in Objective-C will cause a crash due to a memory violation.
- C array buffer overflow – Reading or writing beyond the bounds of an array is undefined behavior. By itself, it won’t crash, but it could lead to other memory access violations. Note that this differs from the assertions provided by
NSArray, which proactively look for out-of-bounds accesses.
- Stack overflow – No, not that stack overflow. This stack overflow:
Stack overflow refers specifically to the case when the execution stack grows beyond the memory that is reserved for it. For example, if you call a function which recursively calls itself without termination, you will cause a stack overflow as each function call creates a new stack frame and the stack will eventually consume more memory than is reserved for it. (Nick Meyer, “What is the difference between a stack overflow and buffer overflow?”)
Obtaining Crash Logs
If an app crashes in the woods, but there was no log left behind, would anyone be able to fix it? A detailed, accurate crash log is necessary to correctly diagnose a crash. So what transpires between the moment a process is notified of an impending crash and when the crash log is written to disk? There are, on Apple platforms, two mechanisms that produce crash logs:
- Out-of-process – The operating system’s own crash handler will capture information about a crashed process and will encode that information in a
.crashlog. Those logs are stashed in an OS-specific location for later use. This all happens outside of your app’s process. If the crashed app was distributed via Test Flight or the App Store, the crash log will also be uploaded to App Store Connect servers and can be viewed in the Xcode Organizer.
- In-process – In addition to the operating system’s handler, your app can register its own crash handler. Unlike the system handler, however, your crash handler runs in the same process as the crashed code.
The differences between capturing crashes in-process and capturing them out-of-process is more than superficial. There are profound risks and challenges to overcome to safely and reliably capture crashes in-process. Like all third-party crash reporting tools, PLCrashReporter is obligated to run in-process. These challenges will be a recurring theme across future posts in this series.
That’s it for the first post in the series! Next, we will discuss crash log creation, POSIX signals, Mach exceptions, and async-signal safety.
 From Plausible Labs website: “PLCrashReporter was the first crash logging solution available for iOS. Today, PLCrashReporter is used by most 3rd-party crash logging services for iOS and macOS, and can be found reporting crashes in the Netflix, Amazon Prime Video, Dropbox, Yahoo Mail, Kindle, and Chase Mobile iOS apps, as well as tens of thousands of other applications. With Microsoft’s purchase of HockeyApp in 2014, PLCrashReporter sits at the core of Microsoft’s crash logging solutions for Apple’s platforms. And in October 2019, Plausible and Microsoft reached an agreement to transfer stewardship of PLCrashReporter to the App Center team at Microsoft, where it will continue to be developed as an open-source project.