iOS: Как получить трассировку стека необработанного исключения std::?
Если возникает необработанное исключение NSException, трассировка стека имеет следующий раздел:
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
...
Но если std:: исключение выбрасывается, я получаю только это:
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
Как получить точную информацию о сбое из этого журнала сбоев?
Обновление --
Я дал HockeyApp шанс, но у него есть то же ограничение, что и у iTunes crash logs - он не сообщает мне стек для необработанного исключения C++.
4 ответа:
То, что вы видите, - это неудачная причуда AppKit и UIKit. Как iOS, так и OS X имеют обработчик исключений в
CFRunLoop
, который ловит все необработанные исключения. В OS X обработчик отображает исключение пользователю с помощью диалогового окна, но в iOS обработчик просто повторно создает исключение.Исключения Objective-C, реализованные в
NSException
, сохраняют свои обратные следы в объекте exception непосредственно перед фактическим "броском", как часть[NSException init]
(или аналогичного метода инициализатора). К сожалению, исключения C++ не делают того же самого. Обычно исключение C++ имеет обратный путь, потому что библиотека времени выполнения обнаруживает, что нетcatch
и немедленно вызываетstd::terminate
, который в свою очередь вызываетabort()
, оставляя весь стек вызовов нетронутым, например: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
Однако, когда обработчик исключений цикла выполнения ловит исключение, выполнение обычно возобновляется в блоке
catch
. Таким образом, первоначальный обратный след теряется. Ретроу впоследствии вызываетstd::terminate
, но в этот момент вызов стек отражает обработчик исключений цикла выполнения.Чтобы получить обратную трассировку из исключения C++ в этом случае, вы должны создать объект исключения, который имитирует
NSException
и считывает стек вызовов как часть своего конструктора, и убедитесь, что все исключения, созданные в вашем коде, делают то же самое.std::exception
этого не делает, и маловероятно, что какой-либо сторонний код, который вы используете, будет также.Отправьте сообщение об ошибке в Apple с просьбой удалить обработчик исключений
CFRunLoop
, или по адресу по крайней мере, предоставить API для его отключения. Нет никакого API, даже SPI, для выполнения этого прямо сейчас.
Мне повезло с улучшением ответа mpipe3.
В моем основном методе я использую try-catch, чтобы получить исключение C++, а затем вызываю
dispatch_sync
наdispatch_get_global_queue
. Теперь полная трассировка стека с номерами строк будет отображаться на 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; } }
Это работает, поскольку он избегает обработчика исключений CFRunLoop, используя GCD для создания исключения.
Следуя ответу Гвинна Раскинда, вы можете избежать обработчика исключений CFRunLoop, используя GCD для синхронной отправки вызова блоку кода C++:
namespace { void MyThrowingCode() { throw std::runtime_error("My Exception"); } } - (void)objcHandlerCalledFromARunLoop { dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ MyThrowingCode(); }); }
Возможно, вам придется создавать свои собственные очереди, а не использовать одну общую очередь.
Журнал сбоев выглядит следующим образом:
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