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_lookUpImpOrForwardThe function sets the required parameters,The first eight arguments of the function are stored in x0 through x7
  • Jump to_lookUpImpOrForwardMethod execution lookup
  • Returns what the function returnsIMPIn thex17In 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

insayHelloMake a break point, make a break point here

Open assembly display, openAlways Show Disassembly.

Go to assembly and stop the breakpoint at objc_msgSend

pointcontrolJump 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 conditionrealizeClassMaybeSwiftAndLeaveLockedinitializeAndLeaveLockedImplement and initialize some information about the class, such asDetermining the chain of inheritanceBecause subsequent processes must have this, you need to check this information for the class before going through the process.
  • thecurClassSet to the class currently being searched
  • The first stepIn:forIn 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 searchAccording toClass inheritance chainOr,Metaclass inheritance chainLook for it. If you find it, justgoto doneAnd start to doInsert log_and_fill_cache for cacheAnd return to IMP.
  • The second stepIf the method list of your own class is not found, use theif (slowpath((curClass = curClass->superclass) == nil)), that is, thecurClassSet to parent if the parent isnilWould bringimp=forward_imp.
  • Then throughcache_getImpGo through the assembly quick lookup process,Check out a quick lookup process written in this articleThe only difference is that hereJumpMiss $0andCheckMissIs take theGETIMPTo return toLGetImpMiss
LGetImpMiss:
	mov	p0, #0
	ret
Copy the code
  • Then judgeimpWhether it isforward_impIf so, justbreakcycle
  • Finally check if it is found in the parent classimp.
  • Then go back toThe first stepContinue 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 SearchAlso 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

Welcome big brother message correction 😄, code word is not easy, feel good to give a thumbs-up 👍 have any expression or understanding error please leave a message; Common progress;