SEL是方法编号,也是方法名,在dyld加载镜像到内存时,通过_read_image方法加载到内存的表中了
IMP是函数实现指针,找IMP就是找函数实现的过程
SEL和IMP的关系就可以解释为:
SEL就相当于书本的⽬录标题IMP就是书本的⻚码函数就是具体页码对应的内容
之前遇到过一个面试题,总所周知,分类会覆盖,覆盖顺序是按照编译顺序来决定的,如果我在项目中,有4个分类,但是我想调用指定的分类,该如何做呢?
- 大多数的朋友都会去查找类的方法列表,然后通过
IMP调用
u_int count;
Method *methods = class_copyMethodList([KryTestPerson class], &count);
for (int i = 0; i < count; i++) {
SEL name = method_getName(methods[i]);
NSString *strName = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
IMP imp = method_getImplementation(methods[i]);
NSLog(@"%@ 的IMP ==== %p",strName,imp);
}
- 还有一种办法,参考一下
fishhook的原理,首先通过String Table分割字符串拿到地址,然后再去Symbol Table中找到对应的偏移地址,我们在项目中只需要拿到项目的首地址 + 偏移地址,这样就可以直接根据函数地址来调用了
//获取基地址
intptr_t get_load_address(void) {
const struct mach_header *exe_header = NULL;
for (uint32_t i = 0; i < _dyld_image_count(); i++) {
const struct mach_header *header = _dyld_get_image_header(i);
if (header->filetype == MH_EXECUTE) {
exe_header = header;
break;
}
}
//返回值即为加载地址
return (intptr_t)exe_header;
}
// 通过 IMP直接调用函数
intptr_t origP = 0x3EC0;
intptr_t loadP = get_load_address();
IMP impxx = origP + loadP;
impxx();
// 带参数正常调用 需要传递实例对象,SEL,参数
// ((void (*)(id, SEL,NSString*))impxx)(stu,sel,@"123");
打印结果为 KryTestPerson1的hello
如何找到0x3EC0这个地址,可以去看看fishhook的原理,这里就不做过多叙述,可以看到0x3EC0就是上面MachO中 -[KryTestPerson(KryTestPerson1)hello]中的地址,只不过我们把前面的1抹掉了,只要偏移量,再加上项目首地址(已经ASLR)过了,就是函数的实际IMP地址
此方法写地址的地址,要保证项目的二进制不在改变,如果你又添加了代码,这个偏移地址是会变的。
对于名称相同的方法,他们都有相同的SEL,方法的名称不包括类名称,所以子类和父类中的同名方法拥有相同的SEL,但是他们的实现可以各不相同, 因而在他们各自的Dispatch表中SEL所对应的IMP是不同的,IMP是一个函数指针,而虽然每一个SEL对应的是一个方法的名称,但考虑到效率,SEL本身是一个整型,编译器会另外生成一张SEL和方法名称对应的表。有了这样的结构,objc就可以实现多态了