This is the seventh day of my participation in the August More text Challenge. For details, see: August More Text Challenge
Add: why is the method called in LLDB with mask 7
We’ve seen methods called in code, but what about methods called in LLDB? Try it on:
The mask here becomes 7. Why is that? If mask becomes 7, it means that cache must have undergone a similar expansion, so it must go into insert, so let’s go into insert implementation.
We have shown that when the cache method is 3/4 of the way up, it will be expanded, so if we call the method in LLDB, we will call the other method at the same time, let’s see, print sel, IMP and Receiver in insert, so it will print all methods. Let’s run it.
Then call the object method in LLDB and see the output.
The case we want to find is the case where receiver is P, so we print the address of P.
Then look in the printed results above and find that the receiver of these three methods is P.
So what we find is that before we call the saySomething method, we’re calling respondsToSelector and the class method, so when we insert saySomething, we’re expanding, so the mask becomes 7.
Job: does the code run method insert a closing symbol
In this case, call twice in insert to print out the sel, IMP, and address of the bucket.
So the reason why we called setName before saySomething is because we put the print method up there before we open up the space, and if we didn’t call the space first we wouldn’t print anything because oldCapacity would be 0, so let’s run that.
And as you can see when you print it out, it still has an end sign.
Here 0x7fff7c838d69 is the setName method because saySomething is inserted below.
Now let’s look at insert inside insert.
So there’s a do while loop, and I said this last time, when I insert if I find a value and I find someone in it, I do a cache_next. Take a look at what cache_next looks like inside.
So if you find something in there, if I is not equal to zero, you move it forward one bit and reinsert it, otherwise you’ll start reinserting it at mask, so what is mask. M = m = m = m = m
General flow chart:
So will there be a situation, is always unable to find the vacancy? If you can’t find it, the loop exits and bad_cache is called.
bad_cache(receiver, (SEL)sel);
Copy the code
Look again at the set method.
Imp is stored before SEL is stored.
Objc_msgSend process
The objc_msgSend assembler source code is explained above, resulting in the following process:
- Check whether the receiver exists
- The ISA is obtained through receiver, and hence the class
Now that you have the class, what do you do with it? We can see that after we get the class, we call CacheLookup NORMAL, _objc_msgSend, __objc_msgsend_cached
Next, let’s search for the implementation of CacheLookup. Here we copy the previous function, and we find that one parameter is missing, so the last parameter is the default parameter.
Look at the code below:
// Insert class into x15; // Insert class into x15
mov x15, x16
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
// CACHE_MASK_STORAGE_HIGH_16 because it's real, arm64, see explanation 1.
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
// Shift x16 #cache size = 16 bytes, see explanation 2. So you get cacht_T.
// Then put the result of p11, so p11 = cache
ldr p11, [x16, #CACHE] // p11 = mask|buckets
#if CONFIG_USE_PREOPT_CACHES
#if __has_feature(ptrauth_calls)
tbnz p11, #0, LLookupPreopt\Function
and p10, p11, #0x0000ffffffffffff // p10 = buckets
// Let's go to explanation 3
#else
// p11 and # 0x0000ffFFFFfffe get the bucket and put it in p10.
// See Explanation 4 for the reason
and p10, p11, #0x0000fffffffffffe // p10 = buckets
If p11 and 0 are not 0, go to LLookupPreopt, otherwise go down
tbnz p11, #0, LLookupPreopt\Function
#endif
// P11 is not zero and you go here
// p12 is null, p1 is sel, here is the operation (_cmd ^ (_cmd >> 7))
eor p12, p1, p1, LSR #7
and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
// p11 is cache, with 0x0000FFFFFFFFFFFF to get bucket
and p10, p11, #0x0000ffffffffffff // p10 = buckets
//p11, LSR #48 ->mask
// p1:sel(_cmd) &mask See explanation 5 for the reason
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 is the bucket, p12 is the index, PTRSHIFT is 3, see 6, so index is shifted 4 bits to the left. Since bucket + memory shift can only shift 1,2,3,4 units, not 0x10XXXXX address, so (_cmd & mask) shift 4 bits to the left is equivalent to index * 16 (the size of a unit of bucket), which will give you the corresponding address in the bucket. Therefore, p13 is the bucket you are currently looking for
add p13, p10, p12, LSL #(1+PTRSHIFT)
// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
// do {
// The second bucket element is obtained from x13 buckets (p13 address-bucket_size), and the IMP -SEL is stored in p17 and P9 respectively
1: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
// Compare sel P9 with P1
// Just go to CacheHit
// If not, go inside 3
cmp p9, p1 // if (sel ! = _cmd) {
b.ne 3f // scan more
// } else {
2: CacheHit \Mode // hit: call or return imp
/ /}
// Check whether p9 is null.
// If no, go to MissLabelDynamic
3: cbz p9, \MissLabelDynamic // if (sel == 0) goto Miss;
// Compare the size of p10 and p13. If p13 is larger than p10, shift the address, that is, hash -1 again, and then loop from 1:. If p13 is not larger than p10, continue to loop from 1:
cmp p13, p10 // } while (bucket >= buckets)
b.hs 1b
// 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
add p13, p10, w11, UXTW #(1+PTRSHIFT)
// p13 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
// If it is not found above, it will shift the number of digits of the result mask<<4, and then put the bucket in p13, which corresponds to the process of hashing see explanation 7
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
Copy the code
CacheHit
.macro CacheHit
// Set LLookupStart to normal
.if $0 == NORMAL
// x17 is cached IMP, x10 is the address of the bucket, x1 is sel to look for, x16 is ISA
// Go to TailCallCachedImp, do X17 xOR X16, see explain 8 and then execute IMP
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
MissLabelDynamic
If not, it will go to MissLabelDynamic, which is the third parameter __objc_msgSend_uncached
__objc_msgSend_uncached: process
STATIC_ENTRY __objc_msgSend_uncached
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band r9 is the class to search
MethodTableLookup NORMAL // returns IMP in r12
bx r12
END_ENTRY __objc_msgSend_uncached
Copy the code
Explanation 1
As you can see here, when __arm64__(64-bit support) and __LP64__ (Unix or Unix like systems – Linux, Mac OS X), If it is # if TARGET_OS_OSX | | TARGET_OS_SIMULATOR) (macOS or macOS simulator) is CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS, Otherwise it’s CACHE_MASK_STORAGE_HIGH_16, so here’s CACHE_MASK_STORAGE_HIGH_16.
Explanation # 2
If I look for cache in the file, it’s 2 * SIZEOF_POINTER which is 16.
Explain the three
Look for CONFIG_USE_PREOPT_CACHES in the source code and find its definition
Because this is 64-bit and it’s an OS system, not an emulator or MACCATALYST, CONFIG_USE_PREOPT_CACHES is 1, in loop. Has_feature (ptrauth_calls): is to determine whether the compiler supports pointer authentication for chips above a12Z. So this is false. So let’s go down here else.
Explanation # 4
Masks and buckets together take up 8 bytes, 64 bits. 0 x0000fffffffffffe into 2 base for 111111111111111111111111111111111111111111111110, 48. Looking at the cache_t structure, maskShift is also 48, bits 1 through 48 hold maskShift, while bucketsMask has the high-end 16 bits. Here p11 cache & 0x0000FFFFFFFFFE nullifies the high 16-bit mask to get the bucket.
Explain the 5
We can see how insert takes the first index. In the source code, insert is the index obtained by sel&mask.
6 explain
Explain the 7
And you see here that if I is equal to 0, then it’s going to take the value of mask.
Explain the eight
The process for entering TailCallCachedImp is as follows
.macro TailCallCachedImp
// $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
// x17 xOR ISA (class)
// call imp
eor $0, $0, $3
br $0
.endmacro
Copy the code
The process to summarize
- Check whether the receiver exists
- The ISA is obtained through receiver, and hence the class
- Enter the cache and obtain the cache through the class memory translation
- The bucket is obtained by the bucket mask
- The mask is obtained by the mask
- Get the index of the first lookup by using the insert hash function (mask_t) (value&mask)
- Bucket + index Finds the right bucket
- If it is equal, we will cacheHit, imp^ ISA, and then we will call IMP. Otherwise, we will shift again. If we find sel, we will cacheHit, and if we cannot find SEL, we will continue the loop. If you don’t find it, it goes to __objc_msgSend_uncached.