Swizzling 带有可变参数的方法并转发消息 - Bad Access [英] Swizzling a method with variable arguments and forward the message - Bad Access

查看:87
本文介绍了Swizzling 带有可变参数的方法并转发消息 - Bad Access的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

FLCodeInjector *injector = [FLCodeInjector injectorForClass:[self class]];[注入器injectCodeBeforeSelector:@selector(aSelector:) 代码:^{NSLog(@"这个代码应该被注入");}];

aSelector 可以是参数个数可变、返回类型可变的方法.参数/和返回类型可以是对象或原始类型.

首先,我附上injectCodeBeforeSelector:的代码,让你明白我在做什么(我删除了代码中不感兴趣的部分):

- (void)injectCodeBeforeSelector:(SEL)method code:(void (^)())completionBlock{NSString *selector = NSStringFromSelector(method);[self.dictionaryOfBlocks setObject:completionBlock forKey:selector];NSString *swizzleSelector = [NSString stringWithFormat:@"SWZ%@", 选择器];//向 swizzled 类添加一个新方法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,选择器,(IMP)genericFunction, encoding);}

基本上,我使用 class_addMethod 将 fake/swizzle 方法添加到目标类,然后进行 swizzle.该方法的实现设置为这样的函数:

id genericFunction(id self, SEL cmd, ...) {//调用块进行注入...//现在将消息转发到正确的方法,因为方法被混淆了//我需要转发到假"选择器 SWZxxxNSString *actualSelector = NSStringFromSelector(cmd);NSString *newSelector = [NSString stringWithFormat:@"SWZ%@", actualSelector];SEL finalSelector = NSSelectorFromString(newSelector);//转发参数列表va_list 参数;va_start ( 参数, cmd );返回 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 中的所有参数,但是我在管理调用的返回值时遇到了其他问题,甚至一个一个复制参数(管理所有不同类型)也需要大量代码,所以我更喜欢回到这种方法,这对我来说似乎更清晰(恕我直言)

你有什么建议吗?谢谢

解决方案

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

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

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

第二个问题,更重要的是,您实际上是将单个变量参数传递给 obj_msgSend(va_list).因此,目标方法显然不会收到预期的结果.

想象一下:

void bar( int x, ... ){}无效 foo( 无效 ){条(1、2、3、4);}

在 ARM 上,对于 foo 函数,这意味着:

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

变量参数在 R1R2R3 中传递,int 参数在 中传递R0.

所以在你的情况下,当调用 objc_msdSend 被用来调用你的方法时,R0 应该是目标对象的指针,R1选择器的指针和变量参数应该从 R2 开始.

当您自己调用 objc_msdSend 时,您至少会用 va_list 覆盖 R2 的内容.>

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

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

I'm implementing a "Code Injector Class", that through method swizzling can give you the possibility to do something like this:

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

aSelector can be a method with variable number of arguments, and variable return type. Arguments / and return type can be objects or primitive type.

First, I attach the code of injectCodeBeforeSelector: to let you understand what I'm doing (I removed not interesting parts of the code):

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

Basically, I use class_addMethod to add the fake/swizzle method to the destination class, then do the swizzle. The implementation of the method is set to a function like this:

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);
}

now the problem: I have an EXC_BAD_INSTRUCTION ( objc_msgSend_corrupt_cache_error ()) on the last line. The problem happens if i forward the va_list arguments to the fake selector. If I change the last line to

return objc_msgSend(self, cmd, arguments);

no error, but obviously an infinite recursion starts.

I've tried to:

  • use va_copy
  • remove the swizzle before sending the message

but no results. I think that the problem is related to this fact: va_list is not a simple pointer, it can be something similar to an offset relative to the stack address of the method. So, I can't call objc_msgsend of a function (the swizzled function) with the arg list of another function (the non-swizzled one).

I tried to change approach and copy all arguments in an NSInvocation, but I had other problems managing the return value of the invocation, and even copy arguments one by one (managing all different types) requires a lot of code, so I preferred to return to this approach, that seems cleaner to me (imho)

Do you have any suggestion? Thanks

解决方案

The main problem here is how the variable arguments are passed to the function.

Usually, they are passed on the stack, but as far as I know, it's not the case with the ARM ABI, where registers are used, at least when possible.

So you've got two issues here.
First, the compiler may mess up those registers, while executing the code of your own method.
I'm not sure about this, as I don't know much about the ARM ABI, so you should check in the reference.

Second issue, more important, you are actually passing a single variable argument to obj_msgSend (the va_list). So the target method won't receive what it expects, obviously.

Imagine the following:

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

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

On ARM, this will mean, for the foo function:

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

Variables arguments are passed in R1, R2 and R3, and the int argument in R0.

So in your case, as a call to objc_msdSend was used to call your method, R0 should be the target object's pointer, R1 the selector's pointer, and variable arguments should begin on R2.

And when issuing your own call to objc_msdSend, you're at least overwriting the content of R2, with your va_list.

You should try not to care about the variable arguments. With a little luck, if the code previous to the objc_msgSend call (where you get the final selector) doesn't mess up those registers, the correct values should still be there, making them available for the target method.

This would of course only work on the real device, and not in the simulator (simulator is x86, so variadic parameters are here passed on the stack).

这篇关于Swizzling 带有可变参数的方法并转发消息 - Bad Access的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆