This is the 9th day of my participation in Gwen Challenge

Cache expansion supplement

Translation memory

We used the following method to find sel and IMP data in the previous article

When we couldn’t find the data in $12, we shifted the pointer and finally found the data:

(lldb) p $9.buckets()[4]
(bucket_t) $19 = {
  _sel = {
    std::__1::atomic<objc_selector *> = "" {
      Value = ""
    }
  }
  _imp = {
    std::__1::atomic<unsigned long> = {
      Value = 45536
    }
  }
}
Copy the code

$buckets()[4] Bucket_t is an array, and bucket_t is a bucket structure. Buckets ()[4] Bucket_t is an array.

$9.buckets()[4] is called fetching memory translation. $9.buckets()[4] is a pointer to a structure. $9.Buckets ()[4] is equal to $12+4.

_bucketsAndMaybeMaskparsing

In the previous operation we had this data structure:

So what is this _bucketsAndMaybeMask thing? Let’s look at it through LLDB:

Check the buckets() method in action:

Retrieve addr from _bucketsAndMaybeMask and cast to hexadecimal address after operation with bucketsMask.

_bucketsAndMaybeMask stores the starting address of buckets()

Cache reading process analysis

In the introduction to cache_t we learned that the method is used to insert the cache. When is that done? We break our insert point and execute code:

Insert (log_and_fill_cache); insert (log_and_fill_cache);

The stack shows that lookUpImpOrForward calls log_AND_fill_cache:

This is the underlying message flow of C++

Runtime understanding of runtime

The runtime overview

To understand the message flow, you must first understand the Runtime

At compile time the compiler will help us translate the source code into code that the machine can recognize, or some intermediate state of the code. In this process, the compiler will help us with grammar analysis, type checking and other work; Run-time is when our program is already running and loaded into memory, where all operations are taking place.

Runtime comes in two versions:

  • LegacyThe version is an earlier versionObjective - 1.0 Cfor32bitMacOS XIn the system
  • ModernIndicates the current versionObjective - 2.0 CforiPhoneandMacOS X v10.5After the64A system of

Objective-C Runtime Programming Guide

Three ways to call the Runtime

  • OC method; Such as[peron run]
  • NSObject interface; Such asisKindoOfClass
  • Objc underlying Api; Such asclass_getInstanceSize

Let’s look at the code below

@interface Teacher : NSObject
- (void)say:(NSString *)string;
- (void)run;
@end

@implementation Teacher

- (void)say:(NSString *)string {
    NSLog(@"%s-->%@", __func__,string);
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Teacher *teacher = [Teacher alloc];
        [teacher say:@"hello"];
        [teacher run];
    }
    return 0;
}
Copy the code

Teacher has two methods say: and run. Say: is implemented, but run is not implemented. This code will compile successfully at compile time, but will crash at run time with an error:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Teacher run]: unrecognized selector sent to instance 0x1007aa0a0'
terminating with uncaught exception of type NSException
Copy the code

Take a look at the code in the CPP file:

Teacher *teacher = ((Teacher *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Teacher"), sel_registerName("alloc"));
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)teacher, sel_registerName("say:"), (NSString *)&__NSConstantStringImpl__var_folders_7z_qx6zs9gj2s58dv3ps88mf9pr0000gn_T_main_388285_mi_1);
((void (*)(id, SEL))(void *)objc_msgSend)((id)teacher, sel_registerName("run"));
Copy the code

For easy viewing, it is simplified as follows:

Teacher *teacher = ((Teacher *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Teacher"), sel_registerName("alloc"));
objc_msgSend)(teacher, sel_registerName("say:"), __NSConstantStringImpl__var_folders_7z_qx6zs9gj2s58dv3ps88mf9pr0000gn_T_main_388285_mi_1);
objc_msgSend(teacher, sel_registerName("run"));
Copy the code

Our common OC code is interpreted using runtime at the bottom

The invocation of the OC method is essentially the process of sending messages

objc_msgSend(id self, SEL _cmd)
Copy the code
  • id selfFor the receiver of the message
  • SEL _cmdIs the body of the messagesel+parameter

Objc_msgSend and objc_msgSendSuper

Now that we understand the nature of this, we can implement the method call directly through objc_msgSend:

You need to change the Enable Strict Checking of objc_msgSend Calls in Build Settings to NO. The default value is YES

We add a method to Teacher, talk, and implement:

- (void)talk;

- (void)talk {
    NSLog(@"-->%s", __func__);
}
Copy the code

Method call successful

We create a Person class, let Teacher inherit from the Person class, implement the run method in the Person class, the code is as follows:

@interface Person : NSObject

@end

@implementation Person

- (void)run {
    NSLog(@"-->%s", __func__);
}

@end

@interface Teacher : Person
- (void)say:(NSString *)string;
- (void)run;
- (void)talk;
@end

@implementation Teacher

- (void)say:(NSString *)string {
    NSLog(@"%s-->%@", __func__,string);
}

- (void)talk {
    NSLog(@"-->%s", __func__);
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Teacher *teacher = [Teacher alloc];
        [teacher say:@"hello"];
        [teacher run];
    }
    return 0;
}
Copy the code

Operating Projects:

There is no crash, although the subclass Teacher does not have an implementation of the run method, but directly found the parent class Person run method

The CPP file is generated:

Teacher *teacher = ((Teacher *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Teacher"), sel_registerName("alloc"));
((void(*) (id, SEL, NSString(*))void *)objc_msgSend)((id)teacher, sel_registerName("say:"), (NSString *)&__NSConstantStringImpl__var_folders_7z_qx6zs9gj2s58dv3ps88mf9pr0000gn_T_main_54da99_mi_3);
((void(*) (id, SEL))(void *)objc_msgSend)((id)teacher, sel_registerName("run"));
Copy the code

Objc_msgSend sends a message to the parent class. The CPP file contains a method objc_msgSendSuper that sends a message to the parent class. The objc_msgSendSuper method definition can be found in objC source code:

OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
Copy the code

It has two parameters:

  • The structure of the bodyobjc_super
  • SEL

Objc struct objc_super

Since the current environment satisfies __OBJC2__–Objective-C 2.0, the structure can be simplified as:

struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull id receiver;
    __unsafe_unretained _Nonnull Class super_class;
    /* super_class is the first class to search */
};
Copy the code

In our project, we try to call the run method using objc_msgSendSuper:

Explanation: Super_class is the first class to look up. If no method is found, look up the superclass and then look up. Because NSObject doesn’t have a run method, and NSObject’s parent class is nil, you can’t pass NSObject here;

_objc_msgSend Process analysis

Let’s analyze _objc_msgSend process analysis implementation, which is implemented in assembly, we in objC source code to analyze:

Not much code, explained as follows:

_objc_msgSend

Unwinding _objc_msgSend, NoFrame CMP P0, #0 // register p0 is the first parameter 'id' of objc_msgSend, Compare with 0 to determine whether a message receiver is available. #if SUPPORT_TAGGED_POINTERS // Determine whether the condition supports tagged pointer #endif LDR p13, [x0] #endif LDR p13, P13 = receiver's isa GetClassFromIsa_p16 p13, 1, x0 // GetClassFromIsa_p16 is explained below, final result p16 = receiver's class LGetIsaDone: // Instruction completion is actually a process of finding the class through the receiver, Cached // calls IMP or objc_msgSend_uncached 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: // return nil // x0 is already zero mov x1, #0 movi d0, #0 movi d1, #0 movi d2, #0 movi d3, Ret END_ENTRY _objc_msgSend // _objc_msgSend EndCopy the code

GetClassFromIsa_p16

// GetClassFromIsa_p16 p13, 1, x0; src=p13=isa needs_auth=1 auth_address=x0 .macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if ! Needs_auth */ #if SUPPORT_INDEXED_ISA // needs_auth */ #if SUPPORT_INDEXED_ISA / \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: Needs_auth == 0 // Needs_auth =1 does not meet _cache_getImp takes an Authed Class already MOV P16, \ src. else // 64-bit Packed ISA ExtractISA P16, \ SRC, \auth_address P16 is the receiver's class.endif #else // 32-bit raw ISA MOV P16, SRC # endif.endmacroCopy the code

ExtractISA

// ExtractISA p16, SRC, auth_address. macro ExtractISA and $0, $1, #ISA_MASK // $0=p16, $1= SRC =isa, $1 and ISA_MASK to get the class stored in $0= p16.endmacroCopy the code

The next chapter continues at……