IOS & OpenGL & OpenGL ES & Metal

Now that you know that the essence of a method is to send messages, let’s move on to the Message lookup of the Runtime

preface

The Runtime’s message lookup is divided into two steps:

  • Quick Lookup process
  • Slow search process

A, objc_msgSend

Objc_msgSend is written in sinks, so let’s start with assembly and explore what objc_msgSend does

Extension: Why is objc_msgSend written in assembly rather than C?

  • Because:
    • C cannot jump to arbitrary Pointers by writing just one function and leaving unknown parameters.
    • Assembler has registers (arm64 has 31 registers, each representing 64 bits)
    • Assembly is easier to be recognized by machines. For some functions or operations that are called too frequently, assembly can improve efficiency and performance

1. Start exploringobjc-msg-arm64.s

Go to the source code and go to objC-msG-arm64.s and then ENTRY _objc_msgSend.

Oh my God! What the hell is all this?

It doesn’t matter, I also don’t understand, we side Baidu instructions, side analysis notes, hard to read ~

2,GetClassFromIsa_p16

Search to see how this method is used to get class from isa:I do see familiar codeISA_MASKIs throughisa & ISA_MASKCompute, get class, and go toCacheLookupmethods

Second, fast process

1.CacheLookup

Let’s take a look at the comment memory, which is what we usually useNORMALMode. At this point, we’ve got itselandclass

This code is too long to capture.

	ldr	p11, [x16, #CACHE]				// p11 = mask|buckets
Copy the code

X16 shifts the memory address of the class object by 16 bits to p11. As we’ve seen before, shifting 16 bits is exactly the cache cache. In fact, the comments behind the explanation

	and	p10, p11, #0x0000ffffffffffff	// p10 = buckets
	and	p12, p1, p11, LSR #48		// x12 = _cmd & mask
Copy the code

P11 uses and = & to assign buckets in the cache to P10. P11 uses LSR to move 48 bits to the right to obtain mask, and calculates and with SEL to assign a value to P12

  • _cmd & maskandcache_tIn thecache_hashIn the same way, after the hash operation, the bucket structure pointer is obtained
	add	p12, p10, p12, LSL #(1+PTRSHIFT)
		             // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

	ldp	p17, p9, [x12]		// {imp, sel} = *bucket
Copy the code

P12 moves left through LSL (1+PTRSHIFT), then performs and operation with P10, and assigns to P12. Assign IMP of P12 to P17 and SEL to P9

1: cmp p9, p1 // if (bucket->sel ! = _cmd) b.ne 2f // scan more CacheHit $0 // call or return impCopy the code

To compare p9 with p1, we want to see if the method number we passed in matches the one found in the cache. A match is a CacheHit and an IMP is returned. A mismatch goes to step 2

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)
					// p12 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
Copy the code

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

  • This is to prevent multithreading, and it just happened to cache in
// Clone scanning loop to miss instead of hang when cache is corrupt. // The slow path may detect any corruption and halt later. 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 LLookupEnd$1: LLookupRecover$1: 3: // double wrap JumpMiss $0Copy the code

If we can’t find the IMP for sel we need the second time, we jump to the JumpMiss method and start the slow process.

Let’s look again at the CheckMiss method and JumpMiss method in the middle of the process

2,CheckMissandJumpMiss

We get the __objc_msgSend_uncached method, and we get it properly because we’re in NORMAL mode

3,__objc_msgSend_uncached

The core logic of the __objc_msgSend_uncached method is the MethodTableLookup method, which looks up a list of methods.

4,MethodTableLookup

Look roughly, calculate again? We go straight to the core point: BL _lookUpImpOrForward, skip this method, search globally for _lookUpImpOrForward and find none. Look for lookUpImpOrForward. There is one!

In fact, let’s get rid of the underline here and find a way to open up the Perspective of God. If we follow the normal process, we should open the assembly, the breakpoint method, and see what methods jump and callq commands in the assembly go through

Because lookUpImpOrForward is a C/C++ method, its arguments must be determined so that the previous line of code through BL _lookUpImpOrForward can be interpreted in preparation for passing in the determined arguments.

Fast process to here, is in the cache through SEL to find IMP, can not find the slow process.

Three, slow process

As we verified in the previous section, methods are stored in class -> bit -> rw -> ro -> methodList.

1.lookUpImpOrForward

Wow, it’s smelly and long inside, so let’s just sketch it out and talk about a few things to be aware of:

  • One step is error tolerance, with the cache_getImp method, which returns the IMP directly if it is found

  • Details of the point

    • runtimeLock.lock();Lock here to prevent access to 2 methods at the same time, imp return error
    • checkIsKnownClass(cls);Determine if the class is compiled, and print an error message if it is not
    • There’s a little bit of preparation here, getting the corresponding class and metaclass information
  • If it is an object method, use binary lookup in the list of methods of the current class to find the IMP, cache it, and return the IMP

    • If it’s a class method, look in the current metaclass
  • If the current class has no parent, it crashes and prints an error message

  • If there is a parent class, look for it directly in the parent’s cache

    • Cache it and return itimp
    • If not found, use it in the parent class’s method listBinary search methodLooking forimp, cache it and return itimp
  • If not, look in the metaclass (the parent of the parent) and continue the loop

    • Current class -> Parent -> metaclass -> root metaclass (NSObject)
  • ResolveMethod_locked (inst, SEL, CLS, behavior);

2. Method crash code

One point to mention in the above flow is the _objc_msgForward_impcache method, which we look at ~

C function did not find this method, assembly found it.

	STATIC_ENTRY __objc_msgForward_impcache

	// No stret specialization.
	b	__objc_msgForward

	END_ENTRY __objc_msgForward_impcache
Copy the code

Keep looking __objc_msgForward

	
	ENTRY __objc_msgForward

	adrp	x17, __objc_forward_handler@PAGE
	ldr	p17, [x17, __objc_forward_handler@PAGEOFF]
	TailCallFunctionPointer x17
	
	END_ENTRY __objc_msgForward
	
    
Copy the code

C functions __objc_forward_handler

void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
Copy the code
// 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);
}
Copy the code

This one found last, so familiar! Can’t find the implementation method crash, print the information in the console!!

Four,

1. Object method search process

  • Object instance method – own
  • Object instance method – does not have its own – finds the parent class
  • Object instance method – no parent – no parent – find the parent – NSObject
  • The instance method of the object – does not have it – does not have its parent – finds its parent – does not have NSObject – crashes

2. Class method search process

  • Class method – own

  • Class method – own no – find the parent class

  • Class method – doesn’t have it – doesn’t have the parent – finds the parent class – NSObject

  • Class method – doesn’t have it – doesn’t have the parent – finds the parent – doesn’t have NSObject – crashes

  • Class method – doesn’t have it – doesn’t have a parent class – doesn’t have a parent class that finds the parent – NSObject doesn’t have it – but it does have object methods

3. Message search process

Message lookup phase:

  1. First of all to enterA rapid processAnd getisaThrough theassemblyThe means of theThe cacheIf you find it, return
  2. Then enter theA slow processPass: current class. Method list -> Parent class. Cache -> parent class. Method list -> metaclass. Cache -> metaclass. Method list process, whichever step is found, return
  3. Finally did not find, entered the ‘ ‘message forwarding stage’