IOS: Как получить трассировку стека необработанной std:: exception?
Если выполняется необработанное 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:: exception, я получаю только это:
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 - он не говорит мне о стеке для необработанного исключения на С++.
Ответы
Ответ 1
То, что вы видите, является неудачной причудой AppKit и UIKit. У iOS и OS X есть обработчик исключений в CFRunLoop
, который захватывает все неперехваченные исключения. В OS X обработчик отображает исключение для пользователя с помощью диалогового окна, но в iOS обработчик просто перерисовывает исключение.
Objective-C, как реализовано NSException
, сохранить их обратные трассы в объекте исключения прямо перед фактическим "броском", как часть [NSException init]
(или аналогичный метод инициализации). К сожалению, исключения С++ не делают то же самое. Как правило, исключение С++ имеет обратную трассировку, потому что библиотека времени выполнения обнаруживает, что там нет 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
. Поэтому исходная обратная трасса теряется. Затем rethrow вызывает std::terminate
, но в этот момент стек вызовов отражает обработчик исключений цикла выполнения.
Чтобы получить обратную трассировку из исключения С++ в этом случае, вы должны выбросить объект исключения, который имитирует NSException
, и считывает стек вызовов как часть его конструктора и следит за тем, чтобы все исключения, введенные в ваш код, одна и та же. std::exception
не делает этого, и совсем не вероятно, что любой сторонний код, который вы используете, будет либо.
Избавьтесь от Apple с просьбой удалить обработчик исключений CFRunLoop
или, по крайней мере, предоставить API для его отключения. Для этого сейчас нет API, даже SPI.
Ответ 2
Мне посчастливилось улучшить ответ mpipe3.
В моем основном методе я использую try-catch для получения исключения С++, а затем вызываю dispatch_sync
on dispatch_get_global_queue
. Теперь полная трассировка стека с номерами строк будет отображаться в Crashlytics (диспетчер сбоев).
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 для исключения исключения.
Ответ 3
После ответа Gwynne Raskind вы можете избежать обработчика исключений CFRunLoop, используя GCD, чтобы синхронно отправлять вызов блоку кода на С++:
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
Ответ 4
похоже, что исключение происходит изнутри UIApplicationMain()... Вы можете попробовать преобразовать файл main.m в файл main.mm и сделать это:
int main(...)
{
try
{
return UIApplicationMain(...);
}
catch( exception e )
{
cerr < e.what() ;
}
}
Я действительно не пробовал, однако...