I’m pleased to announce the first release candidate of PLCrashReporter 1.2. Plausible CrashReporter provides an open source in-process crash reporting framework for use on both the iPhone and Mac OS X, and is used by the preeminent crash reporting and analytics services for Mac OS X and iOS.
This release adds support for more precise stack unwinding on ARM64 using DWARF eh_frame and Apple compact unwind metadata. As far as I’m aware, PLCrashReporter is the only 3rd-party crash reporter on iOS to support eh_frame/compact unwinding, and as such, provides the most accurate backtraces available for the platform. (See below for the technical details.)
Significant changes since 1.2-beta2:
- Production ARM64 support.
- Improved ARM64 unwinding (DWARF eh_frame and Apple compact unwind support).
New ARM64 Unwinding Implementation
On Mac OS X, Apple’s compiler toolchain includes additional metadata within each binary that can be used to perform near-perfect backtraces — whether you’ve crashed in objc_msgSend, inside of a C++ function, or even inside of a custom assembly trampoline.
Without this metadata, crash reporting implementations must apply heuristics to determine best unwind strategy, and in many cases, will do so incorrectly, resulting in incorrect or incomplete backtraces, making bugs harder to understand and more opaque.
With the introduction of ARM64, Apple has brought support for this additional metadata to iOS, where we were able to add support to PLCrashReporter. This involved reverse engineering the incorrectly documented compact unwind format (rdar://15057141 – Incorrect compact unwind documentation in compact_unwind.h), and implementing a full suite of ARM64 hand-written assembly regression tests that we used to ensure the correctness of our unwinder and compatibility with Apple’s implementation.
We’re comfortable saying that PLCrashReporter provides the absolute best crash reporting data available from any 3rd-party on-device crash reporting library for Mac OS X or iOS.
On ARM/ARM64 (ARM), the return address — that is, the address at which execution should resume when the called function returns — is stored in the link register (lr) when a function is called. This address (generally) points to the next valid instruction directly following the branch instruction.
When the called function returns, it uses the saved value stored in the link register to branch to the original caller. If the called function itself calls a function, the link register will be overwritten — thus, the contents of the link register must be saved and restored as to allow the called function to return to its own original caller. In most functions generated by the compiler, the link register will be stored in a standard location, reachable via the frame pointer — this is what a naive crash reporter will rely on to perform walking of the stack frames and product a backtrace.
Where this approach fails is when a function behaves differently than expected — such as objc_msgSend(), where it neither sets up a stack frame nor saves its link register. Objc_msgSend works as a trampoline — it looks up and branches directly to the method implementation, and once complete, is no longer relevant in a backtrace. If a crash occurs in objc_msgSend, and a naive backtrace implementation simply consults the stack where the link register is normally saved, it’ll fetch the contents of the previous function’s link register, and thus, skip a frame. The information will still be available from the crash report — you can fetch the link register from the report and determine what the caller should be — but this is hardly ideal when generating easy-to-use backtraces.
In more complex cases, the frame pointer and link register may be entirely indecipherable by a naive frame walker — for instance, if a crash occurs inside of a custom assembly trampoline that has overwritten both, or if the crash occurs inside of a managed runtime that generates non-standard native code.
To handle these cases, Apple’s toolchain provides information on the location of non-volatile register data — including the link register — and the steps necessary to access it, generating a ‘perfect’ backtrace. These instructions are provided via both DWARF unwind data, and Apple’s compact unwind encoding. The compact unwind encoding provides a strict subset of the DWARF unwinding data, while consuming considerably less space, at the cost of only being able represent a subset of the standard well-defined methods for unwinding a single frame. The DWARF format and implementation, on the other hand, is considerably more complex — it even includes a full, turing complete opcode interpreter that may be used during the unwinding process to express the location and steps to retrieve register values. The DWARF data can be used to express even the most complex and bizarre unwinding steps for a hand-written function. As far as we’re aware PLCrashReporter is the only crash reporting solution to provide full support for client-side unwinding via both DWARF and Apple’s Compact unwinding data.