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,runtime
Introduction 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 as
Objective-C
.
1.runtime
What is the
runtime
Is made up ofC
,C++
andassembly
A set of implementationsAPI
.runtime
forObjective-C
The dynamic properties of the language provide support, acting as a way forObjective-C
The operating system of the language that makes the language work properly.runtime
Added object-oriented, runtime functionality to the OC language.- Everyday written
Objective-C
Code, as the program runs, will eventually be converted toruntime
Related code –runtime
isObjective-C
Behind the scenes.
2,runtime
Version 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,
iPhone
Application andOS X v10.5
And later64
Bit program usemodern
Version of theruntime
.- Other procedures (
OS X
On the desktop32
Bitwise program)legacy
Version of theruntime
.
Second,runtime
Message 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.runtime
Brief 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
&Services
Layer 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 API
call
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_msgSend
The function consists of: objc_msgSend(message receiver, message body (sel + argument)).objc_msgSendSuper
functionobjc_super
Structuresuper_class
Specifies the class that the method first looks for.OC
The essence of a method call is message sending.
Three,objc_msgSend
Assembler analysis (arm64
Architecture)
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_msgSend
Compile 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, #0
checkp0
(x0
The address,receiver
, message receiver), if so, then hereobjc_msgSend
Nonsense.- Determine whether
tagged pointer
Type (SUPPORT_TAGGED_POINTERS
), if so, executeLNilOrTagged
, and then execute insideb.eq LReturnZero
, or executeLReturnZero
To end this timeobjc_msgSend
. - if
p0
Exists, willx0
depositp13
The first address of the object or class isisa
, thenp13 = isa
. - Enter macros
GetClassFromIsa_p16
, pass in parameterssrc = p13, needs_auth = 1, auth_address = x0
. LGetIsaDone:
To obtainisa
Done, enter the macroCacheLookup
, pass in parametersPass Mode = NORMAL, Function = _objc_msgSend, MissLabelDynamic = __objc_msgsend_cached
.MissLabelConstant
Is the default parameter.
2,GetClassFromIsa_p16
Compile 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 the
SUPPORT_INDEXED_ISA
(Indexed isa
.32
positionisa
), enter the__LP64__
(Unix and Unix-like systems (Linux, Mac OS X)
) branch,needs_auth == 1
So go into the macroExtractISA
, pass in parametersp16, \src = p13 = isa, \auth_address = isa
.
3,ExtractISA
Compile 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:
ExtractISA
willThe $1
(isa
) andISA_MASK
doBitwise and
Operation (isa & ISA_MASK
) to obtain the class (class
) or metaclasses (metaclass
), and then deposit the results$0
(p16
). return_objc_msgSend
.
4,CacheLookup
Compile 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
(isa
Assigned tox15
.arm64
Architecture to enterCACHE_MASK_STORAGE_HIGH_16
Branch, executeldr p11, [x16, #CACHE]
, i.e.,x16
translationCACHE
Size (2
A pointer,16
Byte, i.e.,#define CACHE (2 * __SIZEOF_POINTER__)
),cache_t
Pointer address,cache_t
The pointer’s first address is the first member variable_bucketsAndMaybeMask
The address of bothmask|buckets
And then I assign it top11
(p11 = mask|buckets
).- Enter the
CONFIG_USE_PREOPT_CACHES
Branches are not analyzed in this article__has_feature(ptrauth_calls)
(pointer authentication) case, so enterelse
Branch, executeand p10, p11, #0x0000fffffffffffe
, i.e.,p11
and0x0000fffffffffffe
(preoptBucketsMask
Mask) do bitwise and operation, getbuckets
Assigned top10
(p10 = buckets
). - perform
tbnz p11, #0
To determinep11
the0
No.bit
Whether a is0
, not for0
Go,LLookupPreopt
for0
The process continues. - perform
eor p12, p1, p1, LSR #7
.p1
isobjc_msgSend
The second parameter ofsel
, i.e.,_cmd
, so this instruction isp12 = _cmd ^ (_cmd >> 7)
. - perform
and p12, p12, p11, LSR #48
.p11
Moves to the right48
Is to obtainmask
, so this instruction isp12 = p12 & (_bucketsAndMaybeMask >> 48)
. Step 12 and step 13 arecache_hash
Algorithm of function (x12 = (_cmd ^ (_cmd >> 7)) & mask
)sel
andmask
Calculate the lookupbucket
For the first timeindex
Assigned top12
. - perform
add p13, p10, p12, LSL #(1+PTRSHIFT)
.arm64
Under the architecture#define PTRSHIFT 3
, so this instruction isp13 = p10 + p12 << (1 + 3)
, abucket_t
Take up16
Bytes,p12
(index
) left4
Who is theindex * 16
Which is essentiallybuckets
Initial address plusindex
abucket_t
Memory size, foundindex
The location of thebucket
Assigned top13
. - perform
1: ldp p17, p9, [x13], #-BUCKET_SIZE
Value,x13
.imp
Assigned top17
.sel
Assigned top9
And thenx13--
To get the last onebucket
(The expression is{imp, sel} = *bucket--
). cmp p9, p1
, compare currentbucket
thesel
andobjc_msgSend
The second parameter of_cmd
.- Equality is executed
2: CacheHit \Mode
, pass in parametersMode = NORMAL
. - If not, execute
3: cbz p9, \MissLabelDynamic
To judge the currentbucket
thesel
Whether this parameter is null. If this parameter is null, it indicates the object to be searched_cmd
No cache, executeMissLabelDynamic
(The passed in is__objc_msgSend_uncached
Function). - If the current
bucket
thesel
If the value is not empty, the command is executedcmp p13, p10
.b.hs 1b
.b.hs
The instruction is to determine whether unsigned less than, that is, to determinep13
Unsigned 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 again
CACHE_MASK_STORAGE_HIGH_16
Branch, executeadd p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
To obtainmask
The location of thebucket
Deposit (last one)p13
. - perform
add p12, p10, p12, LSL #(1+PTRSHIFT)
, to obtain the first locationindex
The location of thebucket
Is saved to p12. - perform
ldp p17, p9, [x13], #-BUCKET_SIZE
, first take outx13
The correspondingbucket
theimp
andsel
Respectively inp17
andp9
And thenx13
One forwardBUCKET_SIZE
Memory size (16
Byte) to get the last onebucket
. - perform
cmp p9, p1
And compare thesel
andimp
, the same means that the cache is found, jump to process 2, cache hit. - perform
cmp p9, #0
,ccmp p13, p12, #0, ne
andb.hi 4b
.b.hi
The result of the comparison is unsigned greater than, execute the method in the address, otherwise do not jump,ccmp
Compare two conditions, ifsel
Is not empty and is currently queriedbucket
Greater 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,CacheHit
Compile 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 incoming
Mode
forNORMAL
, so enter.if $0 == NORMAL
Branch, executeTailCallCachedImp x17, x10, x1, x16
.
6,TailCallCachedImp
Compile 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
- perform
eor $0, $0, $3
.cached imp ^= isa
.imp
Decoding. - perform
br $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.