3. dyld::_main()

Dyld ::_main() is the key function to start the entire App, and it does a lot of things

The whole loading process can be divided into nine steps:

  • Step 1: Set up the operating environment
  • Step 2: Load the shared cache
  • Step 3: Instantiate the main program
  • Step 4: Load the inserted dynamic library
  • Step 5: Link the main program
  • Step 6: Link the inserted dynamic library
  • Step 7: Perform weak symbol binding
  • Step 8: Perform the initialization method
  • Step 9: Find the entry point and return

The first step is to set up the operating environment

This step is mainly to set operating parameters, environment variables, etc. Code in the beginning, gave the sMainExecutableMachHeader into mainExecutableMH refs assignment, this is a macho_header structure, according to the current main program of the Mach – O header information, The loader can parse the entire Mach-O file based on the Mach-O header information. SetContext () is then called to set the context information, including callback functions, parameters, flag information, and so on. The set callback functions are implemented by the dyLD module itself. For example, the loadLibrary() function actually calls libraryLocator(), which loads the dynamic library. The code snippet is as follows:

static void setContext(const macho_header* mainExecutableMH, int argc, const char* argv[], const char* envp[], const char* apple[]) { gLinkContext.loadLibrary = &libraryLocator; gLinkContext.terminationRecorder = &terminationRecorder; . }Copy the code

GLinkContext is a Struct of type LinkContext, and LinkContext is a structure defined inside ImageLoader. Of course the image does not mean an image, but rather a binary file (an executable or so file) containing compiled symbols, code, etc. What ImageLoader does is load these files into memory. The ImageLoader that gLinkContext belongs to loads the executable file of the current App.

ConfigureProcessRestrictions () is used to configure process is limited, according to the current process is limited, to configure the link again the context and other environmental parameters.

CheckEnvironmentVariables () test environment variables, If gLinkContext allowEnvVarsPath to false and gLinkContext. AllowEnvVarsPrint also to false is returned directly, Otherwise call processDyldEnvironmentVariable () and set the environment variable, the source code is as follows:

void processDyldEnvironmentVariable(const char* key, const char* value, const char* mainExecutableDir) { if ( strcmp(key, "DYLD_FRAMEWORK_PATH") == 0 ) { appendParsedColonList(value, mainExecutableDir, &sEnv.DYLD_FRAMEWORK_PATH); } else if ( strcmp(key, "DYLD_FALLBACK_FRAMEWORK_PATH") == 0 ) { appendParsedColonList(value, mainExecutableDir, &sEnv.DYLD_FALLBACK_FRAMEWORK_PATH); } else if ( strcmp(key, "DYLD_LIBRARY_PATH") == 0 ) { appendParsedColonList(value, mainExecutableDir, &sEnv.DYLD_LIBRARY_PATH); } else if ( strcmp(key, "DYLD_IMAGE_SUFFIX") == 0 ) { gLinkContext.imageSuffix = parseColonList(value, NULL); } else if ( strcmp(key, "DYLD_INSERT_LIBRARIES") == 0 ) { sEnv.DYLD_INSERT_LIBRARIES = parseColonList(value, NULL); #if SUPPORT_ACCELERATE_TABLES sDisableAcceleratorTables = true; #endif } else if ( strcmp(key, "DYLD_PRINT_OPTS") == 0 ) { sEnv.DYLD_PRINT_OPTS = true; } else if ( strcmp(key, "DYLD_PRINT_ENV") == 0 ) { sEnv.DYLD_PRINT_ENV = true; } else if ( strcmp(key, "DYLD_DISABLE_DOFS") == 0 ) { sEnv.DYLD_DISABLE_DOFS = true; }... / / omit}Copy the code

You can see that there are many environment variables starting with DYLD_. We can configure these environment variables in the Argument of Xcode’s Edit Schema, and then output the response information in the Xcode console. These configured EnvironmentVariables will eventually be saved into an instance of a structure of type EnvironmentVariables.

Finally, getHostInfo() is called to get the current program architecture, and the first step of preparation is complete.

Step 2 Load the shared cache

Check whether shared caching is enabled by calling checkSharedRegionDisable().

LoadDyldCache () = loadDyldCache() = loadDyldCache() = loadDyldCache()

  • Only loaded into the current process, calledmapCachePrivate().
  • The shared cache is loaded and no action is taken.
  • Called when the current process loads the shared cache for the first timemapCacheSystemWide().

MapSharedCache () ¶

static void mapSharedCache(a)
{
	dyld3::SharedCacheOptions opts;
	opts.cacheDirOverride	= sSharedCacheOverrideDir;
	opts.forcePrivate		= (gLinkContext.sharedRegionMode == ImageLoader::kUsePrivateSharedRegion);

#if__x86_64__ && ! TARGET_IPHONE_SIMULATOR
	opts.useHaswell			= sHaswell;
#else
	opts.useHaswell			= false;
#endif
	opts.verbose			= gLinkContext.verboseMapping;
	loadDyldCache(opts, &sSharedCacheLoadInfo);

	// update global state
	if( sSharedCacheLoadInfo.loadAddress ! =nullptr ) {
		gLinkContext.dyldCache 								= sSharedCacheLoadInfo.loadAddress;
		dyld::gProcessInfo->processDetachedFromSharedRegion = opts.forcePrivate;
		dyld::gProcessInfo->sharedCacheSlide                = sSharedCacheLoadInfo.slide;
		dyld::gProcessInfo->sharedCacheBaseAddress          = (unsigned long)sSharedCacheLoadInfo.loadAddress;
		sSharedCacheLoadInfo.loadAddress->getUUID(dyld::gProcessInfo->sharedCacheUUID);
		dyld3::kdebug_trace_dyld_image(DBG_DYLD_UUID_SHARED_CACHE_A, (const uuid_t *)&dyld::gProcessInfo->sharedCacheUUID[0] and {0.0}, {{ 0.0}},constmach_header *)sSharedCacheLoadInfo.loadAddress); }}Copy the code

LoadDyldCache () ¶

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

#if TARGET_IPHONE_SIMULATOR
    // simulator only supports mmap()ing cache privately into process
    return mapCachePrivate(options, results);
#else
    if ( options.forcePrivate ) {
        // mmap cache into this process only
        // Only the current process is loaded
        return mapCachePrivate(options, results);
    }
    else {
        // fast path: when cache is already mapped into shared region
        bool hasError = false;
        if ( reuseExistingCache(options, results) ) {
        		// The shared cache is loaded, no action is takenhasError = (results->errorMessage ! =nullptr);
        } else {
            // slow path: this is first process to load cache
            // The current process loads the shared cache for the first time
            hasError = mapCacheSystemWide(options, results);
        }
        return hasError;
    }
#endif
}
Copy the code
Shared cache mechanism

In iOS, every application dependent dynamic library needs through a dyld a loaded into memory, however, many system library is almost used in every program, if repeated in each program running time to load once, certainly will cause slow, in order to optimize start programs to improve the speed and performance, the Shared cache mechanism was born. All the default dynamic link libraries are merged into one large cache file, OS X’s cache files stored in/private/var/db/dyld/directory, iOS in/System/Library/Caches/com. Apple. Dyld/directory, They are kept in different structures. Dyld_shared_cache_armv7s and dyld_shared_cache_armv64 are two files on my iPhone6s.

When loading a Mach-O file (either an executable or a library), Dyld first checks the shared cache to see if the file exists, and if so, pulls it out of the shared cache and uses it. Each process maps this shared cache into its own address space. This approach greatly optimizes the startup time of applications on OS X and iOS.

Step 3 instantiate the main program

In this step, the system creates an ImageLoader for the main program that has been mapped to the process space (the mapping is done during the MachO phase of kernel parsing) and adds it to the sAllImages. InstantiateFromLoadedImage () first call isCompatibleMachO () to detect the Mach – O the magic of the head, cputype and cpusubtype related attributes, determine the Mach -o file compatibility, If the compatibility to meet call ImageLoaderMachO: : instantiateMainExecutable () method of the main program ImageLoader create and add operations, the source code is as follows:

// The kernel maps in main executable before dyld gets control. We need to
// make an ImageLoader* for the already mapped in main executable.
We need to create an ImageLoader for the main executable that has been mapped to the process space before dyld gains control.
static ImageLoaderMachO* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path)
{
	// try mach-o loader
  // Verify that the Mach-o type is supported
	if ( isCompatibleMachO((const uint8_t*)mh, path) ) {
    // Create ImageLoader for the main program mach-o
		ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext);
    // Add the main binary file to the sAllImages array
		addImage(image);
		return (ImageLoaderMachO*)image;
	}
	
	throw "main executable not a known format";
}
Copy the code

ImageLoaderMachO: : instantiateMainExecutable () of the source code is as follows:

// create image for main executable
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;
	unsigned int libCount;
	const linkedit_data_command* codeSigCmd;
	const encryption_info_command* encryptCmd;
	sniffLoadCommands(mh, path, false, &compressed, &segCount, &libCount, context, &codeSigCmd, &encryptCmd);
	// instantiate concrete class based on content of load commands
  // Instantiate the concrete class based on Load Commands
	if ( compressed ) 
		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

The sniffLoadCommands() function is first called to get some data

  • Compressed, whether the Mach-O file is compressed. If mach-O has LC_DYLD_INFO and LC_DYLD_INFO_ONLY load commands, it indicates that mach-O is compressed:

    switch (cmd->cmd) {
    case LC_DYLD_INFO:
    case LC_DYLD_INFO_ONLY:
        if( cmd->cmdsize ! =sizeof(dyld_info_command) )
            throw "malformed mach-o image: LC_DYLD_INFO size wrong";
        dyldInfoCmd = (struct dyld_info_command*)cmd;
        // LC_DYLD_INFO or LC_DYLD_INFO_ONLY indicates a mach-O of compressed type
        *compressed = true;
        break; . }Copy the code
  • SegCount, number of segments. The number of segments cannot exceed 255 according to the LC_SEGMENT_COMMAND load command:

    caseLC_SEGMENT_COMMAND: segCmd = (struct macho_segment_command*)cmd; .if( segCmd->vmsize ! =0 )
            *segCount += 1;
    if ( *segCount > 255 )
        dyld::throwf("malformed mach-o image: more than 255 segments in %s", path);
    Copy the code
  • LibCount, number of dynamic libraries. The number of libraries can be counted according to the loading commands LC_LOAD_DYLIB, LC_LOAD_WEAK_DYLIB, LC_REEXPORT_DYLIB, and LC_LOAD_UPWARD_DYLIB. The number of libraries cannot exceed 4095.

    case LC_LOAD_DYLIB:
    case LC_LOAD_WEAK_DYLIB:
    case LC_REEXPORT_DYLIB:
    case LC_LOAD_UPWARD_DYLIB:
    *libCount += 1;
    if ( *libCount > 4095 )
        dyld::throwf("malformed mach-o image: more than 4095 dependent libraries in %s", path);
    Copy the code
  • CodeSigCmd to get the code signature loading command by parsing LC_CODE_SIGNATURE:

    case LC_CODE_SIGNATURE:
    *codeSigCmd = (struct linkedit_data_command*)cmd;
    break;
    Copy the code
  • EncryptCmd: uses LC_ENCRYPTION_INFO and LC_ENCRYPTION_INFO_64 to obtain the segment encryption information:

    case LC_ENCRYPTION_INFO:
    ...
    *encryptCmd = (encryption_info_command*)cmd;
    break;
    case LC_ENCRYPTION_INFO_64:
    ...
    *encryptCmd = (encryption_info_command*)cmd;
    break;
    Copy the code

ImageLoader is an abstract class whose subclass is responsible for instantiating the Mach-O file into an image. After the sniffLoadCommands() is parsed, it decides which subclass to call to instantiate according to the compressed value.

After the instantiation is complete, call addImage() to add the image to the list of sAllImages global images.

static void addImage(ImageLoader* image)
{
	// add to master list
    allImagesLock(a); sAllImages.push_back(image);
    allImagesUnlock(a);// update mapped ranges
	uintptr_t lastSegStart = 0;
	uintptr_t lastSegEnd = 0;
	for(unsigned int i=0, e=image->segmentCount(a); i < e; ++i) {if ( image->segUnaccessible(i) ) 
			continue;
		uintptr_t start = image->segActualLoadAddress(i);
		uintptr_t end = image->segActualEndAddress(i);
		if ( start == lastSegEnd ) {
			// two segments are contiguous, just record combined segments
			lastSegEnd = end;
		}
		else {
			// non-contiguous segments, record last (if any)
			if( lastSegEnd ! =0 )
				addMappedRange(image, lastSegStart, lastSegEnd); lastSegStart = start; lastSegEnd = end; }}if( lastSegEnd ! =0 )
		addMappedRange(image, lastSegStart, lastSegEnd);

	if( gLinkContext.verboseLoading || (sEnv.DYLD_PRINT_LIBRARIES_POST_LAUNCH && (sMainExecutable! =NULL) && sMainExecutable->isLinked()) ) {
		dyld::log("dyld: loaded: %s\n", image->getPath()); }}Copy the code

When we do LLDB debugging, we type image list to look at all the images, and the first thing that comes up is the main program module, because this is the first place to add the main program to the sAllImages array.

Step 4 loads the inserted dynamic library

DYLD_INSERT_LIBRARIES: loadInsertedDylib(); loadInsertedDylib();

if( sEnv.DYLD_INSERT_LIBRARIES ! =NULL ) {
	for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib ! =NULL; ++lib) 
	loadInsertedDylib(*lib);
}
Copy the code

LoadInsertedDylib () calls load() after setting a LoadContext inside:

static void loadInsertedDylib(const char* path)
{
	ImageLoader* image = NULL;
	unsigned cacheIndex;
	try {
		LoadContext context;
		context.useSearchPaths		= false;
		context.useFallbackPaths	= false;
		context.useLdLibraryPath	= false;
		context.implicitRPath		= false;
		context.matchByInstallName	= false;
		context.dontLoad			= false;
		context.mustBeBundle		= false;
		context.mustBeDylib			= true;
		context.canBePIE			= false;
		context.enforceIOSMac		= true;
		context.origin				= NULL;	// can't use @loader_path with DYLD_INSERT_LIBRARIES
		context.rpath				= NULL;
		image = load(path, context, cacheIndex);
	}
	catch (const char* msg) {
		if ( gLinkContext.allowInsertFailures )
			dyld::log("dyld: warning: could not load inserted library '%s' into hardened process because %s\n", path, msg);
		else
			halt(dyld::mkstringf("could not load inserted library '%s' because %s\n", path, msg));
	}
	catch(...). {halt(dyld::mkstringf("could not load inserted library '%s'\n", path)); }}Copy the code

The load() method is called not only by loadInsertedDylib(), but also by methods such as Dlopen that load dynamic libraries at runtime.

/** Do path expansion, search lookup, rearrange, and cache lookup. The path expansion and search are divided into several phases */
ImageLoader* load(const char* path, const LoadContext& context, unsigned& cacheIndex)
{
	CRSetCrashLogMessage2(path);
	const char* orgPath = path;
	cacheIndex = UINT32_MAX;
	
	//dyld::log("%s(%s)\n", __func__ , path);
	char realPath[PATH_MAX];
	// when DYLD_IMAGE_SUFFIX is in used, do a realpath(), otherwise a load of "Foo.framework/Foo" will not match
	if( context.useSearchPaths && ( gLinkContext.imageSuffix ! =NULL&& *gLinkContext.imageSuffix ! =NULL)) {if ( realpath(path, realPath) ! =NULL )
			path = realPath;
	}
	
	// try all path permutations and check against existing loaded images
	ImageLoader* image = loadPhase0(path, orgPath, context, cacheIndex, NULL);
	if( image ! =NULL ) {
		CRSetCrashLogMessage2(NULL);
		return image;
	}

	// try all path permutations and try open() until first success
	std::vector<const char*> exceptions;
	image = loadPhase0(path, orgPath, context, cacheIndex, &exceptions);
#if! TARGET_IPHONE_SIMULATOR
	// <rdar://problem/16704628> support symlinks on disk to a path in dyld shared cache
	if ( image == NULL)
		image = loadPhase2cache(path, orgPath, context, cacheIndex, &exceptions);
#endif./ / to omit
}
Copy the code

The load() function is implemented as a series of loadPhase*() functions, loadPhase1~6. The search path of these phases corresponds to each environment variable as follows: DYLD_ROOT_PATH -> LD_LIBRARY_PATH -> DYLD_FRAMEWORK_PATH -> original path -> DYLD_FALLBACK_LIBRARY_PATH

If the loadPhase0 return address is empty, then go loadPhase2cache method cache lookup, after finding from ImageLoaderMachO: : instantiateFromCache methods to instantiate, otherwise throw exceptions.

When the internal call to loadPhase5load () function, will first search in the Shared cache, if there is a use ImageLoaderMachO: : instantiateFromCache () to instantiate ImageLoader, Otherwise by loadPhase5open () open the file and read the data into memory, then call loadPhase6 (), through ImageLoaderMachO: : instantiateFromFile instantiation ImageLoader (), Finally, call checkandAddImage() to validate the image and add it to the global image list sAllImages.

According to the above analysis, the main program ImageLoader is at the top of the global sAllImages table, followed by the inserted dynamic library ImageLoader, each dynamic library corresponds to an ImageLoader.

Step 5 Link the main program

This step calls the link() function to dynamically correct the instantiated main program, making the binary executable. The link() function calls ImageLoader::link() internally:

void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool preflightOnly, bool neverUnload, const RPathChain& loaderRPaths, const char* imagePath)
{
	//dyld::log("ImageLoader::link(%s) refCount=%d, neverUnload=%d\n", imagePath, fDlopenReferenceCount, fNeverUnload);
	
	// clear error strings
	(*context.setErrorStrings)(0.NULL.NULL.NULL);

	uint64_t t0 = mach_absolute_time(a);// Load the main program recursively
	this->recursiveLoadLibraries(context, preflightOnly, loaderRPaths, imagePath);
	context.notifyBatch(dyld_image_state_dependents_mapped, preflightOnly);

	// we only do the loading step for preflights
	if ( preflightOnly )
		return;

	uint64_t t1 = mach_absolute_time(a); context.clearAllDepths(a);// Recursively refresh the dependent library hierarchy
	this->recursiveUpdateDepth(context.imageCount());

	__block uint64_t t2, t3, t4, t5;
	{
		dyld3::ScopedTimer(DBG_DYLD_TIMING_APPLY_FIXUPS, 0.0.0);
		t2 = mach_absolute_time(a);// rebase recursively
		this->recursiveRebase(context);
		context.notifyBatch(dyld_image_state_rebased, false);

		t3 = mach_absolute_time(a);if ( !context.linkingMainExecutable )
      // Bind the symbol table recursively
			this->recursiveBindWithAccounting(context, forceLazysBound, neverUnload);

		t4 = mach_absolute_time(a);if ( !context.linkingMainExecutable )
      // Weak symbol binding
			this->weakBind(context);
		t5 = mach_absolute_time(a); }if ( !context.linkingMainExecutable )
        context.notifyBatch(dyld_image_state_bound, false);
	uint64_t t6 = mach_absolute_time(a); std::vector<DOFInfo> dofs;// Register the DOF section
	this->recursiveGetDOFSections(context, dofs);
	context.registerDOFs(dofs);
	uint64_t t7 = mach_absolute_time(a);// interpose any dynamically loaded images
	if ( !context.linkingMainExecutable && (fgInterposingTuples.size() != 0)) {dyld3::ScopedTimer timer(DBG_DYLD_TIMING_APPLY_INTERPOSING, 0.0.0);
		this->recursiveApplyInterposing(context);
	}

	// clear error strings
	(*context.setErrorStrings)(0.NULL.NULL.NULL);

	fgTotalLoadLibrariesTime += t1 - t0;
	fgTotalRebaseTime += t3 - t2;
	fgTotalBindTime += t4 - t3;
	fgTotalWeakBindTime += t5 - t4;
	fgTotalDOF += t7 - t6;
	
	// done with initial dylib loads
	fgNextPIEDylibAddress = 0;
}
Copy the code

As you can see from the source code, this step does the following:

  • recursiveLoadLibraries()According to theLC_LOAD_DYLIBThe load command recursively loads into memory the dependent libraries required by the main program.
  • recursiveUpdateDepth()Recursively refresh the hierarchy of dependent libraries.
  • recursiveRebase()Because of ASLR, relocation operations must be performed recursively on the main program as well as on the dependent libraries.
  • recursiveBind()Perform symbol table binding for all main program binaries and dependent dynamic libraries. For nolazy’s symbols to be recursively bound, lazy’s symbols are dynamically bound at run time (unbound only when first called).
  • weakBind()Weak symbolic binding is performed at this point if the linked binary is not the main binary, while the main binary is atlink()Weak symbol binding is performed after this, which will be analyzed later.
  • recursiveGetDOFSections(),context.registerDOFs()Register the DTrace Object Format (DOF) section.

Step 6 Link the inserted dynamic library

This step is the same as linking the main program. The previous call to addImage() loops through the list of dynamic libraries saved in sAllImages and calls link() to link them. Note that the first item saved in sAllImages is the mirror of the main program, so start with the second. Retrieve the ImageLoader of the dynamic library:

if ( sInsertedDylibCount > 0 ) {
			for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
				ImageLoader* image = sAllImages[i+1];
				link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL.NULL), - 1);
				image->setNeverUnloadRecursive(a); }// only INSERTED libraries can interpose
			// register interposing info after all inserted libraries are bound so chaining works
  		// After all the inserted libraries have been bound, register the insertion information linking work
			for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
				ImageLoader* image = sAllImages[i+1];
				image->registerInterposing(gLinkContext); }}Copy the code

The registerinterposture () function of each image was then cyclically invoked to register the insertion operation, which traversed the LC_SEGMENT_COMMAND load command of Mach-O, read the __DATA,__interpose section, After finding the data that needs to be registered for an insert operation (also known as symbol address substitution), and doing some checking, the symbol to be replaced and the symbol information to be replaced are stored in the fgInterposingTuples list for future reference when specific symbol replacements are made.

void ImageLoaderMachO::registerInterposing(const LinkContext& context)
{
	// mach-o files advertise interposing by having a __DATA __interpose section
	struct InterposeData { uintptr_t replacement; uintptr_t replacee; };
	const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
	const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
	const struct load_command* cmd = cmds;
	for (uint32_t i = 0; i < cmd_count; ++i) {
		switch (cmd->cmd) {
			case LC_SEGMENT_COMMAND:
				{
					const struct macho_segment_command* seg = (struct macho_segment_command*)cmd;
					const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command));
					const struct macho_section* const sectionsEnd = &sectionsStart[seg->nsects];
					for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
						if ( ((sect->flags & SECTION_TYPE) == S_INTERPOSING) || ((strcmp(sect->sectname, "__interpose") = =0) && (strcmp(seg->segname, "__DATA") = =0))) {// <rdar://problem/23929217> Ensure section is within segment
							if ( (sect->addr < seg->vmaddr) || (sect->addr+sect->size > seg->vmaddr+seg->vmsize) || (sect->addr+sect->size < sect->addr) )
								dyld::throwf("interpose section has malformed address range for %s\n".this->getPath());
							const InterposeData* interposeArray = (InterposeData*)(sect->addr + fSlide);
							const size_t count = sect->size / sizeof(InterposeData);
							for (size_t j=0; j < count; ++j) {
								ImageLoader::InterposeTuple tuple;
								tuple.replacement		= interposeArray[j].replacement;
								tuple.neverImage		= this;
								tuple.onlyImage		    = NULL;
								tuple.replacee			= interposeArray[j].replacee;
								// <rdar://problem/25686570> ignore interposing on a weak function that does not exist
								if ( tuple.replacee == 0 )
									continue;
								// <rdar://problem/7937695> verify that replacement is in this image
								if ( this->containsAddress((void*)tuple.replacement) ) {
									// chain to any existing interpositions
									for (std::vector<InterposeTuple>::iterator it=fgInterposingTuples.begin(a); it ! = fgInterposingTuples.end(a); it++) {if ( it->replacee == tuple.replacee ) {
											tuple.replacee = it->replacement;
										}
									}
									ImageLoader::fgInterposingTuples.push_back(tuple);
								}
							}
						}
					}
				}
				break;
		}
		cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); }}Copy the code

Then call applyInterposing () function, through internal doInterpose symbol substitution () virtual functions for operation, to the ImageLoaderMachOCompressed: : doInterpose () function implementation, for instance, This function internally calls eachBind() and eachLazyBind() to apply the insertion operation to the conventional symbol and the lazy loaded symbol respectively. The specific processing function is interposeAt(). This function calls interposedAddress() to find the symbolic address to be replaced in fgInterposingTuples, and performs the final symbolic address replacement as follows:

// apply interposing to initial set of images
for(int i=0; i < sImageRoots.size(a); ++i) { sImageRoots[i]->applyInterposing(gLinkContext); } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --void ImageLoader::applyInterposing(const LinkContext& context)
{
	dyld3::ScopedTimer timer(DBG_DYLD_TIMING_APPLY_INTERPOSING, 0.0.0);
	if ( fgInterposingTuples.size() != 0 )
		this->recursiveApplyInterposing(context); } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --void ImageLoader::recursiveApplyInterposing(const LinkContext& context)
{ 
	if(! fInterposed ) {// break cycles
		fInterposed = true;
		
		try {
			// interpose lower level libraries first
			for(unsigned int i=0; i < libraryCount(a); ++i) { ImageLoader* dependentImage =libImage(i);
				if( dependentImage ! =NULL )
					dependentImage->recursiveApplyInterposing(context);
			}
				
			// interpose this image
			doInterpose(context);
		}
		catch (const char* msg) {
			// this image is not interposed
			fInterposed = false;
			throw; }}} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --void ImageLoaderMachOCompressed::doInterpose(const LinkContext& context)
{
	if ( context.verboseInterposing )
		dyld::log("dyld: interposing %lu tuples onto image: %s\n", fgInterposingTuples.size(), this->getPath());

	// update prebound symbols
	eachBind(context, ^(const LinkContext& ctx, ImageLoaderMachOCompressed* image,
						uintptr_t addr, uint8_t type, const char* symbolName,
						uint8_t symbolFlags, intptr_t addend, long libraryOrdinal,
						ExtraBindData *extraBindData,
						const char* msg, LastLookup* last, bool runResolver) {
		return ImageLoaderMachOCompressed::interposeAt(ctx, image, addr, type, symbolName, symbolFlags,
													   addend, libraryOrdinal, extraBindData,
													   msg, last, runResolver);
	});
	eachLazyBind(context, ^(const LinkContext& ctx, ImageLoaderMachOCompressed* image,
							uintptr_t addr, uint8_t type, const char* symbolName,
							uint8_t symbolFlags, intptr_t addend, long libraryOrdinal,
							ExtraBindData *extraBindData,
							const char* msg, LastLookup* last, bool runResolver) {
		return ImageLoaderMachOCompressed::interposeAt(ctx, image, addr, type, symbolName, symbolFlags, addend, libraryOrdinal, extraBindData, msg, last, runResolver); }); } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --uintptr_t ImageLoaderMachOCompressed::interposeAt(const LinkContext& context, ImageLoaderMachOCompressed* image,
												  uintptr_t addr, uint8_t type, const char*,
                                                  uint8_t.intptr_t.long,
                                                  ExtraBindData *extraBindData,
                                                  const char*, LastLookup*, bool runResolver)
{
	if ( type == BIND_TYPE_POINTER ) {
		uintptr_t* fixupLocation = (uintptr_t*)addr;
		uintptr_t curValue = *fixupLocation;
		uintptr_t newValue = interposedAddress(context, curValue, image);
		if ( newValue != curValue) {
			*fixupLocation = newValue;
		}
	}
	return 0; } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --uintptr_t ImageLoader::interposedAddress(const LinkContext& context, uintptr_t address, const ImageLoader* inImage, const ImageLoader* onlyInImage)
{
	//dyld::log("interposedAddress(0x%08llX), tupleCount=%lu\n", (uint64_t)address, fgInterposingTuples.size());
	for (std::vector<InterposeTuple>::iterator it=fgInterposingTuples.begin(a); it ! = fgInterposingTuples.end(a); it++) {//dyld::log(" interposedAddress: replacee=0x%08llX, replacement=0x%08llX, neverImage=%p, onlyImage=%p, inImage=%p\n",
		// (uint64_t)it->replacee, (uint64_t)it->replacement, it->neverImage, it->onlyImage, inImage);
		// replace all references to 'replacee' with 'replacement'
		if( (address == it->replacee) && (inImage ! = it->neverImage) && ((it->onlyImage ==NULL) || (inImage == it->onlyImage)) ) {
			if ( context.verboseInterposing ) {
				dyld::log("dyld interposing: replace 0x%lX with 0x%lX\n", it->replacee, it->replacement);
			}
			returnit->replacement; }}return address;
}
Copy the code

RecursiveBind () is then called to rebind the symbol that was previously replaced:

// Bind and notify for the inserted images now interposing has been registered
// The image for binding and notification inserts is now registered
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); }}Copy the code

Step 7 Performs weak symbol binding

WeakBind () first merges all the weak symbols of the dynamic library into a list through getCoalescedImages(), and then calls initializeCoalIterator() to sort the weak symbols that need to be bound. Weak_bind_off and Weak_bind_size fields of DyLD_INFO_command structure are then read by incrementCoalIterator(). Weak_bind_size and weak_bind_OFF fields of dyLD_info_command structure are determined. Weak_bind_size and weak_bind_off fields are determined.

void ImageLoader::weakBind(const LinkContext& context) { if ( context.verboseWeakBind ) dyld::log("dyld: weak bind start:\n"); uint64_t t1 = mach_absolute_time(); // get set of ImageLoaders that participate in coalecsing ImageLoader* imagesNeedingCoalescing[fgImagesRequiringCoalescing]; unsigned imageIndexes[fgImagesRequiringCoalescing]; int count = context.getCoalescedImages(imagesNeedingCoalescing, imageIndexes); . // omit #if __arm64e__ for (int I =0; i < count; ++i) { if ( imagesNeedingCoalescing[i]->usesChainedFixups() ) { // during binding of references to weak-def symbols, the dyld cache was patched // but if main executable has non-weak override of operator new or delete it needs is handled  here if ( ! imagesNeedingCoalescing[i]->weakSymbolsBound(imageIndexes[i]) ) { for (const char* weakSymbolName : sTreatAsWeak) { const ImageLoader* dummy; imagesNeedingCoalescing[i]->resolveWeak(context, weakSymbolName, true, false, &dummy); } } } else { // look for weak def symbols in this image which may override the cache ImageLoader::CoalIterator coaler; imagesNeedingCoalescing[i]->initializeCoalIterator(coaler, i, 0); imagesNeedingCoalescing[i]->incrementCoalIterator(coaler); while ( ! coaler.done ) { imagesNeedingCoalescing[i]->incrementCoalIterator(coaler); const ImageLoader* dummy; // a side effect of resolveWeak() is to patch cache imagesNeedingCoalescing[i]->resolveWeak(context, coaler.symbolName, true, false, &dummy); } } } #endif // mark all as having all weak symbols bound for(int i=0; i < count; ++i) { imagesNeedingCoalescing[i]->setWeakSymbolsBound(imageIndexes[i]); } } uint64_t t2 = mach_absolute_time(); fgTotalWeakBindTime += t2 - t1; if ( context.verboseWeakBind ) dyld::log("dyld: weak bind end\n"); }Copy the code

Step 8 Performs the initialization method

Objc & Runtime

InitializeMainExecutable starts the initialization operation and you can see that there are print operations for the environment variables DYLD_PRINT_STATISTICS and DYLD_PRINT_STATISTICS_DETAILS We will use this environment variable when printing the pre-main startup time.

Void initializeMainExecutable() {// record that we've reached this step gLinkContext.startedInitializingMainExecutable = true; / / the run initialzers for any inserted dylibs / / for initialization has been inserted into the dynamic library to execute ImageLoader: : InitializerTimingList initializerTimes[allImagesCount()]; initializerTimes[0].count = 0; const size_t rootCount = sImageRoots.size(); 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 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 // Print a specific start time 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

Internal calls to runInitializers(), followed by processInitializers(), and recursiveInitialization() run the initializer from the bottom up. This ensures that you can safely call the content that you depend on.

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

	if ( fState < dyld_image_state_dependents_initialized- 1 ) {
		uint8_t oldState = fState;
		// break cycles
		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.images[uninitUps.count] = dependentImage;
						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
			// Notify objc that the image is about to be initialized
			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 image
			bool hasInitializers = this->doInitialization(context);

			// let anyone know we finished initializing this image
			// Notify everyone that the image has been initialized
			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

Here we see the notifySingle() function, followed by the notifySingle() function, and see the following processing code:

if( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit ! =NULL) && image->notifyObjC()) {uint64_t t0 = mach_absolute_time(a); (*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); }}Copy the code

All we care about is the sNotifyObjCInit callback. Search for sNotifyObjCInit and find the assignment in the registerObjCNotifiers() function:

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(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

Callbacks to the map, init, and unmap events are passed in. Then globally search for where the registerObjCNotifiers() were called:

void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped)
{
    dyld::registerObjCNotifiers(mapped, init, unmapped);
}
Copy the code

So who called _dyLD_OBJC_notify_register ()? Static analysis is no longer available, except to observe the symbol breakpoint under _dyLD_OBJC_notify_register ().

Libobjc.a.dylib’s _objc_init function calls the _dyLD_objc_notify_register () function to register this notification. Find the _objc_init method in objc-os.mm:

/*********************************************************************** * _objc_init * Bootstrap initialization. Registers our image notifier with dyld. * Called by libSystem BEFORE library initialization time * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
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);lock_init(a);exception_init(a); _dyld_objc_notify_register(&map_images, load_images, unmap_image); }Copy the code

_objc_init initializes libdispatch in libsystem_initializer, an initialize method in libsystem. Libdispatch then calls _os_object_int. Finally, _objc_init is called.

map_images

As you can see from the above code, when initializing the libsystem dynamic library, the Runtime registers the callback function to dyLD. At this time, the map_images callback is recursively called for all images that have been loaded in memory. Dyld issues a dyLD_IMAGe_STATE_bound notification after the bind operation, and then invokes the map_images callback.

Map_images does several things to accomplish Objc Setup:

  1. Read the DATA section of the binary to find objC related information
  2. Registered Objc class
  3. Make sure that the selector is unique
  4. Read protocol and category information

The main thing inside map_images is the _read_images function:

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unopt imizedTotalClasses)
{
    header_info *hi;
    uint32_t hIndex;
    size_t count;
    size_t i;
    Class *resolvedFutureClasses = nil;
    size_t resolvedFutureClassCount = 0; 
    static bool doneOnce;
    TimeLogger ts(PrintImageTimes);
#define EACH_HEADER \
    hIndex = 0;         \
    hIndex < hCount && (hi = hList[hIndex]); \
    hIndex++
    if(! doneOnce) { doneOnce = YES;// Instantiate the hash table of the storage class and dynamically expand the number of classes
         int namedClassesSize = (isPreoptimized()? unoptimizedTotalClasses : totalClasses) *4 / 3;
         gdb_objc_realized_classes = NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
     }
// The editor reads the class list, adds all classes to the hash table, marks lazily loaded classes and initializes the space
    for (EACH_HEADER) {
         if (! mustReadClasses(hi)) { 
            continue;
         }
         bool headerIsBundle = hi->isBundle(a);bool headerIsPreoptimized = hi->isPreoptimized(a);/** Add the new class to the hash table */
         // Get a pointer to classref_t from the compiled class list
         classref_t *classlist = _getObjc2ClassList(hi, &count);
         for (i = 0; i < count; i++) {
             OS_dispatch_queue_concurrent, OS_xpc_object, NSRunLoop, and other system classes 
             Class cls = (Class)classlist[i];
             // Use the readClass function to get the new class. Inside, we operate on the ro and RW structures
             Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
             // Initialize the memory required by all lazy-loaded classes
             if(newCls ! = cls && newCls) {// Add lazy-loaded classes to the array
                 resolvedFutureClasses = (Class *)realloc(resolvedFutureClasses, (resolvedFutureClassCount+1) * sizeof(Class)); resolvedFutureClasses[resolvedFutureClassCount++] = newCls; }}}// The mapped classes and superclasses will be remapped. The remapped classes are non-lazy-loaded classes
     if (!noClassesRemapped()) {
         for (EACH_HEADER) {
         // Remap the Class to retrieve the reference to the Class from the _getObjc2ClassRefs function
             Class *classrefs = _getObjc2ClassRefs(hi, &count);
             for (i = 0; i < count; i++) {
                 remapClassRef(&classrefs[i]); }}// remap the parent class
         classrefs = _getObjc2SuperRefs(hi, &count); 
         for (i = 0; i < count; i++) {
             remapClassRef(&classrefs[i]); }}// Remap SEL
     static size_t UnfixedSelectors; sel_lock(a);for (EACH_HEADER) {
         if (hi->isPreoptimized()) continue;
         bool isBundle = hi->isBundle(a); SEL *sels = _getObjc2SelectorRefs(hi, &count); UnfixedSelectors += count;for (i = 0; i < count; i++) {
             const char *name = sel_cname(sels[i]);
             SEL / / registration
             sels[i] = sel_registerNameNoLock(name, isBundle); }}// Fix old function pointer left behind
     for (EACH_HEADER) {
         message_ref_t *refs = _getObjc2MessageRefs(hi, &count); 
         if (count == 0) continue;
         for (i = 0; i < count; i++) {
             // Internally register the commonly used Pointers to alloc, objc_msgSend, etc., and fix them as new Pointers
             fixupMessageRef(refs+i); }}// Iterate over all Protocol lists and load the Protocol list into the Protocol hash table
     for (EACH_HEADER) {
         extern objc_class OBJC_CLASS_$_Protocol;
         // CLS = Protocol. All protocols and objects have similar structures. Isa corresponds to Protocol
         Class cls = (Class)&OBJC_CLASS_$_Protocol;
         assert(cls);
         // Get the protocol hash table
         NXMapTable *protocol_map = protocols(a);bool isPreoptimized = hi->isPreoptimized(a);bool isBundle = hi->isBundle(a);// Reads and initializes Protocol from the compiler
         protocol_t **protolist = _getObjc2ProtocolList(hi, &count); 
         for (i = 0; i < count; i++) {
            readProtocol(protolist[i], cls, protocol_map, isPreoptimized, isBundle); }}// Fix protocol list application, optimized images are not correct or not
     for (EACH_HEADER) {
         The _getObjc2ProtocolRefs function below is different from the _getObjc2ProtocolRefs function above
         protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
         for (i = 0; i < count; i++) {
            remapProtocolRef(&protolist[i]); }}// Implement classes that are not lazily loaded
     for (EACH_HEADER) {
         classref_t *classlist =
            _getObjc2NonlazyClassList(hi, &count);
         for (i = 0; i < count; i++) {
             Class cls = remapClass(classlist[i]);
             if(! cls)continue;
             // Implement classes that are not to be loaded (instantiate some information, such as RW)
             realizeClass(cls); }}// Walk through the resolvedFutureClasses array to implement all lazily loaded classes
     if (resolvedFutureClasses) {
         for (i = 0; i < resolvedFutureClassCount; i++) {
             // Implement lazy loading classes
             realizeClass(resolvedFutureClasses[i]); resolvedFutureClasses[i]->setInstancesRequireRawIsa(false/*inherited*/);
         }
        free(resolvedFutureClasses);
     }
     
// Discover and process all categories
     for (EACH_HEADER) {
         // The outer loop iterates through the current class to find the Category array corresponding to the class
         category_t **catlist = _getObjc2CategoryList(hi, &count);
         bool hasClassProperties = hi->info() - >hasCategoryClassProperties(a);// The inner loop iterates through all categories of the current class
         for (i = 0; i < count; i++) {
             category_t *cat = catlist[i];
             Class cls = remapClass(cat->cls);
             // First, register the Category by its owning class. If the class is already implemented, the list of methods for the class is reconstructed
             bool classExists = NO;
             if (cat->instanceMethods || cat->protocols || cat->instanceProperties) {
                 // Add the Category to the corresponding value, which is the array of all categories corresponding to the Class
                 addUnattachedCategoryForClass(cat, cls, hi);
                 // Add the method, protocol, and property of the Category to the Class
                 if (cls->isRealized()) {
                     remethodizeClass(cls); classExists = YES; }}// Same logic as below, but in Meta Class
             if (cat->classMethods || cat->protocols || (hasClassProperties && cat->_classProperties)) {
                 addUnattachedCategoryForClass(cat, cls->ISA(), hi); 
                 if (cls->ISA() - >isRealized()) {
                     remethodizeClass(cls->ISA()); }}}}// Initialize all classes. Category must be executed last
     // DebugNonFragileIvars = -1, not executed
     if (DebugNonFragileIvars) {
         realizeAllClasses(a); }#undef EACH_HEADER
}
Copy the code

Read_images main logic:

  1. Load all classes into the GBD_objC_readlized_classes table
  2. Remap all classes
  3. Register all SELectors with the namedSelectors
  4. Fixed function pointer legacy
  5. Adds all protocols to the PROTOCOL_map table
  6. Remap all protocols
  7. Initialize all non-lazy-loaded classes for RW and RO operations
  8. Walks through the marked lazy-loaded classes and initializes them
  9. Handles all categories, including Class and Meta Class
  10. Initialize all uninitialized classes
load_images

After receiving a notification from dyLD_IMAGe_STATE_dependentS_INITIALIZED, the load_images callback is called, which calls call_load_methods() to execute all + load methods.

void load_images(const char *path __unused, const struct mach_header *mh) {
  // 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);
    // Find the Class and Category methods
    prepare_load_methods((const headerType *)mh);
  }
  // Call +load methods (without runtimeLock - re-entrant)
  // Call the Class and Category methods list
  call_load_methods(a); }void prepare_load_methods(const headerType *mhdr) {
  size_t count, i;
  runtimeLock.assertLocked(a);// Get a list of non-lazy-loaded classes
  classref_t *classlist = _getObjc2NonlazyClassList(mhdr, &count);
  for (i = 0; i < count; i++) {
    // Set the call list for Class
    schedule_class_load(remapClass(classlist[i]));
  }
  // Get the list of categories
  category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
  for (i = 0; i < count; i++) {
    category_t *cat = categorylist[i];
    Class cls = remapClass(cat->cls);
    // category for ignored weak-linked class Ignored weak links
    if(! cls)continue;  
    // Instantiate the owning class
    realizeClass(cls);
    assert(cls->ISA() - >isRealized());
    // Set the Categoty call list
    add_category_to_loadable_list(cat); }}// This method makes a recursive call to the parent class of the input parameter to ensure that the parent class takes precedence
static void schedule_class_load(Class cls) {

  if(! cls)return;
  // _read_images should realize if _read_images is complete
  assert(cls->isRealized());  
  // Add is complete, return
  if (cls->data()->flags & RW_LOADED) return;

  // Ensure that a superclass is added
  schedule_class_load(cls->superclass);
  // Add imp and class to the call
  add_class_to_loadable_list(cls);  
  // Set the class identifier to indicate that it has been added to the list
  cls->setInfo(RW_LOADED); 
}
    
/ / in the obj - loadmethod. Mm
void add_category_to_loadable_list(Category cat) {
  IMP method;
  // Get the IMP of the Load method in the Category
  loadMethodLock.assertLocked(a); method = _category_getLoadMethod(cat);// Don't bother if cat has no load method, return
  if(! method)return;

  if (PrintLoading) {
    _objc_inform("LOAD: category '%s(%s)' scheduled for +load", 
                 _category_getClassName(cat), _category_getName(cat));
  }
  // Dynamically expand the array if the size is equal to the array size
    if (loadable_categories_used == loadable_categories_allocated) {
      loadable_categories_allocated = loadable_categories_allocated*2 + 16;
      loadable_categories = (struct loadable_category *)
        realloc(loadable_categories,loadable_categories_allocated * sizeof(struct loadable_category));
    }

  loadable_categories[loadable_categories_used].cat = cat;
  loadable_categories[loadable_categories_used].method = method;
  loadable_categories_used++;
}
     
// With the loadable_calss and loadable_categories arrays ready, load_images performs these load methods via call_load_methods
     
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. Prevent re-entry
  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;
}
     
// Get the loadable_class structure by iterating
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, SEL_load);
  }
  // Destroy the detached list.
  if (classes) free(classes);
}
     
     
struct loadable_class {
  Class cls; // may be nil 
  IMP method;
};
Copy the code

When prepare_load_methods is complete, all classes and classes that meet the criteria for the + LOAD method call are stored in the global variables loadable_classes and loadable_categories, respectively.

Once you have your classes and classes ready, it’s time to call their +load method and find the call_load_methods method:

void call_load_methods(void) { ...... [//] [//] [//] [//] [//] [//] [//] [//] [//] [//] [//] [//] [//] [//] { 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); . }Copy the code

In this method, the +load method is called in class precedence over class

There are two key functions call_class_loads() and call_category_loads

These two functions iterate over the +load methods of loadable_classes and loadable_categories prepared in the previous step, Note that they both call the +load method as a function memory address ((*load_method)(CLS, SEL_load)), rather than sending the objc_msgSend message.

Using the code above, you can see that the load methods are called in the order parent -> subclass -> Category.

This way of calling the +load method has the property that implementations of the +load method in subclasses, superclasses, and classes are treated differently. That is, if the subclass does not implement the +load method, then the Runtime will not call the +load method of the parent class when it is loaded. Similarly, when a class and its classes implement the +load method, both methods are called, and when multiple classes implement the +load method, each class’s +load method is called.

@interface Father : NSObject
@end

@implementation Father
+ (void)load {
    NSLog(@"father");
}
@end
---------------------------------------------------
@interface Son : Father
@end

@implementation Son
+ (void)load {
    NSLog(@"son");
}
@end
---------------------------------------------------
@interface Son (load)
@end

@implementation Son (load)
+ (void)load {
    NSLog(@"son_category");
}
@end
Copy the code

After running, the console output:

father
son
son_category
Copy the code
doInitialization

In the final iteration, the doInitialization() method of ImageLoaderMachO is executed. Inside doInitialization(), doImageInit is called to initialize the image. The functions recorded in LC_ROUTINES_COMMAND, and then doModInitFunctions() to parse and execute the functions stored in the _DATA_,__mod_init_func sections, _mod_init_funcs holds the constructor of the global C++ object and all C functions with __attribute__((constructor).

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

Step 9 finds the entry point and returns

This step calls getThreadPC() of the main program image to read the LC_MAIN entry from the load command, or getMain() to read the LC_UNIXTHREAD if there is no LC_MAIN. The main() function is found and returned to the __dyLD_START call. At this point, the startup process ends and the main() function of the main program is entered

Dyld3 optimization

WWDC2017 launched DYLD3, which was applied in the app of the system. Open to tripartite apps in iOS13, greatly optimized startup speed.

Dyld2 is a pure in-process, that is, executed within the application process, which means that dyLD2 can only start executing tasks when the application is started.

Dyld2 main workflow

  • Dyld initialization, the main code indyldbootstrap::start, then executedyld::_main.dyld::_mainMore code, is the core part of dyLD loading;
  • Check and prepare the environment, such as obtaining the binary path, checking the environment variables, parsing the image header of the main binary, etc.
  • Instantiate the image loader of the main binary to verify whether the version of the main binary and DYLD match;
  • Check whether the shared cache has a map. If no, perform the map shared cache operation first.
  • Check DYLD_INSERT_LIBRARIES to load the inserted dynamic libraries (instantiate the Image loader);
  • Perform the link operation. This process is quite complex. It will first load all the dynamic libraries of the dependency recursively (the dependent libraries will be sorted, and the dependent libraries will always come first), and at the same time, symbol binding and rebase and binding operations will be performed at this stage.
  • Execute the initialization method. The OC+loadAnd C’s constructor method are executed at this stage;
  • Read the LC_MAIN section of Mach-O to get the entry address of the program and call the main() method.

Dyld3 optimization idea

For the process of DYLD2, dyLD3 optimization has the following two ideas:

  • Identifying security-sensitive components: Parsing mach-O files and looking for dependencies is security-sensitive, because maliciously tampered Mach-O headers can be used for certain attacks. If an app uses @rPath, maliciously modifying the path or inserting libraries in certain places can destroy the app. So this part of the work needs to be moved outside the process, such as into a daemon process.
  • Identifying parts that can be cached: symbol lookups are one of them, because each symbol in a particular library should have a fixed offset unless the software is updated or the library is changed.

In DYLD 3.0, when mach-O header parsing and symbol lookups are complete, these execution results are written to the hard disk as launch closures.

Launch closure: This is a new concept that refers to all the information an app needs during launch. For example, what dynamic link libraries the app uses, the offsets of each symbol, where the code signature is, and so on. Starting closures is simpler than Mach – O. They are memory-mapped files and do not need to be analyzed in a complex way.

Dyld3 is partly out-of-process and partly in-process. In the figure above, the part above the dotted line is out-of-process, which is performed during App download, installation and version update.

Dyld 3 consists of three components

1. Out-of-process Mach-O profiler/compiler;

In dyLD 2, Parse Mach-o headers and Find Dependencies have security risks (you can modify the Mach-o header and add illegal @rpath). Perform Symbol lookups takes a lot of CPU time because symbols are always at the same offset in the library when a file is unchanged. In DyLD 3, the two sections form a Lauch closure by pre-writing the resulting data into a file.

  • All search Path, @rPaths, and environment variables that can affect startup speed are handled
  • Parsing the Mach-O binaries and analyzing the dynamic libraries on which they depend
  • Performs all symbol lookups
  • Finally, it creates a startup closure and writes it to the cache so that when the application starts, it can read data directly from the cache to speed up startup.
  • This is a normal daemon process that can use the usual test architecture.

Out-of-process is a common daemon in the background, which can improve dyLD3 testability because it is separated from the various APP processes.

2. In-process implement launch closures for parsing engines

In-process verify launch closure is correct, map all dynamic libraries, initialize and jump to main().

Eliminating the need to parse mach-O header files and look up symbols, which is a time-consuming big head, optimizes this part, so it leads to faster startup.

3. Launch Closure’s caching service

The system’s App launch Closures are built directly into a shared cache to run and analyze every Mach-O file in the system

For third-party apps, Launch Closures will be built during application installation and rebuilt during system updates. Because the system libraries have changed by then. This ensures that Lauch Closure is always ready before the APP opens. The startup closure is written to a file that is read and validated directly the next startup.

In iOS, tvOS, watchOS, everything is done before the app starts. On macOS, thanks to the Sideload app, the in-process engine starts a daemon at first startup, after which the startup closure is available. In most cases, this is done before the app launches.

Most startup scenarios do not require this out-of-process mach-O parser to be invoked. Starting the closure is much easier than Mach-O because it is a memory-mapped file that is very simple to parse and validate, and is well optimized for performance. Therefore, the introduction of DYLD 3.0 can significantly improve the startup speed of APP.