EXC_BAD_ACCESS error on arm64 when using NSInvocation
I started preparing one old project to support arm64 architecture. But when I try to execute this code on a 64-bit device, I get the EXC_BAD_ACCESS error in [invocation saveArguments]; Line
- (void)makeObjectsPerformSelector: (SEL)selector withArguments: (void*)arg1, ...
{
va_list argList;
NSArray* currObjects = [NSArray arrayWithArray: self];
for (id object in currObjects)
{
if ([object respondsToSelector: selector])
{
NSMethodSignature* signature = [[object class] instanceMethodSignatureForSelector: selector];
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: signature];
invocation.selector = selector;
invocation.target = object;
if (arg1 != nil)
{
va_start(argList, arg1);
char* arg = arg1;
for (int i = 2; i < signature.numberOfArguments; i++)
{
const char* type = [signature getArgumentTypeAtIndex: i];
NSUInteger size, align;
NSGetSizeAndAlignment(type, &size, &align);
NSUInteger mod = (NSUInteger) arg % align;
if (mod != 0)
arg += (align - mod);
[invocation setArgument: arg
atIndex: i];
arg = (i == 2) ? (char*) argList : (arg + size);
}
va_end(argList);
}
[invocation retainArguments];
[invocation invoke];
}
}
}
This seems to be an argument issue.
source to share
This is what is for the same purpose.
+ (void)callSelectorWithVarArgs:(SEL)selector onTarget:(id)target onThread:(id)thread wait:(BOOL)wait, ...
{
NSMethodSignature *aSignature = [[target class] instanceMethodSignatureForSelector:selector];
if (aSignature)
{
NSInvocation *anInvocation = [NSInvocation invocationWithMethodSignature:aSignature];
void * arg;
int index = 2;
[anInvocation setSelector:selector];
[anInvocation setTarget:target];
va_list args;
va_start(args, wait);
do
{
arg = va_arg(args, void *);
if (arg)
{
[anInvocation setArgument:arg atIndex:index++];
}
}
while (arg);
va_end(args);
[anInvocation retainArguments];
if (thread == nil)
{
[anInvocation performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:wait];
}
else
{
[anInvocation performSelector:@selector(invoke) onThread:thread withObject:nil waitUntilDone:wait];
}
}
}
Please note that this code is potentially unsafe when needed for type conversion. When the method is called has a longer argument that was passed to mine callSelectorWithVarArgs:onTarget:onThread:wait:
(for example, the called method accepts an NSUInteger (which is 64 bits on arm64), but I pass an int (which is 32 bits to arm and arm64)), which causes 64 bits to be read from the starting address of a 32-bit variable - and garbage in the data). Anyway, your implementation is potentially dangerous - you treat all arguments passed to the wrapped method as having the same types as the arguments in the called method.
This is your modified code that works:
- (void)makeObjectsPerformSelector:(SEL)selector withArguments: (void*)arg1, ...
{
NSArray* currObjects = [NSArray arrayWithArray: self];
for (id object in currObjects)
{
if ([object respondsToSelector: selector])
{
NSMethodSignature* signature = [[object class] instanceMethodSignatureForSelector: selector];
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: signature];
invocation.selector = selector;
invocation.target = object;
[invocation setArgument:&arg1 atIndex:2];
NSInteger index = 3;
void * arg;
va_list args;
va_start(args, arg1);
do
{
arg = va_arg(args, void *);
if (arg)
{
[invocation setArgument:&arg atIndex:index++];
}
}
while (arg);
va_end(args);
[invocation retainArguments];
[invocation invoke];
}
}
}
source to share
This code makes portable assumptions about the layout of different arguments in va_list
and which does not work on arm64.
You can see, for example, that other tricks (to solve a different problem) that relied on the argument layout in va_list
that worked in the 32-bit version, but which also don't work in the 64-bit version.
The only portable way to access arguments from va_list
is through va_arg
, but compilation requires a fixed type.
source to share
You use the argument list more than once. This behavior is undefined. You can work around this issue by using va_copy
instead.
Move va_start(argList, arg1)
out of the outer loop for
and create a copy of the argument list using the following: va_list copyArgList; va_copy(copyArgList, argList);
. Then use the copied argument list as usual.
Additional information on va_copy
source to share
I think you need to look at moving from this approach and re-coding things to a safer mechanism based on va_arg
which is the only safe mechanism to move variables around. Something like what @Nikita posted.
If you want to continue with the current approach, you will need to delve deeper into the iOS naming conventions for each architecture. You can find the ARM64 conventions here: https://developer.apple.com/library/ios/documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARM64FunctionCallingConventions.html
It's just that at first glance this is clearly not straightforward, and variational functions are different from the usual calling convention.
source to share