Last time we explored link loading for dyld, this article begins with a run-time class loading process. This article is only the introduction.

_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? // Read the environment variable environ_init() that affects the runtime; // Bind tls_init() to the thread key; // Run C ++ static constructor static_init(); // Runtime runtime environment initialization runtime_init(); // Initialize libobJC exception_init(); #if __OBJC2__ // Cache condition initialization cache_t::init(); # implementationWithBlock_init(); //map_images: this function is triggered when dyld loads an image file into memory //load_images: this function is triggered when dyld initialifies an image //unmap_image: _dyLD_OBJC_NOTIFY_register (&map_images, load_images, unmap_image) is fired when dyld removes an image. #if __OBJC2__ didCallDyldNotifyRegister = true; #endif }Copy the code

As you can see, _objc_init mainly performs some initialization methods, including

  • environ_init(): Reads environment variables that affect the runtime, printing environment variables to help if needed.
  • tls_init(): binds to a thread key, such as a per-thread data destructor.
  • static_init()Run:C++Static constructors.
  • runtime_init()Initialization of the: Runtime environment, which will be examined in detail later.
  • exception_init()Initialization:libobjcException handling system.
  • cache_t::init(): Cache condition is initialized.
  • _imp_implementationWithBlock_init(): Starts the callback mechanism.
  • _dyld_objc_notify_register: Registration of dyld.

Environ_init Initializes the environment variable

The source code for environ_init is as follows:

Void environ_init (void) {/ / / / / omitted code Print OBJC_HELP and OBJC_PRINT_OPTIONS output. The if (PrintHelp | | PrintOptions) {if (PrintHelp) { _objc_inform("Objective-C runtime debugging. Set variable=YES to enable."); _objc_inform("OBJC_HELP: describe available environment variables"); if (PrintOptions) { _objc_inform("OBJC_HELP is set"); } _objc_inform("OBJC_PRINT_OPTIONS: list which options are set"); } 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

As you can see, this is the initialization of some environment variables. See this code, we can print the environment variables.

  • Remove the condition and call the for loop directly

    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
  • Run export OBJC_hrlp = 1 on the terminal to print environment variables

We may have several Environment Variables in our xcode configuration (target — Edit Scheme — Run –Arguments — Environment Variables) :

  • DYLD_PRINT_STATISTICSSet:DYLD_PRINT_STATISTICSYES, the console will print the App loading time, including the overall loading time and the dynamic library loading time, i.eStart time before main (see pre-main time)You can set it up to see how long it takes. We’ll do thatStart the optimizationWill be used.
  • OBJC_DISABLE_NONPOINTER_ISA: do not usenonpointer isaNonpointer Isa pointer addressAt the end of 1), the generated isa is normal, which we will not change in the project, while exploring the source code can try to look at bothisaStructural differences.
  • OBJC_PRINT_LOAD_METHODS: printingClassCategory+ (void)loadMethod call information,Start the optimizationIt can also be referenced becauseloadHaving too many methods can also slow down a startup.

Tls_init: binds the thread key

The main function is the local thread pool initialization and destruct, source code:

void tls_init(void)
{
#if SUPPORT_DIRECT_THREAD_KEYS
    pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
    _objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}
Copy the code

Static_init runs the system level C++ static constructor

The main thing is to run system-level C++ static constructors. Before dyld calls our static constructor, libc calls the _objc_init method, which means the system-level C++ constructor runs before the custom C++ constructor

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

Runtime_init Runtime environment initialization

The main part is runtime initialization, which is divided into two parts: class initialization and class table initialization, which we will explore in detail in the next article.

void runtime_init(void)
{
    objc::unattachedCategories.init(32);
    objc::allocatedClasses.init();
}
Copy the code

Exception_init Exception initialization

The main is to initialize libobJC’s exception handling system, register the exception handling callback, so as to monitor the exception handling

/***********************************************************************
* exception_init
* Initialize libobjc's exception handling system.
* Called by map_images().
**********************************************************************/
void exception_init(void)
{
    old_terminate = std::set_terminate(&_objc_terminate);
}
Copy the code
  • _objc_terminateThe implementation of the:
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

When crash occurs, the _objc_terminate method is executed and finally uncaught_handler is executed to raise the exception.

  • uncaught_handler:
  • Objc_uncaught_exception_handler objc_setUncaughtExceptionHandler objc_uncaught_exception_handler (fn) {/ / fn for setting the exception handle Objc_uncaught_exception_handler result = uncaught_handler; uncaught_handler = fn; Return result; }Copy the code
  • It is mainly used to handle exceptions. We can add an exception handle to our AppNSSetUncaughtExceptionHandlerTo handle exceptions.

Cache_init: cache initialization

The main thing is cache initialization

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++;
    }
​
    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

_imp_implementationWithBlock_init: Starts the callback mechanism

This method is basically to start the callback mechanism, see the code on the iOS side is not doing anything.

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

_dyLD_OBJC_NOTIFY_register: dyLD register

This is a method that we talked about in the last article

  • Used only by the OBJC runtime

  • Register handlers to be called when the objC image is mapped, unmapped, and initialized

  • Dyld will call the mapped function through an array containing the image file objc-image-info

The role of three parameters:

Map_images: dyld Triggered when an image is loaded into memory

Load_image: dyld This function is triggered when an image is initialized

Unmap_image: dyld This function is triggered when an image is removed

Now that we have an idea of the methods _objc_init calls, let’s explore class loading.

_read_images

The introduction of _read_images

The first argument to _dyLD_OBJC_NOTIFy_register is &map_images, so we’ll start with map_images.

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

Map_images is easy, so let’s call map_images_nolock, and let’s keep exploring.

void map_images_nolock(unsigned mhCount, const char * const mhPaths[], const struct mach_header * const mhdrs[]) { static bool firstTime = YES; header_info *hList[mhCount]; uint32_t hCount; size_t selrefCount = 0; // Perform first-time initialization if necessary. // This function is called before ordinary library initializers. // fixme defer initialization until an objc-using image is found? if (firstTime) { preopt_init(); } if (PrintImages) { _objc_inform("IMAGES: processing %u newly-mapped images... \n", mhCount); } // Find all images with Objective-C metadata. hCount = 0; // Count classes. Size various table based on the total. int totalClasses = 0; int unoptimizedTotalClasses = 0; { uint32_t i = mhCount; while (i--) { const headerType *mhdr = (const headerType *)mhdrs[i]; auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses); if (! hi) { // no objc data in this entry continue; } if (mhdr->filetype == MH_EXECUTE) { // Size some data structures based on main executable's size #if __OBJC2__ // If dyld3 optimized the main executable, then there shouldn't // be any selrefs needed in the dynamic map so we can just init // to a 0 sized map if ( ! hi->hasPreoptimizedSelectors() ) { size_t count; _getObjc2SelectorRefs(hi, &count); selrefCount += count; _getObjc2MessageRefs(hi, &count); selrefCount += count; } #else _getObjcSelectorRefs(hi, &selrefCount); #endif #if SUPPORT_GC_COMPAT // Halt if this is a GC app. if (shouldRejectGCApp(hi)) { _objc_fatal_with_reason (OBJC_EXIT_REASON_GC_NOT_SUPPORTED, OS_REASON_FLAG_CONSISTENT_FAILURE, "Objective-C garbage collection " "is no longer supported."); } #endif } hList[hCount++] = hi; if (PrintImages) { _objc_inform("IMAGES: loading image for %s%s%s%s%s\n", hi->fname(), mhdr->filetype == MH_BUNDLE ? " (bundle)" : "", hi->info()->isReplacement() ? " (replacement)" : "", hi->info()->hasCategoryClassProperties() ? " (has class properties)" : "", hi->info()->optimizedByDyld()?" (preoptimized)":""); } } } // Perform one-time runtime initialization that must be deferred until // the executable itself is found. This needs to be done before // further initialization. // (The executable may not be present in this infoList if the // executable does not contain Objective-C code but Objective-C // is dynamically loaded later. if (firstTime) { sel_init(selrefCount); arr_init(); #if SUPPORT_GC_COMPAT // Reject any GC images linked to the main executable. // We already rejected the app itself above. // Images loaded after launch will be rejected by dyld. for (uint32_t i = 0; i < hCount; i++) { auto hi = hList[i]; auto mh = hi->mhdr(); if (mh->filetype ! = MH_EXECUTE && shouldRejectGCImage(mh)) { _objc_fatal_with_reason (OBJC_EXIT_REASON_GC_NOT_SUPPORTED, OS_REASON_FLAG_CONSISTENT_FAILURE, "%s requires Objective-C garbage collection " "which is no longer supported.", hi->fname()); }} #endif #if TARGET_OS_OSX // Disable +initialize fork safety if the app is too old (< 10.13) fork safety if the app has a // __DATA,__objc_fork_ok section. // if (! dyld_program_sdk_at_least(dyld_platform_version_macOS_10_13)) { // DisableInitializeForkSafety = true; // if (PrintInitializing) { // _objc_inform("INITIALIZE: disabling +initialize fork " // "safety enforcement because the app is " // "too old.)"); // } // } for (uint32_t i = 0; i < hCount; i++) { auto hi = hList[i]; auto mh = hi->mhdr(); if (mh->filetype ! = MH_EXECUTE) continue; unsigned long size; if (getsectiondata(hi->mhdr(), "__DATA", "__objc_fork_ok", &size)) { DisableInitializeForkSafety = true; if (PrintInitializing) { _objc_inform("INITIALIZE: disabling +initialize fork " "safety enforcement because the app has " "a __DATA,__objc_fork_ok section"); } } break; // assume only one MH_EXECUTE image} #endif} if (hCount > 0) {// unoptimizedTotalClasses); } firstTime = NO; // Call image load funcs after everything is set up. for (auto func : loadImageFuncs) { for (uint32_t i = 0; i < mhCount; i++) { func(mhdrs[i]); }}}Copy the code

The map_images_NOLock method is long, and a large part of the code is preparing to read the image. The core call is the _read_images method.

_read_images analysis

_read_images has more than 300 lines of code, so it’s easy to get lost reading line by line. Let’s hide the {} statement inside the method first, and see what this method does globally:

The feature of this method is that it executes a block of code until it has finished executing itts.log()What does the function print code snippet do so that we have a general idea_read_imagesMain process:

  • Conditional control to perform a load
  • Fixed @selector confusion during precompile
  • Error messy class handling
  • Fixed remapping of some classes that were not loaded by the image file
  • Fixed some messages
  • When we have a protocol in our class: readProtocol
  • Fixed protocol not being loaded
  • Classification process
  • Class loading processing
  • Classes that have not been processed optimize those that have been violated

First time tasks: conditional control to perform a load

if (! doneOnce) { doneOnce = YES; launchTime = YES; #if SUPPORT_NONPOINTER_ISA // Disable non-pointer isa under some conditions. # if SUPPORT_INDEXED_ISA // Disable nonpointer isa if any image contains old Swift code for (EACH_HEADER) { if (hi->info()->containsSwift() && hi->info()->swiftUnstableVersion() < objc_image_info::SwiftVersion3) { DisableNonpointerIsa = true; if (PrintRawIsa) { _objc_inform("RAW ISA: Disabling non-pointer isa because "" The app or a framework contains Swift code" "older than Swift 3.0"); } break; }} # endif # if TARGET_OS_OSX // Disable non-pointer isa is too old // (linked before OSX 10.11) // if (! dyld_program_sdk_at_least(dyld_platform_version_macOS_10_11)) { // DisableNonpointerIsa = true; // if (PrintRawIsa) { // _objc_inform("RAW ISA: disabling non-pointer isa because " // "the app is too old."); // } // } // Disable non-pointer isa if the app has a __DATA,__objc_rawisa section // New apps that load old extensions may need this. for (EACH_HEADER) { if (hi->mhdr()->filetype ! = MH_EXECUTE) continue; unsigned long size; if (getsectiondata(hi->mhdr(), "__DATA", "__objc_rawisa", &size)) { DisableNonpointerIsa = true; if (PrintRawIsa) { _objc_inform("RAW ISA: disabling non-pointer isa because " "the app has a __DATA,__objc_rawisa section"); } } break; // assume only one MH_EXECUTE image } # endif #endif if (DisableTaggedPointers) { disableTaggedPointers(); } / / / initializes TaggedPointer confuse initializeTaggedPointerObfuscator (); if (PrintConnecting) { _objc_inform("CLASS: found %d classes during launch", totalClasses); } // namedClasses // Preoptimized classes don't go in this table. // 4/3 is NXMapTable's load factor // objc::unattachedCategories.init(32); // objc::allocatedClasses.init(); Int namedClassesSize = (isPreoptimized()? unoptimizedTotalClasses : totalClasses) * 4 / 3; Gdb_objc_realized_classes = NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize); ts.log("IMAGE TIMES: first time tasks"); }Copy the code
  • Initialize a small object (TaggedPointer)
  • Create a master table for all classes, noting that this table andruntime_initIn theallocatedClassessThis is a table for all classes, whereasallocatedClassessIs already implemented (allocated) class.

Fix up Selector References: Fix @selector confusion during precompilation

static size_t UnfixedSelectors; { mutex_locker_t lock(selLock); for (EACH_HEADER) { if (hi->hasPreoptimizedSelectors()) continue; bool isBundle = hi->isBundle(); 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); if (sels[i] ! = sel) { sels[i] = sel; } } } } ts.log("IMAGE TIMES: fix up selector references");Copy the code

Selecotr is the name + address of the class.

  • SEL *sels = _getObjc2SelectorRefs(hi, &count);fromMach-oFile read
  • SEL sel = sel_registerNameNoLock(name, isBundle);fromdyldAfter the link
  • If the twoselSame name, different addressfix up.

Discover Classes: Error-cluttered class handling

// Discover classes. Fix up unresolved future classes. Mark bundle classes. bool hasDyldRoots = dyld_shared_cache_some_image_overridden(); for (EACH_HEADER) { if (! mustReadClasses(hi, hasDyldRoots)) { // Image is sufficiently optimized that we need not call readClass() continue; Classref_t const * classList = _getObjc2ClassList(hi, &count); bool headerIsBundle = hi->isBundle(); bool headerIsPreoptimized = hi->hasPreoptimizedClasses(); for (i = 0; i < count; Class CLS = (Class)classlist[I]; Class newCls = readClass(CLS, headerIsBundle, headerIsPreoptimized); 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; } } } ts.log("IMAGE TIMES: discover classes");Copy the code

Fix unhandled future classes. Name the class association. Let’s focus on readClass:

readClass

Class readClass(CLS, bool headerIsBundle, bool headerIsPreoptimized) {/// Const char *mangledName = CLS ->nonlazyMangledName(); const char *customerClassName = "JSPerson"; If (STRCMP (mangledName, customerClassName) == 0) {printf("%s -: class to study: - %s\n",__func__,mangledName); } if (missingWeakSuperclass(cls)) { // No superclass (probably weak-linked). // Disavow any knowledge of this subclass. if (PrintConnecting) { _objc_inform("CLASS: IGNORING class '%s' with " "missing weak-linked superclass", cls->nameForLogging()); } addRemappedClass(cls, nil); cls->setSuperclass(nil); return nil; } cls->fixupBackwardDeployingStableSwift(); Class replacing = nil; if (mangledName ! = nullptr) {if (Class newCls = popFutureNamedClass(mangledName))  as a future class. // Copy objc_class to future class's struct. // Preserve future's rw data block. if (newCls->isAnySwift()) { _objc_fatal("Can't complete future class request for '%s' " "because the real class is too big.", cls->nameForLogging()); } class_rw_t *rw = newCls->data(); const class_ro_t *old_ro = rw->ro(); memcpy(newCls, cls, sizeof(objc_class)); // Manually set address-discriminated ptrauthed fields // so that newCls gets the correct signatures. newCls->setSuperclass(cls->getSuperclass()); newCls->initIsa(cls->getIsa()); rw->set_ro((class_ro_t *)newCls->data()); newCls->setData(rw); freeIfMutable((char *)old_ro->getName()); free((void *)old_ro); addRemappedClass(cls, newCls); replacing = cls; cls = newCls; } } if (headerIsPreoptimized && ! replacing) { // class list built in shared cache // fixme strict assert doesn't work because of duplicates // ASSERT(cls  == getClass(name)); ASSERT(mangledName == nullptr || getClassExceptSomeSwift(mangledName)); } else {if (mangledName) {//some Swift generic classes can lazily generate their names addNamedClass(CLS, mangledName, replacing); } else {/// Class meta = CLS ->ISA(); const class_ro_t *metaRO = meta->bits.safe_ro(); ASSERT(metaRO->getNonMetaclass() && "Metaclass with lazy name must have a pointer to the corresponding nonmetaclass."); ASSERT(metaRO->getNonMetaclass() == cls && "Metaclass nonmetaclass pointer must equal the original class."); } /// add to the class table addClassTableEntry(CLS); } // for future reference: shared cache never contains MH_BUNDLEs if (headerIsBundle) { cls->data()->flags |= RO_FROM_BUNDLE; cls->ISA()->data()->flags |= RO_FROM_BUNDLE; } return cls; }Copy the code

Let’s start by looking at CLS with the return value. CLS is also an input parameter. After readClass, CLS has a name.

conclusion

In this article, we mainly made a simple review of the process of objC_init, and analyzed the function of readClass method. In the next article, we began to analyze the class loading process in detail.