Friday, July 22, 2011

Notes from Debugging live apps in the App Store

I recently released an app to the App Store. We are getting reports back from users that the app crashes on certain versions of iOS. Following are the steps I took to figure out the cause of the crash.
  • Firstly, I logged in to iTunes Connect
  • clicked "Manage Your Applications" -> MyApp -> View Details -> Crash Reports
  • downloaded the crash report to my local machine and unzipped it
  • opened Xcode -> Organizer -> Archives
  • selected the build that I had submitted to the App Store, control-clicked it, chose Show in Finder
  • control-clicked the .xcarchive file and chose "Show Package Contents"
  • copied the files /dSYMs/myapp.app.dSYM and /Products/Applications/myapp
  • pasted these files in the same directory as the unzipped crash reports. The contents of that folder looked something like this:
MyApp_1-1_2011-07-22_iOS-5-0_Top-Crash_1_1.crash
MyApp_1-1_2011-07-22_iOS-5-0_Top-Crash_1_2.crash
MyApp_1-1_2011-07-22_iOS-5-0_Top-Crash_1_3.crash
myapp.app
myapp.app.dSYM
  • I opened the first crash report, MyApp_1-1_2011-07-22_iOS-5-0_Top-Crash_1_1.crash, in a text editor. Here's what the first little bit looked like:
Date/Time:       2011-07-19 14:44:19.660 -0700
OS Version: iPhone OS X.X (XXXXXXX)
Report Version: 104

Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0xa0020008
Crashed Thread: 0

Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0 libobjc.A.dylib 0x3490807c objc_msgSend + 20
1 Foundation 0x31f2e2a4 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:]
2 UIKit 0x359071bc -[NSIndexPath(UITableView) row]
3 myapp 0x0000a728 0x0000a728
4 myapp 0x0000a074 0x0000a074
...

Thread 0 is the thread that crashed. Below the statement "Thread 0 Crashed:" you can see the first few lines of the offending backtrace log. Each line is the next level up in the backtrace, also called a stack frame. Stack frame 0 is the message that crashed, it was called by stack frame 1 which was called by stack frame 2, etc.

The first thing I looked for is _my_ code that was nearest the top of the call stack. In this instance, it was stack frame 3. Next to the name of my binary (myapp) are two similar hex numbers. These numbers correspond to the address of the command that is being called in that stack frame. Since frames 0 - 2 are Apple code, I can see the names of the messages. However, the crash was generated from ... somebody's ... iPhone. Since that person does not have my app's debugging symbols loaded on their phone (no one ever would) the crash report only shows the address of the message, not the name. So, my task is to figure out the line of my code that corresponds to address from the crash report. The command line tool 'atos' will help in this instance.
  • copied the address of stack frame 3
  • opened terminal, navigated to the directory
  • issued the following command
atos -o myapp.app/myapp -arch armv7 0x0000a728

This showed me my offending line:
-[MyClass myMessageThatDoesStuff:] (in myapp) (MyClass.m:327)

OK! I could see that the offending line was the message myMessageThatDoesStuff: in MyClass at line 327. I opened my code and had a look
I needed to see what message called myMessageThatDoesStuff:, so I copied the address from stack frame 4 and ran atos again.
atos -o myapp.app/myapp -arch armv7 0x0000a074
with the response of something like:
-[MyOtherClass myOtherMessage:didDoStuff:forUser:] (in myapp) (MyOtherClass.m:231)
So, I inspected MyOtherClass at line 231.

Eventually, this technique helped me to find my code that had released an object and then turned around and tried to use it. derp.

No comments: