• As an iOS developer, App launch is an essential part. Today we will understand the App launch process and master the application loading.

The official source

  • Libdispatch – 1271.40.12
  • Libsystem – 1292.60.1
  • Objc4-818.2 –
  • dyld-852

Application loading

  • Loading flow chart

  1. Source files:.c/.m/.cpp etc.
  2. Preprocessing: Comments are removed, conditional compilation is processed, headers are expanded, macros are replaced during processing.
  3. Compilation: conducts lexical distraction syntax analysis and intermediate layer IR file, finally generates.s file
  4. Assembler: replace.s files with machine language to generate.o files
  5. Link: Generate a Macho type executable file from all the.o files and linked third-party libraries

Dynamic and static libraries

  • Static libraries: The assembler generated target and reference libraries are linked and packaged into an executable during the linking phase
  • Dynamic libraries: program compilations are not linked to object code, but are loaded while the program is running
Static Coulter point
  1. The static library is packaged into an executable and can be run independently of the external environment upon successful compilation
  2. Compiled files become larger and must be recompiled if static libraries are updated
Dynamic Kurt point
  1. Reduce the size of your packaged App
  2. Share content and resources
  3. By updating the dynamic library to achieve the purpose of updating the program
  4. Executable files cannot be run alone and must depend on the external environment

Static library dynamic library diagram

Dyld loading process exploration

  • Since we are exploring the flow before main, we go directly to the main breakpoint

  • The stack shows that the libdyld. Dylib library’s start function is the starting position, and then directly to the main function.

  • Breakpoint to load run

Simulator version

process

  1. _dyld_start
  2. dyldbootstrap::start
  3. dyld::_main
  4. dyld::useSimulatorDyld
  5. start_sim
  6. dyld::_main
  7. dyld::initializeMainExecutable
  8. ImageLoader::runInitializers
  9. ImageLoader::processInitializers
  10. ImageLoader::recursiveInitialization
  11. dyld::notifySingle
  12. load_images
  13. +[ViewController load]

Real machine version

process

  1. _dyld_start
  2. dyldbootstrap::start
  3. dyld::_main
  4. dyld::initializeMainExecutable
  5. ImageLoader::runInitializers
  6. ImageLoader::processInitializers
  7. ImageLoader::recursiveInitialization
  8. dyld::notifySingle
  9. load_images
  10. +[ViewController load]

Mainly explore the real machine

  • Dyld_start assembly source code

Assembler _dyLD_start calls dyLDbootstrap to view the source code

Dyldbootstrap is a namespace that allows you to search for the start method in the dyldinitialization.cpp file


// Relocate dyLD
rebaseDyld(dyldsMachHeader);

// stack balancing protection
__guard_setup(apple);

// Get the virtual memory offset
uintptr_t appsSlide = appsMachHeader->getSlide();

// Returns the return value of the _main method
return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);

Copy the code
  • relocationdyldBecause theAppThe system will give it to you automaticallyAppRandomly assignedASLR.dyldRelocation is required because it needs to go to the current process to get its own information
  • calldyld::_mainMethod to get the return result

Dyld ::_main source code analysis

  • System testing
  • Get the Mach-O header and silder of the main program
  • Set the context, put all the variables here in the glinkContext, save it
  • Check whether the process is restricted, if the restricted process, the environment variable ENVP may change, reset the context
  • Load the shared cache by reading the macho-header
  • Loading framework
  • If dyLD3 is loaded,
  1. Launch closure, find the main function, and return
  • If non-DYLD3 is loaded
  1. InitializeMainExecutable ();
  2. Add the main program to AllImages
  3. Insert dynamic libraries (if any)
  4. Link main program
  5. Linked dynamic library
  6. Binding symbols
  7. Initialize the main method
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
		int argc, const char* argv[], const char* envp[], const char* apple[], 
		uintptr_t* startGlue)

        // Create space for the main program cdHash
	uint8_t mainExecutableCDHashBuffer[20];
        // Get the cdHash of the main executable from the environment
	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;
	}
        // Get the mach-o header and silder (ASLR offset) from the main program.
	getHostInfo(mainExecutableMH, mainExecutableSlide);
        
        / / assignment
        // The information can be found through silder+ASLR
        uintptr_t result = 0;
	sMainExecutableMachHeader = mainExecutableMH;
	sMainExecutableSlide = mainExecutableSlide;
        
        // 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);
        // Check if dyLD3 should be forced.
        // Apple Mobile File Integrity (AFMI)
        if ( dyld3::internalInstall() ) {
            // The implementation is deleted at line 6667-6678
	}
#if TARGET_OS_OSX
    // If it is a restricted process, the environment variable ENvp may change, reset here
    if(! gLinkContext.allowEnvVarsPrint && ! gLinkContext.allowEnvVarsPath && ! gLinkContext.allowEnvVarsSharedCache ) { pruneEnvironmentVariables(envp, &apple); setContext(mainExecutableMH, argc, argv, envp, apple); }else
#endif
	{
                // Check environment variables
		checkEnvironmentVariables(envp);
                // Set the environment variable defaults
		defaultUninitializedFallbackPaths(envp);
	}
        
        // If the environment variable DYLD_PRINT_OPTS or DYLD_PRINT_ENV is set in the project, the information will be printed before load
        if ( sEnv.DYLD_PRINT_OPTS )
		printOptions(argv);
	if ( sEnv.DYLD_PRINT_ENV ) 
		printEnvironmentVariables(envp);
                
        // Load shared cache (UIKit, Foundation, etc.) by reading macho header
	checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
        
#if! TARGET_OS_SIMULATOR
        // Check whether dyLD3 load is used, off does not use dyLD3, else is dyLD3
	if ( sClosureMode == ClosureMode::Off ) {
		if ( gLinkContext.verboseWarnings )
			dyld::log("dyld: not using closures\n");
	} else {
                // Set the loading mode to Clorure
		sLaunchModeUsed = DYLD_LAUNCH_MODE_USING_CLOSURE;
                // Configure closure so dyLD3 knows how to load the main program
		const dyld3::closure::LaunchClosure* mainClosure = nullptr;
		dyld3::closure::LoadedFileInfo mainFileInfo;
		mainFileInfo.fileContent = mainExecutableMH;
		mainFileInfo.path = sExecPath;
                // Delete some code
                // First check if mainClosure is already in the cache
		if( sSharedCacheLoadInfo.loadAddress ! = nullptr ) {// If not, go to the shared cache and initialize the mainClosure instance
			mainClosure = sSharedCacheLoadInfo.loadAddress->findClosure(sExecPath);
                        // If it is not empty, use it
			if( mainClosure ! = nullptr ) sLaunchModeUsed |= DYLD_LAUNCH_MODE_CLOSURE_FROM_OS; }// If the closure is not empty, but the validation is already invalid
                if( (mainClosure ! = nullptr) && ! closureValid(mainClosure, mainFileInfo, mainExecutableCDHash,true, envp) ) {
                        // Set mainClosure to null
			mainClosure = nullptr;
			sLaunchModeUsed &= ~DYLD_LAUNCH_MODE_CLOSURE_FROM_OS;
		}
                
                // If we do not find a valid cache closure, we try to build a new one
		if ( (mainClosure == nullptr) && allowClosureRebuilds ) {
			// If close is forced and there is no closure in the cache, or it is invalid, check the closure of the cache
			if ( !sForceInvalidSharedCacheClosureFormat )
				mainClosure = findCachedLaunchClosure(mainExecutableCDHash, mainFileInfo, envp, bootToken);
			if ( mainClosure == nullptr ) {
				// If there is none in the cache, create one
				mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp, bootToken);
				if ( mainClosure != nullptr )
					sLaunchModeUsed |= DYLD_LAUNCH_MODE_BUILT_CLOSURE_AT_LAUNCH;
			}
		}
                // Create mainClosure if it doesn't exist
                / / start mainClosure
                bool launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress, (dyld3::MachOLoaded*)mainExecutableMH,
											  mainExecutableSlide, argc, argv, envp, apple, diag, &result, startGlue, &closureOutOfDate, &recoverable);
                  
                // If startup fails and colSure expires
                if ( !launched && closureOutOfDate && allowClosureRebuilds ) {
			// Create a new closure
			mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp, bootToken);
                        // Then restart mainClosure
                        launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress, (dyld3::MachOLoaded*)mainExecutableMH,
												 mainExecutableSlide, argc, argv, envp, apple, diag, &result, startGlue, &closureOutOfDate, &recoverable);
                 }
                 // If the startup succeeds, the main function of the main program will be found and returned
                 if ( launched ) {
			gLinkContext.startedInitializingMainExecutable = true;
			if (sSkipMain)
				result = (uintptr_t)&fake_main;
			returnresult; }}// If not dyLD3 loading mode
  reloadAllImages:
        // instantiate the load main program
        sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
	gLinkContext.mainExecutable = sMainExecutable;
        // Code signature
	gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);
        
        // Insert dynamic library, which can be modified in jailbreak environment
	if( sEnv.DYLD_INSERT_LIBRARIES ! =NULL ) {
		for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib ! =NULL; ++lib) 
		loadInsertedDylib(*lib);
	}
        // Link main program to dynamic library, insert dynamic library
        link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL.NULL), - 1);
        
        // Link any inserted libraries
        // Do this after linking the main executable to insert any dylib
        // Dylibs (such as libSystem) do not precede dylibs used by programs
        if ( sInsertedDylibCount > 0 ) {
		for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
                        // Load the dynamic library into AllImages and start inserting at position [I +1], since position 0 is the main program
			ImageLoader* image = sAllImages[i+1];
			link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL.NULL), - 1);
			image->setNeverUnloadRecursive();
		}
		if ( gLinkContext.allowInterposing ) {
			// Only the INSERTED library can be INSERTED
                        // Register the insertion information after all the inserted libraries are bound so that the link works
			for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
				ImageLoader* image = sAllImages[i+1]; image->registerInterposing(gLinkContext); }}}#if SUPPORT_ACCELERATE_TABLES
	if( (sAllCacheImagesProxy ! =NULL) && ImageLoader::haveInterposingTuples() ) {
            // Refresh all images, ready to reload
            resetAllImages();
            // If the load failed, go back to reloadAllImages and try again
            goto reloadAllImages;
	}
#endif

        // bind inserts the dynamic library
	if ( sInsertedDylibCount > 0 ) {
		for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
			ImageLoader* image = sAllImages[i+1];
			image->recursiveBind(gLinkContext, sEnv.DYLD_BIND_AT_LAUNCH, true, nullptr); }}// Weak symbol binding
        sMainExecutable->weakBind(gLinkContext);
        
  #if SUPPORT_OLD_CRT_INITIALIZATION
	// The old way was to run the initializer through the crt1.o callback
	if(! gRunInitializersOldWay ) initializeMainExecutable();#else
            // Initialize the main method
            initializeMainExecutable(); 
	#endif
}
Copy the code
  • IOS must have a shared cache, and the shared cache is all system-level dynamic libraries, such as UIKit, CoreFoundation, etc. Self-created dynamic libraries or third-party libraries will not be placed in the shared cache

  • checkSharedRegionDisableThe method is to detect whether different schemas need to share caches
  • mapSharedCacheLoading the Shared cache

  • checkSharedRegionDisableThe method is an obvious hintIOSYou have to have a shared cache.
  • Now, how do you load a shared cachemapSharedCacheMethod has a callloadDyldCacheA method to load the shared cache

bool loadDyldCache(const SharedCacheOptions& options, SharedCacheLoadInfo* results)
{
    results->loadAddress        = 0;
    results->slide              = 0;
    results->errorMessage       = nullptr;

#if TARGET_OS_SIMULATOR
    // simulator only supports mmap()ing cache privately into process
    return mapCachePrivate(options, results);
#else
    // If forcePrivate = YES, it is mandatory private
    // Instead of recording the shared cache, the system library you need is stored in the current process and used only in the current process
    if ( options.forcePrivate ) {
        // mmap cache into this process only
        return mapCachePrivate(options, results);
    }
    else {
        // fast path: when cache is already mapped into shared region
        bool hasError = false;
        // If the system library you need is in the shared cache, just take it and use it, no need to do anything else
        if( reuseExistingCache(options, results) ) { hasError = (results->errorMessage ! = nullptr); }else {
        // If it is the first time to load the shared cache, load it directly
            // slow path: this is first process to load cache
            hasError = mapCacheSystemWide(options, results);
        }
        return hasError;
    }
#endif
}
Copy the code
There are three scenarios for loading a shared cache
  1. Forcible private: forcePrivate = YES: forcible private. It is only loaded into the robbed App process, not in the shared cache
  2. Shared cache loaded: If the library you depend on is already loaded in the shared cache, you can use it without further action
  3. First load: If the library shared cache summary you depend on is not there, it will be loaded back into the shared cache
  • IOS must have a shared cache, which stores system libraries. The purpose of creating a shared cache is for multiple processes to use system libraries together

Dyld3 and dyld2

  • Dyld3, also known as closure mode, is faster and more efficient to load. IOS11 after the main program is loaded with DYLD3, iOS13 after the dynamic library and tripartite library with dyLD3 loading
  Start ClosureMode::on use dyLD3; otherwise use dyLD2
  if ( sClosureMode == ClosureMode::Off ) {
    //dyld2
    if ( gLinkContext.verboseWarnings )
            dyld::log("dyld: not using closures\n");
  } else {
    // dyLD3 DYLD_LAUNCH_MODE_USING_CLOSURE uses closure mode
    sLaunchModeUsed = DYLD_LAUNCH_MODE_USING_CLOSURE;
    constdyld3::closure::LaunchClosure* mainClosure = nullptr; dyld3::closure::LoadedFileInfo mainFileInfo; mainFileInfo.fileContent = mainExecutableMH; mainFileInfo.path = sExecPath; .// Check the shared cache to see if there is a mainClosure for dyLD3
    if( sSharedCacheLoadInfo.loadAddress ! = nullptr ) { mainClosure = sSharedCacheLoadInfo.loadAddress->findClosure(sExecPath); . }...// If there is one in the shared cache, then verify that closure is valid
    if( (mainClosure ! = nullptr) && ! ClosureValid (mainClosure, mainFileInfo, mainExecutableCDHash,true, envp) ) {
            mainClosure = nullptr;
            sLaunchModeUsed &= ~DYLD_LAUNCH_MODE_CLOSURE_FROM_OS;
    }
    
    bool allowClosureRebuilds = false;
    if ( sClosureMode == ClosureMode::On ) {
            allowClosureRebuilds = true; }...// If no valid closure is found in the shared cache, a closure is automatically created
    if ( (mainClosure == nullptr) && allowClosureRebuilds ) {
        ...
        if ( mainClosure == nullptr ) { 
        // Create a mainClosure
        mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp, 
        bootToken);
        if ( mainClosure != nullptr )
                sLaunchModeUsed |= DYLD_LAUNCH_MODE_BUILT_CLOSURE_AT_LAUNCH;
        }
    }
   
    // try using launch closure
    // dyLD3 starts
    if( mainClosure ! = nullptr ) { CRSetCrashLogMessage("dyld3: launch started"); ./ / start launchWithClosure
        boollaunched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress,(dyld3::MachOLoaded*)mainExecutableMH,...) ;// Failed to start
        if ( !launched && closureOutOfDate && allowClosureRebuilds ) {
                // closure is out of date, build new one
                // Re-create mainClosure if it fails
                mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, 
                envp, bootToken);
                if( mainClosure ! = nullptr ) { ...// dyLD3 starts again
                    launched = launchWithClosure(mainClosure,  sSharedCacheLoadInfo.loadAddress,
                    (dyld3::MachOLoaded*)mainExecutableMH,...);
                }
            }
            if ( launched ) {
                    gLinkContext.startedInitializingMainExecutable = true;
                    if (sSkipMain)
                    // Return the address of main on success
                    result = (uintptr_t)&fake_main;
                    return result;
            }
            else {  
            // Failed to start}}}Copy the code
  • Dyld3 startup process is after a lot of attempts, the system gave a lot of opportunities, generally there will not be a startup failure

  • If you don’t use dyLD3, you will use dyLD2

{
// could not use closure info, launch old way
	// Use dyLD2 instead of dyLD3
	sLaunchModeUsed = 0;
	// install gdb notifier
	stateToHandlers(dyld_image_state_dependents_mapped, sBatchHandlers)->push_back(notifyGDB);
	stateToHandlers(dyld_image_state_mapped, sSingleHandlers)->push_back(updateAllImages);
	// make initial allocations large enough that it is unlikely to need to be re-alloced
	sImageRoots.reserve(16);
	sAddImageCallbacks.reserve(4);
	sRemoveImageCallbacks.reserve(4);
	sAddLoadImageCallbacks.reserve(4);
	sImageFilesNeedingTermination.reserve(16);
	sImageFilesNeedingDOFUnregistration.reserve(8);

#if! TARGET_OS_SIMULATOR
#ifdef WAIT_FOR_SYSTEM_ORDER_HANDSHAKE
	  file generation process
	WAIT_FOR_SYSTEM_ORDER_HANDSHAKE(dyld::gProcessInfo->systemOrderFlag);
#endif
#endif
	try {
		// add dyld itself to UUID listaddDyldImageToUUIDList(); . }Copy the code

Dyld3 Starts the process

  1. Get mainClosure, an instance of dyLD3, from the shared cache
  2. Verify that mainClosure is valid
  3. Then go to the shared cache and look for a valid mainClosure, if any
  4. If not, create mainClosure
  5. Start mainClosure, start dyLD3
  6. After successful startup, the main program is successfully started. Result is the address of the main function and is returned to the dyldBootstrap ::start method.
  7. Then go to main

Instantiate the main program

  • Dyld3 and DyLD2 go through the same process, dyLD3 uses closure mode.
  • Image is often found in source code. Image is an image file
  • A mirror file is a macho file that’s mapped from disk to memory that’s loaded into memory and that’s called a mirror file
		// instantiate ImageLoader for main executable
                // instantiate the main program
		sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
		gLinkContext.mainExecutable = sMainExecutable;
                // Main program signature
		gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);


Copy the code

Instantiation of the main program is the need of the main program loaded into memory, part of the information through instantiateMainExecutable method returns ImageLoader instance of type object, and then the main program for signature

static ImageLoaderMachO* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path)
{
	// try mach-o loader
//	if ( isCompatibleMachO((const uint8_t*)mh, path) ) {
		ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext);
		addImage(image);
		return (ImageLoaderMachO*)image;
//	}
	
//	throw "main executable not a known format";
}

Copy the code

Add the instantiated image to the image file array. The image of the main program is the first to be added to the array. Then explore ImageLoaderMacho: : instantiateMainExecutable method

ImageLoader* ImageLoaderMachO::instantiateMainExecutable(const macho_header* mh, uintptr_t slide, const char* path, const LinkContext& context)
{
	//dyld::log("ImageLoader=%ld, ImageLoaderMachO=%ld, ImageLoaderMachOClassic=%ld, ImageLoaderMachOCompressed=%ld\n",
	//	sizeof(ImageLoader), sizeof(ImageLoaderMachO), sizeof(ImageLoaderMachOClassic), sizeof(ImageLoaderMachOCompressed));
	bool compressed;
	unsigned int segCount;/ / segment number
	unsigned int libCount;// Number of dynamic libraries
	const linkedit_data_command* codeSigCmd;// Signature information
	const encryption_info_command* encryptCmd;// Encrypt information
        / / load command
	sniffLoadCommands(mh, path, false, &compressed, &segCount, &libCount, context, &codeSigCmd, &encryptCmd);
	// instantiate concrete class based on content of load commands
	if ( compressed ) // Different ways to initialize
		return ImageLoaderMachOCompressed::instantiateMainExecutable(mh, slide, path, segCount, libCount, context);
	else
#if SUPPORT_CLASSIC_MACHO
		return ImageLoaderMachOClassic::instantiateMainExecutable(mh, slide, path, segCount, libCount, context);
#else
		throw "missing LC_DYLD_INFO load command";
#endif
}
Copy the code

	// fSegmentsArrayCount is only 8-bits
        // The maximum value of the segment is 256
	if ( *segCount > 255 )
		dyld::throwf("malformed mach-o image: more than 255 segments in %s", path);

	// fSegmentsArrayCount is only 8-bits
        // The maximum dynamic library is 4096
	if ( *libCount > 4095 )
		dyld::throwf("malformed mach-o image: more than 4095 dependent libraries in %s", path);
               

        // Make sure you rely on the libSystem library
	if ( needsAddedLibSystemDepency(*libCount, mh) )
		*libCount = 1;
Copy the code
  • The sniffLoadCommands loads the segment and COMmod information, along with some validation

Insert dynamic library

		// load any inserted libraries
                // load the insert dynamic library DYLD_INSERT_LIBRARIES
		if( sEnv.DYLD_INSERT_LIBRARIES ! =NULL ) {
			for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib ! =NULL; ++lib) 
				loadInsertedDylib(*lib);
		}
		// record count of inserted libraries so that a flat search will look at 
		// inserted libraries, then main, then others.    
                // Get the number of dynamic libraries to insert. The first is the main program, so -1 is required
		sInsertedDylibCount = sAllImages.size()- 1;
Copy the code

Link main program

		// link main executable
                // Start linking the main program
		gLinkContext.linkingMainExecutable = true;
#if SUPPORT_ACCELERATE_TABLES
		if ( mainExcutableAlreadyRebased ) {
			// previous link() on main executable has already adjusted its internal pointers for ASLR
			// work around that by rebasing by inverse amount
                        // If the main program does not relocate, relocate
			sMainExecutable->rebase(gLinkContext, -mainExecutableSlide);
		}
#endif
                // Link the main program
		link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL.NULL), - 1);
		sMainExecutable->setNeverUnloadRecursive();
		if ( sMainExecutable->forceFlat() ) {
			gLinkContext.bindFlat = true;
			gLinkContext.prebindUsage = ImageLoader::kUseNoPrebinding;
		}
Copy the code
  • linkCall theImageLoader::linkMethod,ImageLoaderResponsible for loadingimageFile (main program, dynamic library) eachimageCorresponds to aImageLoaderInstances of the class
void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool 
preflightOnly, bool neverUnload, const RPathChain& loaderRPaths, const char* imagePath{
	 
	...
	// Load all dynamic libraries recursively
	this->recursiveLoadLibraries(context, preflightOnly, loaderRPaths, imagePath); context.notifyBatch(dyld_image_state_dependents_mapped, preflightOnly); . __block uint64_t t2, t3, t4, t5; { dyld3::ScopedTimer(DBG_DYLD_TIMING_APPLY_FIXUPS,0.0.0);
		t2 = mach_absolute_time();
		// Recursive relocation
		this->recursiveRebaseWithAccounting(context);
		context.notifyBatch(dyld_image_state_rebased, false);

		t3 = mach_absolute_time();
		if ( !context.linkingMainExecutable )
		     // Recursive binding is not lazy loading
		     this->recursiveBindWithAccounting(context, forceLazysBound, neverUnload);

		t4 = mach_absolute_time();
		if ( !context.linkingMainExecutable )
			/ / weak binding
			this->weakBind(context); t5 = mach_absolute_time(); }...// The link process can count the link dynamic library time, in the environment variable setting can print information
}

Copy the code
  • The link main process
  1. Load all dynamic libraries recursively
  2. Recursive image relocation
  3. Recursive binding blue loading
  4. If the binding
  • recursiveLoadLibrariesThe method is recursive loading dynamic library, explore the specific how recursive loading
void ImageLoader::recursiveLoadLibraries(const LinkContext& context, bool 
preflightOnly, const RPathChain& loaderRPaths, const char* loadPath){
    ...
    // get list of libraries this image needs
    // Get the current image-dependent dynamic library
    DependentLibraryInfo libraryInfos[fLibraryCount]; 
    this->doGetDependentLibraries(libraryInfos);

    // get list of rpaths that this image adds
    // Get the file path of the current image-dependent dynamic library
    std::vector<const char*> rpathsFromThisImage;
    this->getRPaths(context, rpathsFromThisImage);
    const RPathChain thisRPaths(&loaderRPaths, &rpathsFromThisImage);

    // load the image-dependent dynamic library
    for(unsigned int i=0; i < fLibraryCount; ++i){
      ...
      dependentLib = context.loadLibrary(requiredLibInfo.name, true.this->getPath(),
      &thisRPaths, cacheIndex);
      // Save the loaded dynamic librarysetLibImage(i, dependentLib, depLibReExported, requiredLibInfo.upward); . `} `// Tell the image-dependent dynamic libraries to load the required dynamic libraries
    for(unsigned int i=0; i < libraryCount(); ++i) {
            ImageLoader* dependentImage = libImage(i);
            if( dependentImage ! =NULL) { dependentImage->recursiveLoadLibraries(context, preflightOnly, thisRPaths, libraryInfos[i].name); }}}Copy the code
  • Load the dynamic library main flow
  1. Gets the currentimageDependent dynamic library and the file path of the dynamic library
  2. loadingimageDependency dynamic library and save it
  3. Tell the image-dependent dynamic libraries to load the required dynamic libraries

Linked dynamic library

		if ( sInsertedDylibCount > 0 ) {
			for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
				ImageLoader* image = sAllImages[i+1];
                                Dynamic libraries may also depend on other dynamic libraries
				link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL.NULL), - 1);
				image->setNeverUnloadRecursive();
			}
			if ( gLinkContext.allowInterposing ) {
				// only INSERTED libraries can interpose
				// register interposing info after all inserted libraries are bound so chaining works
				for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
					ImageLoader* image = sAllImages[i+1]; image->registerInterposing(gLinkContext); }}}Copy the code
  • The logic for linking the dynamic library is basically the same as linking the main program. Going to the image file starts in Table 1 below, since location 0 is the main program

We can see that the first data in the Image list is the main program

Weakly bound main program

		sMainExecutable->weakBind(gLinkContext);
		gLinkContext.linkingMainExecutable = false;

Copy the code
  • In the link to the main programlinkingMainExecutable = true, solinkInside the weak binding in the main program is not called, such as dynamic libraries are weak binding, and finally weak binding to the main program

Run the initialization method

	#if SUPPORT_OLD_CRT_INITIALIZATION
		// Old way is to run initializers via a callback from crt1.o
		if ( ! gRunInitializersOldWay ) 
			initializeMainExecutable(); 
	#else
		// run all initializers
		initializeMainExecutable(); 
	#endif
Copy the code

Return main


	if (sSkipMain) {
		notifyMonitoringDyldMain();
		if (dyld3::kdebug_trace_dyld_enabled(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE)) {
			dyld3::kdebug_trace_dyld_duration_end(launchTraceID, DBG_DYLD_TIMING_LAUNCH_EXECUTABLE, 0.0.2);
		}
		ARIADNEDBG_CODE(220.1);
                // Get the address of main
		result = (uintptr_t)&fake_main;
		*startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit;
	}

	return result;
Copy the code

Once you’ve got the main function, you go back to the main function

Review the DYLD loading process

  1. dyld::main
  2. Configuring environment Variables
  3. Loading the Shared cache
  4. Instance master
  5. Insert dynamic library
  6. Link main program
  7. Linked dynamic library
  8. Weakly bound main program
  9. Run the initialization method
  10. Return main

initializeMainExecutable

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

    // run initialzers for any inserted dylibs
    // Run all the Initialzers methods in dylibs
    ImageLoader::InitializerTimingList initializerTimes[allImagesCount()];
    initializerTimes[0].count = 0;
    const size_t rootCount = sImageRoots.size();
    // Run the dynamic library initialization method first
    if ( rootCount > 1 ) {
            for(size_t i=1; i < rootCount; ++i) {
               sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]); }}// run initializers for main executable and everything it brings up 
    // Run the initialization method of the main program
    sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]); . }Copy the code
  • Run the initialization method of the dynamic library first, and then run the initialization method of the main program

  • Initialize runInitializers methods


void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo)
{
	uint64_t t1 = mach_absolute_time();
	mach_port_t thisThread = mach_thread_self();
	ImageLoader::UninitedUpwards up;
	up.count = 1;
	up.imagesAndPaths[0] = { this.this->getPath() };
        // Recursively instantiate the list of images for the current image
	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();
	fgTotalInitTime += (t2 - t1);
}

Copy the code
processInitializers

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.
        // Recurse all 'images' in the list of all images
	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.
        // To ensure that all upward dependencies are initialized, uninitialize the image again
	if ( ups.count > 0 )
		processInitializers(context, thisThread, timingInfo, ups);
}

Copy the code
recursiveInitialization
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t 
this_thread, const char* pathToInitialize, InitializerTimingList& timingInfo, UninitedUpwards& uninitUps) { recursive_lock lock_info(this_thread); recursiveSpinLock(lock_info); .// initialize lower level libraries first
         // Initialize the most dependent library first
        for(unsigned int i=0; i < libraryCount(); ++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); }}}// The image to be initialized
        uint64_t t1 = mach_absolute_time();
        fState = dyld_image_state_dependents_initialized;
        oldState = fState;
        context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);

        // initialize this image
        // Initialize the image
        bool hasInitializers = this->doInitialization(context);
        // image initialization is complete
        // let anyone know we finished initializing this image
        fState = dyld_image_state_initialized;
        oldState = fState;
        context.notifySingle(dyld_image_state_initialized, this.NULL); . recursiveSpinUnLock(); }Copy the code
  1. The dynamic library image that needs to be initialized is fetched from libImage(). LibImage () is linked to the dynamic libraryrecursiveLoadLibrariesIn thesetLibImageThe savedimage
  2. The system will initialize each library according to its dependency depth. The one with the largest depth value will be initialized first. Each initialization will have an image file
  3. NotifySingle is called load_images and load is called
  4. DoInitialization is the initialization of libraries that have no dependencies
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();
		info.imageFilePath		= image->getRealPath();
		info.imageFileModDate	= image->lastModified();
		for(std::vector<dyld_image_state_change_handler>::iterator it = handlers->begin(); it ! = handlers->end(); ++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); throw str; }}}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(); addNonSharedCacheImageUUID(info); }}}if( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit ! =NULL) && image->notifyObjC() ) {
		uint64_t t0 = mach_absolute_time();
		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();
		uint64_t t2 = mach_absolute_time();
		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
SNotifyObjCInit The place to copy
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
	// record functions to call
	sNotifyObjCMapped	= mapped;
        // Here is the assignment
	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
_dyld_objc_notify_register

  1. doInitialization –> doModInitFunctions –> libSystem_initializer –> libdispatch_init –> _os_object_init –> _objc_init –> _dyld_objc_notify_register –> registerObjCNotifiers
  2. libSystem_initializerMethods in thelibSystemSystem in the library
  3. libdispatch_initand_os_object_initMethods in thelibdispatchSystem in the library
  4. _objc_initMethods in thelibobjcIn the system library,objcThe source code library is the most
  • Let’s explore*sNotifyObjCInitWhat exactly is being done, finding the assignment first_objc_initOf the call_dyld_objc_notify_registerSo the assignment should be in_objc_initmethods

  • sNotifyObjCInitload_imagesIt’s basically callingload_imagesMethod, let’s exploreload_imagesmethods

  • call_load_methodsThe method name is calledloadMethod, and then to explorecall_load_methodsmethods

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();
        }

        // 2. Call category +loads ONCE
        more_categories = call_category_loads();

        // 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
  • call_class_loads(void)
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));
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}
Copy the code

  • Both classes and categories are calledloadMethod, from which the order of calls can be drawn such a conclusion
  1. Of the classloadClassification of thanloadMethod is called first in the classloadMethod is not called until the class is calledloadmethods
  2. In the classloadMethods are compiled in the order in which they are compiledloadMethod first calls
  3. classifiedloadMethods are compiled in the order in which they are compiledloadMethod first calls

_objc_init process

from_objc_initWork backwards. call_objc_initMethod is_os_object_initMethods,libdispatchGlobal search in the source library_os_object_init

  • _os_object_init

void _os_object_init(void) { _objc_init(); Block_callbacks_RR callbacks = { sizeof(Block_callbacks_RR), (void (*)(const void ))&objc_retain, (void ()(const void ))&objc_release, (void ()(const void *))&_os_objc_destructInstance }; _Block_use_RR2(&callbacks); #if DISPATCH_COCOA_COMPAT const char *v = getenv(“OBJC_DEBUG_MISSING_POOLS”); if (v) _os_object_debug_missing_pools = _dispatch_parse_bool(v); v = getenv(“DISPATCH_DEBUG_MISSING_POOLS”); if (v) _os_object_debug_missing_pools = _dispatch_parse_bool(v); v = getenv(“LIBDISPATCH_DEBUG_MISSING_POOLS”); if (v) _os_object_debug_missing_pools = _dispatch_parse_bool(v); #endif }

  • _os_object_initMethod does call_objc_initMethods._os_object_initMethods are beinglibdispatch_initCall to continue validation

  • libdispatch_initMethod does call_os_object_initMethods.libdispatch_initbelibSystem_initializerThe call,libSystem_initializerMethod is inlibSystemSystem in the library

  • libSystem_initializerMethod does calllibdispatch_initMethods.libSystem_initializerMethods are beingdoModInitFunctionsThe call,doModInitFunctionsMethod is indyldIn the source library

LibSystem initializers must be run first and then run other doModInitFunctions to call all C++ functions

  • doModInitFunctionsMethod calls the globalc++The method is inloadMethods after
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
  • recursiveInitializationcalldoInitializationMethods, again, in recursive methods, perfectly concatenated

Load method call flow

  • _dyld_start –> dyldbootstrap::start –> dyld::_main –> intializeMainExecutable –> runInitializers –> processInitializers –> runInitializers –>recursiveInitialization –> notifySingle –> load_images –>+[ViewController load]

Procedure for calling the _objc_init method

  • doInitialization –> doModInitFunctions –> libSystem_initializer –> libdispatch_init –> _os_object_init –> _objc_init –> _dyld_objc_notify_register –>registerObjCNotifiers

These two call flows form a complete flow through doInitialization and notifySingle