Block的Hook方法

作者: 耿杰 | 来源:发表于2019-01-25 11:37 被阅读40次

    ABI Block底层实现

    利用RunTime动态交换老生常谈的问题了,现在接下来,通过两个例子,在运行时动态替换Block里面的函数

    Block内存结构

    image.png

    一、编写一个Block,调用了printHelloWorld函数之后,再执行Block,打印出Hello World

    1、编写Block函数,并且编译成C++文件
    1、在main.m文件中, 编写一个简单Block,并且调用它,并有两个int类型的参数
    int main(int argc, char * argv[]) {
        @autoreleasepool {
            
            void(^block)(int, int) = ^(int a, int b) {
                NSLog(@"block函数");
            };
            
            block(1, 2);
            
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        }
    }
    
    2、在项目目录根路径,利用Clang命令,把main.m文件转换main.cpp文件
    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
    
    3、查看main.m文件,刚刚编写的Block是由一个结构体__main_block_impl_0实现的,快速介绍下几个重点参数
    struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
    };
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_6x_r5wnz2v529j0mmdsb9y1ht440000gn_T_main_d6346c_mi_0);
            }
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    
    • 1、__main_block_impl_0,有两个参数,加一个构造函数
    • 2、__block_impl第一个参数是一个指针, 第二个参数是Flags,第四个参数是个指针,指向着Block回调函数的地址, 指向是__main_block_func_0的地址。
    • 3、__main_block_desc_0第一个参数是保留字段,没有用,第二个参数是__main_block_impl_0占用内存的大小。
    • 4、__main_block_func_0Block调用要执行的函数
    2、查看在main.cppmain函数是如何调用Block函数,也是基本C语法调用
    // 创建一个指向函数的指针`block`,指向`__main_block_impl_0`,观察`__main_block_impl_0 `构造函数,发现把`__main_block_func_0`函数的指针赋值给了`impl.FuncPtr`
    void(*block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    // 利用`block`这个指针取出`FuncPtr`的地址值,并调用,并且传了三个参数
    // `Block`的本身、1、2。
    ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 1, 2);
    
    3、如果我们把__main_block_impl_0中的implFuncPtr地址值给修改掉,就可以达到替换Block要执行函数的实现
    • 1、在main函数中声明一个block函数,在调用了printHelloWorld函数之后,调用block函数,打印出Hello World\n
    void printHelloWord() {
        printf("Hello World\n");
    }
    
    typedef struct WT_Block_Desc {
        size_t reserved;
        size_t Block_size;
    } WT_Block_Desc;
    
    struct WT_Block_impl {
        void *isa;
        int Flags;
        int Reserved;
        void *funPtr;
        WT_Block_Desc *desc;
    };
    
    
    
    void printHelloWorld(id block) {
        struct WT_Block_impl *blockImpl = (__bridge struct WT_Block_impl *)block;
        blockImpl->funPtr = &printHelloWord;
    }
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
           
            void(^block)(int a, NSString *b) = ^(int a, NSString *b) {
                NSLog(@"block invoke");
            };
            printHelloWorld(block);
            
            block(1, @"3");
        }
        return 0;
    }
    

    二、编写一个Block,调用printArguments函数之后,调用Block的时候,什么先打印出Block的参数,并且实现Block函数原有的实现

    void(^block)(NSString *,int, double, CGSize) = ^(NSString *a, int b, double c, CGSize size){
        NSLog(@"原始方法实现");
    };
            
    hookBlock(block);
            
    block(@"1", 2, 3.1, CGSizeMake(20, 20));
    

    结果

    1,2,3.100000,CGSize:{20, 20}
    方法实现
    
    1、实现逻辑

    利用libffi能够动态创建方法、根据函数地址,调用任意C函数

    • 1、取出原Block的参数,给每个参数申请内存空间,按ffi要求把参数数据组装成ffi_type **
    • 2、利用参数个数、返回值类型、参数数组组装成ffi_cif对象
    • 3、利用ffi_closure_alloc创建一个ffi_closure的指针, 并且把要替换函数的指针赋值
    • 4、利用ffi_closure的指针、ffi_cif对象、(void (*)(ffi_cif *, void *, void **, void *))、替换函数的指针, 来动态定义一个函数
    • 5、交换原始函数和替换方法的指针,并利用g_origin_funcPtr保存原始函数的指针
    • 6、在替换函数forwardInvation中,从第3个参数中取出Block的参数并打印
    • 7、把cif函数原型、原始函数指针,返回值内存指针,参数数据传入ffi_call调用原始函数实现

    方法实现

    //
    //  main.m
    //  Block的Hook
    //
    //  Created by 无头骑士 GJ on 2019/1/24.
    //  Copyright © 2019 无头骑士 GJ. All rights reserved.
    //
    
    #import <Foundation/Foundation.h>
    #import "AppDelegate.h"
    #import "WTPerson.h"
    #import "ffi.h"
    
    int cout;           // block的参数个数
    ffi_cif g_cif;
    ffi_closure *g_closure;
    void *g_replace_funcPtr;    // 被替换方法指针
    void *g_origin_funcPtr;     // 原始方法指针
    NSMutableArray *argTypes;
    NSMutableDictionary *registeredStruct;
    NSMutableArray *argStructTypes;
    
    enum {
        // Set to true on blocks that have captures (and thus are not true
        // global blocks) but are known not to escape for various other
        // reasons. For backward compatiblity with old runtimes, whenever
        // BLOCK_IS_NOESCAPE is set, BLOCK_IS_GLOBAL is set too. Copying a
        // non-escaping block returns the original block and releasing such a
        // block is a no-op, which is exactly how global blocks are handled.
        BLOCK_IS_NOESCAPE      =  (1 << 23),
        
        BLOCK_HAS_COPY_DISPOSE =  (1 << 25),
        BLOCK_HAS_CTOR =          (1 << 26), // helpers have C++ code
        BLOCK_IS_GLOBAL =         (1 << 28),
        BLOCK_HAS_STRET =         (1 << 29), // IFF BLOCK_HAS_SIGNATURE
        BLOCK_HAS_SIGNATURE =     (1 << 30),
    };
    
    ffi_type *ffiTypeWithEncodingChar(const char *c)
    {
        switch (c[0]) {
            case 'v':
                
                return &ffi_type_void;
            case 'c':
                return &ffi_type_schar;
            case 'C':
                return &ffi_type_uchar;
            case 's':
                return &ffi_type_sshort;
            case 'S':
                return &ffi_type_ushort;
            case 'i':
                return &ffi_type_sint;
            case 'I':
                return &ffi_type_uint;
            case 'l':
                return &ffi_type_slong;
            case 'L':
                return &ffi_type_ulong;
            case 'q':
                return &ffi_type_sint64;
            case 'Q':
                return &ffi_type_uint64;
            case 'f':
                return &ffi_type_float;
            case 'd':
                return &ffi_type_double;
            case 'F':
    #if CGFLOAT_IS_DOUBLE
                return &ffi_type_double;
    #else
                return &ffi_type_float;
    #endif
            case 'B':
                return &ffi_type_uint8;
            case '^':
                return &ffi_type_pointer;
            case '@':
                return &ffi_type_pointer;
            case '#':
                return &ffi_type_pointer;
            case '{':
            {
                NSString *typeStr = [NSString stringWithCString: c encoding: NSASCIIStringEncoding];
                NSUInteger end = [typeStr rangeOfString:@"}"].location;
                if (end != NSNotFound) {
                    NSString *structName = [typeStr substringWithRange:NSMakeRange(1, end - 1)];
                    NSUInteger eqEnd = [typeStr rangeOfString:@"="].location;
                    if (end != NSNotFound)
                    {
                        structName = [structName substringWithRange: NSMakeRange(0, eqEnd - 1)];
                    }
                    ffi_type *type = malloc(sizeof(ffi_type));
                    type->alignment = 0;
                    type->size = 0;
                    type->type = FFI_TYPE_STRUCT;
                    NSDictionary *structDefine = [registeredStruct objectForKey: structName];
                    NSUInteger subTypeCount = [structDefine[@"keys"] count];
                    NSString *subTypes = structDefine[@"types"];
                    ffi_type **sub_types = malloc(sizeof(ffi_type *) * (subTypeCount + 1));
                    for (NSUInteger i=0; i<subTypeCount; i++) {
                        sub_types[i] = ffiTypeWithEncodingChar([subTypes cStringUsingEncoding:NSASCIIStringEncoding]);
                        type->size += sub_types[i]->size;
                    }
                    sub_types[subTypeCount] = NULL;
                    type->elements = sub_types;
                    
                    if (structName) [argStructTypes addObject: structName];
                    return type;
                }
            }
        }
        return NULL;
    }
    
    struct WT_block_Desc {
        size_t reserved;
        size_t block_size;
        void *pointer[1];
    };
    
    typedef struct WT__block_impl {
        void *isa;
        int Flags;
        int Reserved;
        void *FuncPtr;
        struct WT_block_Desc *desc;
    } WT__block_impl;
    
    const char * size_and_alignment(const char *str, NSUInteger *sizep, NSUInteger *alignp, long *len) {
        const char *arg = NSGetSizeAndAlignment(str, sizep, alignp);
        if (len) {
            *len = arg - str;
        }
        while (isdigit(*arg))
        {
            arg++;
        }
        return arg;
    }
    
    void * allocate(size_t howmuch) {
        NSMutableData *data = [NSMutableData dataWithLength:howmuch];
        //    [g_allocations addObject: data];
        return [data mutableBytes];
    }
    
    static int arg_count(const char *str) {
        int arg_count = -1;
        while (str && *str) {
            str = size_and_alignment(str, NULL, NULL, NULL);
            arg_count ++;
        }
        return arg_count;
    }
    
    ffi_type ** args_with_encode_string(const char *signature, int *out_count)
    {
        // 1、获取参数的个数
        int count = arg_count(signature);
        // 2、创建参数类型数组
        ffi_type **arg_types = allocate(sizeof(*arg_types) * count);
        
        // 3、第一个参数是返回值
        int i = -1;
        while(signature && *signature) {
            
            // 4、把方法编码 v20@0i4i4 中取出 i i这两个参数编码
            const char *next = size_and_alignment(signature, NULL, NULL, NULL);
            if (i >= 0)
            {
                arg_types[i] = ffiTypeWithEncodingChar(signature);
                NSString *argType = [NSString stringWithFormat:@"%c", signature[0]];
                [argTypes addObject: argType];
            }
            i++;
            signature = next;
        }
        
        *out_count = count;
        return arg_types;
        
    }
    
    static void forwardInvation(ffi_cif *cif, void *ret, void **args, void *user_data)
    {
        if (argTypes.count > 1)
        {
            __block NSUInteger structIndex = 0;
            [argTypes enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                
                if (idx > 0)
                {
                    const char *type = [obj UTF8String];
                    
                    switch (*type) {
                        case 'i':
                        {
                            int result = *((int *)args[idx]);
                            NSLog(@"int:%d\n", result);
                            break;
                        }
                        case '@': // id
                        {
                            id result = (__bridge id)(*((void **)args[idx]));
                            NSLog(@"id:%@\n", result);
                            break;
                        }
                        case 'B': // BOOL bool
                        {
                            bool result = *((bool *)args[idx]);
                            NSLog(@"bool:%d\n", result);
                            break;
                        }
                        case 'd': // double
                        {
                            double result = *((double *)args[idx]);
                            NSLog(@"double:%f\n", result);
                            break;
                        }
                        case 'f': // float
                        {
                            float result = *((float *)args[idx]);
                            NSLog(@"float:%f\n", result);
                            break;
                        }
                        case '#': // Class
                        {
                            Class result = *((Class *)args[idx]);
                            NSLog(@"Class:%@\n", NSStringFromClass(result));
                            break;
                        }
                        case '{':
                        {
                            NSString *structName = [argStructTypes objectAtIndex: structIndex];
                            if ([structName isEqualToString: @"CGSize"])
                            {
                                CGSize size = *((CGSize *)args[idx]);
                                NSLog(@"CGSize:%@\n", NSStringFromCGSize(size));
                            }
                            
                            structIndex++;
                            break;
                        }
                        default:
                            break;
                    }
                }
            }];
        }
        else
        {
            NSLog(@"没有参数");
        }
        
        ffi_call(&g_cif, g_origin_funcPtr, ret, args);
    }
    
    void initGlobalMember()
    {
        argTypes = [NSMutableArray array];
        
        registeredStruct = [NSMutableDictionary dictionary];
        registeredStruct[@"CGSize"] = @{@"name": @"CGSize", @"types": @"dd", @"keys": @[@"width", @"height"]};
        
        argStructTypes = [NSMutableArray array];
    }
    
    const char * getBlockSignature(WT__block_impl *blockImpl)
    {
        struct WT_block_Desc *desc = blockImpl->desc;
        
        int index = 0;
        if (blockImpl->Flags & BLOCK_HAS_COPY_DISPOSE)
        {
            index = 2;
        }
        return desc->pointer[index];
    }
    
    void initG_cif(const char *signature)
    {
        int args_count;
        ffi_type **arg_types = args_with_encode_string(signature, &args_count);
        
        ffi_status status = ffi_prep_cif(&g_cif, FFI_DEFAULT_ABI, args_count, ffiTypeWithEncodingChar(signature), arg_types);
        
        if (status != FFI_OK)
        {
            printf("ffi_prep_cif ffi_status:%ld", (long)status);
            abort();
        }
    }
    
    void initG_closure(WT__block_impl *blockImpl) {
        g_closure = ffi_closure_alloc(sizeof(ffi_closure), &g_replace_funcPtr);
        ffi_status status = ffi_prep_closure_loc(g_closure, &g_cif, forwardInvation, NULL, g_replace_funcPtr);
        
        if (status != FFI_OK)
        {
            printf("ffi_prep_closure_loc ffi_status:%ld", (long)status);
            abort();
        }
        
        g_origin_funcPtr = blockImpl->FuncPtr;
        blockImpl->FuncPtr = g_replace_funcPtr;
    }
    
    void hookBlock(id block) {
        // 初始化全局变量
        initGlobalMember();
        // 转换成block结构体
        struct WT__block_impl *blockImpl = (__bridge WT__block_impl *)block;
        // 获取Block方法参数编码
        const char *signature = getBlockSignature(blockImpl);
        // 初始化方法原型
        initG_cif(signature);
        // 根据原型动态创建一个方法
        initG_closure(blockImpl);
    }
    
    int main(int argc, char * argv[]) {
        @autoreleasepool {
            
            void(^block)(NSString *,int, double, CGSize) = ^(NSString *a, int b, double c, CGSize size){
                NSLog(@"原始方法实现");
            };
            
            hookBlock(block);
            
            block(@"1", 2, 3.1, CGSizeMake(20, 20));
            
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        }
        
    }
    
    

    参考:

    http://iosre.com/t/block/6779
    https://github.com/mikeash
    http://blog.cnbang.net/tech/3332/

    相关文章

      网友评论

        本文标题:Block的Hook方法

        本文链接:https://www.haomeiwen.com/subject/lytujqtx.html