iOS面试题目——hook block(1)
// 1、实现下面的函数,将 block 的实现修改为 NSLog(@"Hello world");
//也就是说,在调用完这个函数后调用用block()时,并不调用原始实现,而是打 "Hello world"
void HookBlockToPrintHelloWorld(id block){
}
分析:题目的意思很明白,就是要实现一个函数,将作为参数传入的block的实现修改为一句log。有研究过block的结构的都知道,block实际上就是一个接口体。而它的实现部分,是作为函数指针被保存在结构体的`invoke`中(即第16个字节处):
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
uintptr_t invoke; // 此处保存的是实现代码的起始地址
struct Block_descriptor_1 *descriptor;
// imported variables
};
所以这题的解法也很明朗:就是将block结构体中的invoke函数指针地址替换为我们的函数:
// 定义一个函数
void block1_replace(void){
NSLog(@"Hello world");
};
void HookBlockToPrintHelloWorld(id block){
// 解析 block 为 struct Block_layout 结构体
struct Block_layout *layout = (__bridge struct Block_layout *)block;
//此处不能直接修改,因为该处地址属性为不可写状态,强行替换,会导致程序崩溃
//layout->invoke = (uintptr_t)block1_replace;
//修改内存属性
vm_address_t invoke_addr = (vm_address_t)&layout->invoke;
vm_size_t vmsize = 0;
mach_port_t object = 0;
vm_region_basic_info_data_64_t info;
mach_msg_type_number_t infoCnt = VM_REGION_BASIC_INFO_COUNT_64;
kern_return_t ret = vm_region_64(mach_task_self(), &invoke_addr, &vmsize, VM_REGION_BASIC_INFO, (vm_region_info_t)&info, &infoCnt, &object);
if (ret != KERN_SUCCESS) {
NSLog(@"获取失败");
return;
}
vm_prot_t protection = info.protection;
// 判断内存是否可写
if ((protection&VM_PROT_WRITE) == 0) {
// 修改内存属性 ===> 可写
ret = vm_protect(mach_task_self(), invoke_addr, sizeof(invoke_addr), false, protection|VM_PROT_WRITE);
if (ret != KERN_SUCCESS) {
NSLog(@"修改失败");
return;
}
}
layout->invoke = (uintptr_t)block1_replace;
}