iOS plus

hook forwardingTargetForSelector

2019-04-26  本文已影响0人  Crazy2015
方法调用流程

runtime中具体的方法调用流程大致如下:

1.首先,在相应操作的对象中的缓存方法列表中找调用的方法,如果找到,转向相应实现并执行。

2.如果没找到,在相应操作的对象中的方法列表中找调用的方法,如果找到,转向相应实现执行

3.如果没找到,去父类指针所指向的对象中执行1,2.

4.以此类推,如果一直到根类还没找到,转向拦截调用,走消息转发机制。

5.如果没有重写拦截调用的方法,程序报错。

拦截调用

在方法调用中说到了,如果没有找到方法就会转向拦截调用。

那么什么是拦截调用呢?

拦截调用就是,在找不到调用的方法程序崩溃之前,你有机会通过重写NSObject的四个方法来处理:

+ (BOOL)resolveClassMethod:(SEL)sel;
+ (BOOL)resolveInstanceMethod:(SEL)sel;

//后两个方法需要转发到其他的类处理
- (id)forwardingTargetForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;

拦截调用的整个流程即Objective-C的消息转发机制。其具体流程如下图:


message_forwarding.png

由上图可见,在一个函数找不到时,runtime提供了三种方式去补救:

1、调用resolveInstanceMethod给个机会让类添加这个实现这个函数

2、调用forwardingTargetForSelector让别的对象去执行这个函数

3、调用forwardInvocation(函数执行器)灵活的将目标函数以其他形式执行。

如果都不中,调用doesNotRecognizeSelector抛出异常。

unrecognized selector crash 防护方案

既然可以补救,我们完全也可以利用消息转发机制来做文章。那么问题来了,在这三个步骤里面,选择哪一步去改造比较合适呢。

这里我们选择了第二步forwardingTargetForSelector来做文章。原因如下:

resolveInstanceMethod 需要在类的本身上动态添加它本身不存在的方法,这些方法对于该类本身来说冗余的

forwardInvocation可以通过NSInvocation的形式将消息转发给多个对象,但是其开销较大,需要创建新的NSInvocation对象,并且forwardInvocation的函数经常被使用者调用,来做多层消息转发选择机制,不适合多次重写

forwardingTargetForSelector可以将消息转发给一个对象,开销较小,并且被重写的概率较低,适合重写
选择了forwardingTargetForSelector之后,可以将NSObject的该方法重写,做以下几步的处理:

  1. hook forwardingTargetForSelector 方法
  2. 添加白名单,限制hook的范围,排除内部类,并自定义需要hook的类
  3. 创建桩类 ForwardingTarget
  4. 为桩类动态添加对应的selector的imp,指向一个函数,返回 NSNull 对象
  5. 将消息转移到该桩类对象 ForwardingTarget 上
  6. 将 hook 掉的 crash 信息进行上报,方便发现问题,后期修复掉。

需要说明的是:如何排除内部类?

dladdr可获得一个函数所在模块,名称以及地址。(hook非系统类)

static struct dl_info app_info;
    if (app_info.dli_saddr == NULL) {
        dladdr((__bridge void *)[UIApplication.sharedApplication.delegate class], &app_info);
    }
    struct dl_info self_info = {0};
    dladdr((__bridge void *)[self class], &self_info);
    //    ignore system class
    if (self_info.dli_fname == NULL || strcmp(app_info.dli_fname, self_info.dli_fname)) {
        return XXHookOrgin(aSelector);
    }

创建forwardingTarget (桩类)---XXShieldStubObject

XXShieldStubObject *stub = [XXShieldStubObject shareInstance];

将消息转移到桩类来实现

[stub addFunc:aSelector];

- (BOOL)addFunc:(SEL)sel {
    return __addMethod([XXShieldStubObject class], sel);
}

int smartFunction(id target, SEL cmd, ...) {
    return 0;
}

static BOOL __addMethod(Class clazz, SEL sel) {
    NSString *selName = NSStringFromSelector(sel);
    
    NSMutableString *tmpString = [[NSMutableString alloc] initWithFormat:@"%@", selName];
    
    int count = (int)[tmpString replaceOccurrencesOfString:@":"
                                                withString:@"_"
                                                   options:NSCaseInsensitiveSearch
                                                     range:NSMakeRange(0, selName.length)];
    
    NSMutableString *val = [[NSMutableString alloc] initWithString:@"i@:"];
    
    for (int i = 0; i < count; i++) {
        [val appendString:@"@"];
    }
    const char *funcTypeEncoding = [val UTF8String];
    return class_addMethod(clazz, sel, (IMP)smartFunction, funcTypeEncoding);
}

转载:
https://neyoufan.github.io/2017/01/13/ios/BayMax_HTSafetyGuard/?spm=a2c4e.11153940.blogcont348673.9.32a54df44MBYCY

上一篇下一篇

猜你喜欢

热点阅读