5

我正在实现一个“代码注入器类”,通过方法调配可以让你有可能做这样的事情:

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 不是一个简单的指针,它可以是类似于相对于方法的堆栈地址的偏移量的东西。所以,我不能用另一个函数(非混合函数)的 arg 列表调用函数(混合函数)的 objc_msgsend。

我试图改变方法并复制 NSInvocation 中的所有参数,但我在管理调用的返回值时遇到了其他问题,甚至一个一个地复制参数(管理所有不同的类型)需要大量代码,所以我更喜欢返回对于这种方法,这对我来说似乎更清洁(恕我直言)

你有什么建议吗?谢谢

4

2 回答 2

3

这里的主要问题是如何将变量参数传递给函数。

通常,它们在堆栈上传递,但据我所知,ARM ABI 并非如此,至少在可能的情况下使用寄存器。

所以这里有两个问题。
首先,编译器可能会在执行您自己方法的代码时弄乱这些寄存器。
我对此不确定,因为我对 ARM ABI 了解不多,因此您应该查看参考资料。

第二个问题,更重要的是,您实际上将单个变量参数传递给obj_msgSendva_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,R2R3,int参数传入R0.

因此,在您的情况下,作为objc_msdSend用于调用您的方法的调用,R0应该是目标对象的指针、R1选择器的指针和变量参数应该开始于R2.

并且在发出您自己的调用时objc_msdSend,您至少会R2用您的. 覆盖 , 的内容va_list

您应该尽量不关心变量参数。运气好的话,如果调用之前的代码objc_msgSend(你得到最终选择器的地方)没有弄乱这些寄存器,那么正确的值应该仍然存在,使它们可用于目标方法。

这当然只适用于真实设备,而不适用于模拟器(模拟器是 x86,所以这里的可变参数在堆栈上传递)。

于 2013-08-08T01:06:45.327 回答
1

抱歉不行。这就是为什么接受可变参数的函数应该提供与va_lists 相同的实现。自 ObjC 2 以来,提供此功能(例如objc_msgSendv)的 API 已被标记为“不可用”。也许弄清楚为什么删除此功能也会很好。

Variadic Macros通常会解决这个问题,但在你的情况下不是,因为你需要一个函数指针来调配。

因此,我认为您需要查看您的系统实现,以了解目标实现中 VA 列表的机制。

于 2013-08-08T00:38:54.243 回答