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 11

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

Похоже, что исключение происходит изнутри UIApplicationMain ()... Вы можете попробовать преобразовать свой основной.M файл в a main.mm файл, и делает это:

int main(...)
{
    try
    {
        return UIApplicationMain(...);
    }
    catch( exception e )
    {
        cerr < e.what() ;
    }
}

Я на самом деле не пытался это, однако...