If an unhandled NSException is thrown, the stack trace has a section like this:
Last Exception Backtrace:
0 CoreFoundation 0x32bd688f __exceptionPreprocess + 163
1 libobjc.A.dylib 0x34b7b259 objc_exception_throw + 33
2 CoreFoundation 0x32bd65c5 -[NSException init] + 1
3 Foundation 0x37296bd7 -[NSObject(NSKeyValueCoding) valueForUndefinedKey:] + 263
...
But if std::exception is thrown, i get only this:
Thread 0 Crashed:
0 libsystem_kernel.dylib 0x34f2632c __pthread_kill + 8
1 libsystem_c.dylib 0x31e4c208 pthread_kill + 48
2 libsystem_c.dylib 0x31e45298 abort + 88
3 libc++abi.dylib 0x33bcaf64 abort_message + 40
4 libc++abi.dylib 0x33bc8346 default_terminate() + 18
5 libobjc.A.dylib 0x349f4368 _objc_terminate + 164
6 libc++abi.dylib 0x33bc83be safe_handler_caller(void (*)()) + 70
7 libc++abi.dylib 0x33bc844a std::terminate() + 14
8 libc++abi.dylib 0x33bc981e __cxa_rethrow + 82
9 libobjc.A.dylib 0x349f42a2 objc_exception_rethrow + 6
10 CoreFoundation 0x329a5506 CFRunLoopRunSpecific + 398
11 CoreFoundation 0x329a5366 CFRunLoopRunInMode + 98
12 GraphicsServices 0x32af2432 GSEventRunModal + 130
13 UIKit 0x34f84cce UIApplicationMain + 1074
14 APP_NAME 0x00086b10 main (main.m:68)
15 APP_NAME 0x00071b98 start + 32
How do I get the exact crash info from this crash log?
Update --
I've given HockeyApp a shot, but it has the same limitation as iTunes crash logs - it doesn't tell me the stack for an unhandled C++ exception.
What you're seeing is an unfortunate quirk of AppKit and UIKit. Both iOS and OS X have an exception handler in CFRunLoop
that traps all uncaught exceptions. On OS X, the handler displays the exception to the user with a dialog box, but on iOS, the handler simply rethrows the exception.
Objective-C exceptions, as implemented by NSException
, save their backtraces within the exception object right before the actual "throw" happens, as part of [NSException init]
(or a similar initializer method). Unfortunately, C++ exceptions don't do the same. Normally, a C++ exception has a backtrace because the runtime library detects that there's no catch
and immediately calls std::terminate
, which in turn calls abort()
, leaving the entire call stack intact, like so:
Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0 libsystem_kernel.dylib 0x00007fff93ef8d46 __kill + 10
1 libsystem_c.dylib 0x00007fff89968df0 abort + 177
2 libc++abi.dylib 0x00007fff8beb5a17 abort_message + 257
3 libc++abi.dylib 0x00007fff8beb33c6 default_terminate() + 28
4 libobjc.A.dylib 0x00007fff8a196887 _objc_terminate() + 111
5 libc++abi.dylib 0x00007fff8beb33f5 safe_handler_caller(void (*)()) + 8
6 libc++abi.dylib 0x00007fff8beb3450 std::terminate() + 16
7 libc++abi.dylib 0x00007fff8beb45b7 __cxa_throw + 111
8 test 0x0000000102999f3b main + 75
9 libdyld.dylib 0x00007fff8e4ab7e1 start + 1
However, when the run loop exception handler traps the exception, execution resumes normally within the catch
block. The original backtrace is therefore lost. The rethrow subsequently calls std::terminate
, but at this point, the call stack reflects the run loop exception handler.
To get a backtrace from a C++ exception in this circumstance, you have to throw an exception object which mimics NSException
and reads the call stack as part of its constructor, and make sure that all exceptions thrown in your code do the same. std::exception
doesn't do this, and it's not at all likely that any third-party code you're using will either.
File a bug with Apple asking them to remove the CFRunLoop
exception handler, or at least provide an API for shutting it off. There is no API, not even an SPI, for doing this right now.
I've had luck with improving mpipe3's answer.
In my main method, I use a try-catch to get the C++ exception and then call a dispatch_sync
on dispatch_get_global_queue
. Now the full stack trace with line numbers will show on Crashlytics (crash manager).
int main(int argc, char *argv[]) {
@autoreleasepool {
@try{
return UIApplicationMain(argc, argv, nil, @"AppControllerClassName");
} @catch (NSException *exception) {/* Catch any uncaught exceptions and print out a friendly call stack. Instead of an ugly memory addresses. */
NSString * message = [NSString stringWithFormat:@"Uncaught exception %@ : %@\n %@", exception.name, exception.reason, [exception callStackSymbols]];
[LogManager e:@"main.m" Message:message Exception: exception];
@throw;
} @catch (...){
//Attempt to get the correct stacktrace for C++ exceptions.
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
@throw;
});
}
return 0;
}
}
This works as it avoids the CFRunLoop exception handler by using GCD to throw the exception.
Following on from Gwynne Raskind answer you can avoid the CFRunLoop exception handler by using GCD to dispatch a call to a block of C++ code synchronously:
namespace
{
void MyThrowingCode()
{
throw std::runtime_error("My Exception");
}
}
- (void)objcHandlerCalledFromARunLoop
{
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
MyThrowingCode();
});
}
You might need to create your own queues rather than using a single shared queue.
The crash log looks like this:
0 libsystem_kernel.dylib 0x3aa39350 __pthread_kill + 8
1 libsystem_c.dylib 0x3a9affb2 pthread_kill + 54
2 libsystem_c.dylib 0x3a9ec366 abort + 90
3 libc++abi.dylib 0x39f94dda abort_message + 70
4 libc++abi.dylib 0x39f92094 default_terminate() + 20
5 libobjc.A.dylib 0x3a545a70 _objc_terminate() + 168
6 libc++abi.dylib 0x39f92118 safe_handler_caller(void (*)()) + 76
7 libc++abi.dylib 0x39f921b0 std::terminate() + 16
8 libc++abi.dylib 0x39f9359a __cxa_throw + 118
9 Test 0x000fddfc MyThrowingCode() (MainViewController.mm:177)
10 Test 0x000fe38c __35-[MainViewController toolsClick:]_block_invoke (MainViewController.mm:184)
11 libdispatch.dylib 0x3a95f5d8 _dispatch_client_callout + 20
12 libdispatch.dylib 0x3a962776 _dispatch_sync_f_invoke + 22
13 Test 0x000fde90 -[MainViewController toolsClick:] (MainViewController.mm:183)
14 UIKit 0x34744082 -[UIApplication sendAction:to:from:forEvent:] + 66
15 UIKit 0x3474410c -[UIBarButtonItem(UIInternal) _sendAction:withEvent:] + 116
16 UIKit 0x34744082 -[UIApplication sendAction:to:from:forEvent:] + 66
17 UIKit 0x34744036 -[UIApplication sendAction:toTarget:fromSender:forEvent:] + 26
18 UIKit 0x34744010 -[UIControl sendAction:to:forEvent:] + 40
19 UIKit 0x347438c6 -[UIControl(Internal) _sendActionsForEvents:withEvent:] + 498
20 UIKit 0x34743db4 -[UIControl touchesEnded:withEvent:] + 484
21 UIKit 0x3466c5f4 -[UIWindow _sendTouchesForEvent:] + 520
22 UIKit 0x346598dc -[UIApplication sendEvent:] + 376
23 UIKit 0x346591ea _UIApplicationHandleEvent + 6194
24 GraphicsServices 0x363715f4 _PurpleEventCallback + 588
25 GraphicsServices 0x36371222 PurpleEventCallback + 30
26 CoreFoundation 0x3281f3e4 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 32
27 CoreFoundation 0x3281f386 __CFRunLoopDoSource1 + 134
28 CoreFoundation 0x3281e20a __CFRunLoopRun + 1378
29 CoreFoundation 0x32791238 CFRunLoopRunSpecific + 352
30 CoreFoundation 0x327910c4 CFRunLoopRunInMode + 100
31 GraphicsServices 0x36370336 GSEventRunModal + 70
32 UIKit 0x346ad2b4 UIApplicationMain + 1116
33 Test 0x00120462 main (main.mm:55)
34 libdyld.dylib 0x3a972b1c start + 0
looks like the exception is happening from inside UIApplicationMain()... You could try converting your main.m file to a main.mm file, and doing this:
int main(...)
{
try
{
return UIApplicationMain(...);
}
catch( exception e )
{
cerr < e.what() ;
}
}
I didn't actually try it, however...