This is the 10th day of my participation in Gwen Challenge

In this article, we will walk through the process of objc_msgSend

Let’s take the ARM64 architecture as an example to analyze the flow:

Objc_msgSend process

ENTRY _objc_msgSend

CMP p0, #0 register p0 is the first parameter of objc_msgSend, id self, isa, which is compared with 0 to determine whether there isa receiver, no receiver finally executes LReturnZero, SUPPORT_TAGGED_POINTERS Determines whether tagged Pointer is supported. If so, execute LNilOrTagged. LDR p13, [x0] is to give ISA to p13 register, then execute GetClassFromIsa_p16 p13, 1, x0;

GetClassFromIsa_p16

ExtractISA p16, \src, \auth_address:

// SRC = p13=isa. Add class to $0. EndmacroCopy the code

Final result: P16 is class

CacheLookup

Parameter assignment: Mode=NORMAL Function=_objc_msgSend MissLabelDynamic=__objc_msgSend_uncached; Mov x15, x16: assign the value of register X16 to x15, x16 i.e. P16 as class, result: x15=class, then call LLookupStart

LLookupStart

LLookupStart\Function: // p1 = SEL, p16 = isa #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS ldr p10, [x16, #CACHE] // p10 = mask|buckets lsr p11, p10, #48 // p11 = mask and p10, p10, #0xffffffffffff // p10 = buckets and w12, W1, w11 // x12 = _cmd & mask #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 #CACHE] // x16 address translation CACHE=(2 * __SIZEOF_POINTER__) size into p11 = cache_t CACHE #if CONFIG_USE_PREOPT_CACHES // #define CONFIG_USE_PREOPT_CACHES 1 #if __has_feature(ptrauth_calls) // A12, X series, TBNZ p11, #0, LLookupPreopt\Function and p10, p11, # 0x0000ffffFFFFFF // p10 = buckets #else and p10, p11 P10 = bucketsMask TBNZ p11, #0, LLookupPreopt\Function //p11 = cache_t Whether the cache exists, not 0, Function #endif eor p12, p1, p1, LSR #7 // p11=0 LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask = begin #else and p10, p11, #0x0000ffffffffffff // p10 = buckets and p12, p1, p11, LSR #48 // x12 = _cmd & mask #endif // CONFIG_USE_PREOPT_CACHES #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4 ldr  p11, [x16, #CACHE] // p11 = mask|buckets 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 p13, p10, p12, LSL #(1+PTRSHIFT) // PTRSHIFT = 3, p12 << 4, P13 = bucketsMask + ((_cmd & mask) << (1+PTRSHIFT)) = bucketsMask + ((_cmd & mask) << (1+PTRSHIFT)) = bucket // do {1: LDP p17, p9, [x13], # -bucket_size // bucket-- = {imp,sel}, p17=imp, p9=sel CMP p9, p1 = _cmd) {b.ne 3f // scan more //} else {2: CacheHit \Mode // hit: call or return imp CacheHit Mode = normal // CBZ p9, \MissLabelDynamic // p9=sel exists if (sel == 0) goto MissLabelDynamic = __objc_msgSend_uncached cmp p13, p10 // } while (bucket >= buckets) b.hs 1bCopy the code

Get cache_t

ldr	p11, [x16, #CACHE]
Copy the code

Cache_t CACHE is put in p11 by translating x16, the class address translation CACHE, to 16 bytes

Get the bucket

And p10, p11, # 0x0000FFFFFFfffffe // p11=cache_t with the mask and after the operation put p10 = bucketsMaskCopy the code

P11 is cache_t cache, and is computed with mask # 0x0000FFFFFFFFfffe to get bucket P10.

TBNZ p11, #0, LLookupPreopt\Function //p11 = cache_t Specifies whether the cache exists. If yes, LLookupPreopt\Function is executedCopy the code

Check whether the cache_t cache of P11 exists. If yes, run LLookupPreopt. If no, run LLookupPreopt

Eor p12, P1, p1, LSR #7 // p11=0 and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_CMD >> 7)) & mask = beginCopy the code

Find the subscript begin by moving the sum to the right,

LLookupPreopt

LLookupPreopt\Function: P10, p11, #0x007ffffffffffffe // p10 = buckets autdb x10, x16 // auth as early as possible #endif // x12 = (_cmd - first_shared_cache_sel) adrp x9, _MagicSelRef@PAGE ldr p9, [x9, _MagicSelRef@PAGEOFF] sub p12, p1, p9 // w9 = ((_cmd - first_shared_cache_sel) >> hash_shift & hash_mask) #if __has_feature(ptrauth_calls) // bits 63.. 60 of x11 are the number of bits in hash_mask // bits 59.. 55 of x11 is hash_shift lsr x17, x11, #55 // w17 = (hash_shift, ...) lsr w9, w12, w17 // >>= shift lsr x17, x11, #60 // w17 = mask_bits mov x11, #0x7fff lsr x11, x11, x17 // p11 = mask (0x7fff >> mask_bits) and x9, x9, x11 // &= mask #else // bits 63.. 53 of x11 is hash_mask // bits 52.. 48 of x11 is hash_shift lsr x17, x11, #48 // w17 = (hash_shift, hash_mask) lsr w9, w12, w17 // >>= shift and x9, x9, x11, LSR #53 // &= mask #endif ldr x17, [x10, x9, LSL #3] // x17 == sel_offs | (imp_offs << 32) cmp x12, w17, uxtw .if \Mode == GETIMP b.ne \MissLabelConstant // cache miss sub x0, x16, x17, LSR #32 // imp = isa - imp_offs SignAsImp x0 ret .else b.ne 5f // cache miss sub x17, x16, x17, LSR #32 // imp = isa - imp_offs .if \Mode == NORMAL br x17 .elseif \Mode == LOOKUP orr x16, x16, #3 // for instrumentation, note that we hit a constant cache SignAsImp x17 ret .else .abort unhandled mode \Mode .endif 5: ldursw x9, [x10, #-8] // offset -8 is the fallback offset add x16, x16, x9 // compute the fallback isa b LLookupStart\Function // lookup again with a new isa .endif #endif // CONFIG_USE_PREOPT_CACHES .endmacroCopy the code

CacheHit

// CacheHit: x17 = cached IMP, x10 = address of buckets, x1 = SEL, X16 = isa. macro cachehit. if $0 == NORMAL // p17=imp, p10= p10 X1 = sel, x16=isa .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

Call TailCallCachedImp, passing in the arguments x17= IMP,x10=bucket address,x1 =sel,x16= ISA

TailCallCachedImp

P17 =imp, p10=bucket, x1=sel, x16=isa // $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa eor $0, $0, $3 // imp ^ ISA code br $0.endmacroCopy the code

$0=imp,$3=isa; Hash the result to $0, then jump to $0, which is imp’s method

Objc_msgSend (id self, SEL _cmd) is the process of finding imp through SEL

The flow chart