In this article, we’ll explore the application loading process, which is what the linker does before the Main method. Understanding these is of great help to our project startup optimization.

Compilation process and libraries

Compilation process

We know that a library is an executable, and the process from source code to executable goes through the following steps:

  • The source file: Mainly the code we wrote,.h,.m,.cpp and other files.
  • precompiled: Mainly deals with which source code files to#Precompiled instructions to start, such as#include,#defineDelete all comments//and/ * * /, add line number and file name identification, and keep all#pragmaCompile – time instruction, generation.iFile.
  • compileWill:pretreatmentFinish the fileLexical analysis,Semantic analysisAnd optimized output assembly code file that.sFile.
  • assemblyWill:Assembly codeInto instructions that the machine can execute.oFile.
  • link: References other libraries in the.o file to generate the final executable

Dynamic and static libraries

We often use dynamic and static libraries in our projects. The differences are as follows:

  • Static libraries: In the linking phase, the assembler generated object files are linked and packaged into the executable along with the referenced libraries. That is, static libraries inlinkThe phase is loaded.
    • Advantages: the target program does not compile after completionExternal dependencies, can run directly.
    • Disadvantages: Static libraries can have multiple copies, which can lead toThe target programAs the volume increases, the consumption of memory, performance and speed is large.
  • Dynamic libraries: the program is compiled without linking to object code. References to dynamic libraries are kept in the program executableThe runtimeIs loaded, most of Apple’s official libraries areThe dynamic library.
    • Advantages:
      1. Can reduce theAppPackage size: because there is no need to copy toThe target programSo it won’t affectThe target programThe volume.
      2. Shared memory saves resources: The same library can be used by multiple programs.
      3. Dynamic libraries can be updated, and the target program does not need to be recompiled: this is because of dynamic librariesThe runtimeThe library can be replaced at any time without recompiling the code.
    • Disadvantages: Because isThe runtimeThere is a performance penalty associated with loading, and using dynamic libraries makes the program dependent on the external environment, without which the program will not run.

What is dyld

Dyld is a dynamic linker. The latest version is DYLD3. Let’s take a look at the version evolution of DYLD first. Before DYLD, NeXT used static binary data.

dyldVersion evolution

  • dyld1.0(1996-2004),
    • Included in theNeXTStep 3.3In the
    • History precedes standardizationPOSIX dlopen()The call
    • macOS 10Third-party wrappers were previously written to support the standardUnixSoftware, but these wrappers do not perfectly support the same semantics and do not work well in boundary cases.
    • In most useC++Dynamic library system written before.
    • inMAC OS 10.0increasedEarly binding. useEarly bindingThe technology is all in the systemdylibAnd our program to findPermanent address.Dynamic loaderAll the contents of these addresses will be loaded.
  • dyld2.0(2004-2007),
    • Included in themacOS TigerIn the
    • Compared with the1.0The version is Complete rewrite.
    • To support theC++Initialization semantics, extendedmach-oFormat.
    • There’s a whole set of nativedlopenanddlsymThe implementation of the.
    • 2.0The goal of the version design isIncrease speedOnly limited and security checks are carried out.
    • Improved security.
    • To reduceprecompiledAmount of work (time)
  • dyld2.x(2007-2017),
    • More infrastructure and platforms have been added, for examplex86,x86_64,arm,arm64,iOS,tvOS,watchOS.
    • Enhanced security. increaseCode signingandASLR(Address space configuration loading randomly), addedmach-oItems in header filesThe border checkIt prevents malicious binary data from being added.
    • Enhanced performance: withShared cacheInstead of aEarly binding.Shared cacheIs one that contains mostSystem dylibThe Single file can save a lot of memory as it actually isPreliminary linkLibrary.
  • dyld3(2017-present)
    • Completely changeDynamic linkerThe concept of
    • The default works for mostApple OSSystem application.
    • Completely replacedyld2.x.
    • Improved performance, as far as possible to improve the start-up speed and running speed.
    • Improved security: will mostdyldMove out of process, allow partdyldReside in the process, reside as small as possible, thereby reducing the area under attack.
    • Testability and reliability

dyld 2anddyld 3Difference in loading process

  • Dyld 2 loading process

    1. Parse mach-o headersAnalysis:mach-oFile through analysismach-oThe file figures out which libraries are needed, which might need other libraries, so it does recursive analysis until it gets all of themdylibThe complete diagram of. ordinaryiOSThe program needs 3-600dylibThe data is huge and requires a lot of processing.
    2. Map mach-o files: Mapping allmach-oFiles and put them into the address space
    3. Perform symbol lookups: Performs symbol lookup, such as the program usedprintfFunction, will findprintfIf it’s in the library system, then find its address and copy it into your program’s function pointer.
    4. Bind and rebase: binding and base address reset, copy3Pointer to step, because random addresses are used, all Pointers must use base addresses.
    5. Run initializers: Runs the initializer, and is ready to executemainFunction.

    The flow chart is shown below, where red represents the steps that affect performance and security:

  • Dyld3 loading process

    Dyld3 consists of three parts:

    • An out of Process MachO Parser/Compiler: Out-of-process Mach-O parser and compiler.

      1. Resolves all search paths, @rpaths, environment variables: Resolves all search paths,rpaths, environment variables.
      2. Parses the mach-o binariesAnalysis:mach-oBinary data
      3. Perform symbol lookups: Performs symbol lookup, such as the program usedprintfFunction, will findprintfIf it’s in the library system, then find its address and copy it into your program’s function pointer.
      4. Creates a launch closure with results: Create closure
    • An in-process engine that runs launch: An in-process engine that runs launch

      1. Validates launch closure: Check that the startup closure is correct.
      2. Maps in all dylibs: maps to alldylibIn the
      3. Applies fixups: Application fixes
      4. Run initializers: Runs the initializer, and is ready to executemainFunction.
    • A launch Closure Caching service: Enables the closure caching service. Most programs start using a cache but never need to call an out-of-process Mach-O profiler or compiler. Starting a finalizer is easier than Mach – O. The startup finalizer is a memory-mapped file that does not need to be analyzed in a complex way to improve speed.

      The flow chart (WWDC PPT) is as follows:

WWDC2017 App Startup Time: Past, Present, and Future

Dyld loading process analysis

Through the previous section we actually have a preliminary understanding of dyLD loading, this section mainly by looking at the source code to explore the loading process. The source code required for this section is:

  • dyld
  • libobjc
  • libSystem
  • libdispatch

The Source code can be downloaded directly from Apple Source Browser

dyldStart exploring

  • Using the main function

    Since the entry point of our program is main and dyLD is executed before main, it is easy to think of breaking a breakpoint in main and then looking at the call stack to see how dyLD is called:

    (lldb) bt
    * thread #1.queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
      * frame #0: 0x000000010000339b DyldTest`main(argc=3, argv=0x00007ffeefbff500) at main.m:13:5
        frame #1: 0x00007fff6e7d3cc9 libdyld.dylib`start + 1
    Copy the code

    This way we can see the start method, but we can’t find the start method by hitting the sign breakpoint, so this method is invalid.

  • Load method

    Load (ViewController) {load (ViewController) {load (ViewController) {load (ViewController);

    (lldb) bt
    * thread #1.queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
      * frame #0: 0x00000001000032d7 DyldTest`+[ViewController load](self=ViewController, _cmd="load") at ViewController.m:19:5
        frame #1: 0x00007fff6d61e560 libobjc.A.dylib`load_images + 1529
        frame #2: 0x000000010001626c dyld`dyld::notifySingle(dyld_image_states, ImageLoader const*, ImageLoader::InitializerTimingList*) + 418
        frame #3: 0x0000000100029fe9 dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int.char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 475
        frame #4: 0x00000001000280b4 dyld`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 188
        frame #5: 0x0000000100028154 dyld`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 82
        frame #6: 0x00000001000166a8 dyld`dyld::initializeMainExecutable() + 199
        frame #7: 0x000000010001bbba dyld`dyld::_main(macho_header const*, unsigned long.int.char const* *,char const* *,char const* *,unsigned long*) + 6667
        frame #8: 0x0000000100015227 dyld`dyldbootstrap::start(dyld3::MachOLoaded const*, int.char const**, dyld3::MachOLoaded const*, unsigned long*) + 453
        frame #9: 0x0000000100015025 dyld`_dyld_start + 37
    Copy the code

    From this stack we can see that _dyLD_START is the function from which dyLD starts, and we will explore the methods in the stack in turn.

_dyld_start

We searched the dyLD source code for _dyLD_START and found that it was assembly code

We can see from the comment that the start function of dyldBootstrap is called.

dyldbootstrap::start

We search the source code for dyldbootstrap to find the namespace and continue looking for the start function

// This is code to bootstrap dyld. This work in normally done for a program by dyld and crt.
// In dyld we have to do this manually.
//
uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],
				const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue)
{
	/// omit code
	// now that we are done bootstrapping dyld, call dyld's main
	uintptr_t appsSlide = appsMachHeader->getSlide(a);return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}
Copy the code

The key to this function is the last line, which calls dyld::_main.

dyld::_main

This method is quite long (900+ lines), and you can look backwards from the return value to see what this method does. Method is too long and we omit most of the code (because the return value is associated with mainExecutable, the intercepted code is mostly associated with mainExecutable) :

uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
		int argc, const char* argv[], const char* envp[], const char* apple[], 
		uintptr_t* startGlue)
{
  /// omit code
  // Grab the cdHash of the main executable from the environment
  // Create space for the main program cdHash
	uint8_t mainExecutableCDHashBuffer[20];
	const uint8_t* mainExecutableCDHash = nullptr;
	if ( const char* mainExeCdHashStr = _simple_getenv(apple, "executable_cdhash")) {unsigned bufferLenUsed;
		if ( hexStringToBytes(mainExeCdHashStr, mainExecutableCDHashBuffer, sizeof(mainExecutableCDHashBuffer), bufferLenUsed) )
			mainExecutableCDHash = mainExecutableCDHashBuffer;
	}
	///// Configuration information to obtain the Main program's Mach-O header, silder (ASLR offset)
	getHostInfo(mainExecutableMH, mainExecutableSlide);
  // The information can be found by silder+ASLR
	uintptr_t result = 0;
	sMainExecutableMachHeader = mainExecutableMH;
	sMainExecutableSlide = mainExecutableSlide;
  
  /// omit code
  // Set the context, put all the variables in the gLinkContext, save it
  setContext(mainExecutableMH, argc, argv, envp, apple);
  Envp is an environment variable
  configureProcessRestrictions(mainExecutableMH, envp);
      configureProcessRestrictions(mainExecutableMH, envp);
	// check whether dyLD3 is forced
	// Check if we should force dyld3. Note we have to do this outside of the regular env parsing due to AMFI
	if ( dyld3::internalInstall()) {if (const char* useClosures = _simple_getenv(envp, "DYLD_USE_CLOSURES")) {
			if ( strcmp(useClosures, "0") = =0 ) {
				sClosureMode = ClosureMode::Off;
			} else if ( strcmp(useClosures, "1") = =0 ) {
	#if! __i386__// don't support dyld3 for 32-bit macOS
				sClosureMode = ClosureMode::On;
				sClosureKind = ClosureKind::full;
	#endif
			} else if ( strcmp(useClosures, "2") = =0 ) {
				sClosureMode = ClosureMode::On;
				sClosureKind = ClosureKind::minimal;
			} else {
				dyld::warn("unknown option to DYLD_USE_CLOSURES. Valid options are: 0 and 1\n"); }}}#if TARGET_OS_OSX
    /// restricted process, environment variables may change, need to be reset
    if(! gLinkContext.allowEnvVarsPrint && ! gLinkContext.allowEnvVarsPath && ! gLinkContext.allowEnvVarsSharedCache ) {pruneEnvironmentVariables(envp, &apple);
		// set again because envp and apple may have changed or moved
		setContext(mainExecutableMH, argc, argv, envp, apple);
	}
	else
#endif
	{
    /// check environment variables
		checkEnvironmentVariables(envp);
    ///default value for DYLD_FALLBACK_FRAMEWORK_PATH, if not set in environment
    /// If no environment variable is set to a default value
		defaultUninitializedFallbackPaths(envp);
	}
  // Load shared cache Loads shared cache
	checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide)
	#if! TARGET_OS_SIMULATOR
  Dyld3 has a closure
	if ( sClosureMode == ClosureMode::Off ) {
		if ( gLinkContext.verboseWarnings )
			dyld::log("dyld: not using closures\n");
	} else {
    / / / dyld3 closures
    // set the load startup mode
		sLaunchModeUsed = DYLD_LAUNCH_MODE_USING_CLOSURE;
    /// configure closures
		const dyld3::closure::LaunchClosure* mainClosure = nullptr;
		dyld3::closure::LoadedFileInfo mainFileInfo;
		mainFileInfo.fileContent = mainExecutableMH;
		mainFileInfo.path = sExecPath;
    // check for closure in cache first
    // Determine if there are already closures in the cache
		if( sSharedCacheLoadInfo.loadAddress ! =nullptr ) {
			mainClosure = sSharedCacheLoadInfo.loadAddress->findClosure(sExecPath);
			if( gLinkContext.verboseWarnings && (mainClosure ! =nullptr) )
				dyld::log("dyld: found closure %p (size=%lu) in dyld shared cache\n", mainClosure, mainClosure->size());
			if( mainClosure ! =nullptr )
				sLaunchModeUsed |= DYLD_LAUNCH_MODE_CLOSURE_FROM_OS;
		}
    /// If the closure is invalid
    if( (mainClosure ! =nullptr) &&!closureValid(mainClosure, mainFileInfo, mainExecutableCDHash, true, envp) ) {
			mainClosure = nullptr;
			sLaunchModeUsed &= ~DYLD_LAUNCH_MODE_CLOSURE_FROM_OS;
		}
    /// No closure is created
    if ( (mainClosure == nullptr) && allowClosureRebuilds ) {
			// if forcing closures, and no closure in cache, or it is invalid, check for cached closure
			if ( !sForceInvalidSharedCacheClosureFormat )
				mainClosure = findCachedLaunchClosure(mainExecutableCDHash, mainFileInfo, envp, bootToken);
			if ( mainClosure == nullptr ) {
				// if no cached closure found, build new one
				mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp, bootToken);
				if( mainClosure ! =nullptr) sLaunchModeUsed |= DYLD_LAUNCH_MODE_BUILT_CLOSURE_AT_LAUNCH; }}// Try using launch closure
		if( mainClosure ! =nullptr ) {
			CRSetCrashLogMessage("dyld3: launch started");
			if ( mainClosure->topImage() - >fixupsNotEncoded() )
				sLaunchModeUsed |= DYLD_LAUNCH_MODE_MINIMAL_CLOSURE;
      bool closureOutOfDate;
			bool recoverable;
      // start the closure
			bool launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress, (dyld3::MachOLoaded*)mainExecutableMH,
											  mainExecutableSlide, argc, argv, envp, apple, diag, &result, startGlue, &closureOutOfDate, &recoverable);
      /// If the startup fails
      if ( !launched && closureOutOfDate && allowClosureRebuilds ) {
				// closure is out of date, build new one
				mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp, bootToken);
        // restart
        launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress, (dyld3::MachOLoaded*)mainExecutableMH,
												 mainExecutableSlide, argc, argv, envp, apple, diag, &result, startGlue, &closureOutOfDate, &recoverable);
      }
      // return main on successful startup
      if ( launched ) {
				gLinkContext.startedInitializingMainExecutable = true;
				if (sSkipMain)
					result = (uintptr_t)&fake_main;
				return result;
			}
      /// is not a dyld3 ellipsis code
    }
   #endif
   /// omit code
   // Load any inserted libraries
		if( sEnv.DYLD_INSERT_LIBRARIES ! =NULL ) {
			for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib ! =NULL; ++lib) 
				loadInsertedDylib(*lib);
		}
   /// weak reference binding main program
   sMainExecutable->weakBind(gLinkContext);
    // run all initializers
    // Run all initializers
		initializeMainExecutable(a);// notify any montoring proccesses that this process is about to enter main()
    /// the notification can enter the main function
		notifyMonitoringDyldMain(a); }Copy the code

The general process is:

  • Configuring environment Variables
  • Check whether the shared cache is enabled and whether the shared cache maps to the shared area
  • The main program initialization isinstantiateFromLoadedImage
  • Insert dynamic library
  • The link of the main program
  • Dynamic link library
  • Weak sign binding
  • Execute the initialization method
  • Main program entry

dyld::initializeMainExecutable

Basically loops through the execution of runInitializers

void initializeMainExecutable(a)
{
	// record that we've reached this step
	gLinkContext.startedInitializingMainExecutable = true;

	// run initialzers for any inserted dylibs
	ImageLoader::InitializerTimingList initializerTimes[allImagesCount()];
	initializerTimes[0].count = 0;
	const size_t rootCount = sImageRoots.size(a);if ( rootCount > 1 ) {
    /// iterate over the execution
		for(size_t i=1; i < rootCount; ++i) {
			sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]); }}// run initializers for main executable and everything it brings up 
	sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
	
	// register cxa_atexit() handler to run static terminators in all loaded images when this process exits
	if( gLibSystemHelpers ! =NULL ) 
		(*gLibSystemHelpers->cxa_atexit)(&runAllStaticTerminators, NULL.NULL);

	// dump info if requested
	if ( sEnv.DYLD_PRINT_STATISTICS )
		ImageLoader::printStatistics((unsigned int)allImagesCount(), initializerTimes[0]);
	if ( sEnv.DYLD_PRINT_STATISTICS_DETAILS )
		ImageLoaderMachO::printStatisticsDetails((unsigned int)allImagesCount(), initializerTimes[0]);
}
Copy the code

ImageLoader::runInitializers

The core code is to call the processInitializers method

void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo)
{
	uint64_t t1 = mach_absolute_time(a);mach_port_t thisThread = mach_thread_self(a); ImageLoader::UninitedUpwards up; up.count =1;
	up.imagesAndPaths[0] = { this.this->getPath() };
  / / / calls
	processInitializers(context, thisThread, timingInfo, up);
	context.notifyBatch(dyld_image_state_initialized, false);
	mach_port_deallocate(mach_task_self(), thisThread);
	uint64_t t2 = mach_absolute_time(a); fgTotalInitTime += (t2 - t1); }Copy the code

ImageLoader::processInitializers

Call recursiveInitialization to recursively instantiate the mirror list

void ImageLoader::processInitializers(const LinkContext& context, mach_port_t thisThread,
									 InitializerTimingList& timingInfo, ImageLoader::UninitedUpwards& images)
{
	uint32_t maxImageCount = context.imageCount() +2;
	ImageLoader::UninitedUpwards upsBuffer[maxImageCount];
	ImageLoader::UninitedUpwards& ups = upsBuffer[0];
	ups.count = 0;
	// Calling recursive init on all images in images list, building a new list of
	// uninitialized upward dependencies.
  // Instantiate recursively
	for (uintptr_t i=0; i < images.count; ++i) {
		images.imagesAndPaths[i].first->recursiveInitialization(context, thisThread, images.imagesAndPaths[i].second, timingInfo, ups);
	}
	// If any upward dependencies remain, init them.
	if ( ups.count > 0 )
		processInitializers(context, thisThread, timingInfo, ups);
}
Copy the code

ImageLoader::recursiveInitialization

Mainly after loading the mirror out of the notification

void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
										  InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
  / / / recursive locking
	recursive_lock lock_info(this_thread);
	recursiveSpinLock(lock_info);
	if ( fState < dyld_image_state_dependents_initialized- 1 ) {
		uint8_t oldState = fState;
		// Break cycles ends recursion
		fState = dyld_image_state_dependents_initialized- 1;
		try {
			// initialize lower level libraries first
			for(unsigned int i=0; i < libraryCount(a); ++i) { ImageLoader* dependentImage =libImage(i);
				if( dependentImage ! =NULL ) {
					// don't try to initialize stuff "above" me yet
					if ( libIsUpward(i) ) {
						uninitUps.imagesAndPaths[uninitUps.count] = { dependentImage, libPath(i) };
						uninitUps.count++;
					}
					else if ( dependentImage->fDepth >= fDepth ) {
						dependentImage->recursiveInitialization(context, this_thread, libPath(i), timingInfo, uninitUps); }}}// record termination order
			if ( this->needsTermination() )
				context.terminationRecorder(this);

			// let objc know we are about to initialize this image
      // Let objC know that we want to initialize this image
			uint64_t t1 = mach_absolute_time(a); fState = dyld_image_state_dependents_initialized; oldState = fState; context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
			
			// initialize this image
      // initialize the mirror
			bool hasInitializers = this->doInitialization(context);

			// let anyone know we finished initializing this image
      /// let everyone know that we have completed the initialization of the image
			fState = dyld_image_state_initialized;
			oldState = fState;
			context.notifySingle(dyld_image_state_initialized, this.NULL);
			
			if ( hasInitializers ) {
				uint64_t t2 = mach_absolute_time(a); timingInfo.addTime(this->getShortName(), t2-t1); }}catch (const char* msg) {
			// this image is not initialized
			fState = oldState;
			recursiveSpinUnLock(a);throw; }}recursiveSpinUnLock(a); }Copy the code

dyld::notifySingle

static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
{
	//dyld::log("notifySingle(state=%d, image=%s)\n", state, image->getPath());
	std::vector<dyld_image_state_change_handler>* handlers = stateToHandlers(state, sSingleHandlers);
	if( handlers ! =NULL ) {
		dyld_image_info info;
		info.imageLoadAddress	= image->machHeader(a); info.imageFilePath = image->getRealPath(a); info.imageFileModDate = image->lastModified(a);for (std::vector<dyld_image_state_change_handler>::iterator it = handlers->begin(a); it ! = handlers->end(a); ++it) {const char* result = (*it)(state, 1, &info);
			if( (result ! =NULL) && (state == dyld_image_state_mapped) ) {
				//fprintf(stderr, " image rejected by handler=%p\n", *it);
				// make copy of thrown string so that later catch clauses can free it
				const char* str = strdup(result);
				throwstr; }}}if ( state == dyld_image_state_mapped ) {
		// <rdar://problem/7008875> Save load addr + UUID for images from outside the shared cache
		// <rdar://problem/50432671> Include UUIDs for shared cache dylibs in all image info when using private mapped shared caches
		if(! image->inSharedCache()
			|| (gLinkContext.sharedRegionMode == ImageLoader::kUsePrivateSharedRegion)) {
			dyld_uuid_info info;
			if ( image->getUUID(info.imageUUID) ) {
				info.imageLoadAddress = image->machHeader(a);addNonSharedCacheImageUUID(info); }}}if( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit ! =NULL) && image->notifyObjC()) {uint64_t t0 = mach_absolute_time(a);dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0.0);
    ///
		(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
		uint64_t t1 = mach_absolute_time(a);uint64_t t2 = mach_absolute_time(a);uint64_t timeInObjC = t1-t0;
		uint64_t emptyTime = (t2-t1)*100;
		if( (timeInObjC > emptyTime) && (timingInfo ! =NULL) ) {
			timingInfo->addTime(image->getShortName(), timeInObjC); }}// mach message csdlc about dynamically unloaded images
	if ( image->addFuncNotified() && (state == dyld_image_state_terminated) ) {
		notifyKernel(*image, false);
		const struct mach_header* loadAddress[] = { image->machHeader() };
		const char* loadPath[] = { image->getPath() };
		notifyMonitoringDyld(true.1, loadAddress, loadPath); }}Copy the code

The emphasis is (*sNotifyObjCInit)(image->getRealPath(), image->machHeader()); , our global search sNotifyObjCInit is not implemented, but there is an assignment:

void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
	// record functions to call
	sNotifyObjCMapped	= mapped;
  /// assign
	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(a); it ! = sAllImages.end(a); 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

Registerobjobjc_notify_register is called in the _dyLD_OBJC_notify_register function, and the _DYLD_OBJC_notify_register function is used in libobJC source code _objc_init. So sNotifyObjCInit assigns to load_images in objC, and load_images calls all the +load methods. So in summary, notifySingle is a callback function, so let’s move on to the load_images method

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(a);tls_init(a);static_init(a);runtime_init(a);exception_init(a);#if __OBJC2__
    cache_t: :init(a);#endif
    _imp_implementationWithBlock_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}
Copy the code

load_images

load_images(const char *path __unused, const struct mach_header *mh)
{
    if(! didInitialAttachCategories && didCallDyldNotifyRegister) { didInitialAttachCategories =true;
        loadAllCategories(a); }// 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);
    }

    // Call +load methods (without runtimeLock - re-entrant)
 		// The load method is called
    call_load_methods(a); }Copy the code

The call_load_methods method is called

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

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

    void *pool = objc_autoreleasePoolPush(a);do {
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            call_class_loads(a); }// 2. Call category +loads ONCE
        more_categories = call_category_loads(a);// 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

At the heart of the call_load_methods method is a circular call to the load method.

conclusion

The source chain for load is: _dyld_start –> dyldbootstrap::start –> dyld::_main –> dyld::initializeMainExecutable –> ImageLoader::runInitializers — > ImageLoader: : processInitializers — > ImageLoader: : recursiveInitialization – > dyld: : notifySingle (is a callback processing) — > sNotifyObjCInit –> load_images(libobjc.A.dylib)