底层学习01_Runtime

337 阅读4分钟

Runtime

OC对象的本质

  • 定义一个MyPerson类然后编译
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MyPerson *p = [[MyPerson alloc] init];
        [p run];
    }
    return 0;
}
// 在arm64下编译
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o testMain.c++

  • 得到的结果的主要部分:

#ifndef _REWRITER_typedef_MyPerson
#define _REWRITER_typedef_MyPerson
typedef struct objc_object MyPerson;
typedef struct {} _objc_exc_MyPerson;
#endif

struct MyPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
};

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 


        MyPerson *p = ((MyPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MyPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MyPerson"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("run"));

    }
    return 0;
}

上边代码中我们可以看到OC对象的本质其实是一个objc_object结构体

  • 继续分析objc_object结构体
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

objc_object结构体里只有一个isa

继续分析Class(这里我们只看objc-runtime-new 忽略objc-runtime-old)

typedef struct objc_class *Class;
struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }
    
    // 省略n多...
    
}

MyPerson的对象p 其实就是一个包含objc_class结构体的objc_object结构体.

方法调用的本质

  • 调用一个对象的方法,其实本质就是给结构体objc_object发送消息
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("run"));
// sel_registerName("run") 相当于@selector(run)
// (id)p 消息接收者
// sel_registerName("run") 是一个IMP 相当于方法的key

也就是说

[p run];
// 等同于
objc_msgSend(p, sel_registerName("run")); // 或者objc_msgSend(p, @selector(run));

  • 调用一个类的方法,相当于给objc_class发送消息
[MyPerson walk];
((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MyPerson"), sel_registerName("walk"));
  • 调该对象的父类方法(MyPerson 的 -(void)run)
#import <objc/runtime.h>

MyStudent *s = [[MyStudent alloc] init];
        
struct objc_super mySuper;
mySuper.receiver = s;
mySuper.super_class = class_getSuperclass([s class]);
objc_msgSendSuper(&mySuper, @selector(run));

注意:Xcode11 中如上述 调用父类的方法会报编译错误: Too many arguments to function call, expected 0, have 2

引用stackoverflow上的回答:这里

修正后的完整版:

#import <Foundation/Foundation.h>
#import "MyPerson.h"
#import "MyStudent.h"
#import <objc/runtime.h>
#import <objc/message.h>

typedef void *(*mySendSuperType)(struct objc_super *super_class, SEL selector);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MyStudent *s = [[MyStudent alloc] init];
        
        struct objc_super mySuper;
        mySuper.receiver = s;
        mySuper.super_class = class_getSuperclass([s class]);
//        objc_msgSendSuper(&mySuper, @selector(run));
        ((mySendSuperType)objc_msgSendSuper)(&mySuper, @selector(run));
    }
    return 0;
}
  • 调用该父类的类方法(MyPerson的 +(void)walk)
struct objc_super mySuper2;
mySuper2.receiver = [s class];
mySuper2.super_class = class_getSuperclass(object_getClass([s class]));
((mySendSuperType)objc_msgSendSuper)(&mySuper2, @selector(walk));
  • objc_super结构体
/// Specifies the superclass of an instance. 
struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull id receiver;

    /// Specifies the particular superclass of the instance to message. 
#if !defined(__cplusplus)  &&  !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained _Nonnull Class class;
#else
    __unsafe_unretained _Nonnull Class super_class;
#endif
    /* super_class is the first class to search */
};
#endif

_objc_msgSend发送消息

  • _objc_msgSend 是汇编部分 下面贴一下核心的代码
ENTRY _objc_msgSend
	UNWIND _objc_msgSend, NoFrame

	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:
	// calls imp or objc_msgSend_uncached
	CacheLookup NORMAL, _objc_msgSend
/********************************************************************
 *
 * CacheLookup NORMAL|GETIMP|LOOKUP <function>
 *
 * Locate the implementation for a selector in a class method cache.
 *
 * When this is used in a function that doesn't hold the runtime lock,
 * this represents the critical section that may access dead memory.
 * If the kernel causes one of these functions to go down the recovery
 * path, we pretend the lookup failed by jumping the JumpMiss branch.
 *
 * Takes:
 *	 x1 = selector
 *	 x16 = class to be searched
 *
 * Kills:
 * 	 x9,x10,x11,x12, x17
 *
 * On exit: (found) calls or returns IMP
 *                  with x16 = class, x17 = IMP
 *          (not found) jumps to LCacheMiss
 *
 ********************************************************************/

#define NORMAL 0
#define GETIMP 1
#define LOOKUP 2
.macro CacheLookup
	//
	// Restart protocol:
	//
	//   As soon as we're past the LLookupStart$1 label we may have loaded
	//   an invalid cache pointer or mask.
	//
	//   When task_restartable_ranges_synchronize() is called,
	//   (or when a signal hits us) before we're past LLookupEnd$1,
	//   then our PC will be reset to LLookupRecover$1 which forcefully
	//   jumps to the cache-miss codepath which have the following
	//   requirements:
	//
	//   GETIMP:
	//     The cache-miss is just returning NULL (setting x0 to 0)
	//
	//   NORMAL and LOOKUP:
	//   - x0 contains the receiver
	//   - x1 contains the selector
	//   - x16 contains the isa
	//   - other registers are set as per calling conventions
	//
LLookupStart$1:

	// p1 = SEL, p16 = isa
	ldr	p11, [x16, #CACHE]				// p11 = mask|buckets

#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
	and	p10, p11, #0x0000ffffffffffff	// p10 = buckets
	and	p12, p1, p11, LSR #48		// x12 = _cmd & mask
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
	and	p10, p11, #~0xf			// p10 = buckets
	and	p11, p11, #0xf			// p11 = maskShift
	mov	p12, #0xffff
	lsr	p11, p12, p11				// p11 = mask = 0xffff >> p11
	and	p12, p1, p11				// x12 = _cmd & mask
#else
#error Unsupported cache mask storage for ARM64.
#endif


	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
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
	add	p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
					// p12 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
	add	p12, p12, p11, LSL #(1+PTRSHIFT)
.macro CacheHit
.if $0 == NORMAL
	TailCallCachedImp x17, x12, x1, x16	// authenticate and call imp
	// ...省略n多
.macro CheckMiss
	// miss if bucket->sel == 0
.if $0 == GETIMP
	cbz	p9, LGetImpMiss
.elseif $0 == NORMAL
	cbz	p9, __objc_msgSend_uncached
	
	// ... 省略n多
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves

// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band p16 is the class to search
	
MethodTableLookup
TailCallFunctionPointer x17

END_ENTRY __objc_msgSend_uncached

.macro MethodTableLookup
	
	// ...省略n多

	// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
	// receiver and selector already in x0 and x1
	mov	x2, x16
	mov	x3, #3
	bl	_lookUpImpOrForward

	// ... 省略n多

.endmacro

lookUpImpOrForward的实现:

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;

    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
    if (fastpath(behavior & LOOKUP_CACHE)) {
        imp = cache_getImp(cls, sel);
        if (imp) goto done_nolock;
    }

    // runtimeLock is held during isRealized and isInitialized checking
    // to prevent races against concurrent realization.

    // runtimeLock is held during method search to make
    // method-lookup + cache-fill atomic with respect to method addition.
    // Otherwise, a category could be added but ignored indefinitely because
    // the cache was re-filled with the old value after the cache flush on
    // behalf of the category.

    runtimeLock.lock();

    // We don't want people to be able to craft a binary blob that looks like
    // a class but really isn't one and do a CFI attack.
    //
    // To make these harder we want to make sure this is a class that was
    // either built into the binary or legitimately registered through
    // objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair.
    //
    // TODO: this check is quite costly during process startup.
    checkIsKnownClass(cls);

    if (slowpath(!cls->isRealized())) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
        // runtimeLock may have been dropped but is now locked again
    }

    if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
        // runtimeLock may have been dropped but is now locked again

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

    runtimeLock.assertLocked();
    curClass = cls;

    // The code used to lookpu the class's cache again right after
    // we take the lock but for the vast majority of the cases
    // evidence shows this is a miss most of the time, hence a time loss.
    //
    // The only codepath calling into this without having performed some
    // kind of cache lookup is class_getInstanceMethod().

    for (unsigned attempts = unreasonableClassCount();;) {
        // curClass method list.
        Method meth = getMethodNoSuper_nolock(curClass, sel);
        if (meth) {
            imp = meth->imp;
            goto done;
        }

        if (slowpath((curClass = curClass->superclass) == nil)) {
            // No implementation found, and method resolver didn't help.
            // Use forwarding.
            imp = forward_imp;
            break;
        }

        // Halt if there is a cycle in the superclass chain.
        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }

        // Superclass cache.
        imp = cache_getImp(curClass, sel);
        if (slowpath(imp == forward_imp)) {
            // Found a forward:: entry in a superclass.
            // Stop searching, but don't cache yet; call method
            // resolver for this class first.
            break;
        }
        if (fastpath(imp)) {
            // Found the method in a superclass. Cache it in this class.
            goto done;
        }
    }

    // No implementation found. Try method resolver once.

    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

 done:
    log_and_fill_cache(cls, imp, sel, inst, curClass);
    runtimeLock.unlock();
 done_nolock:
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    return imp;
}

找方法的getMethodNoSuper_nolock实现:

getMethodNoSuper_nolock(Class cls, SEL sel)
{
    runtimeLock.assertLocked();

    ASSERT(cls->isRealized());
    // fixme nil cls? 
    // fixme nil sel?

    auto const methods = cls->data()->methods();
    for (auto mlists = methods.beginLists(),
              end = methods.endLists();
         mlists != end;
         ++mlists)
    {
        // <rdar://problem/46904873> getMethodNoSuper_nolock is the hottest
        // caller of search_method_list, inlining it turns
        // getMethodNoSuper_nolock into a frame-less function and eliminates
        // any store from this codepath.
        method_t *m = search_method_list_inline(*mlists, sel);
        if (m) return m;
    }

    return nil;
}
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());

    runtimeLock.unlock();

    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNil(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel

+ (BOOL)resolveClassMethod:(SEL)sel
2020-05-11 12:22:27.470486+0800 TheClassStudy[5518:2012866] resolve instance method
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x0000000100000dc5 TheClassStudy`+[MyStudent resolveInstanceMethod:](self=MyStudent, _cmd="resolveInstanceMethod:", sel="run") at MyStudent.m:23:12
    frame #1: 0x00007fff7216fabb libobjc.A.dylib`resolveInstanceMethod(objc_object*, objc_selector*, objc_class*) + 80
    frame #2: 0x00007fff7216f954 libobjc.A.dylib`resolveMethod_locked(objc_object*, objc_selector*, objc_class*, int) + 319
    frame #3: 0x00007fff7215a2ad libobjc.A.dylib`lookUpImpOrForward + 603
    frame #4: 0x00007fff72159bd9 libobjc.A.dylib`_objc_msgSend_uncached + 73
    frame #5: 0x0000000100000ea6 TheClassStudy`main(argc=1, argv=0x00007ffeefbff508) at main.m:33:9
    frame #6: 0x00007fff734c82e5 libdyld.dylib`start + 1
    frame #7: 0x00007fff734c82e5 libdyld.dylib`start + 1
2020-05-11 12:22:57.934615+0800 TheClassStudy[5518:2012866] resolve instance method
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x0000000100000dc5 TheClassStudy`+[MyStudent resolveInstanceMethod:](self=MyStudent, _cmd="resolveInstanceMethod:", sel="run") at MyStudent.m:23:12
    frame #1: 0x00007fff7216fabb libobjc.A.dylib`resolveInstanceMethod(objc_object*, objc_selector*, objc_class*) + 80
    frame #2: 0x00007fff7216f954 libobjc.A.dylib`resolveMethod_locked(objc_object*, objc_selector*, objc_class*, int) + 319
    frame #3: 0x00007fff7215a2ad libobjc.A.dylib`lookUpImpOrForward + 603
    frame #4: 0x00007fff72160d0e libobjc.A.dylib`class_getInstanceMethod + 51
    frame #5: 0x00007fff3c0557f4 CoreFoundation`__methodDescriptionForSelector + 282
    frame #6: 0x00007fff3c08eca2 CoreFoundation`-[NSObject(NSObject) methodSignatureForSelector:] + 38
    frame #7: 0x00007fff3c0462d0 CoreFoundation`___forwarding___ + 408
    frame #8: 0x00007fff3c0460a8 CoreFoundation`__forwarding_prep_0___ + 120
    frame #9: 0x0000000100000ea6 TheClassStudy`main(argc=1, argv=0x00007ffeefbff508) at main.m:33:9
    frame #10: 0x00007fff734c82e5 libdyld.dylib`start + 1
    frame #11: 0x00007fff734c82e5 libdyld.dylib`start + 1
Program ended with exit code: 9

结果到这里 也就是我们经常看到的输出:

// Default forward handler halts the process.
__attribute__((noreturn, cold)) 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);
}
extern void instrumentObjcMessageSends(BOOL);
// 用这个方法查方法调用log -> Macintosh HD/private/tmp/msgSends..1234.  1234是getpid的结果
instrumentObjcMessageSends(YES);
[s run];
instrumentObjcMessageSends(NO);

代码解释:

  1. ENTRY _objc_msgSend // 进入汇编
  2. CacheLookup NORMAL, _objc_msgSend // 执行CacheLookup方法 参数传 NORMAL 也就是0
  3. 再看 .macro CacheLookup部分 有3种结果
    • CacheHit $0 // call or return imp
    • CheckMiss $0 // miss if bucket->sel == 0
    • add
  4. 刚开始会进入CheckMiss 然后走__objc_msgSend_uncached
  5. __objc_msgSend_uncached 中 继续走到MethodTableLookup
  6. MethodTableLookup里 走到_lookUpImpOrForward //
  7. lookUpImpOrForward 是在objc-runtime-new.mm里 也就是说这里终于回到我们的c++,而不再是汇编部分了.
  8. lookUpImpOrForward 主要是找方法, 循环:一直找imp 如果cache_getImp能找到imp就万事大吉, 找不到就getMethodNoSuper_nolock 在自己的cls->data()->methods();里找(class_rw_t 里的 method_array_t 格式的 methods)
  9. 找到了就跳到done, 并且cache这个imp
  10. 没找到就去 父类里找(也是先cache_getImp缓存里找,然后找自己)
  11. 父类找到了,ok 我们cache这个imp
  12. 都没找到?? resolveMethod_locked 找resolve解决吧
  13. 没找到方法的情况下 我们有两次机会补救,第一次是resolveInstanceMethod/resolveClassMethod 时候处理一次 比如动态的给该类加一个IMP
  14. 第二次是forward时候 实现3个方法
      • (BOOL)forwardingTargetForSelector:(SEL)aSelector.
      • (MethodSignature *)methodSignatureForSelector:(SEL)aSelector
      • (void)forwardInvocation:(NSInvocation *)anInvocation
  15. 转发之后也没有 结果就到我们熟悉的unrecognized selector sent to instance...了

知道了这些,我们就可以做一些动态的事情,比如创建一个没有写过的ViewController然后跳转等等....