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.

Crash inside objc_msgSend

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 [2335]
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 [1]

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. [obj compute], 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 (compute). 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.

Digging

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 x1 or 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 above. Executing arm64 code we’ll go straight to the x1 register, which had the value 0x1000b50b8. 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 0x1000b50b8.

We first need to calculate the relative address inside the binary, which is 0x1000b50b8 - 0x100084000 = 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.

MachOView

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 0x310B8 - 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 344064+ 0+ 0x3038c = 0x8438C. A further 3372 bytes in and we are at our final destination of 0x850B8:

MachOView

Let us have a look at the line of code (TPTAFirstViewController.m:84) that probably caused the crash:

[(id)1 foobar];

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 UIKit:

Binary Images:
    0x30fcb000 - 0x3173efff UIKit armv7 ...

Having found the correct version of the UIKit binary, it turns out that the selector is tableView:cellForRowAtIndexPath:. Maybe someone forgot to implement this method. Or, more likely, the receiver — located at memory address 0x16ffbf10 (=r0) — is no longer a UITableViewDataSource. Probably because what used to be the table view’s data source has been deallocated and the memory now contains something completely different.

Digging deeper

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 x2 (arm64).

In this case r2=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 to, i.e. 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.


  1. objc_msgSend implementations (part of the Objective-C Runtime source code) 

  2. in fact, we can only provide the selector, not the entire message, since we don’t have access to message parameters. 

  3. 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 foobar and 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.