2

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?

aroth
  • 51,522
  • 20
  • 132
  • 168
  • Are you using a SQLite db type? – Warren Burton May 13 '15 at 15:13
  • @WarrenBurton - Yes, the app uses `NSSQLiteStoreType` with CoreData. – aroth May 13 '15 at 15:14
  • What is `addSelector`? – Cy-4AH May 13 '15 at 15:15
  • If possible can you change to `NSBinaryStoreType` and see if the crash reproduces. Not saying thats a long term solution. Just trying to see if maybe theres a glitch in the SQLite engine. – Warren Burton May 13 '15 at 15:16
  • @Cy-4AH - It's somewhat variable, however `addSelector` will always be a 'CoreDataGeneratedAccessor' method for manipulating a 'to-many' relationship, as appropriate to the `entity` and `object` being manipulated. – aroth May 13 '15 at 15:27
  • @WarrenBurton - Gave that a try, but with that setting the app doesn't even get to the point where the error occurs. It complains about an "Invalid Controller" and basically just sits there. – aroth May 13 '15 at 15:28
  • You will need to frag the install as the current persistent store won't be compatible . What ever you do to build your store content will need to be redone if you want to do this experiment. – Warren Burton May 13 '15 at 15:41

1 Answers1

2

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).

Community
  • 1
  • 1
Rob Napier
  • 250,948
  • 34
  • 393
  • 528
  • "The fact that you're using this to call arbitrary selectors suggests that some of them have struct or floating point returns" - The return type is always `void`, which should mean that `objc_msgSend` is the correct API to use? – aroth May 14 '15 at 00:04
  • If it were, you probably wouldn't be having the crashes on only one platform. Do all of them take one parameter with no varargs? I would try building putting all of the possible selectors in static code and compiling it and looking at the assembly ("assembler view" in the assistant). Make sure they all compile down to exactly the same setup and function call. As I said, objc_msgSend is tricky and platform-specific. It's difficult to get it right. – Rob Napier May 14 '15 at 00:08