preface

You learned about method caching from the cache_T analysis of the structure of the iOS class. This article will explore the process of reading and storing method caches.

To explore the cache read and write process, start with the cache_t:: INSERT function we explored earlier. A global search for cache_t:: INSERT revealed the cache read and write process annotated by Apple in the objc-cache.mm source code.

 * Cache readers (PC-checked by collecting_in_critical())
 * objc_msgSend*
 * cache_getImp
 *
 * Cache readers/writers (hold cacheUpdateLock during access; not PC-checked)
 * cache_t::copyCacheNolock    (caller must hold the lock)
 * cache_t::eraseNolock        (caller must hold the lock)
 * cache_t::collectNolock      (caller must hold the lock)
 * cache_t::insert             (acquires lock)
 * cache_t::destroy            (acquires lock)
Copy the code

Illustration:

From here we can see that cache reads start with objc_msgSend, and we’ll start with objc_msgSend.

A,runtimeIntroduction to the

Before exploring objc_msgSend, let’s talk a little about Runtime.

Objective-c is a dynamic language. It defer some decisions from the compile and link phases to the run-time phase, based on the Runtime (also known as the runtime).

  • Static languages: The data types of all variables are determined at compile time, along with the functions to be called and the implementation of those functions. Common static languages, such as:C/C++, Java and C#And so on.
  • Dynamic languages: Programs can change their structure at run time. This means that the variable data type is checked at run time, and the specific function to call is looked up at run time based on the function name, such asObjective-C.

1.runtimeWhat is the

  • runtimeIs made up ofC,C++andassemblyA set of implementationsAPI.
  • runtimeforObjective-CThe dynamic properties of the language provide support, acting as a way forObjective-CThe operating system of the language that makes the language work properly.
  • runtimeAdded object-oriented, runtime functionality to the OC language.
  • Everyday writtenObjective-CCode, as the program runs, will eventually be converted toruntimeRelated code –runtimeisObjective-CBehind the scenes.

2,runtimeVersion and platform

Runtime Versions and Platforms

There are different versions of Objective-C Runtime on different platforms. Runtime Open source

Version 2.1,

There are two versions of Objective-C Runtime: Modern and Legacy. The modern version was introduced in Objective-C 2.0 and includes many new features. The programming interface for the older runtime is described in the Objective-C 1.0 Runtime Reference; The Modern version of the Runtime’s programming interface is described in the Objective-C Runtime Reference.

The most notable new feature is that instance variables in modern runtimes are non-fragile:

  • In older runtimes, if you changed the layout of instance variables in a class, you had to recompile the inherited class.
  • In modern runtimes, if you change the layout of instance variables in a class, you don’t have to recompile the inherited class.

In addition, modern runtimes support instance variable synthesis of Declared Properties (see The Objective-C Programming Language and Declared Properties).

2.2, platform,

  • iPhoneApplication andOS X v10.5And later64Bit program usemodernVersion of theruntime.
  • Other procedures (OS XOn the desktop32Bitwise program)legacyVersion of theruntime.

Second,runtimeMessage mechanism based on

Method calls in OC are implemented through Runtime. The most important underlying nature of method calls in Runtime is the messaging mechanism. Let’s explore runtime’s messaging mechanism.

The objc_msgSend function defines:

OBJC_EXPORT void
objc_msgSend(void /* id self, SEL op, ... * / )
    OBJC_AVAILABLE(10.0.2.0.9.0.1.0.2.0);
Copy the code

1.runtimeBrief introduction to Architecture

We write Code in the Objective-C Code layer, which contains many of the Framework, Services and apis, Runtime System Library is the underlying System Library for Runtime, and we convert it in the middle layer with complier. Support is provided for the upper Objective-C Code layer.

2. Three ways to send messages

2.1 method invocation at the OC level

XJPerson *p1 = [XJPerson alloc];
[p1 thatGirlSayToMe:@"I love you"]; * * * * * * * * * * * * * * * * * * * * * * * * the underlying implementation * * * * * * * * * * * * * * * * * * * * * * * *// The clang directive is compiled into a.cpp file
XJPerson *p1 = ((XJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("XJPerson"), sel_registerName("alloc"));

((void(*) (id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)p1, sel_registerName("thatGirlSayToMe:"), (NSString *)&__NSConstantStringImpl__var_folders_14_tbs866611ll6tx7n__d__41m0000gn_T_main_267efe_mi_0);
Copy the code

Illustration:

2.2,Framework&ServicesLayer method call

XJPerson *p = [XJPerson performSelector:@selector(alloc)];

[p performSelector:@selector(thatGirlSayToMe:) withObject:@"I love you"]; * * * * * * * * * * * * * * * * * * * * * * * * the underlying implementation * * * * * * * * * * * * * * * * * * * * * * * * + (id)performSelector:(SEL)sel {
    if(! sel) [self doesNotRecognizeSelector:sel];
    return ((id(*) (id, SEL))objc_msgSend)((id)self, sel);
}

- (id)performSelector:(SEL)sel withObject:(id)obj {
    if(! sel) [self doesNotRecognizeSelector:sel];
    return ((id(*) (id, SEL, id))objc_msgSend)(self, sel, obj);
}
Copy the code

Illustration:

2.3,runtime APIcall

XJPerson *p2 = ((XJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("XJPerson"), sel_registerName("alloc"));

((void(*) (id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)p2, sel_registerName("thatGirlSayToMe:"), @"I love you");
Copy the code

Remark:

OC layer using objc_msgSend preparation:

1. Import the relevant API header in the file, i.e. #import

.

2. Xcode Settings: Target ->Build Settings->Apple Clang Preprocessing->Enable Strict Checking of objc_msgSend Calls Close the Clang compiler’s check on objc_msgSend.

3. If step 2 is invalid after Xcode 12, you need to force objc_msgSend and use it. Ex. : ((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)p2, sel_registerName(“thatGirlSayToMe:”), @”I love you”);

2.4 code examples

//OC layer method invocation
void methodCall(void) {
    // Allocate memory to XJPerson
    XJPerson *person = [XJPerson alloc];
    // Call method: OC
    [person thatGirlSayToMe:@"I love you"];
    // Call method: Framework
    [person performSelector:@selector(thatGirlSayToMe:) withObject:@"I love you"];
}

/ / objc_msgSend calls
void objc_msgSendCall(void) {
    // Allocate memory to XJPerson
    XJPerson *person = ((XJPerson *(*)(id, SEL))(void *)objc_msgSend)(objc_getClass("XJPerson"), sel_registerName("alloc"));
    // Call the method
    ((void(*) (id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)person, sel_registerName("thatGirlSayToMe:"), @"I love you");
}

/ / objc_msgSendSuper calls
void objc_msgSendSuperCall(void) {
    // Allocate memory to XJPerson
    XJBoy *boy = ((XJBoy *(*)(id, SEL))(void *)objc_msgSend)(objc_getClass("XJBoy"), sel_registerName("alloc"));
    // Create an objc_super object
    struct objc_super boySuper;
    boySuper.receiver = boy;
    
    // * super_class is the first class to search */
    // The super_class can be the current class XJBoys or XJPerson. The super_class is only the first class to look for the method. If the current class is not found, the super_class will be looked for in the superclass
// boysSuper.super_class = objc_getClass("XJBoys");
    boySuper.super_class = objc_getClass("XJPerson");
    // Call the super method
    ((void(*) (id, SEL, NSString * _Nonnull))(void *)objc_msgSendSuper)((id)&boySuper, sel_registerName("thatGirlSayToMe:"), @"I love you");
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        methodCall();
        
        objc_msgSendCall();
        
        objc_msgSendSuperCall();
        
    }
    return 0; } * * * * * * * * * * * * * * * * * * * * * * * * printing * * * * * * * * * * * * * * * * * * * * * * * *202107 -- 01 17:52:26.163465+0800 KCObjcBuild[2748:71486] -[XJPerson thatGirlSayToMe:] I love you
202107 -- 01 17:52:26.163880+0800 KCObjcBuild[2748:71486] -[XJPerson thatGirlSayToMe:] I love you
202107 -- 01 17:52:26.163944+0800 KCObjcBuild[2748:71486] -[XJPerson thatGirlSayToMe:] I love you
202107 -- 01 17:52:26.163980+0800 KCObjcBuild[2748:71486] -[XJPerson thatGirlSayToMe:] I love you
Program ended with exit code: 0

Copy the code

Conclusion:

  • objc_msgSendThe function consists of: objc_msgSend(message receiver, message body (sel + argument)).
  • objc_msgSendSuperfunctionobjc_superStructuresuper_classSpecifies the class that the method first looks for.
  • OCThe essence of a method call is message sending.

Three,objc_msgSendAssembler analysis (arm64Architecture)

Search for objc_msgSend in objC4-818.2 source code, then hold command, click on the small arrow next to the file name, fold up all files, select objc-msg-arm64.s file and open it. Select ENTRY _objc_msgSend to start looking at the objc_msgSend assembly source code.

Note: Support for __has_feature(ptrauth_calls) pointer authentication (A12 and later bionic chips) is not analyzed in this article.

Illustration:

1._objc_msgSendCompile the source code

	ENTRY _objc_msgSend
	UNWIND _objc_msgSend, NoFrame
        // p0, register first value x0 (id receiver) address
        // Check if p0 (id receiver) is 0 (nil),
        // If there is no recipient, then objc_msgSend is meaningless
	cmp	p0, #0			// nil check and tagged pointer check
        // If tagged pointer is supported
#if SUPPORT_TAGGED_POINTERS
        // Receiver is set to 0 and is tagged pointer.
        // Run the b.llnilortagged command
	b.le	LNilOrTagged		// (MSB tagged pointer looks negative)
#else
        // Receiver is set to 0 and is not tagged pointer
        // Run the b.turn LReturnZero command
	b.eq	LReturnZero
#endif
        // Receiver is not empty, p13 = x0 stack memory value,
        // The first address of the object and class is isa, which is equivalent to giving ISA to P13
	ldr	p13, [x0]		// p13 = isa
        // Get the class or metaclass through ISA, and store it in P16 (see later analysis)
	GetClassFromIsa_p16 p13, 1, x0	// p16 = class
// Obtain isa from receiver.
// The reason for this is that the message is sent first to see if there is a cache.
// Cache is stored in a class or metaclass
LGetIsaDone:
	// calls imp or objc_msgSend_uncached
        // Call CacheLookup
	CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached

#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
	b.eq	LReturnZero		// nil check
	GetTaggedClass
	b	LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif

LReturnZero:
	// x0 is already zero
	mov	x1, #0
	movi	d0, #0
	movi	d1, #0
	movi	d2, #0
	movi	d3, #0
	ret

	END_ENTRY _objc_msgSend
Copy the code

Resolution:

  • cmp p0, #0checkp0(x0The address,receiver, message receiver), if so, then hereobjc_msgSendNonsense.
  • Determine whethertagged pointerType (SUPPORT_TAGGED_POINTERS), if so, executeLNilOrTagged, and then execute insideb.eq LReturnZero, or executeLReturnZeroTo end this timeobjc_msgSend.
  • ifp0Exists, willx0depositp13The first address of the object or class isisa, thenp13 = isa.
  • Enter macrosGetClassFromIsa_p16, pass in parameterssrc = p13, needs_auth = 1, auth_address = x0.
  • LGetIsaDone:To obtainisaDone, enter the macroCacheLookup, pass in parametersPass Mode = NORMAL, Function = _objc_msgSend, MissLabelDynamic = __objc_msgsend_cached.MissLabelConstantIs the default parameter.

2,GetClassFromIsa_p16Compile the source code

// Pass p13 for isa(SRC), 1(needs_auth), x0 for isa(auth_address)
//.macro represents the method macro definition
.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if ! needs_auth */

// #if __ARM_ARCH_7K__ >= 2 || (__arm64__ && ! __LP64__)
// # define SUPPORT_INDEXED_ISA 1
// #else
// # define SUPPORT_INDEXED_ISA 0
// #endif
// Arm64 is not Indexed Isa
#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__  // For 64-bit UNIx-like systems, arm64 goes here
Needs_auth = 1; needs_auth = 1
.if \needs_auth == 0 // _cache_getImp takes an authed class already
	mov	p16, \src
.else Needs_auth == 1, go here
	// 64-bit packed isa
        // Get the class or metaclass through ISA, and store it in P16 (see later analysis)
	ExtractISA p16, \src, \auth_address
.endif
#else  // 32-bit, not here
	// 32-bit raw isa
	mov	p16, \src

#endif

.endmacro
Copy the code

Resolution:

  • Does not meet theSUPPORT_INDEXED_ISA(Indexed isa.32positionisa), enter the__LP64__(Unix and Unix-like systems (Linux, Mac OS X)) branch,needs_auth == 1So go into the macroExtractISA, pass in parametersp16, \src = p13 = isa, \auth_address = isa.

3,ExtractISACompile the source code

#if __has_feature(ptrauth_calls) // Bionic processors A12 or later support pointer authentication
// JOP

// Pass p16($0), SRC ($1), auth_address ($2)
.macro ExtractISA
        // Remove class(or metaclass) and deposit it in P16 ($0)
        // p16 = isa & ISA_MASK,
	and	$0, $1, #ISA_MASK
        // Isa pointer authentication related operations
#if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_STRIP
	xpacd	$0
#elif ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
	mov	x10, $2
	movk	x10, #ISA_SIGNING_DISCRIMINATOR, LSL #48
	autda	$0, x10
#endif
.endmacro

// JOP
#else
// not JOP

// Pass p16($0), SRC ($1), auth_address ($2)
.macro ExtractISA
        // Remove class(or metaclass) and deposit it in P16 ($0)
        // p 16 = isa & ISA_MASK,
	and    $0, $1, #ISA_MASK
.endmacro

// not JOP
#endif
Copy the code

Resolution:

  • ExtractISAwillThe $1(isa) andISA_MASKdoBitwise andOperation (isa & ISA_MASK) to obtain the class (class) or metaclasses (metaclass), and then deposit the results$0(p16). return_objc_msgSend.

4,CacheLookupCompile the source code

#define NORMAL 0
#define GETIMP 1
#define LOOKUP 2

// Pass parameters NORMAL, _objc_msgSend, __objc_msgSend_uncached, and __objc_msgsend_cached.
// MissLabelConstant is not passed and is the default parameter
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
	//
	// Restart protocol:
	//
	// As soon as we're past the LLookupStart\Function 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\Function,
	// then our PC will be reset to LLookupRecover\Function 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
	//
        
        // Assign x16 to x15
	mov	x15, x16			// stash the original isa
LLookupStart\Function:
	// p1 = SEL, p16 = isa
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS // M1 iMAC
	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 / / iPhone, arm64
        // #define CACHE (2 * __SIZEOF_POINTER__)
        // Shift the CACHE size of x16 and assign it to p11
        // Cache_t = cache_t;
        // Cache_t starts with _bucketsAndMaybeMask
	ldr	p11, [x16, #CACHE]		// p11 = mask|buckets
#if CONFIG_USE_PREOPT_CACHES // If iPhone is 1, go here
#if __has_feature(ptrauth_calls) // A12 and then go here
        // Check whether bit 0 of P11 is 0. If it is not 0->LLookupPreopt, set it to 0 and proceed with the following process
	tbnz	p11, #0, LLookupPreopt\Function
        // p10 = p11 & 0x0000ffffffffffff = buckets
	and	p10, p11, #0x0000ffffffffffff	// p10 = buckets
#else
        // p10 = p11 & 0x0000fffffffffffe = buckets
	and	p10, p11, #0x0000fffffffffffe	// p10 = buckets
        // Check whether bit 0 of p11 is 0. If it is not 0->LLookupPreopt, set it to 0 and proceed with the following process
	tbnz	p11, #0, LLookupPreopt\Function
#endif
        // The cache_hash algorithm calculates the index of the bucket
        // p1 就是 _cmd
        // p12 = _cmd ^ (_cmd >> 7)
	eor	p12, p1, p1, LSR #7
        // p11 >> 48
        // p12 = (_cmd ^ (_cmd >> 7)) & mask to get hash index
	and	p12, p12, p11, LSR #48		// x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
	and	p10, p11, #0x0000ffffffffffff	// p10 = buckets
        // The cache_hash algorithm calculates the index of the bucket
	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
        
        P10 = buckets, p12 = index, PTRSHIFT = 3
        // #define PTRSHIFT 3
        // A bucket_t takes 16 bytes, that is, << 4
        // P12 (index) If the p12 (index) moves four digits to the left, the index * 16 is displayed
        // This is the address of buckets plus the index of bucket_t memory.
        // Find the bucket at index and assign it to p13
        P13 = p10 + (p12 << 4) = buckets + index * 16
        // p13 = bucket at index position
	add	p13, p10, p12, LSL #(1+PTRSHIFT)
						// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

						// do {
        // #define BUCKET_SIZE (2 * __SIZEOF_POINTER__)
        // set x13, imp to P17,sel to P9,
        // Then subtract the memory size of BUCKET_SIZE to get the last bucket
1:	ldp	p17, p9, [x13], #-BUCKET_SIZE	// {imp, sel} = *bucket--
        P9 = p1; p9 = p1; p9 = p1;
	cmp	p9, p1				// if (sel ! = _cmd) {
	b.ne	3f				// scan more
						// } else {
2:	CacheHit \Mode				// hit: call or return imp
						/ /}
        // Check whether sel is empty. If sel is empty, __objc_msgSend_uncached
3:	cbz	p9, \MissLabelDynamic		// if (sel == 0) goto Miss;
        // the b.hs instruction is to determine whether the unsigned is less than p13, and to determine whether the unsigned is less than P10, and to go through process 1
        // If not, go on down
	cmp	p13, p10			// } while (bucket >= buckets)
	b.hs	1b
        
        // If no sel is found up to the first bucket, then it will continue down
        // The first time the index is located, the bucket may not be queried after the index
        
	// wrap-around:
	// p10 = first bucket
	// p11 = mask (and maybe other bits on LP64)
	// p12 = _cmd & mask
	//
	// A full cache can happen with CACHE_ALLOW_FULL_UTILIZATION.
	// So stop when we circle back to the first probed bucket
	// rather than when hitting the first bucket again.
	//
	// Note that we might probe the initial bucket twice
	// when the first probed slot is the last entry.


#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS / / M1 version of iMac
	add	p13, p10, w11, UXTW #(1+PTRSHIFT)
						// p13 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 / / iPhone, arm64
        // p11 = _bucketsAndMaybeMask, p10 = buckets, PTRSHIFT = 3
        // p11 >> 48 -> mask
        P13 = buckets + (mask << 4) = buckets + mask * 16,
        // Get the bucket with the mask position (last)
	add	p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
						P13 = buckets + (mask << 1+PTRSHIFT
						// see comment about maskZeroBits
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
	add	p13, p10, p11, LSL #(1+PTRSHIFT)
						// p13 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
        // p12 = (_cmd ^ (_cmd >> 7)) &mask = index Specifies the first subscript
        // p10 = buckets, PTRSHIFT = 3
        // p12 = p10 + p12 << 4 = buckets + index * 16
	add	p12, p10, p12, LSL #(1+PTRSHIFT)
						// p12 = first probed bucket

						// do {
        // BUCKET_SIZE = 16 bytes
        // Select x13, imp to P17,sel to P9, and then subtract a BUCKET_SIZE from the memory.
        // Get the last bucket
4:	ldp	p17, p9, [x13], #-BUCKET_SIZE	// {imp, sel} = *bucket--
        // Compare sel and IMP
	cmp	p9, p1				// if (sel == _cmd)
        // Equal means the cache is found, jump to process 2, cache hit
	b.eq	2b				// goto hit
        // if the result of the comparison is unsigned greater than, execute the method in the address, otherwise do not jump,
        // CCMP, which compares two conditions,
        // sel ! = 0 && bucket > first_probed,
        // If you are satisfied, proceed to 4. If you are not satisfied, proceed to the next step
	cmp	p9, #0				// } while (sel ! = 0 &&
	ccmp	p13, p12, #0, ne		// bucket > first_probed)
	b.hi	4b

LLookupEnd\Function: // The query is complete
LLookupRecover\Function:
	b	\MissLabelDynamic // Cache not found, run __objc_msgSend_uncached

#if CONFIG_USE_PREOPT_CACHES / / the iPhone to 1
#ifCACHE_MASK_STORAGE ! = CACHE_MASK_STORAGE_HIGH_16
#error config unsupported
#endif
LLookupPreopt\Function:
#if __has_feature(ptrauth_calls) // A12 and then go here
        // p10 = p11 & 0x007ffffffffffffe
	and	p10, p11, #0x007ffffffffffffe	// p10 = buckets
	autdb	x10, x16			// auth as early as possible
#endif
        // Other operations to share the cache
	// 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

.endmacro
Copy the code

Resolution:

  • mov x15, x16, hide initialisa.x16(isaAssigned tox15.
  • arm64Architecture to enterCACHE_MASK_STORAGE_HIGH_16Branch, executeldr p11, [x16, #CACHE], i.e.,x16translationCACHESize (2A pointer,16Byte, i.e.,#define CACHE (2 * __SIZEOF_POINTER__)),cache_tPointer address,cache_tThe pointer’s first address is the first member variable_bucketsAndMaybeMaskThe address of bothmask|bucketsAnd then I assign it top11(p11 = mask|buckets).
  • Enter theCONFIG_USE_PREOPT_CACHESBranches are not analyzed in this article__has_feature(ptrauth_calls)(pointer authentication) case, so enterelseBranch, executeand p10, p11, #0x0000fffffffffffe, i.e.,p11and0x0000fffffffffffe(preoptBucketsMaskMask) do bitwise and operation, getbucketsAssigned top10(p10 = buckets).
  • performtbnz p11, #0To determinep11the0No.bitWhether a is0, not for0Go,LLookupPreoptfor0The process continues.
  • performeor p12, p1, p1, LSR #7.p1isobjc_msgSendThe second parameter ofsel, i.e.,_cmd, so this instruction isp12 = _cmd ^ (_cmd >> 7).
  • performand p12, p12, p11, LSR #48.p11Moves to the right48Is to obtainmask, so this instruction isp12 = p12 & (_bucketsAndMaybeMask >> 48). Step 12 and step 13 arecache_hashAlgorithm of function (x12 = (_cmd ^ (_cmd >> 7)) & mask)selandmaskCalculate the lookupbucketFor the first timeindexAssigned top12.
  • performadd p13, p10, p12, LSL #(1+PTRSHIFT).arm64Under the architecture#define PTRSHIFT 3, so this instruction isp13 = p10 + p12 << (1 + 3), abucket_tTake up16Bytes,p12(index) left4Who is theindex * 16Which is essentiallybucketsInitial address plusindexabucket_tMemory size, foundindexThe location of thebucketAssigned top13.
  • perform1: ldp p17, p9, [x13], #-BUCKET_SIZEValue,x13.impAssigned top17.selAssigned top9And thenx13--To get the last onebucket(The expression is{imp, sel} = *bucket--).
  • cmp p9, p1, compare currentbuckettheselandobjc_msgSendThe second parameter of_cmd.
  • Equality is executed2: CacheHit \Mode, pass in parametersMode = NORMAL.
  • If not, execute3: cbz p9, \MissLabelDynamicTo judge the currentbuckettheselWhether this parameter is null. If this parameter is null, it indicates the object to be searched_cmdNo cache, executeMissLabelDynamic(The passed in is__objc_msgSend_uncachedFunction).
  • If the currentbuckettheselIf the value is not empty, the command is executedcmp p13, p10.b.hs 1b.b.hsThe instruction is to determine whether unsigned less than, that is, to determinep13Unsigned less thanp10, is to go through process 1 and execute step 15 again (the expression iswhile (bucket >= buckets)).
  • If no sel match is found up to the first bucket, then it will continue to go down, because the index of the first location after the bucket may not be queried, enter againCACHE_MASK_STORAGE_HIGH_16Branch, executeadd p13, p10, p11, LSR #(48 - (1+PTRSHIFT))To obtainmaskThe location of thebucketDeposit (last one)p13.
  • performadd p12, p10, p12, LSL #(1+PTRSHIFT), to obtain the first locationindexThe location of thebucketIs saved to p12.
  • performldp p17, p9, [x13], #-BUCKET_SIZE, first take outx13The correspondingbuckettheimpandselRespectively inp17andp9And thenx13One forwardBUCKET_SIZEMemory size (16Byte) to get the last onebucket.
  • performcmp p9, p1And compare theselandimp, the same means that the cache is found, jump to process 2, cache hit.
  • performcmp p9, #0,ccmp p13, p12, #0, neandb.hi 4b.b.hiThe result of the comparison is unsigned greater than, execute the method in the address, otherwise do not jump,ccmpCompare two conditions, ifselIs not empty and is currently queriedbucketGreater than the first locationbucket, jump to Process 4 and continue the search.
  • If the condition is not met, go to the following, the query ends, and execute__objc_msgSend_uncached.

5,CacheHitCompile the source code

// Pass the parameter NORMAL
// 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
.endmacro
Copy the code
  • The incomingModeforNORMAL, so enter.if $0 == NORMALBranch, executeTailCallCachedImp x17, x10, x1, x16.

6,TailCallCachedImpCompile the source code

#if __has_feature(ptrauth_calls)
// JOP
.macro TailCallCachedImp
	// $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
	eor	$1, $1, $2  // mix SEL into ptrauth modifier
	eor	$1, $1, $3  // mix isa into ptrauth modifier
	brab	$0, $1
.endmacro

// JOP
#else
// not JOP

.macro TailCallCachedImp
	// $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
        // cached IMP ^= ISA, imp decoded
	eor $0, $0, $3
        // Jump to the decoded IMP, i.e. call IMP
	br $0
.endmacro
// not JOP
#endif
Copy the code
  • performeor $0, $0, $3.cached imp ^= isa.impDecoding.
  • performbr $0, after the jump decodingimp, i.e.,call imp.

7. Cache lookup (quick lookup) process

8, summary

Objc_msgSend looks for the IMP through sel, first of all it looks for the method cache, which is the fast method lookup process.