preface

inBasic principles of iOS — Cache Exploration in Objc_classWhen we explored cache_t, we found that the object actually calls instance methods before INSERTobjc_msgSendMethod. If you’ve seen this article, you’ll remember the flowchart for the INSERT method.

But we were focusing on CACHE_T at the time, so we skipped this important step. Today in this article, we begin the formal studyobjc_msgSendMethods.Before we begin, we need to understand two important concepts:Compile timeandThe runtime

Introduction to compile time and run time

  • Compile time

As the name implies, compilation time. Compilation means that the compiler translates source code into machine-readable code. At compile time, it does some simple type checking (static type checking). If there is an error or warning message, the compiler will check it. Static type checking is called static type checking because you don’t actually run the code in memory, you just scan the code as text. So it is definitely wrong to say that memory is allocated at compile time.

  • The RunTime (RunTime)

As the name implies, the code is loaded into memory as it runs. Run-time type checking is different from compile-time type checking (static type checking). Instead of simply scanning code, you start making judgments in memory, doing operations, and so on.

  • The RunTime version
    • Legacy version(Earlier version) : Corresponding programming interface :Objective-C 1.0 for 32-bit Mac OS X platforms.
    • The Modern version(Current version) : Corresponding programming interfaces :Objective-C 2.0, iPhone programs and 64-bit programs in Mac OS X V10.5 and later

Three ways to tune the RunTime

There are three common ways we call the RunTime.

  • Call from OC code, for example: [Person SayHelloWord]
  • Called with the NSObject method, for example, isKindOfClass
  • Call from the Runtime API, for example, class_getInstanceSize

Our 👨💻 Code is in the Objective-C Code layer. Complier (compiler layer) is our compiler(LLVM). The Runtime System Library is a low-level Library that uses Complier (the compiler layer) to intercept and forward the framework and RuntimeAPI.

Objc_msgSend Low-level exploration

Methods the essence

Let’s create a YSHPerson class and define an instance methodsayHelloWord, and then call the method in main.m. Then use clang to compile main.m into main. CPP file and see how the underlying implementation of method calls.

Added: Clang syntax

  1. Clang-rewrite-objc main.m -o main.cpp compile main.m to main.cpp

  2. Compile viewController.m to viewController.cpp

Clang-rewrite-objc-fobjc-arc-fobjc-runtime = ios-13.0.0-isysroot / / Applications/Xcode. App/Contents/Developer/Platforms/iPhoneSimulator platform/Developer/SDKs/iPhoneSimulator13.7. The SDK ViewController. M]

The following two ways are through the command line specifying schema patterns, using the Xcode tool XCRun

  1. [-xcrun-sdk iphonesimulator clang-arch arm64-rewrite-objc main.m -o main-arm64.cpp]

  2. [-xcrun-sdk iphoneos clang-arch arm64-rewrite-objc main.m -o main-arm64.cpp]

By analyzing the underlying code implementation, we find that the objc_msgSend method is actually called, so the essence of the method can also be thought of as message sending.

objc_msgSend(void /* id self, SEL op, … */ )

To test this hypothesis, we can call objc_msgSend directly to see if it has the same effect as OC.

If we want to call the msgSend method directly, we need to pay attention to the following two things:

2. Change the enable strict checking of obc_msgSend calls from YES to NO in build Settings to disable the strict checking. Otherwise, the objc_msgSend parameter will fail.

Let’s take a look at the print:The printout is perfect, which proves our guess.

Invoke the instance method to perform the implementation of the parent class

Let’s create a YSHStudent class that inherits from YSHPerson. Declare a sayHelloWord method, but it doesn’t implement it, so let’s call it and see what the underlying implementation is?We found the same result by printing, which is the implementation method of calling YSHPerson.Let’s analyze itobjc_msgSendMethods:

objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, …)

Two parameters are found:

  • Of the structure typeobjc_super
struct objc_super {
    __unsafe_unretained _Nonnull id receiver;
    __unsafe_unretained _Nonnull Class super_class;
};
Copy the code
  • SEL

So far we have found that both [student sayHelloWord] and objc_msgSendSuper implement the sayHelloWord implementation of the parent YSHPerson class. We can take a bold guess at how a method is called: first, it looks in its own class, and if not in its own class, it looks in its parent class. As usual 👇 takes our guess and starts exploring the source code implementation of objc_msgSend.

Note: objc_msgSend is no longer a C++/C implementation, but an assembly implementation.

Objc_msgSend Quick lookup process source code analysis

First let’s find the source code of _objc_msgSend, according to the source code, step by step.

  1. cmp p0, #0 P0 can be understood as register X0, which is the first argument to the method, i.ep0=receiver. Here the message receiver is compared to 0, and the first step is mainly used to determine whether there isMessage receiverIf there is no message receiver, then the call to this method means nothing.
  2. #if SUPPORT_TAGGED_POINTERSIf the value is 1 on Linux or MacOS X, runb.le LNilOrTaggedIf p0<=#0, executeLNilOrTaggedIf p0=#0, we’ll just returnZero; If it is notSUPPORT_TAGGED_POINTERSIs executedb.eq LReturnZero, directly returnZero, end the message sending.
  3. Execute if P0 exists, that is, receiver existsldr p13, [x0], save the value of register X0 to P13. X0 stores the address of the object, that is, isa, p13=isa.
  4. performGetClassFromIsa_p16 p13, 1, x0Get the class from isa.

  • SUPPORT_INDEXED_ISAThe arm64 pointer isa 32-bit isa pointer, but not a 64-bit one. So let’s go straight to it__LP64__Conditional.
  • The value we passedneeds_auth=1, so execute hereExtractISA p16, \src, \auth_addressThe SRC = p13 (isa). According to the source code, we willisaandISA_MASKforwithOperation, according to the previous weThe underlying principle of iOS — ISA & Class StructureThis operation is to obtain the current object’s Class and assign the Class to register P16.
.macro ExtractISA
	and    $0, $1, #ISA_MASK //$0=p16,$1=p13(isa)
.endmacro
Copy the code
  1. Continue to performLGetIsaDone:CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached.
  • mov x15, x16, add x16(class) to register X15
  • Our main research object is ARM64 architecture, so let’s take a look hereCACHE_MASK_STORAGE==CACHE_MASK_STORAGE_HIGH_16Assembly code. Equivalent to shifting class down 16 bytes, where cache_t is located, and storing cache in register P11, i.ep11=cache.
#define CACHE (2 * __SIZEOF_POINTER__) //2*8=16 LDR p11, [x16, #CACHE] // x16 + #16Copy the code
  • We seeCONFIG_USE_PREOPT_CACHESIt’s actually the real thing. It happens to be what we’re working on.__has_feature(ptrauth_calls)It refers to the A12 chip and has been on, not the general pattern, here we will not do more research. We looked directly atelseAssembly code in a branch.
#if defined(__arm64__) && TARGET_OS_IOS && ! TARGET_OS_SIMULATOR && ! TARGET_OS_MACCATALYST #define CONFIG_USE_PREOPT_CACHES 1 #else #define CONFIG_USE_PREOPT_CACHES 0 #endifCopy the code
  • performand p10, p11, #0x0000fffffffffffe

    tbnz p11, #0, LLookupPreopt\Function
    • P11 is the cache addresscacheThe address and0x0000fffffffffffeforwithOperation, get buckets, send buckets to register P10.
    • tbnzIf the 0th bit of p11 is not 0(p11[0]! = 0), execute directlyLLookupPreoptMethods.
  • So let’s go ahead and executeeor p12, p1, p1, LSR #7

    and p12, p12, p11, LSR #48.
    • P1 is the second argument SEL _cmd,eorisThe bitwise exclusive or;LSRisThe logic moves to the right bit
    • p12= _cmd >> 7 ^ _cmd
    • P11 is cache_t, or _bucketsAndMaybeMask. Move bucketsAndMaybeMask 48 bits to the right to get mask. And then it goes up to the P12 and gives it to the P12. p12=(_cmd >> 7 ^ _cmd)&mask
  • So let’s go ahead and executeadd p13, p10, p12, LSL #(1+PTRSHIFT)
    • PTRSHIFT==3P13 = buckets + ((_cmd ^ (_cmd >> 7)) & mask)<<4
  • So let’s go ahead and executeldp p17, p9, [x13], #-BUCKET_SIZE
    • Pan the current bucket by one bucket, equivalent to *bucket–
    • P17 = IMP, p9= SEL
  • So let’s go ahead and executecmp p9, p1
    • Compare the sel passed in and sel in the bucket. If the sel is not equal, jump to the instruction at the third place for execution. If the sel is equal, the cache hits, and execute the specified in article 2
    • Indicates a direct backward jump to the local label 1 when it is not equal (b: Backward, f: forward)
    • cbz p9, \MissLabelDynamic, compares whether p9 is 0. If the value is 0, the command is executedMissLabelDynamic, MissLabelDynamic =__objc_msgSend_uncachedThat is, if the SEL does not exist, the sel is executed__objc_msgSend_uncachedMethods.
      • If p9 is not 0, go tocmp p13, p10, compare P13 and P10, if P13 >=p10, jump to the first instruction
  • If the cache hits, it is executedCacheHit, find sel corresponding IMP, and then return IMP.