【问题标题】:EXC_BAD_ACCESS crash on arm64 when use NSInvocation使用 NSInvocation 时,ARM64 上的 EXC_BAD_ACCESS 崩溃
【发布时间】:2015-02-26 18:22:12
【问题描述】:

我已经开始准备一个支持 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];
        }
    }
}

看来参数有问题。

【问题讨论】:

  • 您能否提供调用该代码并崩溃的代码示例?
  • 给定的代码是 NSArray 类的一个类别,为数组中的每个对象提供了使用多个参数执行选择器的能力。数组中的每个对象 - 都是一个侦听器(委托),正如“多个侦听器”设计模式所要求的那样。例如 - 在服务器响应后,我们应该让每个监听器执行选择器。调用位于服务器成功回调中,看起来像 - [self.listeners makeObjectsPerformSelector: @selector(serverManager:didLikeVideo:withError:) withArguments: self, operation.video, operation.error, nil];
  • 所以,没有不安全的类型转换,请查看我更新的答案。无法理解你为什么要在内存中定位参数来使用复杂的技巧

标签: ios nsinvocation arm64


【解决方案1】:

这是出于相同目的。

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

【讨论】:

  • 您修改后的代码在 64 位上运行良好,但在 32 位上崩溃了,因为我们在 [NSInvocation setArgument:atIndex:] 处超出了界限。所以,我替换了你的循环: do { arg = va_arg(args, void *); if (arg) { [调用 setArgument:&arg atIndex:index++]; } } 而 (arg);与: for (NSUInteger i = index; i
  • 我们怎样才能到达那里的界限?你用 2,147,483,647 个参数调用方法吗?
  • arg = va_arg(args, void *); 为超出signature.numberOfArguments的索引返回一些指针(无法识别)但不返回nil
  • 我怎样才能重现它?
  • 我发现了问题 - 我错过了 arg_list 末尾的 nil - 所以你的解决方案很完美,非常感谢。
【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 我很确定,方法中的参数少于 2,147,483,647 个,所以事实并非如此。
    • 你能保证你的int的高32位在64位架构上没有垃圾吗?我不这么认为。
    • 对不起,什么?将 32bit 变量赋值给 64big one 时会进行类型转换。
    【解决方案4】:

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

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

    更多关于va_copy的信息

    【讨论】:

      【解决方案5】:

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

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

      乍一看,它显然不是直截了当的,可变参数函数不同于正常的调用约定。

      【讨论】:

        猜你喜欢
        • 2014-03-27
        • 2015-05-28
        • 1970-01-01
        • 2013-03-24
        • 2010-11-18
        • 2013-01-07
        • 1970-01-01
        • 1970-01-01
        • 2015-06-02
        相关资源
        最近更新 更多