The _objc_msgSend sequel to the underlying analysis of cache_t

You’ve seen the underlying structure of cache_T in cache_t analysis, and you’ve seen how buckets are created in Inset. I wonder when insert was called? This is also the focus of today’s query, when does the cache insert?

Look at the call stack by setting breakpoints to find out when inset was called.

You can see by looking at the call stackinsetI calledLog_and_fill_cache, lookUpImpOrForward, _objc_msgSend_uncacheAs can be seen, when the object calls the method, the internal call execution function flow is as follows:_objc_msgSend_uncache -> lookUpImpOrForward -> log_and_fill_cache -> inset.

Global lookup _objc_msgSend_uncache

The _objc_msgSend_uncache call was found in an assembler file. (Meng… Put it aside and it will be solved later.

We now know that _objc_msgSend_uncache was internally called when [p saySomething] was called, so let’s use clang to rewrite the OC file and look at the underlying C++ implementation.

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






        LGPerson *p = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc")) ;



        ((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("saySomething"));


    }
    return 0;
}
Copy the code

The underlying c++ implementation of [p saySomething] calls objc_msgSend. (void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName(“saySomething”)) The return value is void and requires two parameters of type ID and SEL.

Void msgsend (void *)objc_msgSend (void *)objc_msgSend (void *)

((id)p, sel_registerName(“saySomething”)) is the argument list of the object function. That is, p of type ID, and SEL by sel_registerName.

As you can see, the underlying method that an object calls is by calling the function objc_msgSend, or message send. Try sending messages directly to objects using the underlying RuntimeAPI.

The call results are consistent. Without further ado, start by viewingcachetheinsertWhen the method is called, _objc_msgSend_uncache is called in assembly, so through the rewrite of OC file, it is found that the object method is actually called when the underlying C++ implementationobjc_msgSendFunction.

Next step, explorationobjc_msgSend

Global search

foundobjc_msgSendAlso in the assembly file, thus visibleobjc_msgSendIs combined with assembly implementation (probably for operational efficiency).

Analysis of theobjc_msgSendAssembly code implementation

ENTRY _objc_msgSend: ENTRY represents the ENTRY of the program, that is, the ENTRY of _objc_msgSend

UNWIND _objc_msgSend, NoFrame: UNWIND is a stack frame register that records the pointer to the current top of the stack.

CMP P0, #0: cap compares the pointer to Po, the first parameter of objc_msgSend, with 0

#if SUPPORT_TAGGED_POINTERS The TAGGED_POINTERS type is supported

B. LNilOrTagged If the comparison number is less than 0, LNilOrTagged is executed

#else

B. LReturnZero If the comparison number is 0, LReturnZero is executed

#endif

LDR p13, [x0]// p13 = isa. X0 is now the pointer to the receiver of the message, which is isa (the first address of the object is the address of ISA).

GetClassFromIsa_p16 p13, 1, x0 // p16 = class dropped GetClassFromIsa_p16 takes three arguments

GetClassFromIsa_p16

.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if ! needs_auth */ #if SUPPORT_INDEXED_ISA // Indexed isa mov p16, \src // optimistically set dst = src tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f // done if not non-pointer isa // isa in p16 is indexed adrp x10, _objc_indexed_classes@PAGE add x10, x10, _objc_indexed_classes@PAGEOFF ubfx p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS // extract index ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array 1: #elif __LP64__ .if \needs_auth == 0 // _cache_getImp takes an authed class already mov p16, \src .else // 64-bit packed isa ExtractISA p16, \src, \auth_address .endif #else // 32-bit raw isa mov p16, \src #endif .endmacroCopy the code

SUPPORT_INDEXED_ISA Because it is not an ARM architecture, SUPPORT_INDEXED_ISA == 0, so we analyze LP64. Because needs_auth passed in 1, we execute else. ExtractISA p16, \src, \auth_address

.macro ExtractISA
	and    $0, $1, #ISA_MASK
.endmacro
Copy the code

In ExtractISA, $1 is isa and is_mask to get the current class address and write the class address to $0 (p16).

LGetIsaDone:
	// calls imp or objc_msgSend_uncached
	CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
Copy the code

Once we get the class, we execute CacheLookup, passing in three arguments, including the __objc_msgSend_uncached method we found on the stack when we looked at the INSERT call at the breakpoint. Note When objects send messages, such as _objc_msgSend, to internal CacheLookup, it is possible to execute __objc_msgSend_uncached, and then cache ached for the cached classes.

conclusion

After analyzing cache_t, we learned about the structure of cache_t and how buckets are extended in INSERT. By looking at the call stack before INSERT, we found that the underlying object call method is _objc_msgSend. _objc_msgSend assembler implementation _objc_msgSend assembler implementation logic _objc_msgSend assembler implementation logic _objc_msgSend assembler implementation logic _objc_MSgsend_uncached in CacheLookup (To be continued, analyze CacheLookup)