1. Review

In the first two posts, we have explored the underlying source code of the Dyld dynamic linker, but we do not know how dyld links images to memory. The next few posts will focus on exploring the process.

IOS Underlying Exploration of DYLD (1): Dynamic linker process analysis

IOS underlying exploration of DYLD (2): Dynamic linker process source code analysis

The _objc_init method registers a callback function with dyld, so let’s add a little more to this by looking at the _objc_init method.

2. _objc_init Simple analysis

Let’s start with the underlying source code for _objc_init

void _objc_init(void)
{
    **static* * * *bool** initialized = **false* *; 支那if** (initialized) **return* *; initialized = **true* *;// fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    runtime_init();
    exception_init();

#if __OBJC2__
    cache_t::init();
#endif
    _imp_implementationWithBlock_init();
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
 
#if __OBJC2__
    didCallDyldNotifyRegister = true ;
#endif
}

Copy the code
  • environ_init(): Reads environment variables that affect the runtime. You can also print environment variable help if desired.
  • tls_init() : About threadskey– such as a per-thread data destructor
  • static_init()Run:C ++Static constructors. Before dyld calls our static constructor,libcWill be called_objc_init ()So we have to do it ourselves
  • lock_init(): No rewrite, adoptC++ The characteristics of the
  • exception_init ()Initialize libobJC’s exception handling system
  • cache_init(): Cache condition is initialized
  • runtime_init() : runtimeRuntime environment initialization, which is mainly

Is: unattachedCategories, allocatedClasses later analysis

  • _imp_implementationWithBlock_init: Starts the callback mechanism. Usually this doesn’t do anything, because all initializations are

Is lazy, but for some processes, we can’t wait to load trampolines dylib.

2.1 environ_init

Environ_init () : print out all environment variables in the console under PrintHelp or PrintOptions.

 void  environ_init(void) 

{    
     // Print OBJC_HELP and OBJC_PRINT_OPTIONS output.
    if(PrintHelp || PrintOptions) { 
         ...
    if  (PrintOptions) {
        _objc_inform("OBJC_PRINT_OPTIONS is set");
    }

    for(size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++){
             const  option_t *opt = &Settings[i];            
    if(PrintHelp) _objc_inform("%s: %s", opt->env, opt->help);
    if(PrintOptions && *opt->var) _objc_inform("%s is set", opt->env); }}}Copy the code

So now remove the if(PrintHelp) and if(PrintOptions && *opt->var) criteria and see what the console prints.

for(size_t i = 0; i <sizeof(Settings)/sizeof(Settings[0]); i++) {
      const option_t *opt = &Settings[i];
     _objc_inform("%s: %s", opt->env, opt->help);
     _objc_inform("%s is set", opt->env);
  }
Copy the code

The print result is as follows:

objc[2964]: OBJC_PRINT_IMAGES: log image and library names as they are loaded
objc[2964]: OBJC_PRINT_IMAGES is set
objc[2964]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps
objc[2964]: OBJC_PRINT_IMAGE_TIMES is set
objc[2964]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods
objc[2964]: OBJC_PRINT_LOAD_METHODS is set
objc[2964]: OBJC_PRINT_INITIALIZE_METHODS: log calls to class +initialize methods
objc[2964]: OBJC_PRINT_INITIALIZE_METHODS is set... Omit... objc[2964]: OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields
objc[2964]: OBJC_DISABLE_NONPOINTER_ISA is set
objc[2964]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY: disable safety checks for +initialize after fork
objc[2964]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY is set
objc[2964]: OBJC_DISABLE_FAULTS: disable os faults
objc[2964]: OBJC_DISABLE_FAULTS is set
objc[2964]: OBJC_DISABLE_PREOPTIMIZED_CACHES: disable preoptimized caches
objc[2964]: OBJC_DISABLE_PREOPTIMIZED_CACHES is set
objc[2964]: OBJC_DISABLE_AUTORELEASE_COALESCING: disable coalescing of autorelease pool pointers
objc[2964]: OBJC_DISABLE_AUTORELEASE_COALESCING is set
objc[2964]: OBJC_DISABLE_AUTORELEASE_COALESCING_LRU: disable coalescing of autorelease pool pointers using look back N strategy
objc[2964]: OBJC_DISABLE_AUTORELEASE_COALESCING_LRU is set
Copy the code

So many environmental variables, looking at dizzy, have we familiar with ah??

There are such as:

  • OBJC_PRINT_IMAGESPrint image file
  • OBJC_DISABLE_NONPOINTER_ISADetermine if it is an optimized pointer
  • OBJC_PRINT_LOAD_METHODSPrint out everything in the programloadMethods and so on.

So how do I view environment variables without objc source code? You can use the terminal command export OBJC_HELP=1 to print the following

2.1.1 Using Terminal Commands to View environment variables

2.1.2 Setting environment variables for Xcode projects

Where environment variables are configured in Xcode: select the running target–> Edit Scheme… –> Run –> Arguments –> Environment Variables

2.1.2.1 OBJC_DISABLE_NONPOINTER_ISA

Set the environment variable OBJC_DISABLE_NONPOINTER_ISA to indicate whether ISA pointer optimization is enabled. YES indicates a pure pointer, and NO indicates that the optimized pointer is nonpointer ISA. ISA is introduced in the initIsa(below) blog, which explores the nature of objects and the association of classes in iOS.

Let’s look at Nonpointer ISA without setting the environment variable

The low isa bit 0 is 1, indicating that the ISA is optimized, and other data exists in the high ISA bit

Set the environment variable OBJC_DISABLE_NONPOINTER_ISA = YES to YES and see how ISA looks.

The low isa bit 0 is 0, indicating that isa isa pure pointer, and the high isa bit has no data other than CLS.

2.1.2.2 OBJC_PRINT_LOAD_METHODS

The environment variable OBJC_PRINT_LOAD_METHODS prints out all the load methods in the program, adds the load method to the user-defined class, and sets the environment variable OBJC_PRINT_LOAD_METHODS = YES

+[JPStudent Load] This is our custom JPStudent load method, the rest are system level load methods. If you have too many load methods and your application starts slowly, or if someone is doing something hidden in the load method, you can use this environment variable to check which class implements the load method.

2.2 tls_init

Tls_init bindings for thread keys, such as destructors for per-thread data.

 void  tls_init(void)
{ 
#if SUPPORT_DIRECT_THREAD_KEYS
    // Create the jan cache pool for the thread
    pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
    // The destructor
    _objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}

Copy the code

2.3 static_init

The global static C++ function, before dyld calls our static constructor, libobjc will call _objc_init, it will call its own C++ constructor, in short libobjc will call its own global C++ function, because the global constructor is a very important function, in order to be timely, On their first call up, from the bottom source annotations can also know, first call their own global static C++ functions to go dyld.

/*********************************************************************** * static_init * Run C++ static constructor functions. * libc calls _objc_init() before dyld would call our static constructors, * so we have to do it ourselves. **********************************************************************/
static void static_init()
{
    size_t count;
    auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
    for(size_t i = 0; i < count; i++) {
        inits[i]();
    }
    auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count);
    for(size_t i = 0; i < count; i++) { UnsignedInitializer init(offsets[i]); init(); }}Copy the code

2.4 runtime_init

Runtime runtime environment initialization, which is mainly the initialization of unattachedCategories and allocatedClasses tables.

void runtime_init(void)
{  
   objc::unattachedCategories.init(32);// Initialization of the classification table
   objc::allocatedClasses.init();// Class table initialization
}

Copy the code

2.5 exception_init

Initialize the libobJC library’s exception handling, which is similar to objc registering a callback to Dyld, to allow you to handle exceptions.

 void  exception_init(void)
 {
  old_terminate = std::set_terminate(&_objc_terminate);
 }

Copy the code

When your code crashes, a crash is not a code error, it doesn’t necessarily crash. Crash is an abnormal signal sent by the system when the upper-level code does not conform to the underlying logic and rules of the Apple system. The _objc_terminate method goes in by having an exception.

/***********************************************************************
* _objc_terminate
* Custom std::terminate handler.
*
* The uncaught exception callback is implemented as a std::terminate handler. 
* 1. Check if there's an active exception
* 2. If so, check if it's an Objective-C exception
* 3. If so, call our registered callback with the object.
* 4. Finally, call the previous terminate handler.
**********************************************************************/
static void (*old_terminate)(void) = nil;
static void _objc_terminate(void)
{
    if (PrintExceptions) {
        _objc_inform("EXCEPTIONS: terminating");
    }
    if (! __cxa_current_exception_type()) {
        // No current exception.
        (*old_terminate)();
    }
    else {
        // There is a current exception. Check if it's an objc exception.
        @try {
            __cxa_rethrow();
        } @catch (id e) {
            // It's an objc object. Call Foundation's handler, if any.
            (*uncaught_handler)((id)e);
            (*old_terminate)();
        } @catch(...). {// It's not an objc object. Continue to C++ terminate.(*old_terminate)(); }}}Copy the code

(*uncaught_handler)((id)e) is found in the _objc_terminate method. It throws the exception and searches globally for uncaught_handler

objc_uncaught_exception_handler 
objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
{
    objc_uncaught_exception_handler result = uncaught_handler;
    uncaught_handler = fn;
    return result;
}

Copy the code

Uncaught_handler = fn tells the programmer to pass a handle to the function. Fn can be a function defined by the programmer, and then the callback can handle the exception thrown by the program itself.

2.6 cache_t: : init

Initialization of a cache condition

void cache_t::init()
{
#if HAVE_TASK_RESTARTABLE_RANGES
    mach_msg_type_number_t count = 0;
    kern_return_t kr;
    while (objc_restartableRanges[count].location) {
        count++;
    }
    // Enable cache
    kr = task_restartable_ranges_register(mach_task_self(),
                                         objc_restartableRanges, count)
    if (kr == KERN_SUCCESS) return;
    _objc_fatal("task_restartable_ranges_register failed (result 0x%x: %s)",
                kr, mach_error_string(kr));
#endif // HAVE_TASK_RESTARTABLE_RANGES
}

Copy the code

2.7 _imp_implementationWithBlock_init

This is the trigger callback mechanism, which usually does nothing. For example, the trampolines dylib and the implementationwithblock_init implementations of the trampolines dylib system are instantiated in the following way:

void
_imp_implementationWithBlock_init(void)
{
#if TARGET_OS_OSX
    // Eagerly load libobjc-trampolines.dylib in certain processes. Some
    // programs (most notably QtWebEngineProcess used by older versions of
    // embedded Chromium) enable a highly restrictive sandbox profile which
    // blocks access to that dylib. If anything calls
    // imp_implementationWithBlock (as AppKit has started doing) then we'll
    // crash trying to load it. Loading it here sets it up before the sandbox
    // profile is enabled and blocks it.
    //
    // This fixes EA Origin (rdar://problem/50813789)
    // and Steam (rdar://problem/55286131)
    if (__progname &&
        (strcmp(__progname, "QtWebEngineProcess") = =0 ||
         strcmp(__progname, "Steam Helper") = =0)) {
        Trampolines.Initialize();
    }
#endif
}

Copy the code

2.8 _dyld_objc_notify_register

_dyLD_OBJC_NOTIFy_register registers callbacks with dyld, which is very important!!

  • _dyld_objc_notify_registerSource code implementation is as follows:
// _dyld_objc_notify_init
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init 
init, _dyld_objc_notify_unmapped unmapped)
{
// record functions to call
sNotifyObjCMapped = mapped;
sNotifyObjCInit = init;
sNotifyObjCUnmapped = unmapped;
// call 'mapped' function with all images mapped so far
try {
    notifyBatchPartial(dyld_image_state_bound, true.NULL.false.true);
}
catch (const char* msg) {
// ignore request to abort during registration
}
// call 'init' function on all images already init'ed (below libSystem)
for(std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it ! = sAllImages.end(); it++) { ImageLoader* image = *it;if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() )  {
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0.0); (*sNotifyObjCInit)(image->getRealPath(), image->machHeader()); }}}Copy the code

In fact, in the iOS underlying exploration of dyLD (2): Dynamic linker process source analysis blog has also been introduced, so let’s review.

  • &map_images:dyldwillimageThis function is called when an image file is loaded into memory.
  • load_images:dyldInitialize allimageThe image file file is called.
  • unmap_imageWill:imageCalled when an image file is removed.

The load_images method actually calls the load method, the map_image method, and the map_images method is a pointer passed to the address of the same block of implementation, so if there’s a change, you know it first. In dyld, the place where sNotifyObjcmcmelementary is called is in notifyBatchPartial, which is called in registerObjCNotifiers when OBJC initializes the register notification, So map_images is called first and load_images is called first.

So let’s go to map_images

3. map_images

3.1 map_images

Enter the map_images source code

void
map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    mutex_locker_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);

}

Copy the code

3.2 map_images_nolock

  • Enter theOBJC_PRINT_LOAD_METHODSThe source code

We are looking for nothing more than the image file loading and mapping related, the rest of the code need not look at, directly folded, go to the key code to see.

3.3 read_images

Looking at the _read_images method, I realized that there was too much code to start with, so I had to analyze it globally, so I folded it up again.

void _read_images(header_info **hList, uint32_t hCount, int 
totalClasses, int 
unoptimizedTotalClasses)
{
   ... // Omit some code
#define EACH_HEADER \
    hIndex = 0;         \
    hIndex < hCount && (hi = hList[hIndex]); \
    hIndex++
    // Conditional control to perform a load
    if(! doneOnce) { ... }// Fix the '@selector' mess in precompile
    // That is, different classes have the same method but the same method address is different
    // Fix up @selector references
    staticsize_t UnfixedSelectors; {... } ts.log("IMAGE TIMES: fix up selector references");
    
    // Error messy class handling
    // Discover classes. Fix up unresolved future classes. Mark bundle classes.
    bool hasDyldRoots = dyld_shared_cache_some_image_overridden();
    for (EACH_HEADER) { ... }
    ts.log("IMAGE TIMES: discover classes");
    
    // Fix remapping some classes that were not loaded by the image file
    // Fix up remapped classes
    // Class list and nonlazy class list remain unremapped.
    // Class refs and super refs are remapped for message dispatching.
    if(! noClassesRemapped()) { ... } ts.log("IMAGE TIMES: remap classes");

#if SUPPORT_FIXUP
    // Fix some messages
    // Fix up old objc_msgSend_fixup call sites
    for (EACH_HEADER) { ... }
    ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");

#endif
    // When there is a protocol in the class: 'readProtocol'
    // Discover protocols. Fix up protocol refs.
    for (EACH_HEADER) { ... }
    ts.log("IMAGE TIMES: discover protocols");
    
    // Fix the protocol that was not loaded
    // Fix up @protocol references
    // Preoptimized images may have the right 
    // answer already but we don't know for sure.
    for (EACH_HEADER) { ... }
    ts.log("IMAGE TIMES: fix up @protocol references");
    
    // Classification processing
    // Discover categories. Only do this after the initial category
    // attachment has been done. For categories present at startup,
    // discovery is deferred until the first load_images call after
    // the call to _dyld_objc_notify_register completes.  
    if (didInitialAttachCategories) { ... }
    ts.log("IMAGE TIMES: discover categories");
    
    // Class loading processing
    // Category discovery MUST BE Late to avoid potential races
    // when other threads call the new category code befor
    // this thread finishes its fixups.
    // +load handled by prepare_load_methods()
    // Realize non-lazy classes (for +load methods and static instances)
    for (EACH_HEADER) { ... }
    ts.log("IMAGE TIMES: realize non-lazy classes");
    
    // For classes that have not been processed, optimize those that have been violated
    // Realize newly-resolved future classes, in case CF manipulates them
    if (resolvedFutureClasses) { ... }
    ts.log("IMAGE TIMES: realize future classes"); .#undef EACH_HEADER

}

Copy the code

In general, it can be seen that some log printouts, mainly as follows:

  • Conditional control for a load
  • Fixed precompile phase@selectorThe confusion of the question
  • Error messy class handling
  • Fixed remapping of some classes that were not loaded by the image file
  • Fixed some messages
  • When there is a protocol in the class:readProtocol
  • Fixed protocol not being loaded
  • Classified treatment
  • Class loading processing
  • Classes that have not been processed, optimize those that have been violated

The following focuses on the analysis of a few more main;

3.3.1 doneOnce

  • doneOnce
 if(! doneOnce) { doneOnce =YES; DoneOnce = YES = YES
        launchTime = YES; .// Preoptimized classes don't go in this table.
        // 4/3 is NXMapTable's load factor
        int namedClassesSize = 
        (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;  
        // Create a hash table to hold all classes
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
        ts.log("IMAGE TIMES: first time tasks");
  }

Copy the code

You load it once and you don’t enter it again. I’m going to create a table called gdb_objc_realized_classes. It’s going to hold all the classes, implemented and unimplemented. It’s going to be a master table.

3.3.1 UnfixedSelectors

  • UnfixedSelectors
static size_t UnfixedSelectors;
{
    mutex_locker_t lock(selLock);
    for (EACH_HEADER) {
        if (hi->hasPreoptimizedSelectors()) continue;
        bool isBundle = hi->isBundle();
        // Get the list of method names from the macho file
        SEL *sels = _getObjc2SelectorRefs(hi, &count);
        UnfixedSelectors += count;
        for (i = 0; i < count; i++) {
            const char *name = sel_cname(sels[i]);
            SEL sel = sel_registerNameNoLock(name, isBundle);// Get the method name from dyld
            if(sels[i] ! = sel) { sels[i] = sel; }}}}Copy the code

The same method may exist in different classes, but the same method address is different.

Sels [I] is for _getObjc2SelectorRefs, MachO has relative displacement and offset, sel is for sel_registerNameNoLock, dyld is for dyld, dyld is for dyld. Because methods are stored in classes, and each class has a different location, methods have a different address, so you have to fix the mess of methods.

for (EACH_HEADER) {
    if (! mustReadClasses(hi, hasDyldRoots)) {
        // Image is sufficiently optimized that we need not call readClass()
        continue;
    }
    // Read the class list from Macho
    classref_t const *classlist = _getObjc2ClassList(hi, &count);
    bool headerIsBundle = hi->isBundle();
    bool headerIsPreoptimized = hi->hasPreoptimizedClasses();

    for (i = 0; i < count; i++) {
        Class cls = (Class)classlist[i];
        Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
        
        // The class information is confused. The class may be moved at runtime, but it is not deleted
        if(newCls ! = cls && newCls) {// Class was moved but not deleted. Currently this occurs 
            // only when the new class resolved a future class.
            // Non-lazily realize the class below.
            resolvedFutureClasses = (Class *)
                realloc(resolvedFutureClasses, 
                        (resolvedFutureClassCount+1) * sizeof(Class)); resolvedFutureClasses[resolvedFutureClassCount++] = newCls; }}}Copy the code

CLS refers to a memory address, newCls has not yet been assigned a value, but the system will randomly assign a dirty address to newCls. The breakpoint goes down to the if judgment, and the console LLDB debugs again to see what the value is after the assignment:

As can be seen from the above verificationreadClassMethod is used to associate the class name and address, so now go to the source code

3.3.3 readClass

  Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    // Get the class name
    const char *mangledName = cls->nonlazyMangledName();
    if (missingWeakSuperclass(cls)) { ... }
    cls->fixupBackwardDeployingStableSwift();
    Class replacing = nil;

    if(mangledName ! = nullptr) { ... }if(headerIsPreoptimized && ! replacing) {... }else {
        if (mangledName) { 
        //some Swift generic classes can lazily generate their names
            // Associate the class name with the address
            addNamedClass(cls, mangledName, replacing);
        } else { ...}
        // Insert the associated class into another hash table
        addClassTableEntry(cls);
    }
    // for future reference: shared cache never contains MH_BUNDLEs
    if (headerIsBundle) { ... }
    return cls;

}
  
Copy the code
  • nonlazyMangledNameGets the class name.
  • addNamedClassBind the class name and address association.
  • addClassTableEntryInsert the associated class into the hash table, which is all initialized classes.

For research purposes, we can filter the CLS to filter out the class we want to study, JPStudent.

CLS ->nonlazyMangledName();

  • nonlazyMangledName
// Get the class's mangled name, or NULL if the class has a lazy
    // name that hasn't been created yet.
    const char *nonlazyMangledName() const {
        return bits.safe_ro()->getName();
    }
Copy the code
  • safe_ro
// Get the class's ro data, even in the presence of concurrent realization.
    // fixme this isn't really safe without a compiler barrier at least
    // and probably a memory barrier when realizeClass changes the data field
    const class_ro_t *safe_ro() const {
        class_rw_t *maybe_rw = data();
        if (maybe_rw->flags & RW_REALIZED) {
            // maybe_rw is rw
            return maybe_rw->ro();
        } else {
            // maybe_rw is actually ro
            return(class_ro_t *)maybe_rw; }}Copy the code
  • AddNamedClass binds the class name to the address association
static void addNamedClass(Class cls, const char *name, Class replacing = nil)
{
    runtimeLock.assertLocked();
    Class old;
    if((old = getClassExceptSomeSwift(name)) && old ! = replacing) { inform_duplicate(name, old, cls);// getMaybeUnrealizedNonMetaClass uses name lookups.
        // Classes not found by name lookup must be in the
        // secondary meta->nonmeta table.
        addNonMetaClass(cls);
    } else{ NXMapInsert(gdb_objc_realized_classes, name, cls); } ASSERT(! (cls->data()->flags & RO_META));// wrong: constructed classes are already realized when they get here
    // ASSERT(! cls->isRealized());
}
Copy the code

Update the gdb_objc_realized_classes hash table with the NXMapInsert method, key being name and value being CLS

  • NXMapInsert implements part of the code
void *NXMapInsert(NXMapTable *table, const void *key, const void *value) {
    MapPair	*pairs = (MapPair *)table->buckets;
    unsigned	index = bucketOf(table, key);
    MapPair	*pair = pairs + index;
    if (key == NX_MAPNOTAKEY) {
	_objc_inform("*** NXMapInsert: invalid key: -1\n");
	return NULL;
    }

    unsigned numBuckets = table->nbBucketsMinusOne + 1;

    if (pair->key == NX_MAPNOTAKEY) {
	pair->key = key; pair->value = value;
	table->count++;
	if (table->count * 4 > numBuckets * 3) _NXMapRehash(table);
	return NULL; }... Omit the code...... }Copy the code
  • addClassTableEntry
static void
addClassTableEntry(Class cls, bool addMeta = true)
{
    runtimeLock.assertLocked();
    // This class is allowed to be a known class via the shared cache or via
    // data segments, but it is not allowed to be in the dynamic 
    //table already.
    // allocatedClasses
    auto &set = objc::allocatedClasses.get();
    ASSERT(set.find(cls) == set.end());
    if(! isKnownClass(cls)) set.insert(cls);if (addMeta)
        // Insert metaclass into hash table
        addClassTableEntry(cls->ISA(), false);
}

Copy the code

AllocatedClasses is initialized in the runtime_init runtime environment in _objc_init, mainly with unattachedCategories and allocatedClasses tables, At this point the addClassTableEntry operation is inserted into the allocatedClasses table. At the same time, the metaclasses are also processed accordingly. When the class is processed, the metaclasses also have to be processed.

Through source code analysis, breakpoint debugging, found that the rW and ro to obtain and assign operations, not in the readClass, so back to _read_images to see.

I’m going to follow the breakpoint of the _read_images method, and I’m going to go step by step, and I see that I’m finally at realizeClassWithoutSwift, so I’m going to go in and see.

  • realizeClassWithoutSwift
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    class_rw_t *rw;
    Class supercls;
    Class metacls;

    if(! cls)return nil;
    if (cls->isRealized()) {
        validateAlreadyRealizedClass(cls);
        returncls; } ASSERT(cls == remapClass(cls)); . Omit the code........... }Copy the code

Well, it turns out that the fetching and assigning of rw and ro is handled in realizeClassWithoutSwift, and the whole process is connected from dyld to _objc_init to read_images, The next few blog posts will also explore the underlying principles of class loading!

Stay tuned! Stay tuned!

4. To summarize

  • Environment variables can be set through Xcode and terminal commandsexport OBJC_HELP=1Print to see
  • _read_imagesFor somelogPrinting of information
  • readClassAssociate the class name with the address
  • rwThe assignment androThe acquisition is not inreadClassinside
  • _dyld_objc_notify_registerExecution flow chart

More to come

🌹 just like it 👍🌹

🌹 feel learned, can come a wave, collect + concern, comment + forward, lest you can’t find me next 😁🌹

🌹 welcome everyone to leave a message to exchange, criticize and correct, learn from each other 😁, improve self 🌹