如何防止iOS崩溃报告器导致MonoTouch应用程序崩溃?

50

在iOS中有很多崩溃报告库,包括TestFlightHockeyApp。如果你不想依赖服务,你仍然可以使用类似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 开发者 Rolf 的话:

空引用异常实际上首先是一个 SIGSEGV 信号。通常,mono 运行时会处理此异常并将其转换为空引用异常,使执行继续进行。问题在于,在 ObjC 应用程序中(以及在托管代码之外发生时),SIGSEGV 信号是一件非常糟糕的事情,因此任何崩溃报告解决方案都会将其报告为崩溃(并杀死应用程序)- 这发生在 MonoTouch 有机会处理 SIGSEGV 之前,因此 MonoTouch 无法对此做任何事情。

我相信许多人在使用 TestFlight 时不知道它会导致应用程序崩溃。
这不是很讽刺吗?

如何使崩溃报告库不会导致 MonoTouch 应用程序崩溃?


1
如果有人感兴趣,这是我们的MonoTouch的HockeyApp绑定HockeyApp不是免费的,但他们的支持非常好(这是我不能说TestFlight的一点),总体上我们对他们很满意。我将此修复添加到示例项目中。(免责声明:我们没有任何关联。) - Dan Abramov
1
Crittercism 的联合创始人在此 - 我们实际上发布了一个官方支持的插件,适用于 Xamarin,可以很好地与 Mono 配合使用 - 例如,它不会阻止 Mono 捕获 SIGSEGV 信号。我们让 Mono 运行时处理这些信号,然后将其发送到 Crittercism。您可以在此处下载:http://components.xamarin.com/view/crittercism - crittercismrob
2个回答

57
将这个放在 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 ();
}

FinishedLaunching方法的开头调用EnableCrashReporting()
如果需要,可以将此调用包装在#if !DEBUG指令中。

它是如何工作的?

我遵循了Rolf的建议:

一个可能的解决方案是允许mono处理所有的SIGSEGV信号(从技术上讲,崩溃报告库应该不处理SIGSEGV信号,或者它应该链接到mono的处理程序并且不进行任何自身处理)。如果mono确定SIGSEGV信号不是来自托管代码(也就是说发生了非常糟糕的事情),它将引发SIGABORT信号(崩溃报告库应该已经处理并将其视为崩溃)。正如您所理解的,这是必须在崩溃报告库中完成的事情。

还有Landon Fuller的Objective 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源代码作为如何从MonoTouch调用sigaction的参考点。希望能有所帮助!

@Nic 我只是按照Landon和Rolf的建议去做的,所以功劳应归于他们! - Dan Abramov
太好了!我之前一直在使用TestFlight,但因为这个原因将其删除了。期待能再次获得适当的现场崩溃报告。 - Dermot
非常感谢这个,我希望它能更广泛地被知晓,这将为我节省很多麻烦。 - MarkDaniel
我尝试使用Xamarin.Insights,但它不起作用,我仍然遇到应用程序崩溃的问题。 - Peyman
@Peyman,已经有一段时间了,所以我不能保证答案仍然准确。您应该在他们的Bugzilla上提交一个问题。 - Dan Abramov

4
从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 ();
}

空的 try{}finally{} 的目的是什么? - David Riha
1
@DavidRiha:这是为了确保代码不会被中断,因为Mono运行时不会在finally子句中中断任何代码(例如,如果另一个线程执行了Thread.Abort(),那么该Abort调用只会在finally执行完成后中止执行)。 - Rolf Bjarne Kvinge

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接