[图片上传失败...(image-f0ae06-1556960021893)]
前言
日常开发中我们得知,当我们通过对象调用一个方法时,本质是通过objc_msgSend
给对象发送消息。这点我们可以通过clang
编译后的代码得知。
MyPerson *p = [MyPerson new];
通过xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件
编译得:
MyPerson *p = ((MyPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MyPerson"), sel_registerName("new"));
可知接收消息的对象是:(id)objc_getClass("MyPerson")
。
接收的消息编号:sel_registerName("new")
== @selector(new)
。
通过分析objc4-750源码,以objc_msgSend
为入口,接下来我们开始分析整个消息发送及处理流程。
整个流程分为快速和慢速两种方式。
快速:通过汇编,在缓存(cache)的imp哈希表中寻找。这样的好处是C、C++等语言不能通过写一个函数,来直接保留未知的参数,跳转到任意的指针。而汇编通过调用寄存器,可很好的实现这一点。
慢速: 通过C、C++在方法列表中寻找。找到了会往chche中存。以上方法找不到,就会通过特殊的动态处理。
0x01 汇编缓存查找
在objc4-750源码中搜索_objc_msgSend
,点击查看在arm64
架构中的ENTRY _objc_msgSend
。代码和注释如下:
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
//tagged pointer:特殊的数据类型,更为轻量。
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone://isa处理完毕。//这里可以作为后面传参的一个参考。
//!!主要函数!!
//在缓存列表中找imp
//这里CacheLookup有三种方式:NORMAL|GETIMP|LOOKUP
//1、成功:call imp
//2、失败:objc_msgSend_uncached
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq LReturnZero // nil check
// tagged
adrp x10, _objc_debug_taggedpointer_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
ubfx x11, x0, #60, #4
ldr x16, [x10, x11, LSL #3]
adrp x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE
add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF
cmp x10, x16
b.ne LGetIsaDone
// ext tagged
adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
ubfx x11, x0, #52, #8
ldr x16, [x10, x11, LSL #3]
b LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
ret
END_ENTRY _objc_msgSend
通过查看CacheLookup
的宏定义代码,得知缓存中寻找的三种形式:
CacheHit | CheckMiss | add
//1:找到直接返回
//2:找不到的话直接checkmiss
//3:在其它地方找到的话通过汇编直接add进缓存中。
.macro CacheLookup
// p1 = SEL, p16 = isa
ldp p10, p11, [x16, #CACHE] // p10 = buckets, p11 = occupied|mask
#if !__LP64__
and w11, w11, 0xffff // p11 = mask
#endif
and w12, w1, w11 // x12 = _cmd & mask
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
3: // wrap: p12 = first bucket, w11 = mask
add p12, p12, w11, UXTW #(1+PTRSHIFT)
// p12 = buckets + (mask << 1+PTRSHIFT)
查看CacheHit
的定义文件即可得知找到imp
后可直接返回.
.macro CacheHit
.if $0 == NORMAL
TailCallCachedImp x17, x12 // authenticate and call imp
.elseif $0 == GETIMP
mov p0, p17
AuthAndResignAsIMP x0, x12 // authenticate imp and re-sign as IMP
ret // return IMP
.elseif $0 == LOOKUP
AuthAndResignAsIMP x17, x12 // authenticate imp and re-sign as IMP
ret // return imp via x17
查看CheckMiss
的定义文件即可得知找不到imp
,便调用__objc_msgSend_uncached
。
.macro CheckMiss
// miss if bucket->sel == 0
.if $0 == GETIMP
cbz p9, LGetImpMiss
//因为前面声明了CacheLookup NORMAL ,所以会走下面这个判断。
.elseif $0 == NORMAL
cbz p9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
cbz p9, __objc_msgLookup_uncached
查看__objc_msgSend_uncached
的代码中发现MethodTableLookup
的调用,继续跟进,便发现了__class_lookupMethodAndLoadCache3
的调用。
.macro MethodTableLookup
// receiver and selector already in x0 and x1
mov x2, x16
bl __class_lookupMethodAndLoadCache3//这里跳转到C++中。
0x02 分析C++代码
继上:
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
//第一个YES,接上文,已经完成了isa的初始化,所以为YES.
//第一个NO,接上文,通过汇编没有在cache中完成查找,所以为NO。
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
lookUpImpOrForward
:是寻找imp的关键函数。runtime
中涉及imp的获取底层都会走这个方法。比如class_getMethodImplementation
、class_getInstanceMethod
、class_getInstanceMethod
也是通过lookUpImpOrNil
,最后底层走这个方法的。
在下面的方法中大致操作为:
1、首先检测缓存,如果cache有的话直接就在缓存中查找返回imp.
2、如果类没被创建,便进行实例化操作。
3、第一次调用类的时候,执行初始化。
4、为了防止并发,再次从缓存中查找。
5、遍历当前类的父类,在父类中缓存的imp中查找
6、在父类的方法列表中,获取method_t对象。如果找到则缓存查找到的IMP
7、如果都没有找到,就尝试动态方法解析和消息转发。
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// 如果cache是YES,则从缓存中查找IMP。
if (cache) {
// 通过cache_getImp函数查找IMP,查找到则返回IMP并结束调用
// cache_getImp:还是通过汇编来寻找的。
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
runtimeLock.read();
// 判断类是否已经被创建,如果没有被创建,则将类实例化
if (!cls->isRealized()) {
// Drop the read-lock and acquire the write-lock.
// realizeClass() checks isRealized() again to prevent
// a race while the lock is down.
runtimeLock.unlockRead();
runtimeLock.write();
// 对类进行实例化操作
realizeClass(cls);
runtimeLock.unlockWrite();
runtimeLock.read();
}
// 第一次调用当前类的话,执行initialize的代码
if (initialize && !cls->isInitialized()) {
runtimeLock.unlockRead();
// 对类进行初始化,并开辟内存空间
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
//以下重点!!!
retry:
runtimeLock.assertReading();
//再次从缓存中获取的原因:
//并发-remap(cls)
imp = cache_getImp(cls, sel);
if (imp) goto done;
{
// 如果没有从cache中查找到,则从方法列表中获取Method
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
// 如果获取到对应的Method,则加入缓存并从Method获取IMP
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
//在父类中找。
// Try superclass caches and method lists.
{
unsigned attempts = unreasonableClassCount();
// 循环遍历父类。获取这个类的缓存IMP 或 方法列表的IMP
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
//内存溢出
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
// 获取父类缓存的IMP
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
// 如果发现父类的方法,并且不再缓存中,在下面的函数中缓存方法
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}
// Superclass method list.
// 在父类的方法列表中,获取method_t对象。如果找到则缓存查找到的IMP
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// No implementation found. Try method resolver once.
// 如果没有找到,则尝试动态方法解析
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
//解析。
_class_resolveMethod(cls, sel, inst);
runtimeLock.read();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
//动态解析只能解析一次。
triedResolver = YES;
goto retry;
}
// No implementation found, and method resolver didn't help.
// Use forwarding.
// 如果没有IMP被发现,并且动态方法解析也没有处理,则进入消息转发阶段。只有汇编调用,没有源码实现。
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlockRead();
return imp;
}
0x03 动态方法解析
// 如果没有找到,则尝试动态方法解析
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
//解析。
_class_resolveMethod(cls, sel, inst);
runtimeLock.read();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
//动态解析只能解析一次。
triedResolver = YES;
goto retry;
}
_class_resolveMethod:
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {//解析实例方法。
// try [cls resolveInstanceMethod:sel]
// _class_resolveInstanceMethod:接收消息的是类对象。
_class_resolveInstanceMethod(cls, sel, inst);
}
else {//解析类方法。
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
在_class_resolveClassMethod
的实现中有如下代码,表示了消息的发送。可知消息的接受者_class_getNonMetaClass(cls, inst)
。
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(_class_getNonMetaClass(cls, inst),
SEL_resolveClassMethod, sel);
进入class_getNonMetaClass
的实现中,得知返回的依旧是类对象,这样是方便能够在同一个类中处理,方便管理,而避免了去虚拟的元类中进行改动。
static Class getNonMetaClass(Class metacls, id inst)
{
static int total, named, secondary, sharedcache;
runtimeLock.assertLocked();
realizeClass(metacls);
total++;
// metacls 元类
// metacls 类对象
//判断是否是NSObject
if (!metacls->isMetaClass()) return metacls;
// metacls really is a metaclass
// special case for root metaclass
// where inst == inst->ISA() == metacls is possible
// 判断是否是根元类。
if (metacls->ISA() == metacls) {
Class cls = metacls->superclass;
assert(cls->isRealized());
assert(!cls->isMetaClass());
assert(cls->ISA() == metacls);
if (cls->ISA() == metacls) return cls;
}
// 类对象
if (inst) {
Class cls = (Class)inst;
realizeClass(cls);
// cls may be a subclass - find the real class for metacls
// 元类 != 元类
while (cls && cls->ISA() != metacls) {
cls = cls->superclass;
realizeClass(cls);
}
// 最终返回的还是类对象
if (cls) {
assert(!cls->isMetaClass());
assert(cls->ISA() == metacls);
return cls;
}
#if DEBUG
_objc_fatal("cls is not an instance of metacls");
#else
// release build: be forgiving and fall through to slow lookups
#endif
}
在动态方法解析的过程中,都会调用lookUpImpOrNil
来递归查找动态解析方法的imp,而不会发生死递归的原因是在NSObject
中实现了动态方法解析,所以最终会找到它。
同时我们通过重写NSObject
中的+ (BOOL)resolveInstanceMethod:(SEL)sel
,在这个方法中通过给没有实现的sel添加imp方法避免崩溃,同时也可以将crash传给后台做崩溃统计等工作。
0x04 消息转发
以下的_objc_msgForward_impcache
因为苹果闭源是无法看到实现的,我们可以通过定义一个instrumentObjcMessageSends
,或者通过反编译函数实现的可执行文件来查看其流程。这里简单介绍一下第二种。
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
通过实现动态方法解析,未实现转发而崩溃的堆栈信息可以看出_objc_msgForward_impcache
具体是在CoreFoundation.framework
中实现。如图:
CoreFoundation.framework
的本地地址:
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation
通过hopper
或者ida
打开,搜索_CFInitialize
,再依次进入_forwarding_prep_0_
,__forwarding__
。通过查看伪代码,会有以下发现:
var_50 = rbx;
if (class_respondsToSelector(object_getClass(r12), @selector(_forwardStackInvocation:)) != 0x0) {
if (*____forwarding___.onceToken != 0xffffffffffffffff) {
dispatch_once(____forwarding___.onceToken, ^ { /* block implemented at ______forwarding____block_invoke */ });
}
r13 = [NSInvocation requiredStackSizeForSignature:r14];
rdx = *____forwarding___.invClassSize;
r12 = rsp - (rdx + 0xf & 0xfffffffffffffff0);
memset(r12, 0x0, rdx);
objc_constructInstance(*____forwarding___.invClass, r12);
var_40 = r13;
[r12 _initWithMethodSignature:r14 frame:var_48 buffer:r12 - (r13 + 0xf & 0xfffffffffffffff0) size:r13];
[var_38 _forwardStackInvocation:r12];
r15 = 0x1;
}
else {
rbx = @selector(forwardInvocation:);
if (class_respondsToSelector(object_getClass(r12), rbx) != 0x0) {
rdi = r12;
r12 = [NSInvocation _invocationWithMethodSignature:r14 frame:var_48];
_objc_msgSend(rdi, rbx);
}
else {
r12 = 0x0;
_CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message", 0x0, object_getClassName(0x0), r8, r9, stack[2037]);
}
var_40 = 0x0;
r15 = 0x0;
}
如下,代码实现消息转发。
// 只有汇编调用 没有源码实现
+ (id)forwardingTargetForSelector:(SEL)aSelector{
return [super forwardingTargetForSelector:aSelector];
}
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(walk)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation{
//在此切面编程
NSString *sto = @"这是参数";
anInvocation.target = [LGStudent class];
[anInvocation setArgument:&sto atIndex:2];
NSLog(@"%@",anInvocation.methodSignature);
anInvocation.selector = @selector(run:);
[anInvocation invoke];
}
如果没有实现消息转发,我们再根据源码追踪一下走位。
进入消息转发的汇编部分。如下:
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
ENTRY _objc_msgSend_noarg
b _objc_msgSend
END_ENTRY _objc_msgSend_noarg
ENTRY _objc_msgSend_debug
b _objc_msgSend
END_ENTRY _objc_msgSend_debug
ENTRY _objc_msgSendSuper2_debug
b _objc_msgSendSuper2
END_ENTRY _objc_msgSendSuper2_debug
ENTRY _method_invoke
// x1 is method triplet instead of SEL
add p16, p1, #METHOD_IMP
ldr p17, [x16]
ldr p1, [x1, #METHOD_NAME]
TailCallMethodListImp x17, x16
END_ENTRY _method_invoke
查看__objc_forward_handler
回调
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
如下可以见到我们常见的崩溃信息打印的源头了。
// Default forward handler halts the process.
__attribute__((noreturn)) void
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : '-',
object_getClassName(self), sel_getName(sel), self);
}