aroth
aroth

Reputation: 54826

A64: objc_msgSend crashes while performSelector:withObject: works

I'm attempting to debug a crash on iOS that reproduces consistently on devices that support the A64 instruction set. Specifically iPads using the A7/A8X SoC's. The exact same code will also consistently not crash when run on any 32-bit iPad (and the same applies if I restrict the build to only 32-bit architectures and then run the 32-bit code on a 64-bit capable iPad).

The crash reports as an EXC_BAD_ACCESS, and there's nothing particularly fancy about the code that triggers it:

if (object && [self respondsToSelector:addSelector]) {
    objc_msgSend(self, addSelector, object);                  //EXC_BAD_ACCESS on A64 devices!
    //[self performSelector:addSelector withObject:object];   //no crash  
}

The offending line is objc_msgSend(self, addSelector, object);. The first perplexing part is that if I replace this line with [self performSelector:addSelector withObject:object];, everything works as it should (though it leaves me with an obnoxious "PerformSelector may cause a leak..." warning). Unless I've completely misinterpreted something, objc_msgSend and performSelector:withObject: should be essentially equivalent in this case.

So why does one crash (and only when using A64) while the other does not?

The next perplexing thing comes when trying to debug the crash when it occurs. Both self and object are NSManagedObject instances, and I can observe in the debugger that they are both valid objects. However, the exception is invariably reported as:

-[NSManagedObjectContext entity]: unrecognized selector sent to instance 0x...

The call is shown as originating from CoreData's internals, and I can't come up with any plausible explanation of how that could be happening, particularly as a side-effect of switching from a 32-bit to a 64-bit architecture/build.

Are there any ideas on what would cause this kind of issue? Or should I just go with that performSelector:withObject: and be happy?

Upvotes: 2

Views: 485

Answers (1)

Rob Napier
Rob Napier

Reputation: 299375

and there's nothing particularly fancy about the code that triggers it

Calling objc_msgSend() directly is fancy, and tricky to do correctly, as you're discovering.

The first perplexing part is that if I replace this line with [self performSelector:addSelector withObject:object];, everything works as it should

Yup. Because these are not the same.

There are several flavors of objc_msgSend* and you need to pick the correct one based on the return type as well as your processor. Specifically, there are three versions:

  • objc_msgSend_fpret -- For floating point return types (applies to OS X; I haven't looked up whether it applies to 64-bit ARM)
  • objc_msgSend_stret -- For structure return types (like CGPoint)
  • objc_msgSend -- For other return types

I can't remember off the top of my head if this is always exactly true on all processors. Some processors treat "large" structs differently than "small" structs. And calling conventions are all very processor-specific, which is why you're seeing it only on one processor. Remember when I said directly calling objc_msgSend() was fancy?

The fact that you're using this to call arbitrary selectors suggests that some of them have struct or floating point returns, in which case things are going to be in the wrong registers and things are going to go completely sideways.

For more discussion on this, see Why does the Objective-C compiler need to know method signatures?.

Are there any ideas on what would cause this kind of issue? Or should I just go with that performSelector:withObject: and be happy?

As the warning says, performSelector: may leak because ARC doesn't know how to memory manage it. The solution is to rework this to use blocks rather than selectors. If you must use selectors, and you know for certain that none of the selectors called here return objects, see https://stackoverflow.com/a/7933931/97337 for how to silence the warning. If these could return objects, then you need to make sure there can't be an extra retain on them, and that's just a rabbit hole that you probably should not go down (or at least should ask as a new question).

Upvotes: 2

Related Questions