EXC_BAD_ACCESS сбой на arm64 при использовании NSInvocation

Я начал готовить один старый проект для поддержки архитектуры arm64. Но когда я пытаюсь выполнить этот код на 64-битном устройстве, я получаю аварийное завершение EXC_BAD_ACCESS в [invocation retainArguments]; линия

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

Кажется, это какая-то проблема с аргументами.

5 ответов

Решение

Это то, что есть для тех же целей.

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

Пожалуйста, примите во внимание, что этот код потенциально небезопасен, когда необходимо выполнить преобразование типов. Когда вызванный метод имеет более длинный аргумент, который был передан моему callSelectorWithVarArgs:onTarget:onThread:wait: (например, вызванный метод получает NSUInteger (который является 64-битным для arm64), но я передаю int (который является 32-битным как для arm, так и для arm64)), что вызывает чтение 64-битного из начального адреса 32-битной переменной - и мусор в данных). В любом случае, ваша реализация потенциально опасна - вы рассматриваете все аргументы, передаваемые в упакованный метод, как имеющие те же типы, что и аргументы в вызываемом методе.

Это ваш модифицированный код, который работает:

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

Этот код делает непереносимые предположения о расположении различных аргументов в va_listи которые не работают на arm64.

Например, вы можете видеть, что есть другие приемы (для решения другой проблемы), которые основывались на расположении аргументов в va_list, который работал в 32-битной, но также не работает в 64-битной.

Единственный портативный способ получить доступ к аргументам из va_list через va_arg, но это требует фиксированного типа во время компиляции.

Вы используете int, и вы говорите, что он работает нормально на 32-битной, но сбой на 64-битной. Переключитесь на NSInteger или NSUInteger для ваших итераций. Угадай, это решит твою проблему

Вы используете список аргументов более одного раза. Это неопределенное поведение. Вы можете обойти эту проблему, используя va_copy вместо.

Переместить va_start(argList, arg1) вне внешнего for зациклите и создайте копию списка аргументов, используя следующее: va_list copyArgList; va_copy(copyArgList, argList);, Затем используйте скопированный список аргументов как обычно.

Больше информации о va_copy

Я думаю, вам нужно взглянуть на отход от этого подхода и перекодировать вещи в более безопасный механизм, основанный на va_arg который является единственным безопасным механизмом обхода переменных аргументов. Что-то вроде того, что было опубликовано @Nikita.

Если вы хотите продолжить работу с текущим подходом, вам нужно углубиться в соглашения о вызовах iOS для каждой архитектуры. Вы можете найти соглашения ARM64 здесь: https://developer.apple.com/library/ios/documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARM64FunctionCallingConventions.html

С первого взгляда это явно не так просто, и функции с переменными числами отличаются от обычного соглашения о вызовах.

Другие вопросы по тегам