Better crash reporting in Xamarin.iOS

13 Jul 2014

Xamarin.iOS (formerly MonoTouch) lets you write memory-safe iOS apps in C#. Because the Mono Project compiler supports compiling CIL (Microsoft bytecode) to machine code, Xamarin.iOS is also fast.

However, ahead-of-time compiling has the side effect of mucking up stack traces in the crash reports generated by iOS, making it hard to find where the crash actually happened. In one case, there were 22 stack frames from helper functions before the ones that actually mattered:

00 0x3a7d11f0 __pthread_kill + 8
01 0x3a8397b3 pthread_kill + 55
02 0x3a781ff5 abort + 73
03 0x004a6d19 mono_handle_native_sigsegv (mini-exceptions.c:2341)
04 0x004aba0b sigabrt_signal_handler (mini-posix.c:206)
05 0x3a834721 _sigtramp + 41
06 0x3a8397b3 pthread_kill + 55
07 0x3a781ff5 abort + 73
08 0x005380c8 monotouch_unhandled_exception_handler (monotouch-glue.m:1562)
09 0x004a70ed mono_invoke_unhandled_exception_hook (mini-exceptions.c:2670)
10 0x004b2753 mono_thread_abort_dummy (mini.c:2821)
11 0x004a6aa7 mono_handle_exception_internal (mini-exceptions.c:1697)
12 0x004a5b2f mono_handle_exception (mini-exceptions.c:1921)
13 0x004a0321 mono_arm_throw_exception (exceptions-arm.c:157)
14 0x002bed70 throw_exception + 64
15 0x004f0783 mono_runtime_class_init (object.c:261)
16 0x004af649 mono_jit_compile_method_with_opt (mini.c:5891)
17 0x004af4c9 mono_jit_compile_method (mini.c:6321)
18 0x004f1b45 mono_compile_method (object.c:587)
19 0x004ac371 common_call_trampoline (mini-trampolines.c:597)
20 0x004ac117 mono_magic_trampoline (mini-trampolines.c:717)
21 0x002be3ec generic_trampoline_jit + 128
22 0x00372c5c MyClass3:MyMethod + 280
23 0x000ce1e0 MyClass2:MyMethod + 232
24 0x000d82f8 MyClass1:m__0 + 36

25 0x00104d8c MonoTouch.Foundation.NSActionDispatcher:Apply (.pmcs-compat.NSAction.cs:107)
26 0x0028e5b0 wrapper_runtime_invoke_object_runtime_invoke_dynamic_intptr_intptr_intptr_intptr + 196
27 0x004b1fff mono_jit_runtime_invoke (mini.c:6716)
28 0x004f06d3 mono_runtime_invoke (object.c:2828)
29 0x004808f7 native_to_managed_trampoline_1 (registrar.m:25)
30 0x004848bd -[__MonoMac_NSActionDispatcher xamarinApplySelector] (registrar.m:1697)

Even worse, any C# exception information that would help you debug the problem is lost. To get around this, wrap your Main.cs in a try-catch, and write the exception details to disk before quitting:

public class Application
{
  // This is the main entry point of the application.
  static void Main(string[] args)
  {
    // We don't dump exceptions to disk when the app is compiled for debugging, because Xamarin
    // Studio can provide more information in that case.
    #if DEBUG
    UIApplication.Main(args: args, principalClassName: null, delegateClassName: typeof(AppDelegate).Name);
    #else
    try
    {
      UIApplication.Main(args: args, principalClassName: null, delegateClassName: typeof(AppDelegate).Name);
    }
    catch (Exception e)
    {
      StreamWriter file = File.CreateText("Library/my-crash.txt");
      file.Write(e.ToString()); // save the exception description and clean stack trace
      file.Close();
    }
    #endif
  }
}

Here’s a sample “crash report” that was produced by this code. Much better!

System.Exception: Could not initialize an instance of the type 'MonoTouch.UIKit.UIImage': the native 'initWithContentsOfFile:' method returned nil.
It is possible to ignore this condition by setting MonoTouch.ObjCRuntime.Class.ThrowOnInitFailure to false.
  at MonoTouch.Foundation.NSObject.InitializeHandle (IntPtr handle, System.String initSelector) [0x00000] in <filename unknown>:0 
  at MonoTouch.UIKit.UIImage..ctor (System.String filename) [0x00000] in <filename unknown>:0 
  at MyClass2.MyProperty.get_MyProperty () [0x00000] in <filename unknown>:0 
  at (wrapper managed-to-native) MonoTouch.ObjCRuntime.Messaging:void_objc_msgSendSuper_IntPtr_IntPtr (intptr,intptr,intptr,intptr)
  at MonoTouch.UIKit.UIViewController.PerformSegue (System.String identifier, MonoTouch.Foundation.NSObject sender) [0x00000] in <filename unknown>:0 
  at MyClass1+<MyMethod>c__AnonStorey0.<>m__0 () [0x00000] in <filename unknown>:0 
  at MonoTouch.Foundation.NSActionDispatcher.Apply () [0x00000] in <filename unknown>:0 
  at (wrapper managed-to-native) MonoTouch.UIKit.UIApplication:UIApplicationMain (int,string[],intptr,intptr)
  at MonoTouch.UIKit.UIApplication.Main (System.String[] args, System.String principalClassName, System.String delegateClassName) [0x00000] in <filename unknown>:0
  at Application.Main (System.String[] args) [0x00000] in <filename unknown>:0

On the next launch, you can use File.Exists("Library/my-crash.txt") check for the presence of a crash report and upload it to your server.