hook钩挂方式分为MethodSwizzle、fishhook、CydiaSubstrate等等多种钩挂方式。
以MethodSwizzle为例研究:
名称为load成员方法是否支持hook钩挂替换:
能不能hook钩挂替换?
首先看下Objc语系中成员方法所持有的实例发生交换的原理,下面是一段典型的实现 实例方法 交换的代码:
//字段名和实例(实现IMP)属于独立的2部分,彼此互不干涉
//完整的成员方法可以分为:成员方法的字段名+成员方法的实例(实现IMP)
Class class = [self class];//类对象
// 原方法名和替换方法名
SEL originalSelector = @selector(isEqualToString:);//字段名选择器selector
SEL swizzledSelector = @selector(swizzle_IsEqualToString:);//字段名选择器selector
// 原方法和替换方法
Method originalMethod = class_getInstanceMethod(class, originalSelector);//实现IMP
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);//实现IMP
// 如果当前类没有原方法的实现IMP,先调用class_addMethod来给原方法添加实现
BOOL didAddMethod = class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {// 添加方法实现IMP成功后,替换方法实现
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else { // 有原方法,交换两个方法的实现
method_exchangeImplementations(originalMethod, swizzledMethod);
}
实现交换的关键方法class_replaceMethod和 method_exchangeImplementations,两个方法的实现原理:
class_replaceMethod原理(底层指令):
IMP
class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
{
if (!cls) return nil;
mutex_locker_t lock(runtimeLock);
return addMethod(cls, name, imp, types ?: "", YES);
}
class_replaceMethod内部实际上调用了addMethods函数,入参replace = YES。
addMethods的实现原理(底层指令):
static IMP
addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace)
{
IMP result = nil;
runtimeLock.assertLocked();
checkIsKnownClass(cls);
ASSERT(types);
ASSERT(cls->isRealized());
method_t *m;
if ((m = getMethodNoSuper_nolock(cls, name))) {
// already exists
if (!replace) {
result = m->imp;
} else {
result = _method_setImplementation(cls, m, imp);
}
} else {
// fixme optimize
method_list_t *newlist;
newlist = (method_list_t *)calloc(sizeof(*newlist), 1);
newlist->entsizeAndFlags =
(uint32_t)sizeof(method_t) | fixed_up_method_list;
newlist->count = 1;
newlist->first.name = name;
newlist->first.types = strdupIfMutable(types);
newlist->first.imp = imp;
prepareMethodLists(cls, &newlist, 1, NO, NO);
cls->data()->methods.attachLists(&newlist, 1);
flushCaches(cls);
result = nil;
}
return result;
}
addMethods函数内部调用_method_setImplementation来替换Method的imp以实现方法执行区替换:
static IMP
_method_setImplementation(Class cls, method_t *m, IMP imp)
{
runtimeLock.assertLocked();
if (!m) return nil;
if (!imp) return nil;
IMP old = m->imp;
m->imp = imp;
// Cache updates are slow if cls is nil (i.e. unknown)
// RR/AWZ updates are slow if cls is nil (i.e. unknown)
// fixme build list of classes whose Methods are known externally?
flushCaches(cls);
adjustCustomFlagsForMethodChange(cls, m);
return old;
}
method_exchangeImplementations原理机制(底层指令):
void method_exchangeImplementations(Method m1, Method m2)
{
if (!m1 || !m2) return;
mutex_locker_t lock(runtimeLock);
//交换方法的IMP实际执行区
IMP m1_imp = m1->imp;
m1->imp = m2->imp;
m2->imp = m1_imp;
// RR/AWZ updates are slow because class is unknown
// Cache updates are slow because class is unknown
// fixme build list of classes whose Methods are known externally?
flushCaches(nil);
adjustCustomFlagsForMethodChange(nil, m1);
adjustCustomFlagsForMethodChange(nil, m2);
}
方法交换的核心本质是对Method的imp执行区实施交换以实现方法的交换效果。method_exchangeImplementations函数的入参是Method(Method通过class_getClassMethod或class_getInstanceMethod获取)。class_getClassMethod最终本质还是调用class_getInstanceMethod:
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
class_getInstanceMethod通过搜索方法列表找到对应方法元素(调用栈较长)。
通过分析:如果名称为load的声明方法已存放在类的方法列表中作为类方法列表元素,则能实现方法交换效果。
新建ClassA类带有load方法和替换方法swizzle_load方法:
@implementation ClassA
+ (void)load {
NSLog(@"load");
}
+ (void)swizzle_load {
NSLog(@"swizzle_load");
}
@end
在main函数中写一段测试代码,获取ClassA类下的方法列表:
int main(int argc, const char * argv[]) {
@autoreleasepool {
runTests(ClassA.class);
}
return 0;
}
void runTests (Class c ) {
unsigned int count;
//class_copyMethodList方法列表函数的入参涉及int基本数据类型的字段名count的地址&count,函数底层指令中必然存在*(&count)指针访问(已实现对字段名count的修改赋值)
//方法列表容器methods
Method *methods = class_copyMethodList(c, &count);
//count栈区内数据值即为方法列表中所囊括的方法元素的数目
for (int i = 0; i < count; i++) {
Method method = methods[i];
SEL selector = method_getName(method);
NSString *name = NSStringFromSelector(selector);
NSLog(@"方法名:%@",name);
}
}
ClassA类涉及的方法列表当前是空的(count==0)。
什么时机hook:我们经常在load方法中hook其他方法,那hook load方法在什么时机呢?