preface

In the previous chapter we looked at ios startup loading, when we saw that Apple calls _dyLD_OBJc_notify_register after _objc_init and passes &map_images and load_images. After the image file is initialized, The map_images function is called. Then load_images is called, and today we’ll explore the contents of a map_imags function.

To explore the map_images

void map_images(unsigned count, const char * const paths[], Const struct mach_header * const MHDRS []) {/* recursive_mutex_locker_t lock(loadMethodLock) is used in old runtime code; Mutex_locker_t lock(runtimeLock); return map_images_nolock(count, paths, mhdrs); }Copy the code

Map_images_nolock: map_images_nolock: map_images_nolock: map_images_nolock: map_images_nolock: map_images_nolock

1.1 preopt_init与sel_init

Map_images_nolock first disables SEL optimization by calling preopt_init(), because selectors in the dyld shared cache are not trusted.

void preopt_init(void){omitted... disableSharedCacheOptimizations(); Omit... }//sel_init() determines that the selector in the dyld shared cache is untrusted
void disableSharedCacheOptimizations(void)
{
    fixed_up_method_list = OBJC_FIXED_UP_outside_dyld;
}
Copy the code

So we call sel_init() in map_images_nolock, initialize the internal selector table and register the c++ constructor and destructor methods

/***********************************************************************
* sel_init
* Initialize selector tables and register selectors used internally.
**********************************************************************/
void sel_init(size_t selrefCount)
{
    namedSelectors.init((unsigned)selrefCount);
    // Register selectors used by libobjc
    mutex_locker_t lock(selLock);
    
    SEL_cxx_construct = sel_registerNameNoLock(".cxx_construct", NO);
    SEL_cxx_destruct = sel_registerNameNoLock(".cxx_destruct", NO);
}
Copy the code

1.2 arr_init

By calling arr_init function in map_images_NOLock, the automatic release of pool pages, the hash table containing reference count table and weak reference table, and the initialization of associated object management table are completed

void arr_init(void) { AutoreleasePoolPage::init(); // The automatic release pool page initializes sidetablesMap.init (); // Hash table initialization _objc_associations_init(); // Initialize the associated object management table}Copy the code

Take a quick look at the structure of SideTablesMap and explore it later

static objc::ExplicitInit<StripedMap<SideTable>> SideTablesMap; struct SideTable { spinlock_t slock; // Spin lock RefcountMap refcnts; // Weak_table_t weak_table; // Weak reference table omit... }Copy the code

1.3 _read_images

After the previous series of initialization preparations, you enter the read_images process. In read_images

1.3.1 Conditional control for one-time loading,Disable Nonpointer ISA for older versions of Swift.Initialize tagged pointer.Create a mapping table for the class.

/ / start tag pointer (tagged pointer) initializeTaggedPointerObfuscator (); Int namedClassesSize = (isPreoptimized()? unoptimizedTotalClasses : totalClasses) * 4 / 3; gdb_objc_realized_classes = NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);Copy the code

1.3.2 Fix @selector at compile stage

static size_t UnfixedSelectors; { mutex_locker_t lock(selLock); If (hi->hasPreoptimizedSelectors()) continue 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]); /* On Mac OS X 10.15 or iOS 13.0 or later, Sel_registerNameNoLock: sel_dyLD_get_objc_selector */ SEL SEL = sel_registerNameNoLock(name, isBundle); if (sels[i] ! = sel) { sels[i] = sel; } } } } ts.log("IMAGE TIMES: fix up selector references");Copy the code

It’s just fixing the address of some SEL with the same name,

1.3.3 Error messy class handling

// Discover classes. Fix up unresolved future classes. Mark bundle classes. Fix unresolved future classes. bool hasDyldRoots = dyld_shared_cache_some_image_overridden(); for (EACH_HEADER) { if (! MustReadClasses (hi, hasDyldRoots)) {// The image is sufficiently optimized to not need to call readClass() continue; } 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); 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. // The class is moved but not deleted. resolvedFutureClasses = (Class *) realloc(resolvedFutureClasses, (resolvedFutureClassCount+1) * sizeof(Class)); resolvedFutureClasses[resolvedFutureClassCount++] = newCls; } } } ts.log("IMAGE TIMES: discover classes");Copy the code

The class whose record was moved but not deleted is left for future resolution.

1.3.4 Remapping Lazily loaded classes that are not remapped

// Fix up remapped classes // Class list and nonlazy class list remain unremapped. // Class refs and super refs are Remelementary for message Dispatching. // The class list and the non-lazy class list are still unmapped. // Class references and hyperreferences are remapped for message scheduling. if (! noClassesRemapped()) { for (EACH_HEADER) { Class *classrefs = _getObjc2ClassRefs(hi, &count); for (i = 0; i < count; i++) { remapClassRef(&classrefs[i]); } classrefs = _getObjc2SuperRefs(hi, &count); for (i = 0; i < count; i++) { remapClassRef(&classrefs[i]); }} ts.log("IMAGE TIMES: remap classes"); // The underlying remapClassRef calls the remappedClasses method and only handles lazy classes /* * Returns oldClass => newClass mapping for implemented future classes. * Ignored weakly linked classes return oldClass => nil mapping. */ static objc::DenseMap<Class, Class> *remappedClasses(bool create) { static objc::LazyInitDenseMap<Class, Class> remapped_class_map; runtimeLock.assertLocked(); // start big enough to hold CF's classes and a few others return remapped_class_map.get(create, 32); }Copy the code

Remap unremapped lazy loaded classes, that is, classes that do not implement the +load method,

1.3.5 Fix some messages

// Fix up old objc_msgSend_fixup call sites for (EACH_HEADER) {message_ref_t *refs = _getObjc2MessageRefs(hi, &count); if (count == 0) continue; if (PrintVtables) { _objc_inform("VTABLES: repairing %zu unsupported vtable dispatch " "call sites in %s", count, hi->fname()); } for (i = 0; i < count; i++) { fixupMessageRef(refs+i); } } ts.log("IMAGE TIMES: fix up objc_msgSend_fixup"); static void fixupMessageRef(message_ref_t *msg) { msg->sel = sel_registerName((const char *)msg->sel); if (msg->imp == &objc_msgSend_fixup) { if (msg->sel == @selector(alloc)) { msg->imp = (IMP)&objc_alloc; } else if (msg->sel == @selector(allocWithZone:)) { msg->imp = (IMP)&objc_allocWithZone; } else if (msg->sel == @selector(retain)) { msg->imp = (IMP)&objc_retain; } else if (msg->sel == @selector(release)) { msg->imp = (IMP)&objc_release; } else if (msg->sel == @selector(autorelease)) { msg->imp = (IMP)&objc_autorelease; } else { msg->imp = &objc_msgSend_fixedup; } } else if (msg->imp == &objc_msgSendSuper2_fixup) { msg->imp = &objc_msgSendSuper2_fixedup; } else if (msg->imp == &objc_msgSend_stret_fixup) { msg->imp = &objc_msgSend_stret_fixedup; } else if (msg->imp == &objc_msgSendSuper2_stret_fixup) { msg->imp = &objc_msgSendSuper2_stret_fixedup; } #if defined(__i386__) || defined(__x86_64__) else if (msg->imp == &objc_msgSend_fpret_fixup) { msg->imp = &objc_msgSend_fpret_fixedup; } #endif #if defined(__x86_64__) else if (msg->imp == &objc_msgSend_fp2ret_fixup) { msg->imp = &objc_msgSend_fp2ret_fixedup; } #endif }Copy the code

Fix some methods, such as alloc, objc_retain, etc., because different system methods are implemented differently, and the underlying library implementation is different, so it needs to be fixed at load time.

1.3.6 When our class has a protocol: readProtocol

For (EACH_HEADER) {extern objc_class OBJC_CLASS_$_Protocol; Class cls = (Class)&OBJC_CLASS_$_Protocol; ASSERT(cls); NXMapTable *protocol_map = protocols(); bool isPreoptimized = hi->hasPreoptimizedProtocols(); // Skip reading protocols if this is an image from the shared cache // and we support roots // Note, after launch we do need to walk the protocol as the protocol // in the shared cache is marked with isCanonical() and That may not be true if some non-shared cache binary was chosen as the canonical If (launchTime && isPreoptimized) {if (PrintProtocols) {_objc_Inform ("PROTOCOLS: Skipping reading protocols in image: %s", hi->fname()); } continue; } bool isBundle = hi->isBundle(); protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count); for (i = 0; i < count; i++) { readProtocol(protolist[i], cls, protocol_map, isPreoptimized, isBundle); } } ts.log("IMAGE TIMES: discover protocols");Copy the code

ReadProtocol using the readProtocol function. The implementation will be explored later

1.3.7 Repairing the protocol reference

// Fix up@protocol References // The optimized imags protocol reference may be correct, but we are not sure. If (launchTime && hi->isPreoptimized()) continue; if (launchTime && hi->isPreoptimized()) continue; if (launchTime && hi->isPreoptimized()) continue; protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count); for (i = 0; i < count; I++) {// fix the protocol reference in case the protocol is reassigned. remapProtocolRef(&protolist[i]); } } ts.log("IMAGE TIMES: fix up @protocol references");Copy the code

1.3.8 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. Rdar :// Problem /53119145 Do this only after the initial category // The attachment is complete. For boot time categories, // discovery is delayed until after the first load_images call // the call to _dyLD_OBJC_NOTIFy_register completes. if (didInitialAttachCategories) { for (EACH_HEADER) { load_categories_nolock(hi); } } // Category discovery MUST BE Late to avoid potential races // when other threads call the new category code before // this thread finishes its fixups. // +load handled by prepare_load_methods() ts.log("IMAGE TIMES: discover categories");Copy the code

1.3.9 Loading processing of non-lazily loaded classes

Realize non-lazy classes (for +load methods and static instances) For (EACH_HEADER) {classref_t const * classList = hi-> nlCLslist (&count); for (i = 0; i < count; i++) { Class cls = remapClass(classlist[i]); // Remap class if (! cls) continue; addClassTableEntry(cls); If (CLS ->isSwiftStable()) {if (CLS ->swiftMetadataInitializer()) {_OBJC_FATAL ("Swift class %s with a metadata initializer " "is not allowed to be non-lazy", cls->nameForLogging()); } // fixme also disallow relocatable classes // We can't disallow all Swift classes because of // classes like Swift.__EmptyArrayStorage // Fixed an issue where classes were not allowed to relocate} // Load realizeClassWithoutSwift(CLS, nil); } } ts.log("IMAGE TIMES: realize non-lazy classes");Copy the code

1.3.10 Unprocessed classes, optimize infringed classes

// Realize newly-resolved future classes in case CF manipulates them If (resolvedFutureClasses) {for (I = 0; i < resolvedFutureClassCount; i++) { Class cls = resolvedFutureClasses[i]; if (cls->isSwiftStable()) { _objc_fatal("Swift class is not allowed to be future"); } realizeClassWithoutSwift(cls, nil); cls->setInstancesRequireRawIsaRecursively(false/*inherited*/); } free(resolvedFutureClasses); } ts.log("IMAGE TIMES: realize future classes");Copy the code

conclusion

In MAP_images, the early stage is mainly to repair the method selector, class, message, and protocol. As well as non-lazy loading of class and classification information (protocols, methods, member variables). The next article will explore the loading of classes and classifications.