Objc_msgSend () : objc_msgSend () : objc_msgSend () : In objC4-818’s source code, find the assembly source for 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, 1, x0 // p16 = class
LGetIsaDone:
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
Copy the code
The default two arguments to objc_msgSend are id self, sel _cmd, self stored in register X0, and _cmd stored in x1. If p0 is less than or equal to LNilOrTagged, it goes to Tagged; if p0 is less than or equal to 0, it goes to LReturnZero, and returns 0. X0 is not 0. If you go down, x0 holds the pointer to the object, p13 takes the pointer to the object, which happens to be ISA.
Enter the GetClassFromIsa_p16 method
//GetClassFromIsa_p16 p13, 1, x0 .macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if ! Needs_auth */ #if SUPPORT_INDEXED_ISA //armv7 or arm64_32 // 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__ // 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
P13, 1, and x0 are assigned to SRC, needs_auth, and auth_address, respectively. First check whether SUPPORT_INDEXED_ISA refers to the architecture. Check whether it is armv7 or ARM64_32. It will enter ExtractISA P16, \ SRC, \auth_address.
.macro ExtractISA
and $0, $1, #ISA_MASK
.endmacro
Copy the code
$0 = $1&isa_mask, 0 = P16, 0 = p16, 0 = p16, 0 = p16, 1 = SRC and p13. P16 = ISA & ISA_mask, that’s the class that pulled out the object.
Cache NORMAL, _objc_msgSend, __objc_msgSend_uncached, and gets the data from both sides. For simplicity’s sake, we get the arm64 schema we need. Small – endian mode code to analyze.
//CacheLookup uses GETIMP, _cache_getImp, LGetImpMissDynamic, LGetImpMissConstant for its parent class. NORMAL, _objc_msgSend, __objc_msgSend_uncached .macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant mov x15, x16 // stash the original isa LLookupStart\Function: // p1 = SEL, p16 = isa #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 ldr p11, [x16, # CACHE] / / p11 = mask | buckets # if CONFIG_USE_PREOPT_CACHES / / = 1 # if __has_feature (ptrauth_calls) / / A12 processor, and later, TBNZ p11, #0, LLookupPreopt\Function and p10, p11, # 0x0000ffffFFFFFF // p10 = buckets #else and p10, p11 #0x0000fffffffffffe // p10 = buckets tbnz p11, #0, LLookupPreopt\Function #endif eor p12, p1, p1, LSR #7 and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask #else and p10, p11, #0x0000ffffffffffff // p10 = buckets and p12, p1, p11, LSR #48 // x12 = _cmd & mask #endif // CONFIG_USE_PREOPT_CACHES add p13, p10, p12, LSL #(1+PTRSHIFT) // p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT)) // do { 1: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket-- cmp p9, p1 // if (sel ! = _cmd) { b.ne 3f // scan more // } else { 2: CacheHit \Mode // hit: call or return imp // } 3: cbz p9, \MissLabelDynamic // if (sel == 0) goto Miss; cmp p13, p10 // } while (bucket >= buckets) b.hs 1b add p13, p10, p11, LSR #(48 - (1+PTRSHIFT)) // p13 = buckets + (mask << 1+PTRSHIFT) add p12, p10, p12, LSL #(1+PTRSHIFT) // p12 = first probed bucket // do { 4: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket-- cmp p9, p1 // if (sel == _cmd) b.eq 2b // goto hit cmp p9, #0 // } while (sel ! = 0 && ccmp p13, p12, #0, ne // bucket > first_probed) b.hi 4bCopy the code
CacheHit mode: NORMAL; CacheHit mode: NORMAL; CacheHit mode: NORMAL; If we don’t get it, we get MissLabelDynamic (__objc_msgSend_uncached).
// CacheHit: x17 = cached IMP, x10 = address of buckets, x1 = SEL, x16 = isa .macro CacheHit .if $0 == NORMAL TailCallCachedImp x17, x10, x1, x16 // authenticate and call imp .elseif $0 == GETIMP mov p0, p17 cbz p0, 9f // don't ptrauth a nil imp AuthAndResignAsIMP x0, x10, x1, x16 // authenticate imp and re-sign as IMP 9: ret // return IMP .elseif $0 == LOOKUP // No nil check for ptrauth: the caller would crash anyway when they // jump to a nil IMP. We don't care if that jump also fails ptrauth. AuthAndResignAsIMP x17, x10, x1, x16 // authenticate imp and re-sign as IMP cmp x16, x15 cinc x16, x16, ne // x16 += 1 when x15 ! = x16 (for instrumentation ; fallback to the parent class) ret // return imp via x17 .else .abort oops .endif .endmacroCopy the code
.macro TailCallCachedImp // $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa eor $0, $0, $3 // bitwise & br $0.endmacroCopy the code
Fetch imp from cache and return it.
Summary: All objc_msgSend does is do a quick lookup first, and a slow lookup if it can’t find one.
- Find the class by object
- From the first address of the class, pan 16 bytes to find the cache
- The sel in cache is traversed and compared with the sel called. If it is found, the corresponding IMP is called. If no, call
__objc_msgSend_uncached
Enter slow search.