使用NSInvocation时,arm64上的EXC_BAD_ACCESS崩溃

时间:2014-12-30 15:15:30

标签: ios nsinvocation arm64

我已经开始准备一个旧项目来支持arm64架构。但是当我尝试在64位设备上执行此代码时,我在[invocation retainArguments]上遇到EXC_BAD_ACCESS崩溃;线

- (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 个答案:

答案 0 :(得分: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(在arm64上是64位)但是我传递int(在arm和arm64上都是32位)),这会导致读取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];
        }
    }
}

答案 1 :(得分:4)

此代码对va_list中不同参数的布局做出了不可移植的假设,并且在arm64上不起作用。

你可以看到,例如,有other tricks(解决不同的问题)依赖于va_list中的参数布局,这些参数在32位工作,但也有用64位工作。

va_list访问参数的唯一可移植方式是va_arg,但这需要在编译时使用固定类型。

答案 2 :(得分:1)

你正在使用int,你说它在32位运行正常,但在64位崩溃。切换到NSInteger或NSUInteger进行迭代。猜猜这会解决你的问题

答案 3 :(得分:1)

您不止一次使用参数列表。这样做是未定义的行为。您可以使用va_copy来解决此问题。

va_start(argList, arg1)移到外部for循环之外,并使用以下代码创建参数列表的副本:va_list copyArgList; va_copy(copyArgList, argList);。然后照常使用复制的参数列表。

有关va_copy

的更多信息

答案 4 :(得分:0)

我认为您需要考虑从这种方法转移并将事物重新编码为基于va_arg的更安全的机制,这是遍历变量参数的唯一安全机制。与@Nikita发布的内容有关。

如果您想继续使用当前的方法,您需要深入研究每种架构的iOS调用约定。您可以在此处找到ARM64约定:https://developer.apple.com/library/ios/documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARM64FunctionCallingConventions.html

从最初看起来,它显然不是直截了当的,可变函数与普通调用约定不同。