A message send is a method call. Selselreceiver objc_msgSend (); selselReceiver (); selselReceiver (); selselReceiver (); selselReceiver (); Instead, rW methods in class slowly search for methods in C mode, find the method, return to the IMP of the method to call, so as to complete the method call. This process can be found.
In the previous article, objc_msgSend compiled the process of finding method caches. But there is no analysis of the slow search of the C method that did not find the cache. This article follows the slow search process of the open method in the previous article.
If you are considering changing your job recently, you are looking for a new job
0x00 – Assembly and process analysisobjc_msgSend
As you learned from the flowchart in the previous article, both CheckMiss NORMAL and JumpMiss NORMAL get the __objc_msgSend_uncached process
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band p16 is the class to search
MethodTableLookup
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
Copy the code
__objc_msgSend_uncached also does very little other than call methodTable ELookup for method lookup
// Save SAVE_REGS // lookUpImpOrForward(obj, sel, CLS, LOOKUP_INITIALIZE | LOOKUP_RESOLVER) / / receiver and the selector already in x0 and x1 / / the third parameter is the CLS, X16 // LOOKUP_INITIALIZE = 1, LOOKUP_RESOLVER = 2, x16 // LOOKUP_INITIALIZE = 1, x16 // LOOKUP_RESOLVER = 2, // Call _lookUpImpOrForward. Imp in x0 // imp in x0 // IMP in x17 mov x17, x0 // restore the register restore_regs.endmacroCopy the code
- Save register
- to
_lookUpImpOrForward
The function sets the required parameters,The first eight arguments of the function are stored in x0 through x7
- Jump to
_lookUpImpOrForward
Method execution lookup - Returns what the function returns
IMP
In thex17
In the same location as the previous cache lookup - Recovery register
The final step is then performed by calling TailCallFunctionPointer X17
.macro TailCallFunctionPointer
// $0 = function pointer value
br $0
.endmacro
Copy the code
TailCallFunctionPointer also simply executes the IMP passed in by the BR instruction. That completes the assembly part, and today we’re going to delve deeper into _lookUpImpOrForward.
Assembly validation
Let’s first demonstrate the above process through code practice
insayHello
Make a break point, make a break point here
Open assembly display, openAlways Show Disassembly
.
Go to assembly and stop the breakpoint at objc_msgSend
pointcontrol
Jump into the message flow
The top shows the entry into the process
You see _obj_msgSend_uncached at the bottom, the same as you would see in assembly code
Again, I’m going to control into the process
Enter the cache missed process and start the slow look-up of the method by calling lookUpImpOrForward
0x01 – Low-level slow lookup process
Open a copy of objC-781’s source code and search for _lookUpImpOrForward based on the results of the previous analysis
😄 one less _ from assembly to C ++ and one less _ from C ++ to C
IMP lookUpImpOrForward(ID INst, SEL SEL, Class CLS, int behavior) into the implementation of lookUpImpOrForward
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
// Defined message forwarding
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
runtimeLock.assertUnlocked(a);// Quickly find, if found, return imp directly
// Purpose: to prevent multithreading from happening when a function is called
if (fastpath(behavior & LOOKUP_CACHE)) {
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
// Add a lock to ensure thread-safe reading
runtimeLock.lock(a);// Check whether the current class is a known class: Check whether the current class is an approved class, that is, a loaded class
checkIsKnownClass(cls);
// Check whether the class is implemented. If not, it needs to be implemented first. In this case, the purpose is to determine the parent class chain, method subsequent loop
if (slowpath(! cls->isRealized())) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
}
// Check whether the class is initialized. If not, initialize it first
if (slowpath((behavior & LOOKUP_INITIALIZE) && ! cls->isInitialized())) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
}
runtimeLock.assertLocked(a); curClass = cls;//---- find the cache of the class
// unreasonableClassCount -- Indicates the upper limit of class iterations
// The recursion is due to the fact that the attempts decrement one during the first loop and are still within the upper limit when they loop again.
for (unsigned attempts = unreasonableClassCount();;) {
//-- List of current class methods (using binary search algorithm), if found, return, cache method in cache
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp;
goto done;
}
// Current class = the parent of the current class, and determines if the parent is nil
if (slowpath((curClass = curClass->superclass) == nil)) {
//-- method implementation not found, method parser also not working, use forward
imp = forward_imp;
break;
}
// Stop if there is a loop in the parent chain
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// -- superclass cache
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) {
// If a forward is found in the parent class, the lookup is stopped, no caching is done, and the method resolver of this class is called first
break;
}
if (fastpath(imp)) {
// If the method is found in the parent class, store it in cache
gotodone; }}// Failed to find method implementation, try method parsing once
if (slowpath(behavior & LOOKUP_RESOLVER)) {
// The control condition of the dynamic method resolution, indicating that the process only moves once
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
// Store to cache
log_and_fill_cache(cls, imp, sel, inst, curClass);
/ / unlock
runtimeLock.unlock(a); done_nolock:if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
Copy the code
There are mainly the following steps:
checkIsKnownClass(cls);
ALWAYS_INLINE
static void
checkIsKnownClass(Class cls)
{
if (slowpath(!isKnownClass(cls))) {
_objc_fatal("Attempt to use unknown class %p.", cls); }}Copy the code
Attempt to use unknown class if this class is loaded correctly and the runtime detects this class. If true is returned, Attempt to use unknown class is reported
- Whether to execute according to the condition
realizeClassMaybeSwiftAndLeaveLocked
和initializeAndLeaveLocked
Implement and initialize some information about the class, such asDetermining the chain of inheritance
Because subsequent processes must have this, you need to check this information for the class before going through the process. - the
curClass
Set to the class currently being searched The first step
In:for
In the loop, first of allgetMethodNoSuper_nolock(curClass, sel);
Here to findimp
.That is, now their own class method list to findThe method I’m looking for here worksBinary search
According toClass inheritance chain
Or,Metaclass inheritance chain
Look for it. If you find it, justgoto done
And start to doInsert log_and_fill_cache for cache
And return to IMP.The second step
If the method list of your own class is not found, use theif (slowpath((curClass = curClass->superclass) == nil))
, that is, thecurClass
Set to parent if the parent isnil
Would bringimp=forward_imp
.- Then through
cache_getImp
Go through the assembly quick lookup process,Check out a quick lookup process written in this articleThe only difference is that hereJumpMiss $0
andCheckMiss
Is take theGETIMP
To return toLGetImpMiss
LGetImpMiss:
mov p0, #0
ret
Copy the code
- Then judge
imp
Whether it isforward_imp
If so, justbreak
cycle - Finally check if it is found in the parent class
imp
. - Then go back to
The first step
Continue the loop until the parent class isnil
You can see this in combination with the sample code and the long flow chart I’ve drawn.
0x02 – Binary searchBinary Search
Also calledhalf-interval search
Binary Search algorithm, also known as split Search algorithm, belongs to Interval Search (Interval Search), a good example of the idea of divide-and-conquer. Binary search for an ordered set, each time by comparing with the intermediate elements, the range of the search is halved until the element is found, or the range is reduced to 0.
- For ordered data
- The amount of data is too small to be useful
- Too much data is also not suitable
In the getMethodNoSuper_nolock lookup method, a binary lookup method is used to search for the IMP of the method
/*********************************************************************** * search_method_list_inline * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
ASSERT(list);
const method_t * const first = &list->first;
const method_t *base = first;
const method_t *probe;
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
/** Binary search sel sort problem */
for(count = list->count; count ! =0; count >>= 1) {
probe = base + (count >> 1);
uintptr_t probeValue = (uintptr_t)probe->name;
if (keyValue == probeValue) {
// `probe` is a match.
// Rewind looking for the *first* occurrence of this value.
// This is required for correct category overrides.
while (probe > first && keyValue == (uintptr_t)probe[- 1].name) {
probe--;
}
return (method_t *)probe;
}
if (keyValue > probeValue) {
base = probe + 1; count--; }}return nil;
}
Copy the code
If I find if (keyValue == probeValue), then this condition here is true,
// If the keyvalue of the key is equal to the probeValue of the probe, the middle position is returned
if (keyValue == probeValue) {
// -- while pan -- exclude categorization methods
while (probe > first && keyValue == (uintptr_t)probe[- 1].name) {
// Remove the same class name method (method storage is first to store the class method, storage classification - according to the principle of first in, last out, classification method first, and we need to remove the class method first)
// If there are two categories, it depends on which one loads first
probe--;
}
return (method_t *)probe;
}
Copy the code
If there is a method with the same name as the class, the class method is pushed last, so if there is a method with the same name as the class method, the class method is returned.
This is easy to understand, based on the example below. Here’s what I did.
int binarySearch(int arr[], int count, int keyValue) {
int probeMid;
int base = 0;
for (intn = count; n ! =0; n >>= 1) {
probeMid = base + (n >> 1);
int probeValue = arr[probeMid];
if (keyValue == probeValue) {
return probeValue;
}
if (keyValue > probeValue) {
base = probeMid + 1; n--; }}return - 1;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
int arr[] = { 5.13.19.21.37.56.64.75.80.88.92 };
int n = sizeof(arr) / sizeof(arr[0]);
int index = binarySearch(arr, n, 65);
}
return 0;
}
Copy the code