Как предотвратить сбои iOS-репортеров с приложениями MonoTouch?

В iOS имеется множество библиотек отчетов о сбоях iOS, включая TestFlight и HockeyApp. Если вы не хотите зависеть от сервисов, вы все равно можете использовать такие библиотеки, как PLCrashReporter. Связывание этих библиотек довольно тривиально, потому что их общедоступный API обычно состоит из пары классов с несколькими методами инициализации.

Однако при попытке использовать TestFlight, а затем и HockeyApp в нашем приложении, наше приложение случайно зависало. Оказывается, это известная проблема, о которой сообщалось несколько раз, но Ксамарин не предупреждает об этом, она относительно неясна, и мы нашли ее трудным путем.

Мы узнали, что все репортеры сбоев iOS не позволяют Mono перехватывать нулевые ссылочные исключения:

try {
    object o = null;
    o.GetHashCode ();
} catch {
    // Catch block isn't called with crash reporting enabled.
    // Instead, the app will crash.
}

Почему это происходит? Цитируя Рольфа, разработчика Xamarin,

Поначалу исключение нулевой ссылки на самом деле является сигналом SIGSEGV. Обычно моно среда выполнения обрабатывает это и переводит его в исключение нулевой ссылки, что позволяет продолжить выполнение. Проблема в том, что сигналы SIGSEGV - очень плохая вещь в приложениях ObjC (и когда это происходит вне управляемого кода), поэтому любое решение для сообщения о сбоях сообщит об этом как о сбое (и уничтожит приложение) - это происходит до того, как MonoTouch получает шанс обрабатывать SIGSEGV, поэтому MonoTouch ничего не может с этим поделать.

Я уверен, что многие используют TestFlight в приложениях MonoTouch, не зная, что это вызывает сбои.
Разве это не иронично?

Как сделать так, чтобы библиотеки отчетов о сбоях не сбивали приложения MonoTouch?

2 ответа

Решение

Поместите это в AppDelegate.cs:

[DllImport ("libc")]
private static extern int sigaction (Signal sig, IntPtr act, IntPtr oact);

enum Signal {
    SIGBUS = 10,
    SIGSEGV = 11
}

static void EnableCrashReporting ()
{
    IntPtr sigbus = Marshal.AllocHGlobal (512);
    IntPtr sigsegv = Marshal.AllocHGlobal (512);

    // Store Mono SIGSEGV and SIGBUS handlers
    sigaction (Signal.SIGBUS, IntPtr.Zero, sigbus);
    sigaction (Signal.SIGSEGV, IntPtr.Zero, sigsegv);

    // Enable crash reporting libraries
    EnableCrashReportingUnsafe ();

    // Restore Mono SIGSEGV and SIGBUS handlers            
    sigaction (Signal.SIGBUS, sigbus, IntPtr.Zero);
    sigaction (Signal.SIGSEGV, sigsegv, IntPtr.Zero);

    Marshal.FreeHGlobal (sigbus);
    Marshal.FreeHGlobal (sigsegv);
}

static void EnableCrashReportingUnsafe ()
{
    // Run your crash reporting library initialization code here--
    // this example uses HockeyApp but it should work well
    // with TestFlight or other libraries.

    // Verify in documentation that your library of choice
    // installs its sigaction hooks before leaving this method.

    var manager = BITHockeyManager.SharedHockeyManager;
    manager.Configure (HockeyAppId, null);
    manager.StartManager ();
}

Вызов EnableCrashReporting () в начале FinishedLaunching метод.
Заверните этот звонок в #if !DEBUG директива, если хотите.


Как это работает?

Я последовал предложению Рольфа:

Одно из возможных решений - разрешить моно обрабатывать все сигналы SIGSEGV (технически говоря, библиотека отчетов о сбоях должна либо не обрабатывать сигнал SIGSEGV, либо она должна связываться с обработчиком моно и не выполнять никакой обработки сама по себе). Если mono определит, что сигнал SIGSEGV не из управляемого кода (то есть произошло что-то очень плохое), он вызовет сигнал SIGABORT (который библиотека отчетов о сбоях должна уже обработать и рассматривать как сбой). Как вы понимаете, это то, что нужно сделать в библиотеке отчетов о сбоях.

И реализация цели C Лэндона Фуллера:

#import <signal.h>

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    /* Save Mono's signal handler actions */
    struct sigaction sigbus_action, sigsegv_action;
    sigaction(SIGBUS, NULL, &sigbus_action);
    sigaction(SIGSEGV, NULL, &sigsegv_action);

    // Enable the crash reporter here. Ie, [[PLCrashReporter sharedReporter] enableCrashReporterAndReturnError:],
    // or whatever is the correct initialization mechanism for the crash reporting service you're using

    /* Restore Mono's signal handlers */
    sigaction(SIGBUS, &sigbus_action, NULL);
    sigaction(SIGSEGV, &sigsegv_action, NULL);

    return YES;
}

Я использовал исходный код Banshee в качестве ориентира для вызова sigaction от MonoTouch.

Надеюсь, поможет!

Начиная с Xamarin.iOS 10.4, теперь есть поддерживаемый способ сделать это:

static void EnableCrashReporting ()
{
    try {
    } finally {
        Mono.Runtime.RemoveSignalHandlers ();
        try {
            EnableCrashReportingUnsafe ();
        } finally {
            Mono.Runtime.InstallSignalHandlers ();
        }
    }
}

static void EnableCrashReportingUnsafe ()
{
    // Run your crash reporting library initialization code here--
    // this example uses HockeyApp but it should work well
    // with TestFlight or other libraries.

    // Verify in documentation that your library of choice
    // installs its sigaction hooks before leaving this method.

    // Have in mind that at this point Mono will not handle
    // any NullReferenceExceptions, if there are any 
    // NullReferenceExceptions on any thread (not just the current one),
    // then the app will crash.

    var manager = BITHockeyManager.SharedHockeyManager;
    manager.Configure (HockeyAppId, null);
    manager.StartManager ();
}
Другие вопросы по тегам