review

The whole dyLD loading process above, We do this by _dyLD_start -> dyLDbootstrap ::start->dyld::_main ->dyld::initializeMainExecutable()->ImageLoader::runInitializers->ImageLoader::processInitializers->ImageLoader::process Initializers – > ImageLoader: : recursiveInitialization – > dyld: : notifySingle notifySingle eventually call sNotifyObjCInit, Search sNotifyObjCInit globally to find the registerObjCNotifiers, registerObjCNotifiers is called at _dyLD_OBJC_NOTIFy_register, and we end up with _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

As you can see, the _dyLD_OBJC_NOTIFy_register function takes three arguments, &map_images, load_images, and unmap_image, which are called when.

map_imagesandload_imagesCalling process

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
	}

	// <rdar://problem/32209809> 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

So you can see sNotifyObjCMapped = Mapped, search globally for sNotifyObjCMapped, Find the notifyBatchPartial function and call (* NotifyObjcmApped)(objcImageCount, Paths, MHS); , search for notifyBatchPartial, and we find that we’re back to the registerObjCNotifiers,

        // call 'mapped' function with all images mapped so far
	try {
		notifyBatchPartial(dyld_image_state_bound, true, NULL, false.true);
	}
Copy the code

Originally, map_images was called as soon as it was assigned.

SNotifyObjCInit = init, search sNotifyObjCInit globally, and find that after map_images is called, load_images will also be called

       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()); / / load_images calls}}Copy the code

SNotifyObjCUnmapped searches globally for removeImage and finds that unmap_image is not called until the program is closed

if( sNotifyObjCUnmapped ! = NULL && image->notifyObjC() ) (*sNotifyObjCUnmapped)(image->getRealPath(), image->machHeader());Copy the code

loadFunction call flow

Earlier we added a breakpoint to the load function to get the load flow

  • dyld`_dyld_start ->
  • dyld`dyldbootstrap::start ->
  • dyld`dyld::_main ->
  • dyld`dyld::initializeMainExecutable() ->
  • dyld`ImageLoader::runInitializers ->
  • dyld`ImageLoader::processInitializers
  • dyld`ImageLoader::recursiveInitialization ->
  • dyld`dyld::notifySingle ->
  • libobjc.A.dylib`load_images

So eventually we’re going to go to the load_images function, and we’re going to call the load method, and we’re going to look at the load_images function,

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    if(! didInitialAttachCategories && didCallDyldNotifyRegister) { didInitialAttachCategories =true;
        loadAllCategories();
    }

    // Return without taking locks if there are no +load methods here.
    if(! hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh); // Prepare the load function
    }

    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();  // Call the load function
}
Copy the code

You can see that prepare_load_methods is called, and it recursively adds the classes and methods that contain the load method to either the add_class_to_loadable_list or the add_category_to_loadable_list category table, Then call call_load_methods directly.

void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();

    do {
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            call_class_loads();   // Call the load method of the class
        }

        // 2. Call category +loads ONCE
        more_categories = call_category_loads(); // Call the load method of the class

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}
Copy the code

The recursion calls the load method of the class and the load method of the class respectively.

static void call_class_loads(void)
{
    int i;
    
    // Detach current loadable list.
    struct loadable_class *classes = loadable_classes;
    int used = loadable_classes_used;
    loadable_classes = nil;
    loadable_classes_allocated = 0;
    loadable_classes_used = 0;
    
    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Class cls = classes[i].cls;
        load_method_t load_method = (load_method_t)classes[i].method;
        if(! cls)continue; 

        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
        }
        (*load_method)(cls, @selector(load));   // Send the load message to the class
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}
Copy the code

Then send a message (*load_method)(CLS, @selector(load)) to the corresponding class or class.

cxxFunction call flow

Add the following CXX function to the main file with a breakpoint.

__attribute__ ((constructor)) void LKTeacher(){
    printf("______%s_____",__func__);
}
Copy the code

Bt prints stack information

  • dyld`_dyld_start ->
  • dyld`dyldbootstrap::start ->
  • dyld`dyld::_main ->
  • dyld`dyld::initializeMainExecutable() ->
  • dyld`ImageLoader::runInitializers ->
  • dyld`ImageLoader::processInitializers
  • dyld`ImageLoader::recursiveInitialization ->
  • dyld`ImageLoaderMachO::doInitialization ->
  • dyld`ImageLoaderMachO::doModInitFunctions

Search for doModInitFunctions

bool ImageLoaderMachO::doInitialization(const LinkContext& context)
{
	CRSetCrashLogMessage2(this->getPath());

	// mach-o has -init and static initializers
	doImageInit(context);
	doModInitFunctions(context);
	
	CRSetCrashLogMessage2(NULL);
	
	return (fHasDashInit || fHasInitializers);
}
Copy the code

Search for doInitialization by calling the doModInitFunctions(context) method in doInitialization

void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
										  InitializerTimingList& timingInfo, UninitedUpwards& uninitUps){
                        context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
			
			// initialize this image
			bool hasInitializers = this->doInitialization(context);

			// let anyone know we finished initializing this image
			fState = dyld_image_state_initialized;
			oldState = fState;
			context.notifySingle(dyld_image_state_initialized, this, NULL);
                        // Omit intermediate code
}
Copy the code

Back to the recursiveInitialization, NotifySingle ->sNotifyObjCInit->registerObjCNotifiers-> _dyLD_OBJC_notify_register The doInitialization(context) method is then called, followed by the notifySingle method again.

Analyzing the entire loading process is a recursive process

//let objc know we are about to initialize this image

// initialize this image

// let anyone know we finished initializing this image

On the first call, the image is not initialized, so the load method is not called. Then the doInitialization method is called to initialize the image. After the image is initialized, the load method is called again.

Verify that CXX functions are added to any objc file

__attribute__ ((constructor)) void objcFunc(){
    printf("______%s_____\n",__func__);
}
Copy the code

Print the result

______objcFunc_____
2021-07-16 16:33:36.482889+0800 KCObjcBuild[8825:231456] ______+[LKPerson load]______
______LKTeacher_____
Copy the code

As you can see, the CXX function of the image is called first, then the load function is called, and finally the CXX function of the program is called.

mainFunction call flow

Type a breakpoint at the load function, open the assembly, and work your way downRead register information

As you can see,x16 = 0x0000000100003e60  KCObjcBuild main at main.m:31mainFunction address existsx16,0x100015078 <+120>: braaz  x16You know, when the execution is finished_dyld_startAfter that, it jumps tomainFunction.

conclusion

Quoted from KC courseware

Load, CXX, and main first load the image file CXX, then load the load function, then load the main program CXX, and finally execute the main function