preface

In addition, we have supplemented the cache structure and introduced objc_msgSend. Today, the main task is to explore the assembler source code of objc_msgSend.

The preparatory work

  • Objective-C Runtime Programming Guide
  • Objc4-818.2 – the source code

1. Runtime understanding of Runtime

The Runtime profile

  • RuntimeIs a useC,C++,assemblyThe runtime library that you write, contains a lotCThe language of theAPI, encapsulates many dynamically related functions.
  • Objective-CIs a dynamic runtime language that allows many operations to be deferred until the program is running.OCThe dynamic nature ofRuntimeTo support and implement,RumtimeThat’s the heart of it.
  • We write it every dayOCCode, the underlying is converted toRuntime APIIs called.

Compile time

Compile time is, as the name implies, when you are compiling.

So what is compiling? The compiler translates your source code into machine-readable code. (In a general sense, of course, and probably translated into some intermediate language.)

When compiling, you simply do some translation work, such as checking if you have carelessly written any wrong keywords. There is no lexical analysis, grammar analysis and so on. Just like a teacher checking a student’s essay for typos and sentences, the compiler will tell you if it finds any errors.

If you’re using Microsoft VS, click Build. Start compiling. If there are errors or warning messages below, they are checked by the compiler. These errors are called compile-time errors, and the type checking that is done is called compile-time type checking, or static type checking (where you don’t actually run the code in memory, you just scan it as text).

So sometimes people who say that they allocate memory at compile time are wrong.

The runtime

Runtime is when the code runs, it’s loaded into memory. Your code is dead until it is stored on disk and not loaded into memory. It only becomes alive when it runs into memory.

Runtime type checking is different from compile-time type checking (or static type checking) mentioned earlier. Instead of simply scanning code, you do something in memory and make some judgment.

The Runtime version

There are two versions of Runtime, a Legacy version (an earlier version) and a Modern version (the current version).

  • Earlier versions of the corresponding programming interface:Objective - 1.0 C;
  • Current version of the corresponding programming interface:Objective - 2.0 C;
  • Earlier versions used forObjective - 1.0 C.32bitMac OS XOn the platform;
  • Current version:iPhoneProcedures andMac OS X v10.5And future systems64A program.

The definitions can be found in the official objective-C Runtime Programming Guide.

How Runtime is initiated

Runtime hierarchy:

Runtime can be initiated using the OC method, NSObject interface, or objc API.

Nature of method calls objc_msgSend

Create SSPerson class, say1: method has implementation, say2 method has no implementation:

instantiationSSPerson, the callsay1:Methods,say2Method,command + BCompile successfully:

Command + R to run the project

This is the difference between compile-time and run-time. Next clang-rewrite-objc main.m -o main.cpp compiles main.m to get the main.cpp file.

View the main function in the file:

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        SSLPerson *person = objc_msgSend((id)objc_getClass("SSLPerson"), sel_registerName("alloc"));

        objc_msgSend((id)person, sel_registerName("say1:"), (NSString *)&__NSConstantStringImpl__var_folders_tb_rpsdqw797gn7n3l8myk82s5w0000gn_T_main_d1fdf2_mi_1);
         ((id)person, sel_registerName("say2"));
    }
    return 0;
}
Copy the code
  • So the method call here, the method call here isRuntime APICall mode;
  • You can see that whatever it isClass methodorObject methodsIt’s all usedobjc_msgSend, the essence of a method callSending a message;
  • Call method =Objc_msgSend (Message recipient, message body (sel + parameter));

We add a say3 method and call it with objc_msgSend:

It can be called normally. Note that the following setting should be changed to NO, normally it is YES.

objc_msgSendSuper

Find the definition of objc_msgSendSuper:

We see a function that has an objc_super argument, so let’s go to the source code and look at its definition:

  • objc_superThere arereceiverandsuper_classTwo member variables;
  • super_classIs the first object to look for, and if there is no method to implement it, it will continue to look up untilNSObjectClass.

SSLPerson inherits SSLAnimal, SSLAnimal to implement say2:

Assign super_class to sslAnimal.class and execute the code to print normally:

Next, assign super_class to sslPerson. class and execute the code:

It also prints normally, which proves what we said above, that the method search goes up.

2. Objc_msgSend process

Objc_msgSend source search

Break the point to the method call and find objC, the source library where objc_msgSend is located, by means of assembly and source code:

Search for objc_msgSend in the source code, the number of calls will be very, very difficult to see:

Hold Command + click the drop-down arrow to shrink the file:

  • objc_msgSendSource code in the assembly, the assembly file is.sAt the end of the;
  • Let’s take a look atobjc-msg-arm64.sFile, becausearm64It’s a real machine architecture,i386Is the simulator architecture,x86_64isMac OSArchitecture.

Let’s explore the flow of objc_msgSend through assembly. ENTRY is the ENTRY point of the assembler, and we find it:

Assembly source parsing

1. Compile source message receiver nulls

Assembly source:

  • The source code parsing
    • Identify message recipientspersonWhether it is null, if it is;
    • Determine whether it istagged pointer, if so, handle it;
    • If it is nottagged pointer, empty, end the method call;

    • If the message receiverpersonIf the value is not empty, the execution continues.

2. Obtain the assembly source codeisa

  • The source code parsing
    • willpersontheisaAssigned top13;
    • p13In the form of parameters, pass inGetClassFromIsa_p16;
    • throughExtractISA.isa & ISA_MASKAssigned top16And you get thatclass;
    • getclassIs to get its member variablescache, the method search;
    • GetIsaDone: getisaThe end.

3. Compile source codeCacheLookup

  • CacheLookupMacros define functions,Mode = NORMAL.Function = _objc_msgSend.MissLabelDynamic = __objc_msgSend_uncached;

4. Obtain the assembly source codehash index

Assembly source:

  • mov x15, x16
    • Save the originalisaValue,p16 = class.x15 = x16 = isa.
  • CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    • A:Architecture is also the architecture of our process analysis.
  • ldr p11, [x16, #CACHE]
    • #define CACHE (2 * __SIZEOF_POINTER__)
      • CACHE = 2 * __SIZEOF_POINTER__ = 16
    • p11 = Isa translational 16 = cache_t
  • CONFIG_USE_PREOPT_CACHES
    • The real machine environment value is1
  • __has_feature(ptrauth_calls)
    • Determine whether it isA12After the model
  • tbnz p11, #0, LLookupPreopt\Function
    • cache_tthe0Is the position of0, not for0Jump toLLookupPreopt\FunctionTo load the shared cache, not right nowLLookupPreopt\FunctionTo explore
  • and p10, p11, #0x0000ffffffffffff
    • p10 = _bucketsAndMaybeMask & 0x0000ffffffffffff = buckets
    • 0x0000ffffffffffff
      • 0000000000000000111111111111111111111111111111111111111111111111
  • eor p12, p1, p1, LSR #7.and p12, p12, p11, LSR #48
    • According to theThe hash functionTo obtainindex.p12 = (_cmd ^ (_cmd >> 7)) & mask

5. Assemble source code forward traversal search

Assembly source:

  • add p13, p10, p12, LSL #(1+PTRSHIFT)
    • PTRSHIFTDefinition:#define PTRSHIFT 3
    • p13 = buckets + (index << 4).p13isindexUnder thebucketIs the first thing we need to look intobucket
      • index << 4The equivalent ofindex * 16
  • 1: ldp p17, p9, [x13], #-BUCKET_SIZE
    • p17 = imp.p9 = selAnd then*bucket--
  • cmp p9, p1
    • To compareseland_cmdWhether the range
  • b.ne 3f
    • If not, go ahead and look for the next onebucketTo jump to3f
  • 2: CacheHit
    • If they are equal, enterCacheHitCache hit, return
  • 3: cbz p9, \MissLabelDynamic
    • judgeselWhether it is0If for0Enter the__objc_msgSend_uncachedfunction
  • cmp p13, p10.b.hs 1b
    • while (bucket >= buckets)If thebucket >= buckets, jump to the top1bOtherwise proceed with the following code

6. The last position of the assembler source code is traversed forward to find

Assembly source:

  • add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
    • p13 = buckets + (mask << 1+PTRSHIFT)Translation tobucketsThe last position of
  • add p12, p10, p12, LSL #(1+PTRSHIFT)
  • 4: ldp p17, p9, [x13], #-BUCKET_SIZE
    • {imp, sel} = *bucket--I’m going to move it forward
  • cmp p9, p1.b.eq 2b
    • if (sel == _cmd)To jump to2b CacheHit
  • cmp p9, #0
    • while (sel ! = 0 &&
  • ccmp p13, p12, #0, ne
    • bucket > first_probed)
  • b.hi 4bAnd come back to4bContinue to perform

Three, the real machine run assembly

Create a new project, the next way to explore with assembly source code, assembly source code have do not understand can go to OC object principle to explore (a) view.

The following is a real machine debugging assembly, can be found with the assembly source code is very similar.

We do some simple debugging, and some validation of the assembly source code.

  • As you can see from the print,x1Is, indeed,sel.

  • readx0get<SSLPerson: 0x280d78060>Is indeed the message receiverperson.
  • readx16Address is0x00000001044415e0, andSLPerson.classSame, you can prove itx16isclass.x16 = x13 & 0x7ffffffffffff8That isisa & isaMask.

  • readx12get0x0000000000000001That is1Through thecache_hashFunction ofThe hash index.