3

I've begun prepare one old project to support arm64 architecture. But when I try to execute this code on 64 bit device I get EXC_BAD_ACCESS crash on [invocation retainArguments]; 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];
        }
    }
}

It seems like its some problem with arguments.

abagmut
  • 911
  • 1
  • 10
  • 22
  • Could you provide code sample that calls that code and crashes? – Nikita Ivaniushchenko Mar 12 '15 at 10:35
  • The given code is a category to NSArray class, providing ability for each object in array to perform selector with multiple arguments. Each object in array - is a listener(delegate), as "Multiple Listeners" design pattern requires . For example - after response from server we should make each listener to perform selector. The call located in server success callback and is look like - `[self.listeners makeObjectsPerformSelector: @selector(serverManager:didLikeVideo:withError:) withArguments: self, operation.video, operation.error, nil];` – abagmut Mar 13 '15 at 09:38
  • So, no unsafe type conversions, please check my updated answer. Can not understand why you do complex tricks with locating arguments in memory – Nikita Ivaniushchenko Mar 13 '15 at 10:30

5 Answers5

5

This is what have for the same purposes.

+ (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 take into account, that this code is potentially unsafe with when necessary to perform type conversion. When invoked method has longer argument that was passed to my callSelectorWithVarArgs:onTarget:onThread:wait: (for example, invoked method receives NSUInteger (which is 64bit on arm64) but i pass int (which is 32bit on both arm and arm64)), that causes read of 64 bit from start address of 32bit variable - and trash in data). Anyway, your implementation is potentially dangerous - you treat all arguments passed to wrapped method as having the same types as arguments in invoked 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];
        }
    }
}
Nikita Ivaniushchenko
  • 1,425
  • 11
  • 11
  • Your modified code work fine on 64 bits, but it crashed on 32 because we reach out the bounds here `[NSInvocation setArgument:atIndex:]`. So, I replaced your loop: do { arg = va_arg(args, void *); if (arg) { [invocation setArgument:&arg atIndex:index++]; } } while (arg); with: for (NSUInteger i = index; i < signature.numberOfArguments; i++) { arg = va_arg(args, void *); if (arg) { [invocation setArgument: &arg atIndex: i]; } } – abagmut Mar 13 '15 at 11:32
  • 1
    How can we reach bounds there? Do you call method with 2,147,483,647 arguments? – Nikita Ivaniushchenko Mar 13 '15 at 11:36
  • `arg = va_arg(args, void *);` return some pointer (not recognized) but not `nil` for index beyond `signature.numberOfArguments` – abagmut Mar 13 '15 at 11:40
  • How can i reproduce that? – Nikita Ivaniushchenko Mar 13 '15 at 11:44
  • I found problem - I missed `nil` in the end of arg_list - so your solution work perfect, thank you so much. – abagmut Mar 13 '15 at 12:34
4

This code is making non-portable assumptions about the layout of different arguments in va_list, and which do not work on arm64.

You can see, for example, that there are other tricks (to solve a different problem) that relied on the layout of arguments in va_list, that worked in 32-bit, but which also don't work in 64-bit.

The only portable way to access arguments from a va_list is through va_arg, but that requires a fixed type at compile-time.

Community
  • 1
  • 1
newacct
  • 119,665
  • 29
  • 163
  • 224
1

You are using int and you say it is running fine on 32bit but crash on 64bit. Switch to NSInteger or NSUInteger for your iterations. Guess that will fix your problem

Helge Becker
  • 3,219
  • 1
  • 20
  • 33
1

You are using the argument list more than once. Doing so is undefined behavior. You can work around this issue by using va_copy instead.

Move the va_start(argList, arg1) outside the outer for loop and create a copy of the arguments list using the following: va_list copyArgList; va_copy(copyArgList, argList);. Then use the copied argument list as normal.

More information about va_copy

Buzzy
  • 3,618
  • 4
  • 30
  • 41
0

I think you need to take a look at moving away from this approach and recode things to a safer mechanism based on va_arg which is the only safe mechanism for traversing variable arguments. Something along the lines of what was posted by @Nikita.

If you want to continue with the current approach you will need to delve into the iOS calling conventions for each architecture. You can find the ARM64 conventions here: https://developer.apple.com/library/ios/documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARM64FunctionCallingConventions.html

Just from an initial look it is clearly not straight forward and variadic functions differ from the normal calling convention.

Rory McKinnel
  • 7,936
  • 2
  • 17
  • 28