I occasionally use O'Reilly Publishing's Bookbag iPhone application to read my Safari Bookshelf books when I'm away from my computer (Safari Bookbag has no relation to Apple's Safari browser). Last night I was suprised to find that the Bookbag application was crashing on launch. Checking the app store application description, I found this note from O'Reilly:
This version does not work with iPhone OS v2.2. We are working on a 2.2-compatible version and will be submitting soon.
That's odd -- the most likely cause of an application breaking on a new iPhoneOS release would be if they used private API. Taking a look at the iPhone crash log, that appears to be the case:
Process: Bookbag [2386] [snip] Thread 0 Crashed: 0 libSystem.B.dylib 0x31459c58 __kill + 8 1 libSystem.B.dylib 0x31459c46 kill + 4 2 libSystem.B.dylib 0x31459c3a raise + 10 3 libSystem.B.dylib 0x31474424 abort + 36 [snip] 8 libobjc.A.dylib 0x300c1f84 objc_exception_throw + 92 9 CoreFoundation 0x302c883e -[NSObject doesNotRecognizeSelector:] + 106 10 CoreFoundation 0x30287222 ___forwarding___ + 490 11 CoreFoundation 0x3026d618 _CF_forwarding_prep_0 + 40 12 Bookbag 0x0000406c 0x1000 + 12396 13 Bookbag 0x0000463a 0x1000 + 13882 14 Bookbag 0x00004402 0x1000 + 13314
We can see here that Bookbag calls some method that triggers a call to -[NSObject doesNotRecognizeSelector:]. The 'doesNotRecognizeSelector:' message is sent by the Objective-C runtime when an object receives a message it can't respond to or forward. The doesNotRecognizeSelector method raises an exception, and the application terminates.
Objective-C methods don't simply disappear from an application, but they can disappear from a library. Private APIs are private because they may be modified or removed at any time -- if a method disappeared from one of the iPhoneOS libraries, chances are it was a private method.
To be sure that Bookbag really is using private API -- and to determine what API they used -- I decided to strip the DRM from the Bookbag application and disassemble their binary. Thanks to the crash log, we already know where to look -- address 0x406c, the last return address located in the Bookbag binary before doesNotRecognizeSelector: was triggered.
Apple's iPhone Application DRM utilizes Mach-O binary encryption to protect the application code. Briefly put, application decryption is handled by the kernel -- within a process space, the encrypted section of its Mach-O binary is automatically decrypted by the kernel as it is read. To strip the DRM from an iPhone app, one can simply attach a debugger to the application on a jailbroken phone, and dump the text section containing the program code. For a more comprehensive description of Mach-O binary encryption, read Amit Singh's article: Understanding Apple's Binary Protection in Mac OS X.
So after stripping the DRM (let's only use this power for good, not evil, yes?), I can disassemble the relevant Bookbag code and determine what private API is being called to trigger the crash. Here's my abridged annotated disassembly. (see unabridged)
-[CoverFlowView initWithFrame:andCount:]: [snip] 0x4050 1c22 mov r2, r4 (add r2, r4, #0) 0x4052 ca0b ldmia r2!,{r0, r1, r3} 0x4054 c50b stmia r5!,{r0, r1, r3} // argument 1 - saved pointer to @selector(initWithFrame:numberOfCovers:) 0x4056 4651 mov r1, r10 0x4058 9b09 ldr r3, [sp, #36] 0x405a 9a20 ldr r2, [sp, #128] 0x405c 9311 str r3, [sp, #68] 0x405e 9202 str r2, [sp, #8] 0x4060 ab10 add r3, sp, #64 0x4062 466a mov r2, sp // argument 0 - UICoverFlowLayer instance 0x4064 4658 mov r0, r11 0x4066 cb30 ldmia r3!,{r4, r5} 0x4068 c230 stmia r2!,{r4, r5} // argument 2 - value of -[UIScreen frame] 0x406a 9a0e ldr r2, [sp, #56] // argument 3 - number of covers 0x406c 9b0f ldr r3, [sp, #60] // Call -[UICoverFlowLayer initWithFrame:numberOfCovers] (UICoverFlow is private API) 0x406e efa8f006 blx 0xafc0 ; symbol stub for: _objc_msgSend
It's clear from the disassembly that Bookbag is using the private UICoverFlowLayer API. When Apple released iPhoneOS 2.2, the -[UICoverFlowLayer initWithFrame:numberOfCovers:] method was removed, and Safari Bookbag started crashing.
Safari Bookbag's use of UICoverFlowLayer is a discovery that has caused me some chagrin -- I held off on implementing Peeps -- which includes a fully functional CoverFlow clone -- until I saw that Apple had approved Safari Bookbag. I had assumed the authors of Bookbag implemented their own CoverFlow, too. To increase my vexation, Apple still hasn't approved Peeps nearly a month after it was submitted for review (on November 9th).
Lastly, this accutely demonstrates why one should be incredibly circumspect about using undocumented API -- it's undocumented because it can change at any time, and if it changes, your application will very likely start crashing. Even if your app slips through the cracks in Apple's review system, users will notice when your app breaks (see the Safari Bookbag reviews).