CrashKit: Helping Your iOS/iPhone Apps Suck Less
First there was the iPhone and iPod Touch. Then the iPad. Then iOS 3.0. Then 3.2 but iPad only. Then 4.0 but some of the features aren’t available on older models. Then there is the 4x3 versus 3x2 aspect ratio to worry about. And then 480x320 versus 1024x768 versus 960x640. The Media Player framework changes at every single point release.
All of this fragmentation means that iOS Apps crash. CrashKit catches uncaught exceptions, traps signals, and sends them to developers by email or straight to your bug database.
Foursquare
Foursquare dumps a stack trace whenever it crashes. It then asks the user to email the crash report to the developers the next time they launch the App.
The problem is that the user may not ever launch your App ever again.
We can do better than this.
What CrashKit Does
CrashKit catches uncaught NSExceptions. NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler);
It also traps signals that the operating system sends when the App touches memory that doesn’t belong to it or executes illegal operations.
signal(SIGABRT, sighandler);
signal(SIGBUS, sighandler);
signal(SIGFPE, sighandler);
signal(SIGILL, sighandler);
signal(SIGPIPE, sighandler);
signal(SIGSEGV, sighandler);
The signal handlers unwind the stack frame and try to find out exactly where the crash happened. This bug report can then be emailed or sent to FogBugz.
FogBugz
FogBugz exposes a BugzScout API that accepts bug reports using HTTP GET/POST methods. It buckets similar bugs together and appends them to existing bug reports.
Pump That Run Loop
There are a number of subtle issues with catching and reporting crashes. In general, UIKit is not re-entrant. Also, UIKit methods should only be called on the main thread. Exceptions and signals are usually trapped on a thread that is not the main thread. And the main thread usually gets evicted once something catastrophic happens to the App.
CrashKit jumps back to the main thread after catching a crash and grabbing the stack frame. It then pumps the main run loop until an email message can be sent or a HTTP method can be completed.
- (void)pumpRunLoop
{
self.finishPump = NO;
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFArrayRef runLoopModesRef = CFRunLoopCopyAllModes(runLoop);
NSArray * runLoopModes = (NSArray*)runLoopModesRef;
while (self.finishPump == NO)
{
for (NSString *mode in runLoopModes)
{
CFStringRef modeRef = (CFStringRef)mode;
CFRunLoopRunInMode(modeRef, 1.0f/120.0f, false); // Pump the loop at 120 FPS
}
}
CFRelease(runLoopModesRef);
}
LLVM and LLDB
LLVM and LLDB are the future. I’ve only tested CrashKit with LLVM. There are some LLDB features I would like to add in the future. CrashKit probably works with GCC but I haven’t really tested it.
GitHub
The best way to download CrashKit is to check out the GitHub page. Go put a fork in it. Crash reporting is hard so patches welcome.