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_msgSend
Method. 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_msgSend
Methods.Before we begin, we need to understand two important concepts:Compile time
andThe 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
-
Clang-rewrite-objc main.m -o main.cpp compile main.m to main.cpp
-
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
-
[-xcrun-sdk iphonesimulator clang-arch arm64-rewrite-objc main.m -o main-arm64.cpp]
-
[-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_msgSend
Methods:
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, …)
Two parameters are found:
- Of the structure type
objc_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.
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 receiver
If there is no message receiver, then the call to this method means nothing.#if SUPPORT_TAGGED_POINTERS
If the value is 1 on Linux or MacOS X, runb.le LNilOrTagged
If p0<=#0, executeLNilOrTagged
If p0=#0, we’ll just returnZero; If it is notSUPPORT_TAGGED_POINTERS
Is executedb.eq LReturnZero
, directly returnZero, end the message sending.- Execute if P0 exists, that is, receiver exists
ldr p13, [x0]
, save the value of register X0 to P13. X0 stores the address of the object, that is, isa, p13=isa. - perform
GetClassFromIsa_p16 p13, 1, x0
Get the class from isa.
SUPPORT_INDEXED_ISA
The 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 passed
needs_auth=1
, so execute hereExtractISA p16, \src, \auth_address
The SRC = p13 (isa). According to the source code, we willisa
andISA_MASK
forwith
Operation, 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
- Continue to perform
LGetIsaDone: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 here
CACHE_MASK_STORAGE==CACHE_MASK_STORAGE_HIGH_16
Assembly 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 see
CONFIG_USE_PREOPT_CACHES
It’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 atelse
Assembly 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
- perform
and p10, p11, #0x0000fffffffffffe
tbnz p11, #0, LLookupPreopt\Function
- P11 is the cache address
cache
The address and0x0000fffffffffffe
forwith
Operation, get buckets, send buckets to register P10. tbnz
If the 0th bit of p11 is not 0(p11[0]! = 0
), execute directlyLLookupPreopt
Methods.
- P11 is the cache address
- So let’s go ahead and execute
eor p12, p1, p1, LSR #7
and p12, p12, p11, LSR #48
.- P1 is the second argument SEL _cmd,
eor
isThe bitwise exclusive or
;LSR
isThe 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
- P1 is the second argument SEL _cmd,
- So let’s go ahead and execute
add p13, p10, p12, LSL #(1+PTRSHIFT)
PTRSHIFT==3
P13 = buckets + ((_cmd ^ (_cmd >> 7)) & mask)<<4
- So let’s go ahead and execute
ldp 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 execute
cmp 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_uncached
That is, if the SEL does not exist, the sel is executed__objc_msgSend_uncached
Methods.- If p9 is not 0, go to
cmp p13, p10
, compare P13 and P10, if P13 >=p10, jump to the first instruction
- If p9 is not 0, go to
- If the cache hits, it is executed
CacheHit
, find sel corresponding IMP, and then return IMP.