This is something I hacked together for a program I’ve been tinkering with. Add this to your application delegate and it will trap uncaught Objective C exceptions, uncaught signals, and uncaught C++ exceptions (if you like messing around with Objective C++), and write a stack trace and detailed report which your application will offer to e-mail in the event that your program crashes in the field.
The preliminaries: the headers to add, global variables and the like:
#include <execinfo.h>
#include <exception>
/************************************************************************/
/* */
/* Globals */
/* */
/************************************************************************/
static NSString *GBug;
/************************************************************************/
/* */
/* Crash Reporter */
/* */
/************************************************************************/
static NSString *GetBugReport()
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *dir = [paths objectAtIndex:0];
return [dir stringByAppendingPathComponent:@"bug.txt"];
}
Note that this defines a function which saves any uncaught exceptions to a file ‘bug.txt’ on your phone. If this file is present the next time your application starts up, the user is presented with the option to e-mail the bug to you.
Now to catching uncaught Objective C exceptions:
/* UncaughtExceptionHandler
*
* Handle uncaught exceptions
*/
static void UncaughtExceptionHandler(NSException *exception)
{
/*
* Extract the call stack
*/
NSArray *callStack = [exception callStackReturnAddresses];
int i,len = [callStack count];
void **frames = new void *[len];
for (i = 0; i < len; ++i) {
frames[i] = (void *)[[callStack objectAtIndex:i] unsignedIntegerValue];
}
char **symbols = backtrace_symbols(frames,len);
/*
* Now format into a message for sending to the user
*/
NSMutableString *buffer = [[NSMutableString alloc] initWithCapacity:4096];
NSBundle *bundle = [NSBundle mainBundle];
[buffer appendFormat:@"PComp version %@ build %@nn",
[bundle objectForInfoDictionaryKey:@"CFBundleVersion"],
[bundle objectForInfoDictionaryKey:@"CIMBuildNumber"]];
[buffer appendString:@"Uncaught Exceptionn"];
[buffer appendFormat:@"Exception Name: %@n",[exception name]];
[buffer appendFormat:@"Exception Reason: %@n",[exception reason]];
[buffer appendString:@"Stack trace:nn"];
for (i = 0; i < len; ++i) {
[buffer appendFormat:@"%4d - %sn",i,symbols[i]];
}
/*
* Get the error file to write this to
*/
NSError *err;
[buffer writeToFile:GetBugReport() atomically:YES encoding:NSUTF8StringEncoding error:&err];
NSLog(@"Error %@",buffer);
exit(0);
}
This exception handler is called when you have an uncaught Objective C excption. This formats a bug report (note that I use build numbers in the info.plist file, under the key ‘CIMBuildNumber’), and writes the bug report both to the phone’s log and to the log file before force quitting the application.
Next, the signal handler:
/* SignalHandler
*
* Handle uncaught signals
*/
void SignalHandler(int sig, siginfo_t *info, void *context)
{
void *frames[128];
int i,len = backtrace(frames, 128);
char **symbols = backtrace_symbols(frames,len);
/*
* Now format into a message for sending to the user
*/
NSMutableString *buffer = [[NSMutableString alloc] initWithCapacity:4096];
NSBundle *bundle = [NSBundle mainBundle];
[buffer appendFormat:@"PComp version %@ build %@nn",
[bundle objectForInfoDictionaryKey:@"CFBundleVersion"],
[bundle objectForInfoDictionaryKey:@"CIMBuildNumber"]];
[buffer appendString:@"Uncaught Signaln"];
[buffer appendFormat:@"si_signo %dn",info->si_signo];
[buffer appendFormat:@"si_code %dn",info->si_code];
[buffer appendFormat:@"si_value %dn",info->si_value];
[buffer appendFormat:@"si_errno %dn",info->si_errno];
[buffer appendFormat:@"si_addr 0x%08lXn",info->si_addr];
[buffer appendFormat:@"si_status %dn",info->si_status];
[buffer appendString:@"Stack trace:nn"];
for (i = 0; i < len; ++i) {
[buffer appendFormat:@"%4d - %sn",i,symbols[i]];
}
/*
* Get the error file to write this to
*/
NSError *err;
[buffer writeToFile:GetBugReport() atomically:YES encoding:NSUTF8StringEncoding error:&err];
NSLog(@"Error %@",buffer);
exit(0);
}
This more or less does the same thing, except for uncaught C signals.
And now C++:
/* TerminateHandler
*
* C++ exception terminate handler
*/
void TerminateHandler(void)
{
void *frames[128];
int i,len = backtrace(frames, 128);
char **symbols = backtrace_symbols(frames,len);
/*
* Now format into a message for sending to the user
*/
NSMutableString *buffer = [[NSMutableString alloc] initWithCapacity:4096];
NSBundle *bundle = [NSBundle mainBundle];
[buffer appendFormat:@"PComp version %@ build %@nn",
[bundle objectForInfoDictionaryKey:@"CFBundleVersion"],
[bundle objectForInfoDictionaryKey:@"CIMBuildNumber"]];
[buffer appendString:@"Uncaught C++ Exceptionn"];
[buffer appendString:@"Stack trace:nn"];
for (i = 0; i < len; ++i) {
[buffer appendFormat:@"%4d - %sn",i,symbols[i]];
}
/*
* Get the error file to write this to
*/
NSError *err;
[buffer writeToFile:GetBugReport() atomically:YES encoding:NSUTF8StringEncoding error:&err];
NSLog(@"Error %@",buffer);
exit(0);
}
Which is the same thing for C++.
Next are the routines for hooking into uncaught signals:
/* SetupUncaughtSignals
*
* Set up the uncaught signals
*/
static void SetupUncaughtSignals()
{
struct sigaction mySigAction;
mySigAction.sa_sigaction = SignalHandler;
mySigAction.sa_flags = SA_SIGINFO;
sigemptyset(&mySigAction.sa_mask);
sigaction(SIGQUIT, &mySigAction, NULL);
sigaction(SIGILL, &mySigAction, NULL);
sigaction(SIGTRAP, &mySigAction, NULL);
sigaction(SIGABRT, &mySigAction, NULL);
sigaction(SIGEMT, &mySigAction, NULL);
sigaction(SIGFPE, &mySigAction, NULL);
sigaction(SIGBUS, &mySigAction, NULL);
sigaction(SIGSEGV, &mySigAction, NULL);
sigaction(SIGSYS, &mySigAction, NULL);
sigaction(SIGPIPE, &mySigAction, NULL);
sigaction(SIGALRM, &mySigAction, NULL);
sigaction(SIGXCPU, &mySigAction, NULL);
sigaction(SIGXFSZ, &mySigAction, NULL);
}
Once an exception has been caught, it’d be nice to send it to you via e-mail. To do this, define the following Objective-C methods in your app delegate:
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 1) {
/*
* Send an e-mail with the specified title.
*/
NSMutableString *url = [NSMutableString stringWithCapacity:4096];
[url appendString:@"mailto:bugs@nowhere.meh?subject=Bug%20Report&body="];
[url appendString:[GBug stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]];
}
[GBug release];
}
- (void)sendBugsIfPresent
{
NSError *err;
NSString *path = GetBugReport();
GBug = [[NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&err] retain];
if (GBug == nil) return;
[[NSFileManager defaultManager] removeItemAtPath:path error:&err];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Unexpected exception"
message:@"An unexpected exception was caught the last time this program ran. Send the developer a bug report by e-mail?"
delegate:self
cancelButtonTitle:@"Cancel"
otherButtonTitles:@"Send Report",nil];
[alert show];
}
Remember to change the e-mail and the message that is displayed. This will display an alert to the user, and if the user selects “Send Report”, he’ll be dropped into the e-mail application to send the e-mail.
Of course you could do other things here, like upload via HTTP to some remote server. I like the e-mail approach; this way the user knows exactly what he’s telling you.
The -sendBugsIfPresent determines if a bug report has been written out, and if one is present, send it.
Now, once you’ve added this code, it’s simply a matter of wiring everything up to catch uncaught exceptions and alert the user if there was a crash the last time. To your -applicationDidFinishLaunching method add:
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
... Other initialization here ...
/* Register for uncaught exceptions, signals */
NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);
SetupUncaughtSignals();
std::set_terminate(TerminateHandler);
[self sendBugsIfPresent];
}
And that does it: we set up for uncaught Objective C exceptions, C++ exceptions and uncaught C signals, and we send a bug report if the last time we ran, we crashed.
Hope you find this as useful as I have.
Oh, and if you want to credit me with this, feel free–or not. (For the legally minded, I’m placing the above code snippets into the public domain. Just don’t sue me if it doesn’t work.)