利用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_0
是Block
调用要执行的函数
2、查看在main.cpp
中main
函数是如何调用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
中的impl
的FuncPtr
地址值给修改掉,就可以达到替换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/
网友评论