简体   繁体   English

使用NSInvocation时,arm64上的EXC_BAD_ACCESS崩溃

[英]EXC_BAD_ACCESS crash on arm64 when use NSInvocation

I've begun prepare one old project to support arm64 architecture. 我已经开始准备一个旧项目来支持arm64架构。 But when I try to execute this code on 64 bit device I get EXC_BAD_ACCESS crash on [invocation retainArguments]; 但是当我尝试在64位设备上执行此代码时,我在[invocation retainArguments]上遇到EXC_BAD_ACCESS崩溃; 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. 这看起来像是争论的一些问题。

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). 当被调用的方法有更长的参数传递给我的callSelectorWithVarArgs:onTarget:onThread:wait:例如,被调用的方法接收NSUInteger(在arm64上是64位)但我传递int(在arm和arm64上都是32位)),这导致从32位变量的起始地址读取64位 - 并且数据中的垃圾。 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];
        }
    }
}

This code is making non-portable assumptions about the layout of different arguments in va_list , and which do not work on arm64. 此代码对va_list的不同参数的布局进行了不可移植的假设,并且在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. 例如,您可以看到还有其他技巧 (解决不同的问题)依赖于va_list的参数布局,这些参数在32位中工作,但在64位中也不起作用。

The only portable way to access arguments from a va_list is through va_arg , but that requires a fixed type at compile-time. va_list访问参数的唯一可移植方法是通过va_arg ,但这需要在编译时使用固定类型。

You are using int and you say it is running fine on 32bit but crash on 64bit. 你正在使用int,你说它在32位运行正常,但在64位崩溃。 Switch to NSInteger or NSUInteger for your iterations. 切换到NSInteger或NSUInteger进行迭代。 Guess that will fix your problem 猜猜这会解决你的问题

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. 您可以使用va_copy来解决此问题。

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); va_start(argList, arg1)外部for循环之外,并使用以下命令创建参数列表的副本: va_list copyArgList; va_copy(copyArgList, argList); va_list copyArgList; va_copy(copyArgList, argList); . Then use the copied argument list as normal. 然后照常使用复制的参数列表。

More information about va_copy 有关va_copy更多信息

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. 我认为您需要考虑从这种方法转移并将事物重新编码为基于va_arg的更安全的机制,这是遍历变量参数的唯一安全机制。 Something along the lines of what was posted by @Nikita. @Nikita发布的内容。

If you want to continue with the current approach you will need to delve into the iOS calling conventions for each architecture. 如果您想继续使用当前的方法,则需要深入研究每种体系结构的iOS调用约定。 You can find the ARM64 conventions here: https://developer.apple.com/library/ios/documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARM64FunctionCallingConventions.html 您可以在此处找到ARM64约定: 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. 从最初的看来,它显然不是直截了当的,可变函数与普通调用约定不同。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM