In the last article, we combined objC source code analysis of objc_msgSend process, today we use a real machine, through the assembly process analysis of objc_msgSend
objc_msgSend
Real machine assembly process
Cache miss
Create a new project and create a new class Person in the project:
@interface Person : NSObject
- (void)func1;
- (void)func2;
- (void)func3;
- (void)func4;
@end
@implementation Person
- (void)func1 {
NSLog(@"%s", __func__);
}
- (void)func2 {
NSLog(@"%s", __func__);
}
- (void)func3 {
NSLog(@"%s", __func__);
}
- (void)func4 {
NSLog(@"%s", __func__);
}
@end
Copy the code
In main, the breakpoint is as follows:
Enter the assembly window:
Debug
->Debug Workflow
->Always Show Disassembly
Enter the objc_msgSend:
To continue:
By LLDB print, verify the objc_msgSend process for object method func3, currently of the Person class:
x16
=isa
x11
=cache
To determine whether position 0 of w11 is not 0 is to determine whether preopt_cache_t is present in cache_t
x10
=buckets
X12 = the first location retrieved
x13
=bucket_t
x17
=imp
x9
=sel
X9 and X1 are not equal, jump to instruction 0x1B01E3210
Jumps to _objc_msgSend_uncached, and checks whether X9 is 0. If yes, continue to _objc_msgSend_uncached
Cached not hit, skip _objc_msgSend_uncached
A cache hit
The main method code is modified as follows:
Breakpoint execution, enter the assembly window, final result:
At this point, the x10 = buckets, x16 = isa
buckets
=buckets
^_cmd
,bucket
=bucket
^isa
The assembly cache
Cached cached = __objc_msgSend_uncached; cached cached = __objc_msgSend_uncached.
__objc_msgSend_uncached
Objc – msG-arm64.s objc-msg-arm64.s
It mainly executes two methods: MethodTableLookup and TailCallFunctionPointer
TailCallFunctionPointer
A direct jump to $0, x17, indicates that the important operation is on the MehtodTableLookup
MehtodTableLookup
After _lookUpImpOrForward is jumped, x0, or IMP, is assigned to X17. X0 is the first register that is often used to receive the return value, so IMP is assigned to x0 register in _lookUpImpOrForward
lookUpImpOrForward
It turns out that we have jumped to C++ lookUpImpOrForward:
So why was the assembly lookup cache used before? That’s because assembly language is closer to machine language, very efficient and relatively safe; Assembly is a quick lookup process, while lookUpImpOrForward is a slow lookup process that iterates through methodList.
Slow search process
lookUpImpOrForward
Since lookUpImpOrForward ultimately needs to return an IMP, a look at the method implementation captures its core:
In addition, we need to pay attention to two methods checkIsKnownClass and realizeAndInitializeIfNeeded_locked
checkIsKnownClass
This method determines whether the current class is already registered with the current cache table allocatedClasses.
realizeAndInitializeIfNeeded_locked
Finally, the parent and metaclass are initialized again in the method, and the data is manipulated against RW and RO, because there are methodLists in ro and RW;
Binary search process
Next, we focus on how to find a method in lookUpImpOrForward. We find that an IMP lookup loop has no end condition, so we find imp because there must be a condition inside the for loop to break out of the FOR loop. So this is the heart of what needs to be studied and analyzed; Before starting our analysis, let’s briefly outline the search process:
- 1. Look up yours first
methodList
Through thesel
findimp
- 2, if their own
methodList
If you can’t find it, you need to look it up based on past experienceThe parent class
orNSObject
Because theNSObject
theThe parent class
isnil
, so if we can’t find it, we need to jump out of the current search process;
Next, we analyze the source code in detail:
This brings us to our core search process:
Let’s analyze this loop with an example:
Count >>= 1
If count = 8, then the binary expression of 8 is 1000, then 8 >> 1 is 1000, which is the decimal 4, then we can know that count >> 1 is halved (divided by 2). If count >>= 1, count = count >> 1;
So if at this point, there are 8 items of data in the list, and the 6th item is the data we are looking for, the flow analysis is as follows:
First loop:
Count = 8, base = 0, count >> 1 = 4, probe = 0 + (4) = 4, keyValue > proValue So base = 4 + 1 = 5, count– = 7,
First loop result: base = 5, count = 7
Second loop:
Count = 7, count >>= 1, count = 3, base = 5, 3 >> 1 = 1, probe = 5 + 1 = 6,
This is the binary search process
Slow lookup recursive process
After the binary lookup is done and meth is found, goto done is executed
After finding imp according to meth, execute goto done
Done cache fills log_AND_fill_cache
Then call insert to cache:
But what if we didn’t find the IMP in the previous flow?
If meth is not currently found, point curClass to the parent class and continue looking for IMP in the parent class
Assembles a quick lookup flow from the parent class, so we’re back to a similar flow as before:
If it’s not found, it’s going to go through the slow search for the parent, and if it’s not found, it’s going to keep looking for the parent, so it’s a recursive search, and when curClass is nil, that means we’ve found all of our parent classes, we haven’t found any, so we’re going to assign forward_IMP to the IMP