Debugging Native Extensions for AIR iOS

In this post I am going to talk about debugging native iOS extensions for AIR. While fdb is sufficient for debugging the ActionScript part of an extension we have to rely on native tools to debug the native code. For iOS this means that the extension can be debugged only on Mac using the XCode toolset.

ADT generates a dSYM resource along with the final IPA when the IPA contains a native extension. The dSYM resource is created in the same directory as the IPA and follows the usual naming convention of having a “.dSYM” appended to the application bundle name. So if the application name is MyApp.app, the dSYM resource will be named MyApp.app.dSYM. This can be used for analyzing crash logs in the same way as for any native iOS application. To know more about post-mortem debugging see the technote TN2151 on Understanding and Analyzing iPhone OS Application Crash Reports from Apple. This post on StackOverflow discusses a few techniques for analyzing crash reports in a much friendlier language.

Setting up your application for live debugging is much more interesting. As far as I know, it is not possible to launch an application on device using gdb outside of the XCode IDE without jailbreaking it. As jailbreaking is not really an option for most people we will have to get XCode to launch our application. We will create a dummy target in XCode project, unpack our application and do some magic to put it in the location XCode expects to launch apps from.

Before we do all that however, if you are using AIR 3.3 or older you will have to take care of one very important point:
As of AIR 3.3 exceptions are not supported in the native code of the extensions. Any exceptions thrown will not be caught and will cause the application to crash. When setting an extension up for debugging it is not enough to simply not use exceptions, you must explicitly disable both C++ and Objective-C++ exceptions in the project settings of your static library. If you miss this step you might see the following problems:
  • Breakpoints mostly get hit, but not always.
  • The values of variables displayed in the debugger sometimes look suspicious.
  • Messages such as “Error from Debugger: Previous frame inner to this frame (gdb could not unwind past this frame)” occur frequently.

Exception support has been added in AIR 3.4 and this constraint no longer holds. You will of course enable these settings if using exceptions. In general, it is recommended that you disable exceptions if you are not using them. However, you may choose to keep them enabled.

 

To start debugging the native library follow these steps:

1. Open the XCode project for the native extension and add a new target to the project. Choose the iOS Application template for the target. In the attached screenshots that name of the project for the static library is cpart and the new target I have added is AnotherApp.

Screenshot: Adding an empty Target to project

Adding an empty Target to project

2. Open the target settings and change the Product Name setting to the name of your application. The name of your application is the name you have specified in the <filename></filename> tag of the application descriptor. If you are debugging on iOS 4.x you will have to set the iOS Deployment Target setting to the appropriate value. If the deployment target is higher than the iOS version on your device XCode will not allow you to install your application.

Screenshot: Changing Product Name setting

Changing Product Name setting

3. Add a new run script build phase to the target.  Remove all other build phases.

Screenshot: Adding "Run" script

Adding “Run” script

4. Add the following script to the build phase:


# change to the proper directory
pushd ~/testproject/build
# package the IPA - skip if already built
adt -package -target ipa-debug-interpreter -provisioning-profile ~/certs/MyProfile.mobileprovision -storetype pkcs12 -keystore ~/certs/Certificates.p12 -storepass XXX AnotherApp.ipa Main-app.xml Main.swf -extdir ext -platformsdk /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk/
# extract the IPA
cp AnotherApp.ipa AnotherApp.ipa.zip
unzip -o AnotherApp.ipa.zip
rm AnotherApp.ipa.zip
# copy the contents of the IPA to the location xcode wants
cp -r Payload/AnotherApp.app/*       "${CONFIGURATION_BUILD_DIR}/${CONTENTS_FOLDER_PATH}/"
cp -r AnotherApp.app.dSYM "${CONFIGURATION_BUILD_DIR}/${CONTENTS_FOLDER_PATH}/"
# remove the following files and folders to avoid signature errors when installing the app
rm "${CONFIGURATION_BUILD_DIR}/${CONTENTS_FOLDER_PATH}/_CodeSignature/CodeResources"
rmdir "${CONFIGURATION_BUILD_DIR}/${CONTENTS_FOLDER_PATH}/_CodeSignature"
rm "${CONFIGURATION_BUILD_DIR}/${CONTENTS_FOLDER_PATH}/CodeResources"
rm "${CONFIGURATION_BUILD_DIR}/${CONTENTS_FOLDER_PATH}/PkgInfo"
# restore working directory
popd

This is the script that contains the magic to extract the contents of the IPA to the folder XCode expects the application to be in and prepare it for launch. I have chosen to package the IPA from this script. You might want to package it separately from the commandline or using Flash Authoring tools.  I have already built the ANE in a post-build step of the static library and copied it to the ext directory referred to in this script.

5. Modify the above script to correct the paths and the file names for your machine. You would have to change the names in italics.

6. Setup is complete! Build this target and launch to start debugging. Typically the build step will extract the application files to the build/Debug-iphoneos sub-directory of your project directory.

UPDATE:

1. Newer versions of XCode use the LLDB debugger. Be sure to select the correct debugger.

2. Newer versions of Flash Builder support packaging apps with native extensions. The steps above will remain mostly unchanged even if you are using FB to package your apps. Just modify the script to exclude packaging apps here. FB 4.7 will also create the dSYM files for you. If you have an older version you will have to package from the command line to obtain the dSYM files.

3. The dsymutil tool used to create dSYM files has been moved out of /usr/bin directory in OS X 10.8.  It now resides in /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer. As a result ADT can no longer find it. If you are using OS X 10.8 either add the new path to your PATH variable or create a link to dsymutil at /usr/bin.