Welcome to the iOS Exploration series.

  • IOS explores the alloc process
  • IOS explores memory alignment &malloc source code
  • IOS explores ISA initialization & pointing analysis
  • IOS exploration class structure analysis
  • IOS explores cache_T analysis
  • IOS explores the nature of methods and the method finding process
  • IOS explores dynamic method resolution and message forwarding mechanisms
  • IOS explores the dyLD loading process briefly
  • The loading process of iOS explore classes
  • IOS explores the loading process of classification and class extension
  • IOS explores isa interview question analysis
  • IOS Explore Runtime interview question analysis
  • IOS explores KVC principles and customization
  • IOS explores KVO principles and customizations
  • IOS explores the principle of multithreading
  • IOS explores multi-threaded GCD applications
  • IOS explores multithreaded GCD underlying analysis
  • IOS explores NSOperation for multithreading
  • IOS explores multi-threaded interview question analysis
  • IOS Explore the locks in iOS
  • IOS explores the full range of blocks to read

Writing in the front

The entry function of the program we usually write is the main function in the main.m file, but is this the beginning of the life of the App? If you’ve ever played with reverse iOSer, you can inject code into the +load method for security purposes, and the + Load method executes before main, so what’s interesting about what happened before main? This article will take you to uncover this mysterious veil!

This article partial novice to, will only go over the main process!!

Static library and dynamic library

1. Compilation process

We use Command + B/R tens of thousands of times in daily development, but few people have ever looked at what Xcode does for us. (iOS developers often joke that Xcode is getting harder to use, but admit that it’s getting better.)

In fact, this process is broken down into 4 steps, namely Prepressing, Compilation, Assembly and Linking.—— From “Programmer self-cultivation — Linking, loading and storing”

In the above four steps, the IDE does the following:

  • precompiled: processing in code# at the beginning“, such as delete#defineAnd expand the macro definition to#includeContains files inserted into the instruction location, etc
  • Compilation: conducts lexical analysis, syntax analysis and semantic analysis on the pre-compiled files, optimizes the source code, and then generates assembly code;
  • assembly: An assembler converts assembly code into instructions that can be executed by the machine and generates object files.o file
  • link: Links the object file to an executable file. In this process, the linker links different object files together, because different object files may have variables that reference each other or call functions, as we often call themFoundationThe framework andUIKitMethods and variables in the framework, but these frameworks are not in the same object file as our code, which requires the linker to link them to our own code

The reuse of shared code by Foundation and UIKit is collectively called a library — a binary of executable code that can be written to memory by the operating system. It is divided into static libraries and dynamic libraries

2. Static library

Static libraries are linked to complete copies of executable files. Multiple copies are used to create redundancy and make packages larger

For example,. A and. Lib are static libraries

3. Dynamic libraries

Dynamic library refers to the link is not copied, when the program is running by the system to add to the memory, for the system call, the system only need to load once, multiple use, shared memory saving.

Dylib,. Framework are dynamic libraries

The framework of the system is dynamic, and the framework created by the developer is static

So what is a linker? How does it link to different object files?

Second, the dyld

1. Dyld profile

Dyld (The dynamic link editor) is a dynamic linker that links and loads programs. It is an important part of The MacOS and exists in The (/usr/lib/dyld) directory of The MacOS. After the application is compiled and packaged into an executable file format, DyLD is responsible for linking and loading the program

2.dyld_shared_cache

Since more than one application needs to use the UIKit system dynamic library, it is impossible to load all the system dynamic libraries at every application load time. Since iOS3.1, apple has compiled all system libraries (private and public) into a large cache file called dyld_shared_cache. The cache file exists under the iOS/System/Library/Caches/com. Apple. Dyld/directory

Iii. Dyld loading process

Create an empty project, write the load method, and break the main method and the load method respectively

Click on the function call stack/print with the LLVM — BT instruction to see the original starting point _dyLD_START

How to study DYLD next, we will be through dyLD source analysis

1._dyld_start

A global search for _dyLD_START in the source code shows that it is implemented by assembly

In ARM64, _dyLD_start calls an unreadable method

From the comments, it is likely to be the dyldBootstrap ::start method (which is already exposed in assembly code in the “function call stack” diagram).

2.dyldbootstrap::start

Global search dyldbootstrap::start does not yield any meaningful results, so we can only use the rule of thumb — global search space start(” lucky “results

Dyldbootstrap ::start refers to the start function in the scope of the dyLDBootstrap namespace

uintptr_t start(const struct macho_header* appsMachHeader, int argc, const char* argv[], 
				intptr_t slide, const struct macho_header* dyldsMachHeader,
				uintptr_t* startGlue)
{
	// if kernel had to slide dyld, we need to fix up load sensitive locations
	// we have to do this before using any global variables
    slide = slideOfMainExecutable(dyldsMachHeader);
    boolshouldRebase = slide ! =0;
#if __has_feature(ptrauth_calls)
    shouldRebase = true;
#endif
    if ( shouldRebase ) {
        rebaseDyld(dyldsMachHeader, slide);
    }

	// allow dyld to use mach messaging
	mach_init();

	// kernel sets up env pointer to be just past end of agv array
	const char** envp = &argv[argc+1];
	
	// kernel sets up apple pointer to be just past end of envp array
	const char** apple = envp;
	while(*apple ! =NULL) { ++apple; }
	++apple;

	// set up random value for stack canary
	__guard_setup(apple);

#if DYLD_INITIALIZER_SUPPORT
	// run all C++ initializers inside dyld
	runDyldInitializers(dyldsMachHeader, slide, argc, argv, envp, apple);
#endif

	// now that we are done bootstrapping dyld, call dyld's main
	uintptr_t appsSlide = slideOfMainExecutable(appsMachHeader);
	return dyld::_main(appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}
Copy the code

The start() function does a few things:

  • According to thedyldsMachHeaderTo calculate theslideThrough theslideDetermine if relocation is required; Here,slideIs based onASLR technologyCalculated a random value, so that each run of the program offset value is not the same, to prevent attackers through the fixed address to launch malicious attacks
  • mach_init()Initialization (allows DyLD to use Mach messaging)
  • Stack overflow protection
  • To calculateappsMachHeaderOffset, calldyld::_main()function

3.dyld::_main()

Click to enter the dyld::_main() function

uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
        int argc, const char* argv[], const char* envp[], const char* apple[], 
        uintptr_t* startGlue)
{
    ......
    uintptr_t result = 0;
    // Save the header of the executable passed in (which is a struct macho_header structure), and then access the information based on the headersMainExecutableMachHeader = mainExecutableMH; .// Set the context information according to the executable header, parameters, etc
    setContext(mainExecutableMH, argc, argv, envp, apple);

    // Pickup the pointer to the exec path.
    // Get the executable file path
    sExecPath = _simple_getenv(apple, "executable_path");

    // <rdar://problem/13868260> Remove interim apple[0] transition code from dyld
    if(! sExecPath) sExecPath = apple[0];
    // Convert relative paths to absolute paths
    if ( sExecPath[0] != '/' ) {
        // have relative path, use cwd to make absolute
        char cwdbuff[MAXPATHLEN];
        if( getcwd(cwdbuff, MAXPATHLEN) ! =NULL ) {
            // maybe use static buffer to avoid calling malloc so early...
            char* s = new char[strlen(cwdbuff) + strlen(sExecPath) + 2];
            strcpy(s, cwdbuff);
            strcat(s, "/");
            strcat(s, sExecPath); sExecPath = s; }}// Remember short name of process for later logging
    // Get the name of the executable
    sExecShortName = ::strrchr(sExecPath, '/');
    if( sExecShortName ! =NULL )
        ++sExecShortName;
    else
        sExecShortName = sExecPath;
    // Whether the configuration process is restrictedconfigureProcessRestrictions(mainExecutableMH); . {// Check setting environment variables
        checkEnvironmentVariables(envp);
        // If DYLD_FALLBACK is nil, set it to the default valuedefaultUninitializedFallbackPaths(envp); }...// If the environment variable DYLD_PRINT_OPTS is set, the parameters are printed
    if ( sEnv.DYLD_PRINT_OPTS )
        printOptions(argv);
    // If the environment variable DYLD_PRINT_ENV is set, the environment variable is printed
    if ( sEnv.DYLD_PRINT_ENV ) 
        printEnvironmentVariables(envp);
    // Get the current operating architecture information based on the Mach-o header
    getHostInfo(mainExecutableMH, mainExecutableSlide);

    // load shared cache
    // Check whether shared cache is enabled. It must be enabled in iOS
    checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
#if TARGET_IPHONE_SIMULATOR
    // <HACK> until <rdar://30773711> is fixed
    gLinkContext.sharedRegionMode = ImageLoader::kUsePrivateSharedRegion;
    // </HACK>
#endif
    if( gLinkContext.sharedRegionMode ! = ImageLoader::kDontUseSharedRegion ) {// Check whether the shared cache maps to the shared regionmapSharedCache(); }...// instantiate ImageLoader for main executable
    // Load the executable and generate an ImageLoader instance objectsMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath); gLinkContext.mainExecutable = sMainExecutable; gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH); .// Now that shared cache is loaded, setup an versioned dylib overrides
    #if SUPPORT_VERSIONED_PATHS
        // Check whether the version of the library is updated, and overwrite the original
        checkVersionedPaths();
    #endif.// load any inserted libraries
        // Load all the libraries specified by 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.
        sInsertedDylibCount = sAllImages.size()- 1;

        // link main executable
        // Link 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
            sMainExecutable->rebase(gLinkContext, -mainExecutableSlide);
        }
#endif
        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;
        }

        // link any inserted libraries
        // Link all inserted dynamic libraries
        // do this after linking main executable so that any dylibs pulled in by inserted 
        // dylibs (e.g. libSystem) will not be in front of dylibs the program uses
        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();
            }
            // 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];
                // Register symbol insertionimage->registerInterposing(gLinkContext); }}// <rdar://problem/19315404> dyld should support interposition even without DYLD_INSERT_LIBRARIES
        for (long i=sInsertedDylibCount+1; i < sAllImages.size(); ++i) {
            ImageLoader* image = sAllImages[i];
            if ( image->inSharedCache() )
                continue;
            image->registerInterposing(gLinkContext);
        }
    #if SUPPORT_ACCELERATE_TABLES
        if( (sAllCacheImagesProxy ! =NULL) && ImageLoader::haveInterposingTuples() ) {
            // Accelerator tables cannot be used with implicit interposing, so relaunch with accelerator tables disabled
            ImageLoader::clearInterposingTuples();
            // unmap all loaded dylibs (but not main executable)
            for (long i=1; i < sAllImages.size(); ++i) {
                ImageLoader* image = sAllImages[i];
                if ( image == sMainExecutable )
                    continue;
                if ( image == sAllCacheImagesProxy )
                    continue;
                image->setCanUnload();
                ImageLoader::deleteImage(image);
            }
            // note: we don't need to worry about inserted images because if DYLD_INSERT_LIBRARIES was set we would not be using the accelerator table
            sAllImages.clear();
            sImageRoots.clear();
            sImageFilesNeedingTermination.clear();
            sImageFilesNeedingDOFUnregistration.clear();
            sAddImageCallbacks.clear();
            sRemoveImageCallbacks.clear();
            sAddLoadImageCallbacks.clear();
            sDisableAcceleratorTables = true;
            sAllCacheImagesProxy = NULL;
            sMappedRangesStart = NULL;
            mainExcutableAlreadyRebased = true;
            gLinkContext.linkingMainExecutable = false;
            resetAllImages();
            goto reloadAllImages;
        }
    #endif

        // apply interposing to initial set of images
        for(int i=0; i < sImageRoots.size(); ++i) {
            // Apply symbol insertion
            sImageRoots[i]->applyInterposing(gLinkContext);
        }
        ImageLoader::applyInterposingToDyldCache(gLinkContext);
        gLinkContext.linkingMainExecutable = false;

        // Bind and notify for the main executable now that interposing has been registered
        uint64_t bindMainExecutableStartTime = mach_absolute_time();
        sMainExecutable->recursiveBindWithAccounting(gLinkContext, sEnv.DYLD_BIND_AT_LAUNCH, true);
        uint64_t bindMainExecutableEndTime = mach_absolute_time();
        ImageLoaderMachO::fgTotalBindTime += bindMainExecutableEndTime - bindMainExecutableStartTime;
        gLinkContext.notifyBatch(dyld_image_state_bound, false);

        // Bind and notify for the inserted images now interposing has been 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); }}// <rdar://problem/12186933> do weak binding only after all inserted images linked
        // Weak symbol bindingsMainExecutable->weakBind(gLinkContext); .#if SUPPORT_OLD_CRT_INITIALIZATION
        // Old way is to run initializers via a callback from crt1.o
        if(! gRunInitializersOldWay ) initializeMainExecutable();#else
        // run all initializers
        // Execute the initialization method
        initializeMainExecutable(); 
    #endif
        // notify any montoring proccesses that this process is about to enter main()
        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);
        }
        notifyMonitoringDyldMain();

        // find entry point for main executable
        // Find the target executable entry and execute
        result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();
        if( result ! =0 ) {
            // main executable uses LC_MAIN, we need to use helper in libdyld to call into main()
            if( (gLibSystemHelpers ! =NULL) && (gLibSystemHelpers->version >= 9) )
                *startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit;
            else
                halt("libdyld.dylib support not present for LC_MAIN");
        }
        else {
            // main executable uses LC_UNIXTHREAD, dyld needs to let "start" in program set up for main()
            result = (uintptr_t)sMainExecutable->getEntryFromLC_UNIXTHREAD();
            *startGlue = 0;
        }
#if __has_feature(ptrauth_calls)
        // start() calls the result pointer as a function pointer so we need to sign it.
        result = (uintptr_t)__builtin_ptrauth_sign_unauthenticated((void*)result, 0.0);
#endif
    }
    catch(const char* message) {
        syncAllImages();
        halt(message);
    }
    catch(...). { dyld::log("dyld: launch failed\n");
    }

    CRSetCrashLogMessage("dyld2 mode");

    if (sSkipMain) {
        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);
        }
        result = (uintptr_t)&fake_main;
        *startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit;
    }
    
    return result;
}
Copy the code

Dyld ::_main() main flow is:

  • Sets context information to detect whether the process is restricted
  • Configure environment variables to get the current operating architecture
  • Check whether shared cache is enabled and load the shared cache library
  • Add dyLD itself to the UUID list
  • Instantiate the main program
  • Load the insert dynamic library
  • Link the main program to the inserted library, performing symbol substitution
  • Execute the initialization method
  • Look for the main program entry

3.1 Setting context Information to check whether the process is restricted

  • callsetContextFunction, passing in the Mach-O header and some parameter setting context
  • configureProcessRestrictionsDetect whether the process is constrained and act accordingly in context
/// in _mainsetContext(mainExecutableMH, argc, argv, envp, apple); . configureProcessRestrictions(mainExecutableMH);Copy the code

3.2 Configuring Environment Variables to obtain the current operating architecture

  • Gets the main executable from an environment variablecdHash
  • checkEnvironmentVariables(envp)Check setting environment variables
  • defaultUninitializedFallbackPaths(envp)inDYLD_FALLBACKSets the default value when null
  • getHostInfo(mainExecutableMH, mainExecutableSlide)Get program architecture
/// in _main
// If the environment variable DYLD_PRINT_OPTS is set, the parameters are printed
if ( sEnv.DYLD_PRINT_OPTS )
    printOptions(argv);
// If the environment variable DYLD_PRINT_ENV is set, the environment variable is printed
if ( sEnv.DYLD_PRINT_ENV ) 
    printEnvironmentVariables(envp);
Copy the code

As long as these two environment variable parameters are set, relevant parameters and environment variable information will be printed when App starts (try to study by yourself).

3.3 Checking whether shared cache is enabled and loading the shared cache library

  • checkSharedRegionDisableCheck whether shared cache is enabled (not disabled on iOS)
  • mapSharedCacheLoad the shared cache library, which is calledloadDyldCacheThere are several cases of functions:
    • Only the current process is loadedmapCachePrivate(Emulator only supports loading into current process)
    • The shared cache is loaded for the first timemapCacheSystemWide
    • The shared cache is not loaded for the first time, so nothing is done

3.4 Adding dyLD itself to the UUID list

AddDyldImageToUUIDList adds dyld itself to the UUID list

Next comes the most important part of the reloadAllImages

3.5 Instantiating the main program

  • isCompatibleMachODetect executable program format, main judgeMach-OOf the fileMagic Number, CPUType, and cpusubTypeWhether compatible or not
  • instantiateMainExecutableInstantiate the main program
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

instantiateMainExecutable
ImageLoaderMachO::sniffLoadCommands

// determine if this mach-o file has classic or compressed LINKEDIT and number of segments it has
void ImageLoaderMachO::sniffLoadCommands(const macho_header* mh, const char* path, bool inCache, bool* compressed,
											unsigned int* segCount, unsigned int* libCount, const LinkContext& context,
											const linkedit_data_command** codeSigCmd,
											const encryption_info_command** encryptCmd)
{
    *compressed = false;
    *segCount = 0;
    *libCount = 0;
    *codeSigCmd = NULL;
    *encryptCmd = NULL;
    / *... * /
    // fSegmentsArrayCount is only 8-bits
    if ( *segCount > 255 )
    	dyld::throwf("malformed mach-o image: more than 255 segments in %s", path);
    
    // fSegmentsArrayCount is only 8-bits
    if ( *libCount > 4095 )
    	dyld::throwf("malformed mach-o image: more than 4095 dependent libraries in %s", path);
    
    if ( needsAddedLibSystemDepency(*libCount, mh) )
    	*libCount = 1; . }Copy the code

Here are several fields related to MachO:

  • Compressed: according to theLC_DYLD_INFO_ONYLTo determine the
  • SegCount: in MachO filesegmentThe maximum number is 255
  • LibCount: MachO fileDependent dynamic librariesThe number of
  • CodeSigCmd: signature information
  • EncryptCmd: encrypts information, such as cryptid

3.6 Loading and inserting the dynamic library

/// in _main
// load any inserted libraries
if( sEnv.DYLD_INSERT_LIBRARIES ! =NULL ) {
    for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib ! =NULL; ++lib) 
        loadInsertedDylib(*lib);
}
Copy the code

The DYLD_INSERT_LIBRARIES environment variable is iterated and loadInsertedDylib is called to load the environment variable. Through this environment variable, we can inject some custom dynamic library code to complete security attack and defense. LoadInsertedDylib checks for dylib signatures from paths such as DYLD_ROOT_PATH, LD_LIBRARY_PATH, and DYLD_FRAMEWORK_PATH

3.7 Link the main program to the inserted library and perform symbol substitution

  • throughImageLoader::link()The function links the main program to the inserted library
  • It will continue after the link is completedrecursiveBindWithAccounting()Recursively bound symbol table,weakBind()Weak binding
/// in _main
// link main executable
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
	sMainExecutable->rebase(gLinkContext, -mainExecutableSlide);
}
#endif
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;
}

// link any inserted libraries
// do this after linking main executable so that any dylibs pulled in by inserted 
// dylibs (e.g. libSystem) will not be in front of dylibs the program uses
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();
	}
	// 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

Here’s what matters most

3.8 Executing the initialization method

Review the function call stack

The initializeMainExecutable method calls runInitializers

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

	// run initialzers for any inserted dylibs
	ImageLoader::InitializerTimingList initializerTimes[allImagesCount()];
	initializerTimes[0].count = 0;
	const size_t rootCount = sImageRoots.size();
	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
	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

②runInitializers call processInitializers in preparation for initialization

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.images[0] = this;
	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

Image initialization is performed recursiveInitialization

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.
	for (uintptr_t i=0; i < images.count; ++i) {
		images.images[i]->recursiveInitialization(context, thisThread, images.images[i]->getPath(), timingInfo, ups);
	}
	// If any upward dependencies remain, init them.
	if ( ups.count > 0 )
		processInitializers(context, thisThread, timingInfo, ups);
}
Copy the code

Click enter but only declare, shift+ CMD +O search recursiveInitialization

(4) recursiveInitialization Obtains the image initialization

void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
										  InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
    ...
    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
	bool hasInitializers = this->doInitialization(context);

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

⑤notifySingle gets the callback of the mirror

static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
{
    ...
    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);
    	}
    }
    ...
}
Copy the code

The notifySingle does not find load_images in the function call stack, which is actually a call to a callback function

⑥sNotifyObjCInit is assigned in the registerObjCNotifiers

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;
}
Copy the code

⑦registerObjCNotifiers are called in the _dyLD_OBJC_notify_register function, which is only available to OBJC at runtime

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

_objc_init calls _dyLD_OBJC_notify_register and load_image

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();
    
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
Copy the code

The _dyLD_OBJC_notify_register can be verified by a symbolic breakpoint in objC source code

Here comes libSystem… (Well, I was naive, the dyLD process is complicated.)

After end context. NotifySingle, call ImageLoaderMachO: : doInitialization, internal calls

  • doImageInit
  • ImageLoaderMachO::doModInitFunctions

⑨doImageInit->libSystemInitialized->libdispatch_init->_os_object_init internally

⑩doModInitFunctions internally call the __mod_init_funcs section, which is the constructor method — the C++ constructor

initializeMainExecutableConclusion:

  • runInitializers->processInitializersThe traverserecursiveInitialization
  • On the first execution, proceedlibsystemInitialize –doInitialization->doImageInit-> libSystemInitialized
  • libsystemClass, is calledlibdispatch_init.libdispatchInitialization is called_os_object_initInternally called_objc_init
  • _objc_initRegistered and saved inmap_images,load_images,unmap_imageFunction addresses
  • Continue to return after registrationrecursiveInitializationRecursive next call

3.9 Finding the main program entry

// find entry point for main executable
result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();
Copy the code

Iv. Schematic diagram of DYLD loading process

Write in the back

Dyld loading process code is more, the first time to see about the process can be understood