IOS martial arts esoteric article summary

Writing in the front

Spring Festival night, very difficult to fall asleep, wake up, open the latest objC4-818.2 source code, there is a guy in gradually hair stay……

I. Clues and directions of exploration

Take secret book of that momently, the brain is running in high speed all the time, how should ability learn?

We want to start exploring “Swordsman “(the bottom of iOS), but we don’t know where to start. What do we do?

Start with the main function!

Let’s start with the God view! To observe a rough loading process. To make preparations:

  • inmainFunction, and then we print the stack at this point to see (btlldbDebug commands print stack information)

Well, we all know that main is very early, but it turns out that the system did something else before main!! So what else comes before main? Let’s see

  • Add three symbolic breakpointslibSystem_initializer,libdispatch_init,_objc_init

Let’s add them as shown abovelibSystem_initializer,libdispatch_init,_objc_initSymbol breakpointThen let’s run the program to see:

LibSystem_initializer is our first symbolic breakpoint, and we can see from the stack information that the program will go to the famous dyld, and after a series of steps we will go to libSystem_initailizer. This leads from dyld to the libSystem library.

Then we come to our second symbol breakpointlibdispatch_init, also camelibdispatchThe librarylibdispatchisGCDWe are working on this later. Go through this breakpoint to the third symbol breakpoint that we set_objc_init, also camelibobjcThe bottom layer, it’s the whole oneruntimeSome source code.

After these three breakpoints, we get to the familiar onemainFunction.

pastmainThe breakpoint of the function will come to something we’re familiar with

After going through this process, some cuties might ask, “What? Why do you have such detailed stack information?

Simply turn off the first button at the bottom of the Debug area on the left of Xcode to show only stack frames with Debug Symbols and between libraries

So here’s a summary. From the above stack information, we can summarize a simple loading process:

  • dyldStart loading dynamic libraries, shared memory, constructor calls for global C++ objects, a series of initializations, dyld registration callback functions
  • libsystemThe initializationlibSystem_initializer
  • libdispatch_initPreparation of the queue environment
  • from_os_object_initThe transition to_objc_init
  • As well as_dyld_objc_notify_registerImage file mapping
  • Class – Classification – attribute – protocol -SEL- method loading
  • An analysis ofRuntimeThe principle of each part
  • mainFunction startup

The analysis and thinking here are more interesting, in order to let everyone have a better sense of experience. Next, let’s start with the OC object that everyone is familiar with.

Alloc principle a OC object alloC

We’re gonna study objects, and we’re gonna start with creation! I have an interesting question for you to think about for ten seconds! The code is as follows:

%@ Print object %p print address &p pointer address

Question:

  • 1. Check whether object P1 is created
  • 2. Check whether P1, P2, P3, and p4 are the same object

I don’t know if the answer in your head matches the one printed above:

  • We created four temporary objects, P1, P2, p3, and p4
  • The Pointers to p1, P2 and P3 are different, but they point to the same memory. The Pointers to p4 are different from p1, P2 and P3. – You’ll know why after reading this one.)
    • Remaining issues:
    • P1, P2, p3 object and address print are consistent, why inconsistent &P print?
    • (2) Why is the address of P4 different from that of P1, P2, p3?
  • We can prove it from the reverseallocCreate objects – open up memory
  • initIt’s just an initialization constructor.
  • newagainallocI have another memory space

Well,alloc already determines the memory address of the object, so how do we determine it? So let’s explore

  • Now we jump into the root of all evilCommand ->Jump to DefintionTo enter)
  • Can’t jump in to view the implementation, how to do, please comeObjc4 official sourceorObjc4 small marshals the executable source code, the next few days will always go in!! I hope every friend is not just rubbing outside, deep communication is meaningful
  • There is no comment
  • No source code implementation
  • I don’t know the next step

What if I find I can’t get in? Can not see the specific source code implementation! A lot of times we often encounter such a situation, is to do some things, is to hit a wall, do not know how to start! Attention here, everyone: I’m going to pretend to be a pussy!

Iii. Alloc Underlying exploration ideas (three methods of underlying exploration and analysis)

Here are three ways to view his implementation.

Method one: symbol breakpoint location directly

Add alloc symbol breakpoints (clues and directions explored earlier show how to add symbol breakpoints)

  • The alloc symbol breakpoint is grayed first (the alloc function is called in many places and grayed before reaching our destination)

  • When Xcode is run, the program reaches the [TCJPerson alloc] breakpoint and opens the alloc symbol breakpoint

  • Click the Continue button in the Xcode log bar

The results are as follows

  • [NSObject alloc]Successfully see the link librarylibobjc.A.dylib
  • The underlying call is_objc_rootAllocfunction

Method 2: code trace – control + step into

  • ① Close the previous symbol breakpoint and go to the object breakpoint

  • ② Hold down the keyboardcontrolKey + mouse clickXcodeThe log columnstep intobutton

You can see it when you go insideobjc_alloc

  • ③ If you are using the real machine, please continue the operation of the second part and come later

  • ④ If you are using an emulator, you will need to add it after part 2objc_allocSymbol breakpoint after clickXcodeThe continue button in the log bar

  • ⑤ Whether you are a real machine or an emulator, you end up in libobjc.a.dylib and see the underlying objc_alloc as well

  • ⑥ And method 1 happen to coincide

Method three: assemble into analysis

  • ① Close other symbol breakpoints to the object breakpoint

  • 2.XcodeToolbar selectionDebug –> Debug Workflow –> Always Show DisassemblyThis option always shows disassembly, that is, through assembly and flow

  • ③ Add a breakpoint to objc_alloc at line 16 of assembly display

  • ④ If you are using a real machine, holdcontrolThe key andstep intoKey results are as follows:And then I’m gonna hold it downcontrolThe key andstep intoThe key is:

  • ⑤ If you are using an emulator, after step 3 to add a symbol breakpoint, press and holdcontrolThe key andstep intoKey results are as follows:And then you need to addobjc_allocSymbol breakpoint after clickXcodeThe continue button in the log bar

  • Libobjc.a. dylib-objc_alloc: Easy to get!

At this moment, who else! Is that the only thing that’s gonna knock us out? There is no the

Iv. Alloc process analysis

①. Assembly with source code and process

Through the frontAlloc underlying exploration idea (three methods of underlying exploration analysis)We know three ways to explore the underlying implementation, so let’s play around. Let’s open the prepared compilableObjc4 sourceWe just found it earlierallocProcess, let’s search in the source code:See it in the source codeallocMethod, oh my god, I’m so happy, come here and have the underlying implementation. We click_objc_rootAllocMethods come to:Continue to clickcallAllocMethods come to:The source code here may make you dizzy and don’t want to see

There are so many if-else logic forks in the road that many people will close Xcode.

See what not good-looking source code, be disrelish oneself hair too exuberant?

Don’t worry, I’ve already cut your hair.

So which process does he go through? So let’s verify that

Assembly and source code synchronization tutorial to follow the process

  • In our first codejoinThe three sign breakpoints that we just smoothed out_objc_rootAlloc,callAlloc,_objc_rootAllocWithZone.
  • Close the symbol breakpoint and go to our object breakpoint
  • Open the three symbol breakpoints we just set and go to the first symbol breakpoint_objc_rootAlloc:
  • Over this_objc_rootAllocThere’s a break point_objc_rootAllocWithZoneBreakpoint:

Let’s make a sketch based on the source code we just looked at:According to the source we know incallAllocWhen the fork appeared:objc_msgSendand_objc_rootAllocWithZoneSo which fork did he go for? According to the compilation of the walks we just did, what we get is a walk_objc_rootAllocWithZone.Whereas when we run assembly and flow, onlyBroken two timesThat is:_objc_rootAllocStraight to_objc_rootAllocWithZone. ThencallAllocDid the breakpoint change? Why is that? See below

②. Compiler optimization

Let’s start with the following example (using real machine debugging, see assembly):

Run the program to get assembly code:

Some of you might say, okay? Why do we have w and x? This involves knowledge of registers. W stands for 32 bits and x stands for 64 bits. Then why do we have a “W” on a real plane? This allows for compatibility issues. For example, if we store data of type int = 10, we can store it in 32 bits instead of 64 bits.

Registers – registers are used for temporary storage of data

  • ARM64 has 31 64-bit general-purpose registers x0 through X30. These registers are usually used to store general data, called general-purpose registers (and sometimes special-purpose registers).
    • For example,x0 to x7 are used to store parameters, and X0 is used to store parameters and receive return values.
    • So w0 through W28 these are 32 bits. 64-bit cpus are 32-bit compatible. So you can use only the lower 32 bits of the 64-bit register.
    • For example, w0 is the lower 32 bits of x0!
  • Typically, the CPU stores the data in memory into a general purpose register, and then performs operations on the data in the general purpose register

We were just talking aboutint a = 10So which one represents him? Let’s print:

And then we come tomov w9, #0x14:

Coming upadd w9, w9, w10That is:10 + 20In thew9Inside:

Fastest [-OS] : Fastest [-OS] : Fastest [-OS]

  • target ->BuildSettingsSearch:optimization

Fastest [-OS] setseterror {setError [-os] setError [-os] setError [-os] setError

  • What we’re going to do isDebugIt is also selected in modeThe Fastest, Smallest [- OS]Mode:

inThe Fastest, Smallest [- OS]In this mode, you will find that the assembly page shows much less code:Let’s just read:

So what does Smallest[-OS] stand for? It’s the fastest and smallest path.

As we’ll see as we look at the source code, there are a lot of processes that are optimized away – that’s the power of the compiler. This means that we need to adjust to the Release version when we Release the version (Now Apple will automatically help us select the Release environment when we Release the version, and we need to manually set the selection in the early stage). In the Release environment, the system automatically selects the Fastest [-OS] mode to optimize the compiler and save performance.

Alloc source code process

Let’s start with the following code

So I’m going to give you their respective callsallocStack detail diagram after method:

Looking at the call stack diagram above, it’s not hard to see the following problems:

  • Question one: No matter what I amNSObjectClass, or customTCJPerson classcallallocWhy did the method go in the first placeobjc_alloc
  • Problem two:NSObjectDidn’t leaveallocmethods
  • Problem three: customTCJPersonWhy did the class go twicecallAlloc

(3). 1 objc_alloc method

Why is objc_alloc in the first place?

The first explanation: source codeCalls [cls alloc]Tell us when we callallocMethod is called at the bottomobjc_alloc

Let’s take a look at assembly code:The assembly code also tells us that the first call isobjc_alloc.

Third explanation: we need the LLVM source code to help us.

  • Open thellvmSource code file (withXcodeSlow to open, usableVisual Studio CodenamelyVSCodeOpen), searchallocTo findCGObjC.cppfile
  • You can see it’s clearly marked here,[self alloc] -> objc_alloc(self)
  • Function is displayed when receivedallocThe name of theselector, the callEmitObjCAllocContinue the global searchEmitObjCAlloc:

This shows that objc_alloc will be called when we call the alloc method, which is actually forwarded to objc_Alloc by the system at the bottom of the LLVM. LLVM has already been handled when we compile and start.

Let’s verify:

  • First come to the breakpoint of our object of study:

  • Then, inObjc4 sourceIn theobjc_allocMethod implementation:

  • It all ends up in the objc_alloc method, which then calls the callAlloc method.

  • So the answer to question one and question two we think we all know.

(3). 2 callAlloc method

Static ALWAYS_INLINE id ALWAYS_INLINE id static ALWAYS_INLINE ID ALWAYS_INLINE id static ALWAYS_INLINE ID ALWAYS_INLINE ID It’s a way of trading space for time.

#define ALWAYS_INLINE inline __attribute__((ALWAYS_INLINE)) The ALWAYS_INLINE macro forces inline to be turned on

(2) if (slowpath (checkNil &&! CLS) judgment

#define fastpath(x) (__builtin_expect(bool(x), 1)) #define slowpath(x) (__builtin_expect(bool(x), 0))

These two macros use the __builtin_expect function

__builtin_expect(EXP, N) __builtin_expect was introduced by GCC

  • What it does: Allows the programmer to tell the compiler which branch is most likely to be executed. The compiler can optimize the code to reduce the performance degradation caused by instruction jumps. Performance optimization
  • Function: __builtin_expect(EXP, N) means EXP==N with a high probability

Fastpath: __builtin_expect((x),1) means that the value of x is more likely to be true; Slowpath: the __builtin_expect((x),0) definition indicates that the value of x is more likely to be false. There’s more chance of executing the else

In daily development, the compiler can also be optimized by setting the path as follows: Build Setting –> Optimization Level –> Debug –> change None to fastest or smallest

(3) if (fastpath (! CLS ->ISA()->hasCustomAWZ())) judge follow uphasCustomAWZ()Implementation discoverable:FAST_CACHE_HAS_DEFAULT_AWZIs defined as:

Again, it depends on whether there is a default alloc/allocWithZone method in the cache (which is stored in metaclass).

And forNSObjectClasses are a little different: becauseNSObjectInitialization of the system inllvmThis is initialized at compile time. So it’s in cachealloc/allocWithZoneMethod, namelyhasCustomAWZ()forfalsethen! cls->ISA()->hasCustomAWZ()fortrue:

Our custom TCJPerson class was first created without the default alloc/allocWithZone implementation. So continue down to the msgSend message sending process, Call [NSObject Alloc, which is the alloc method, then goes to _objc_rootAlloc, then callAlloc again, and this time because it’s calling NSObject, there’s an alloc/allocWithZone implementation in the cache, and then _objc_rootAll OcWithZone method.

The custom class is entered for the first timecallAllocgomsgSendMessage sending process:Second entrycallAllocgo_objc_rootAllocWithZone:

This explains the third problem: why the custom TCJPerson class calls callAlloc twice.

(3). 3 alloc method

(3). 4 _objc_rootAlloc method

③.5 callAlloc method (custom class secondary entry)

callNSObjectthe[NSObject alloc]It’s not going to come to ③.3-③.4-③.5, only custom classesTCJPersoncall[TCJPerson alloc]Then the process of ③.3-③.4-③

6 _objc_rootAllocWithZone method. (3)

7 _class_createInstanceFromZone (alloc)

1) hasCxxCtor ()

HasCxxCtor () is an implementation of the. Cxx_construct constructor that determines whether the current class or superclass has one

(2) hasCxxDtor ()

HasCxxDtor () is an implementation of the. Cxx_destruct method that determines whether the current class or superclass has one

(3) canAllocNonpointer ()

CanAllocNonpointer () specifies whether a class supports optimized ISA, that is, the isa type distinction. If a class and instances of its parent cannot use ISA of type ISA_t, return false. In Objective-C 2.0, most categories are supported.

(4) size = CLS – > instanceSize (extraBytes)

instanceSize(extraBytes)Calculate the size of memory needed to open up, incomingExtraBytes 0

Jump toinstanceSizeSource code implementation

With breakpoint debugging, it executes tocache.fastInstanceSizemethods

Continue with breakpoint, enteralign16Source code implementation (16-byte alignment algorithm):

Since memory alignment is mentioned (more on that later in this article), let’s warm up:

Memory byte alignment principles

Before explaining why 16-byte alignment is needed, it’s important to understand the principles of memory byte alignment. There are three main points:

  • Data member alignment rules:structorunionThe first data member is placed inoffsetfor0The starting location of each subsequent data member store is to start with an integer multiple of the size of the member or its children (as long as the member has children, such as data, structures, etc.) (e.gintin32 -Machine is4 bytes, from4Integer multiples of address start storage)
  • Data members are structs: If a structure has some struct members, the struct members are stored from an integer multiple of the size of the largest element in the structure (for example:struct aInside therestruct b.bThere is aChar, int, double, thenbShould be from8Integer multiples of start storage)
  • The global alignment rule of the structure: the total size of the structure, i.esizeofThe result must beIs an integer multiple of its largest internal memberWhat is lacking must be made up.

Why is 16-byte alignment needed

  • Improved performance and faster storage: Usually memory is made up of bytes,cpuWhen storing data, isFixed byte blockIs the unit of access. This is a toSpace for timeAn optimization way, so do not take into account the byte unaligned data, greatly saving computing resources, improve the speed of access.
  • It’s safer because in an object, the first propertyisaAccount for8 bytesOf course, an object may have other attributes. If there are no other attributes, it will be reserved8 bytes, i.e.,16-byte alignment.because Apple is now using16-byte alignment(early is8 byte alignmentObjc4-756.2 and previous versions), if not reserved, equivalent to the objectisaAnd other objectsisaNext to, inCPUIt is accessed with16 bytesIs the unit length to access, so it will access adjacent objects, easy to cause access chaos, then16 bytesAfter alignment, it can be sped upCPURead speed, while making access more secure, do not have access chaos

The following toalign16(size_t 8)->(8 + size_t(15)) & ~size_t(15)As an example, the calculation process of the 16-byte alignment algorithm is illustrated as follows

  • First put the raw memory8size_t(15)Add them up and you get8 plus 15 is 23Its binary:0000 0000 0001 0111
  • willsize_t(15)Binary of 15:0000 0000 0000 1111for~ (take the inverse)The operation takes the inverse binary as:1111 1111 1111 0000.~ (invert) are:1 becomes 0,0 becomes 1
  • The final will beBinary of 23Take the binary of the negative result of 15for& (and)Operation,& (and)The rules are:1 for both, 0 for both, the final result is0000 0000 0001 0000namely16(decimal), i.eThe size of memory increases by multiples of 16.

(5) calloc ()

Used to dynamically open up memory, return address pointer. There is no implementation code, but the malloc source code will be covered in the following articles

(The zone basically doesn’t go, Apple scrapped the zone to create space, and the zone input is passed nil.)

According to thesize = cls->instanceSize(extraBytes)Calculate the memory size and apply for the memory sizesizeMemory and assign a value toobj.

  • Print before executionobjonlyCLS class name, the first address has been successfully applied for memory.
  • But it’s not in the format we thought it would be<TCJPerson: 0x0000000101906140>This is becauseThis step simply completes the memory request and returns the first address.
  • The association between classes and addresses is what we’ll talk about nextobj->initInstanceIsa(cls, hasCxxDtor)complete

⑥obj->initInstanceIsa(CLS, hasCxxDtor) class is associated with ISA

If zone=false and fast=true, then (! zone && fast)=true

An internal call to initIsa(CLS, true, hasCxxDtor) initializes the ISA pointer and points the ISA pointer to the requested memory address. The isa pointer is then associated with the CLS class.

afterinitIsaAfter printingobjThe address is bound to the class:

In _class_createInstanceFromZone, three main things are done, 1. Calculate the size of the space required by the object; 2. Create space according to the calculated size and return the address pointer. 3. Initialize ISA to associate it with the current object

At this point, a TCJPerson object is created.

Init source code analysis

theninitWhat did you do?initDo nothing but provide an interface for developers to use factory design patterns

If (self = [super init]) returns nil if (self = [super init]) returns nil if (self = [super init]) returns nil

Is an initializing constructor! Provides construction capabilities such as array initialization dictionaries and buttons for factory design!

New source code analysis

thennewWhat did he do?

  • The bottom line is callallocThe lower thecallAllocCreate an object
  • And then calledinitThe initialization method of
  • newThe method also is for convenience direct!

However, using new is generally not recommended during development, mainly because init methods are sometimes overridden to do some custom operations.

Write in the back

Finally, let’s answer the first two questions:

  • P1, P2, p3 object and address print are consistent, why inconsistent &P print?
  • (2) Why is the address of P4 different from that of P1, P2, p3?

Answer:

Problem 1: The objects and addresses of P1, P2, and P3 are printed the same. Why are the &P prints inconsistent? In fact, alloc does exactly that. We open up memory and the real guy is alloc. P4 (p1, P2, p3); p4 (p1, p2, p3); p4 (p1, p2, p3); Since P1, P2 and P3 are derived from the same alloc, and p4 is derived from new, new will call alloc separately. So they must print differently.

Conclusion:

  • The open memory of the object is handed overallocMethods encapsulate
  • initJust a factory design for subclass overrides: custom implementations that provide something along with initialization
  • newEncapsulates theAlloc and init
  • This article also involves some ideas and methods of exploration:
    • The source code with the
    • Assembly analysis
    • Symbol breakpoint setting
  • Study harmoniously without being impatient. I’m still me, a different color of fireworks.