2014-12-30 1 views
3

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

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

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

+0

Не могли бы вы предоставить образец кода, который вызывает этот код и сбой? –

+0

Данный код является категорией класса NSArray, предоставляя возможность для каждого объекта в массиве выполнять селектор с несколькими аргументами. Каждый объект в массиве - это слушатель (делегат), как требует шаблон дизайна «Несколько слушателей». Например, после ответа от сервера мы должны сделать каждый прослушиватель для выполнения селектора. Вызов, расположенный в ответном обратном вызове сервера, выглядит так: '[self.listeners makeObjectsPerformSelector: @selector (serverManager: didLikeVideo: withError :) withArguments: self, operation.video, operation.error, nil];' – abagmut

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

Пожалуйста, примите во внимание, что этот код потенциально опасен при необходимости для преобразования типа. Когда метод invoked имеет более длинный аргумент, который был передан моему callSelectorWithVarArgs:onTarget:onThread:wait: (например, вызываемый метод принимает NSUInteger (который имеет 64 бит на arm64), но я передаю int (который равен 32 бит на обоих плечах и 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]; 
     } 
    } 
} 
+0

Ваш модифицированный код работает нормально на 64 бита, но он разбился на 32, потому что мы получаем границы здесь '[NSInvocation setArgument: atIndex:]'. Итак, я заменил ваш цикл: do { arg = va_arg (args, void *); if (arg) { [вызов setArgument: & arg atIndex: index ++]; } } while (arg); с: для (NSUInteger i = index; i <подпись.numberOfArguments; i ++) { arg = va_arg (args, void *); \t if (arg) \t { \t [номер вызова: Аргумент: & arg atIndex: i]; \t} } – abagmut

+1

Как мы можем добраться до границ? Вы называете метод с 2 147 483 647 аргументами? –

+0

'arg = va_arg (args, void *);' возвращать некоторый указатель (не распознан), но не 'nil' для индекса за пределами' signature.numberOfArguments' – abagmut

4

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

Вы можете увидеть, например, что есть other tricks (для решения другой проблемы), которые основывались на макете аргументов в va_list, которые работали в 32-разрядной версии, но которые также не работают в 64-битных ,

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

1

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

+0

Я уверен, что в методе есть менее 2,147,483,647 аргументов, так что это не так. –

+0

Вы можете гарантировать, что верхний 32-разрядный бит вашего int не содержит мусора в 64-битной архитектуре? Я так не думаю. –

+0

Извините, что? Преобразование типов выполняется при назначении 32-битной переменной до 64 бит. –

1

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

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

Более подробная информации о va_copy

0

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

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

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