Symbolicating iOS crashes

Sometimes when your app crashes it can be pretty hard to determine what went wrong, especially when you are unable to reproduce and/or debug the crash. Luckily the device also stores crash logs!

Getting the device logs
There are 2 ways to get your crash log: with xCode or with iTunes. Because not every tester has xCode installed I will also list the steps required for iTunes:

iTunes

  1. Connect device to pc/mac.
  2. Open your device in iTunes and make sure iTunes is synced. This will also transfer your crash logs.
  3. Open the crash log folder:
    1. Windows: %appdata%/Roaming/Apple computer/Logs/CrashReporter/Mobile Device/{your devicename}
    2. Mac: ~/Library/Logs/CrashReporter/MobileDevice/{your devicename}
  4. In this folder you can find the crash logs with the following format “{appname}.crash”.

    xCode

  5. Connect your device to your Mac.
  6. Open xCode and launch the organizer (Window->Devices or Window->Organizer)
  7. Select your device from the list and click “View device logs”.
  8. Find the crash log based on creation time and right click the entry to export. Click export and save the log to your filesystem.

After pulling the logs from the device, you’ll probably notice that these log files on itself don’t contain a lot of useful information. Bummer!

The logs you pulled from the device are unsymbolicated. This means a lot of technical information isn’t included. You can recognize an unsymbolicated crash log when the function names are missing. Fortunately you are able to add this information by symbolicating your log.

Symbolicating
xCode offers tooling to symbolicate your crashes. The location of this tool depends on your version of xCode.

  • xCode 7 & 8:/Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash
  • xCode6: Applications/Xcode.app/Contents/SharedFrameworks/DTDeviceKitBase.framework/Versions/A/Resources/symbolicatecrash
  • xCode5: /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/PrivateFrameworks/DTDeviceKitBase.framework/Versions/A/Resources/symbolicatecrash

Before you can execute the symbolicatecrash command, you need to set your Developer_Dir enviroment variable:

export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"

To symbolicate your file, you need to run the “symbolicatecrash” command and pass the previous mentioned files as params. The -o parameter indicates where to write the symbolicated file to.

symbolicate -o "symbolicatedCrash.txt" "MyAppName 2-12-14, 9-44 PM.crash" "MyAppName.app"

Services
Because symbolicating your log requires the exact .app and .DSYM file from the build in which the crash occurred, it’s common to use a crash reporting service. Most of the crash reporting services allow you to upload your app and .DSYM manually or from your continuous delivery pipeline. These services also offer an SDK to catch and log unhandled exceptions (crashes) to their system. Normally you can enable this with one line of code in your app. For example, initializing crash reporting for a Xamarin app with Mobile Center can be done with the following code:


MobileCenter.Start("{Your App Secret}", typeof(Crashes));

In addition to this article, you might also want to check out John Miller’s blogpost on Symbolicating iOS crashes. Great post!

Related links:

Xamarin: UITest backdoor

When running your UI tests, locally or in Xamarin Test Cloud, you sometimes want to execute code in your app from your tests. You might for instance want to setup some mock implementations instead of making actual API calls, or you might want to trigger some OS specific code from your tests. In these scenario’s backdoors come in handy!

Backdoors itself are not very complex, but it opens up a lot of possibilities with UI tests. In the past I used backdoors to:

  • Setup authentication for tests
  • Create a specific state in your app to run your tests in.
  • Set mock implementations from my UI tests.
  • Trigger deeplink

And I can think of a lot more useful scenario’s. You can create a backdoor with the following code:

iOS
In your AppDelegate class you need to create an method that returns NSString, is public,  is annotated with Export attribute and takes an NSString as parameter. For example:

public class AppDelegate : UIApplicationDelegate
{
 [Export("myBackdoorMethod:")] // notice the colon at the end of the method name
 public NSString MyBackdoorMethod(NSString value)
 {
 // In through the backdoor - do some work.
 }
}

Android
On Android you need to create a method in your MainActivity that is public, returns string/void, is annotated with Export attribute and takes an string/int/bool as parameter:

public class MainActivity : Activity
{
    [Export("MyBackdoorMethod")]
    public void MyBackdoorMethod(string value)
    {
        // In through the backdoor - do some work.
    }
}

Invoking the backdoor from your UI tests:

if (app is Xamarin.UITest.iOS.iOSApp)
{
    app.Invoke("myBackdoorMethod:", "exampleString");
}
else
{
    app.Invoke("MyBackdoorMethod", "exampleString");
}

Note: The signature of the backdoor methods is very important as the Backdoors – Xamarin docs mention.

If you want to switch out your actual implementations for mocks, the use of compiler flags (combined with build configurations) might be more suited. You can read more on this over here.

Related links: