Note: This article was originally posted on an internal company blog, and since then I lost the original crash report used in the first analysis. Therefore memory addresses do not correspond to the depicted crash report.
Setting the stage
Here is a typical example of a crash report from an iOS app (parts have been removed for brevity):
Incident Identifier: 9E0D6B24-8855-4A15-AED4-63772CCADAE9 CrashReporter Key: ED0C5B1A-4DC5-4CD7-B26C-1147F937B525 Hardware Model: iPhone6,2 Process: ThePerfectTestAp  Path: /private/var/mobile/Containers/Bundle/Application/410D19FC-7AB0-4866-BD4B-63677A3188E2/ThePerfectTestApp.app/ThePerfectTestApp Identifier: com.trifork.enterprise.ch.trifork.ThePerfectTestApp Version: 55 Code Type: ARM-64 Parent Process: launchd  Date/Time: 2014-10-22 19:21:31 +0000 OS Version: iPhone OS 8.1 (12B411) Report Version: 104 Exception Type: SIGSEGV Exception Codes: SEGV_ACCERR at 0x1 Crashed Thread: 0 Thread 0 Crashed: 0 libobjc.A.dylib 0x0000000194cd3bc8 objc_msgSend + 8 1 ThePerfectTestApp 0x0000000100091dac -[TPTAFirstViewController crashButtonTapped:] (TPTAFirstViewController.m:84) 2 UIKit 0x0000000188da90f8 -[UIApplication sendAction:to:from:forEvent:] + 96 3 UIKit 0x0000000188d9222c -[UIControl _sendActionsForEvents:withEvent:] + 612 ... 15 UIKit 0x0000000188dda784 UIApplicationMain + 1488 16 ThePerfectTestApp 0x0000000100091404 main (main.m:14) 17 libdyld.dylib 0x000000019532ea08 <redacted> + 4 ... Thread 0 crashed with ARM-64 Thread State: pc: 0x0000000194cd3bc8 fp: 0x000000016fd71750 sp: 0x000000016fd71740 x0: 0x0000000000000001 x1: 0x00000001000bd040 x2: 0x0000000000000000 x3: 0x0000000170012550 x4: 0x0000000174097ed0 x5: 0x0000000174097ed0 x6: 0x0000000170225fa0 x7: 0x0000000000000370 x8: 0x000001a5000d5521 x9: 0x00000000005952c0 x10: 0x0000000000595201 x11: 0x00000000004c5500 x12: 0x0000000000000002 x13: 0x0000000000000000 x14: 0x0000000000000000 x15: 0x3ff0000000000000 x16: 0x0000000194cd3bc0 x17: 0x0000000100091e00 x18: 0x0000000000000000 x19: 0x0000000170012540 x20: 0x000000015fd10750 x21: 0x00000001000bd010 x22: 0x000000015fe0fcd0 x23: 0x000000017022a960 x24: 0x0000000000000001 x25: 0x0000000000000000 x26: 0x00000001895454ef x27: 0x0000000170059470 x28: 0x0000000170225fa0 lr: 0x0000000100091dac cpsr: 0x0000000020000000 Binary Images: 0x10008c000 - 0x1000c7fff +ThePerfectTestApp arm64 <ffbb75ae8b263829b7be2711ee844c42> /private/var/mobile/Containers/Bundle/Application/410D19FC-7AB0-4866-BD4B-63677A3188E2/ThePerfectTestApp.app/ThePerfectTestApp ...
The crash seems to have its origin in
objc_msgSend, but don’t file a bug report to Apple:
Every time you send a message to an object in Objective-C, e.g.
objc_msgSend (or a friend) takes care of calling
the correct implementation of
compute. So this tiny function1 has been executed trillions
of times since it was written, and it is very unlikely that there are any bugs in it.
We must look at the app itself, and what message was being sent. Unfortunately the crash report does not provide any
information about the receiver (
obj) or the message (
Since we do post-burial crash analysis, we are unable to ask the app for any runtime-information besides the information
already provided in the report, and we must resort to whatever static information we have.
In this case that just might be enough to hand you the message2.
Greg Parker of Sealie Software
has written a great blog post that will get us started on our quest.
It turns out that two pieces of information are all we need: the value of the
r1 register, and the binary
into which this register points.
The register value we get from the thread state, and a reference to the binary can be derived from the binary images list.
Let’s look at an example: The Perfect Test App crashed in
objc_msgSend, generating a crash report similar to the one
Executing arm64 code we’ll go straight to the
x1 register, which had the value
Looking through the binary images, we see that it is pointing at the app’s binary:
Binary Images: 0x100084000 - 0x1000bffff +ThePerfectTestApp ...
Fortunately we saved the .ipa file, and can therefore retrieve the ThePerfectTestApp binary. Let’s open it in our
favourite Mach-O viewer and figure out what was
at memory location
We first need to calculate the relative address inside the binary, which is
0x310B8. We already know that we should be looking at the arm64 slice.
And our magic hat tells us that we should focus our search within the
__TEXT segment’s C-string literals sections.
In this case there are four of those:
__objc_methname Address = 0x10003038C Size = 16979 __cstring Address = 0x1000345DF Size = 17315 __objc_classname Address = 0x1000389D8 Size = 1124 __objc_methtype Address = 0x100038E3C Size = 8570
Given that the virtual memory address of the
__TEXT segment is
0x100000000, we end up with the four sections
spanning these relative addresses:
__objc_methname: 0x3038C-0x345DE __cstring: 0x345DF-0x38981 __objc_classname: 0x389D8-0x38E3B __objc_methtype: 0x38E3C-0x3AFB5
What we are looking for must therefore be in
__objc_methname. All we have to do is figure out what is at the
0x3038c = 3372th byte within that section. But where is it located in the binary? Back to the
__TEXT load segment command, which states that the file offset is 0, to which we have to add the offset of
the section, which is
0x3038c. Adding the arm64 slice’s offset of
344064 (found in the file’s fat header) we
end up at
0x8438C. A further 3372 bytes in and we are at our final destination of
Let us have a look at the line of code (
TPTAFirstViewController.m:84) that probably caused the crash:
Now, isn’t that a coincidence :-)
This example is pretty silly. We should just have started our bug-hunt by looking at the sorce code, but fancy getting the same kind of information when your app crashes inside some iOS framework! Here’s another example, this time from the real life:
Thread 0 Crashed: 0 libobjc.A.dylib 0x38f73626 objc_msgSend + 6 1 UIKit 0x310afc27 -[UITableView _updateVisibleCellsNow:] + 1807 2 UIKit 0x310af47d -[UITableView layoutSubviews] + 185 3 UIKit 0x30fd5d59 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 381 4 QuartzCore 0x30c5362b -[CALayer layoutSublayers] + 143 5 QuartzCore 0x30c4ee3b CA::Layer::layout_if_needed(CA::Transaction*) + 351 6 QuartzCore 0x30c4eccd CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 17 7 QuartzCore 0x30c4e6df CA::Context::commit_transaction(CA::Transaction*) + 231 8 QuartzCore 0x30c4e4ef CA::Transaction::commit() + 315 9 UIKit 0x30fce40b _afterCACommitHandler + 127 10 CoreFoundation 0x2e784255 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 21 11 CoreFoundation 0x2e781bf9 __CFRunLoopDoObservers + 285 12 CoreFoundation 0x2e781f3b __CFRunLoopRun + 731 13 CoreFoundation 0x2e6ecebf CFRunLoopRunSpecific + 523 14 CoreFoundation 0x2e6ecca3 CFRunLoopRunInMode + 107 15 GraphicsServices 0x335f2663 GSEventRunModal + 139 16 UIKit 0x3103914d UIApplicationMain + 1137 17 MyApp 0x0010373f main (main.m:16) 18 libdyld.dylib 0x39476ab7 start + 3
One immediately notices that the only time the app is mentioned is at frame #17, which is just a call to
UIApplicationMain(); nothing to be gained there.
Armed with our new tool, we head for the thread state. We are executing armv7 code, so register
r1 is of interest.
Thread 0 crashed with ARM Thread State: pc: 0x38f73626 r7: 0x27d18978 sp: 0x27d18954 r0: 0x16ffbf10 r1: 0x31603d1b r2: 0x17483c00 r3: 0x16f4b6f0 r4: 0x17483c00 r5: 0x00000350 r6: 0x185163e0 r8: 0x39bc3294 r9: 0xa0000000 r10: 0x16f4b6f0 r11: 0x000001d0 ip: 0x39ab232c lr: 0x311088f7 cpsr: 0x20000030
Looking through the binary images, we find that
r1 points at
Binary Images: 0x30fcb000 - 0x3173efff UIKit armv7 ...
Having found the correct version of the
UIKit binary, it turns out that the selector
tableView:cellForRowAtIndexPath:. Maybe someone forgot to implement this method. Or, more likely, the
receiver — located at memory address
r0) — is no longer a
because what used to be the table view’s data source has been deallocated and the memory now contains something
In case the above analysis results in the message being one of the
performSelector: variants, there is even more
information to be gained. Let’s have a look at another real-life stack trace:
Thread 0 Crashed: 0 libobjc.A.dylib 0x385c5636 objc_msgSend + 21 1 UIKit 0x3068b643 -[UIApplication sendAction:toTarget:fromSender:forEvent:] + 38 2 UIKit 0x3068b613 -[UIControl sendAction:to:forEvent:] + 46 3 UIKit 0x30676d5b -[UIControl _sendActionsForEvents:withEvent:] + 374 4 UIKit 0x30b0768d -[UIStepper _updateCount:] + 412 5 UIKit 0x30b07393 -[UIStepper endTrackingWithTouch:withEvent:] + 26 6 UIKit 0x3068afed -[UIControl touchesEnded:withEvent:] + 484 7 UIKit 0x3064e521 _UIGestureRecognizerUpdate + 5528 8 UIKit 0x30686305 -[UIWindow _sendGesturesForEvent:] + 772 9 UIKit 0x30685c2b -[UIWindow sendEvent:] + 666 10 UIKit 0x3065ae55 -[UIApplication sendEvent:] + 196 11 UIKit 0x30659521 _UIApplicationHandleEventQueue + 7120 12 CoreFoundation 0x2ddeffaf __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 14 13 CoreFoundation 0x2ddef477 __CFRunLoopDoSources0 + 206 14 CoreFoundation 0x2ddedc67 __CFRunLoopRun + 630 15 CoreFoundation 0x2dd58729 CFRunLoopRunSpecific + 524 16 CoreFoundation 0x2dd5850b CFRunLoopRunInMode + 106 17 GraphicsServices 0x32cb76d3 GSEventRunModal + 138 18 UIKit 0x306b9871 UIApplicationMain + 1136 19 MyOtherApp 0x000e63f5 main (main.m:16) 20 libdyld.dylib 0x38ab9ab7 start + 2
It looks like the user tapped on a stepper control, making the app invoke some action, crashing in the process. Pretty much impossible to figure out which stepper control unless there is only one. Wouldn’t it be nice to know which action the app tried to invoke? That could provide a clue as to which exact control was tapped.
The immediate analysis points at
performSelector:withObject:, which doesn’t really provide any additional
information. Fortunately, we can figure out more by looking at
r2 (32-bit arm) or
In this case
0x00139163, smack inside
Binary Images: 0xdf000 - 0x142fff +MyOtherApp ...
Giddy with excitement that something is pointing at our own code, we dig out MyOtherApp, go through the calculations
and end up with the selector named
updateMotorPower — thereby giving the developer a good starting-point for
finding that pesky stepper and crushing another bug.
We do the heavy lifting
Fortunately you don’t have to do all this tedious work yourself. Provide us with your .ipa and .dSYM.zip files and start sending your crashes our way, and we’ll do the hard work and show you a nice stack trace with all the information above3.
In case it is not already clear: Don’t go hunting for the bug in the method that the analysis points
updateMotorPower. Crashes inside
objc_msgSend are most often caused by the receiver having been deallocated, no longer pointing
to the object the message was intended for. A more useful approach is to figure out why the receiver is no longer
what it is supposed to be. And that, my fellow developer, is left for you to do.
in fact, we can only provide the selector, not the entire message, since we don’t have access to message parameters. ↩
Disclaimer: Using this technique is not bullet-proof. The registers could have been overridden during the execution of
objc_msgSend, or the developer has dynamically registered a new selector using e.g.
NSSelectorFromString(). In order to find app-specific selectors (like
updateMotorPower), we need the .ipa file that was installed on the device, so this will only work for non-bitcode apps, and is mostly usable while testing the app. ↩