App Crashing on 32-bit Devices When Resigning Xamarin.iOS IPAs

February 28, 2017

Recently on a project, we were receiving reports from some users that the iOS app we had developed with Xamarin was crashing immediately after opening the application, but only on certain devices. Basically the application would display the splash screen for a second or two, then would immediately return to the home screen. Once we were able to get a hold of a device that presented the issue, the error that was displayed in the application log wasn’t immediately helpful:

HelloWorld_iPhone[200] <Warning>: The assembly mscorlib.dll was not found or could not be loaded. HelloWorld_iPhone[200] <Warning>: It should have been installed in the '/Users/builder/data/lanes/3969/44931ae8/source/xamarin-macios/builds/install/target7/lib/mono/2.1/mscorlib.dll' directory com.apple.xpc.launchd[1] (UIKitApplication:com.example.HelloWorld[0xc4d3][200]) <Warning>: Service exited with abnormal code: 1

What made even less sense was that the same application would launch on some devices and not others. The culprit in particular was an iPhone 5c. Then realization dawned, the iPhone 5c is a 32-bit device. Could this be related to the issue?

In order to support both 32-bit and 64-bit architectures, Xamarin developed the Unified API. This allows support for both architectures using the same binary. Some of what makes this happen is some clever use of symlinks in the IPA file. Inside the IPA file, there are two folders that contain the DLLs for the application code, .monotouch-32 and .monotouch-64. But what is really interesting is that some of the DLLs in the .monotouch-32 folder aren’t actually DLLs at all, but are actually symlinks to the DLLs contained within the .monotouch-64 folder. If you were to extract the IPA into a folder (since it really is just a zip file), the contents of the .monotouch-32 folder should look something like this:

$ unzip HelloWorld.ipa && ls -l Payload/HelloWorld_iPhone.app/.monotouch-32 total 5096 -rw-r--r-- 1   2832 Feb 27 16:05 HelloWorld_iPhone.armv7.aotdata lrwxr-xr-x 1     38 Feb 27 16:14 HelloWorld_iPhone.exe -> ../.monotouch-64/HelloWorld_iPhone.exe -rw-r--r-- 1   1424 Feb 27 16:05 System.armv7.aotdata lrwxr-xr-x 1     27 Feb 27 16:14 System.dll -> ../.monotouch-64/System.dll lrwxr-xr-x 1     31 Feb 27 16:14 System.dll.mdb -> ../.monotouch-64/System.dll.mdb -rw-r--r-- 1 215264 Feb 27 16:05 Xamarin.iOS.armv7.aotdata -rw-r--r-- 1 228352 Feb 27 16:05 Xamarin.iOS.dll -rw-r--r-- 1  66276 Feb 27 16:05 Xamarin.iOS.dll.mdb -rw-r--r-- 1 966656 Feb 27 16:05 mscorlib.armv7.aotdata -rw-r--r-- 1 823296 Feb 27 16:05 mscorlib.dll -rw-r--r-- 1 281173 Feb 27 16:05 mscorlib.dll.mdb

Now at this point, if you are still reading, you are probably asking, “Okay, I don’t see any issues yet. What is the problem?” And I am getting there. The issue comes in how the IPA is created. During development, we had ran into a bit of a snag where we were unable to create IPAs on some of the development machines (namely mine) due to a combination of a few things that I cannot unfortunately remember at this point and thankfully is not core to the issue at hand. Since there is always more than one way to solve a problem, and as I am sure some of you know, you can create IPA files from the app bundle by just dragging and dropping the application bundle into iTunes. Now here comes the sticking point.

For this particular application, we were handing off the IPA to the client where they would be resigning the application with their own enterprise developer account. Part of the resigning process involves unzipping the IPA file, changing some bits of the application bundle, and then zipping it all back up. As it turns out, something in the way that iTunes creates the IPA file does not agree with this process. When unzipping the IPA created by dragging and dropping the application bundle into iTunes, the contents of the .monotouch-32 folder now look something like this:

$ unzip HelloWorld.ipa && ls -l Payload/HelloWorld_iPhone.app/.monotouch-32 total 5096 -rw-r--r-- 1   2832 Feb 27 16:05 HelloWorld_iPhone.armv7.aotdata -rw-r--r-- 1     38 Feb 27 16:06 HelloWorld_iPhone.exe -rw-r--r-- 1   1424 Feb 27 16:05 System.armv7.aotdata -rw-r--r-- 1     27 Feb 27 16:06 System.dll -rw-r--r-- 1     31 Feb 27 16:06 System.dll.mdb -rw-r--r-- 1 215264 Feb 27 16:05 Xamarin.iOS.armv7.aotdata -rw-r--r-- 1 228352 Feb 27 16:05 Xamarin.iOS.dll -rw-r--r-- 1  66276 Feb 27 16:05 Xamarin.iOS.dll.mdb -rw-r--r-- 1 966656 Feb 27 16:05 mscorlib.armv7.aotdata -rw-r--r-- 1 823296 Feb 27 16:05 mscorlib.dll -rw-r--r-- 1 281173 Feb 27 16:05 mscorlib.dll.mdb

As you can see, all of the files are still there, but the symlinks no longer point to the files in the .monotouch-64 folder. These broken symlinks are what lead to the application functioning just fine on 64-bit devices, but ultimately crash on 32-bit devices. And now that error message that we saw at the beginning makes a little more sense. When launching the application on a 32-bit device, it can’t find the DLLs in the places where it expects to find them, because the links are all broken and without them it doesn’t know where else to look.

When building the IPA directly from Xamarin Studio, the symlinks are all maintained during the unzip/zip that takes place during the resigning process. This also holds true when building from the command-line using mdtool or xbuild. So I guess this is a word to the wise, when building a Xamarin.iOS application using the Unified API, use the build tools provided to create the IPA and don’t use iTunes. This may seem like common sense, but this was a fairly strange bug and was not at all what I expecting when investigating this issue.