it-swarm.com.ru

Как предотвратить аварийное завершение работы приложений MonoTouch для репортеров iOS?

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

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

Мы узнали, что все репортеры сбоев 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 при сбое?

50
Dan Abramov

Поместите это в 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 (который библиотека обработки отчетов о сбоях должна уже обработать и рассматривать как сбой). Как вы понимаете, это то, что должно быть сделано в библиотеке отчетов о сбоях.

И Landon Fuller Реализация цели 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.

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

58
Dan Abramov

Начиная с 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 ();
}
4
Rolf Bjarne Kvinge