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.

+3


source to share


5 answers


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];
        }
    }
}

      

+5


source


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.

+4


source


You are using int and you say it works fine on 32 bit, but fails on 64 bit. Go to NSInteger or NSUInteger for your iterations. Guess what will fix your problem.

+1


source


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

+1


source


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.

0


source







All Articles