Swizzling метод с переменными аргументами и пересылка сообщения - Плохой доступ

Я реализую «Класс инжектора кода», который с помощью переключения методов может дать вам возможность сделать что-то вроде этого:

FLCodeInjector *injector = [FLCodeInjector injectorForClass:[self class]];
[injector injectCodeBeforeSelector:@selector(aSelector:) code:^{
    NSLog(@"This code should be injected");
}];

aSelector может быть методом с переменным числом аргументов и переменным типом возвращаемого значения. Аргументы / и возвращаемый тип могут быть объектами или примитивным типом.

Сначала я прикрепляю код injectCodeBeforeSelector:, чтобы вы понимали, что я делаю (я удалил неинтересные части кода):

- (void)injectCodeBeforeSelector:(SEL)method code:(void (^)())completionBlock
{

    NSString *selector = NSStringFromSelector(method);

    [self.dictionaryOfBlocks setObject:completionBlock forKey:selector];

    NSString *swizzleSelector = [NSString stringWithFormat:@"SWZ%@", selector];

    // add a new method to the swizzled class
    Method origMethod = class_getInstanceMethod(self.mainClass, NSSelectorFromString(selector));
    const char *encoding = method_getTypeEncoding(origMethod);

    [self addSelector:NSSelectorFromString(swizzleSelector) toClass:self.mainClass methodTypeEncoding:encoding];
    SwizzleMe(self.mainClass, NSSelectorFromString(selector), NSSelectorFromString(swizzleSelector));

}

-(void)addSelector:(SEL)selector toClass:(Class)aClass methodTypeEncoding:(const char *)encoding
{
    class_addMethod(aClass,
                    selector,
                    (IMP)genericFunction, encoding);
}

По сути, я использую class_addMethod, чтобы добавить метод fake / swizzle к классу назначения, а затем делать swizzle. Реализация метода настроена на такую ​​функцию:

id genericFunction(id self, SEL cmd, ...) {
    // call the block to inject
    ...
    // now forward the message to the right method, since method are swizzled
    // I need to forward to the "fake" selector SWZxxx

    NSString *actualSelector = NSStringFromSelector(cmd);
    NSString *newSelector = [NSString stringWithFormat:@"SWZ%@", actualSelector];
    SEL finalSelector = NSSelectorFromString(newSelector);

    // forward the argument list
    va_list arguments;
    va_start ( arguments, cmd );

    return objc_msgSend(self, finalSelector, arguments);
}

теперь проблема: у меня есть EXC_BAD_INSTRUCTION (objc_msgSend_corrupt_cache_error ()) в последней строке. Проблема возникает, если я пересылаю аргументы va_list поддельному селектору. Если я изменю последнюю строку на

return objc_msgSend(self, cmd, arguments);

ошибки нет, но, очевидно, начинается бесконечная рекурсия.

Я пытался:

  • использовать va_copy
  • удалите swizzle перед отправкой сообщения

но без результатов. Думаю, проблема связана с этим фактом: va_list - это не простой указатель, это может быть что-то вроде смещения относительно адреса стека метода. Итак, я не могу вызвать objc_msgsend функции (swizzled-функцию) со списком arg другой функции (non-swizzled).

Я попытался изменить подход и скопировать все аргументы в NSInvocation, но у меня были другие проблемы с управлением возвращаемым значением вызова, и даже для копирования аргументов один за другим (управление всеми разными типами) требуется много кода, поэтому я предпочел вернуть в этом подходе, который мне кажется чище (имхо)

Есть ли у вас предложения? Спасибо


person LombaX    schedule 07.08.2013    source источник


Ответы (2)


Основная проблема здесь в том, как переменные аргументы передаются функции.

Обычно они передаются в стек, но, насколько мне известно, это не относится к ARM ABI, где используются регистры, по крайней мере, когда это возможно.

Итак, у вас есть две проблемы.
Во-первых, компилятор может испортить эти регистры при выполнении кода вашего собственного метода.
Я не уверен в этом, так как я мало знаю о ARM ABI, поэтому вам следует проверить ссылку.

Вторая проблема, более важная, вы фактически передаете единственный аргумент переменной в obj_msgSend (va_list). Очевидно, целевой метод не получит того, чего ожидает.

Представьте себе следующее:

void bar( int x, ... )
{}

void foo( void )
{
    bar( 1, 2, 3, 4 );
}

В ARM это будет означать для функции foo:

movs    r0, #1
movt    r0, #0
movs    r1, #2
movt    r1, #0
movs    r2, #3
movt    r2, #0
movs    r3, #4
movt    r3, #0
bl      _bar

Аргументы переменных передаются в R1, R2 и R3, а аргумент int - в R0.

Итак, в вашем случае, поскольку для вызова вашего метода использовался вызов objc_msdSend, R0 должен быть указателем целевого объекта, R1 указателем селектора, а переменные аргументы должны начинаться с R2.

И когда вы отправляете свой собственный вызов objc_msdSend, вы, по крайней мере, перезаписываете содержимое R2 своим va_list.

Вам следует постараться не заботиться о переменных аргументах. Если повезет, если код, предшествующий вызову objc_msgSend (где вы получаете последний селектор) не испортил эти регистры, правильные значения все равно должны быть там, что сделает их доступными для целевого метода.

Это, конечно, будет работать только на реальном устройстве, а не в симуляторе (симулятор - x86, поэтому здесь в стек передаются переменные параметры).

person Macmade    schedule 08.08.2013
comment
спасибо вам обоим, оба ответа очень полезны, я отмечу этот как правильный, потому что есть объяснение и интересный пример. Из-за этих ответов я решил повторить попытку с NSInvocation, я отправлю еще один вопрос по этому поводу. Спасибо еще раз - person LombaX; 08.08.2013
comment
Если кому-то интересно, вот ссылка на первую версию класса на github: github.com/lombax85/FLCodeInjector - person LombaX; 08.08.2013

К сожалению нет. Вот почему функции, которые принимают переменные аргументы, должны предоставлять идентичные реализации, которые принимают va_lists. API-интерфейсы, которые обеспечивали эту функциональность (например, objc_msgSendv), были помечены как «Недоступные», начиная с ObjC 2. Возможно, было бы также неплохо выяснить, почему эта функциональность была удалена.

Variadic Macros часто решает проблему, но не в вашем случае, потому что вы нужен указатель на функцию swizzle.

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

person justin    schedule 08.08.2013