通过Runtime源码,分析OC消息发送及处理

前言

日常开发中我们得知,当我们通过对象调用一个方法时,本质是通过objc_msgSend给对象发送消息。这点我们可以通过clang编译后的代码得知。

1
MyPerson *p = [MyPerson new];

通过xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件编译得:

1
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。代码和注释如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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进缓存中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
.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后可直接返回.

1
2
3
4
5
6
7
8
9
10
.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

1
2
3
4
5
6
7
8
9
.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的调用。

1
2
3
4
5
.macro MethodTableLookup

// receiver and selector already in x0 and x1
mov x2, x16
bl __class_lookupMethodAndLoadCache3//这里跳转到C++中。

0x02 分析C++代码

继上:

1
2
3
4
5
6
7
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_getMethodImplementationclass_getInstanceMethodclass_getInstanceMethod也是通过lookUpImpOrNil,最后底层走这个方法的。

在下面的方法中大致操作为:
1、首先检测缓存,如果cache有的话直接就在缓存中查找返回imp.
2、如果类没被创建,便进行实例化操作。
3、第一次调用类的时候,执行初始化。
4、为了防止并发,再次从缓存中查找。
5、遍历当前类的父类,在父类中缓存的imp中查找
6、在父类的方法列表中,获取method_t对象。如果找到则缓存查找到的IMP
7、如果都没有找到,就尝试动态方法解析和消息转发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
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 动态方法解析

1
2
3
4
5
6
7
8
9
10
11
12
// 如果没有找到,则尝试动态方法解析
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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)

1
2
3
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(_class_getNonMetaClass(cls, inst),
SEL_resolveClassMethod, sel);

进入class_getNonMetaClass的实现中,得知返回的依旧是类对象,这样是方便能够在同一个类中处理,方便管理,而避免了去虚拟的元类中进行改动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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,或者通过反编译函数实现的可执行文件来查看其流程。这里简单介绍一下第二种。

1
2
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);

通过实现动态方法解析,未实现转发而崩溃的堆栈信息可以看出_objc_msgForward_impcache具体是在CoreFoundation.framework中实现。如图:

CoreFoundation.framework的本地地址:

1
/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__。通过查看伪代码,会有以下发现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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;
}

经典走位图

如下,代码实现消息转发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 只有汇编调用  没有源码实现
+ (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];
}

如果没有实现消息转发,我们再根据源码追踪一下走位。
进入消息转发的汇编部分。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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回调

1
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;

如下可以见到我们常见的崩溃信息打印的源头了。

1
2
3
4
5
6
7
8
9
10

// 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);
}