Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EXC_BAD_ACCESS crash on arm64 when use NSInvocation

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.

like image 729
abagmut Avatar asked Jan 26 '26 17:01

abagmut


2 Answers

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];
        }
    }
}
like image 163
Nikita Ivaniushchenko Avatar answered Jan 28 '26 11:01

Nikita Ivaniushchenko


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.

like image 21
newacct Avatar answered Jan 28 '26 11:01

newacct