preface

The previous exploration is basically the underlying process after main function, so today I will explore the underlying operation after main function

The preparatory work

  • dyld-852
  • libdispatch
  • libsystem
  • Objc4-818.2 –

Compile and library

IOS programmers tend to write upper-level code, most familiar with the.h and.m files, and how they go through the process of compiling the.h and.m files to produce the executable files

  • The source file:.h,.m,.cppAnd other documents
  • Preprocessing: During preprocessing, comments are removed, conditional compilation is processed, headers are expanded, and macros are replaced
  • Compilation: performs lexical analysis and syntax analysis as well as intermediate layersIRFile, and finally generate assembly file.sfile
  • Assembly:.sFiles are converted to machine language generation.ofile
  • Links: will all.oFiles as well as links to third-party libraries that generate onemachoType of executable file

Compilation flowchart

Dynamic library Static library

  • Static libraries: The assembler generated target and reference libraries are linked and packaged into an executable during the linking phase
  • Dynamic libraries: program compilations are not linked to object code, but are loaded while the program is running

Dynamic library static repository

Advantages and disadvantages of static libraries

advantages

  • Static libraries are packaged into executable files that can run independently of the external environment after successful compilation

disadvantages

  • Compiled files become larger and must be recompiled if static libraries are updated

Advantages and disadvantages of dynamic libraries

advantages

  • After reducing packagingAppThe size of the
  • Share content and resources
  • The purpose of updating program is achieved by updating dynamic library

disadvantages

  • Executable files cannot be run alone and must depend on the external environment

Static and dynamic library diagram

Dyld loading process

Since it is exploring the flow before main, the value can be assembled and flow directly to the breakpoint of main

The stack shows that the libdyld. Dylib library’s start function is the starting position, and then directly to the main function. Nothing in between. The load method is called before main, so continue debugging at the load method breakpoint

The stack information clearly shows how the load method is called

  • _dyld_start –> dyldbootstrap::start –> dyld::_main –> initializeMainExecutable –> runInitializers –> processInitializers –> runInitializers –>recursiveInitialization –> notifySingle –> load_images –>+[ViewController load]
Copy the code

_DYLD_START is in the source library of dyLD, _DYLD_START is compiled in detail below

The dyLDBootstrap ::start method is called after _dyLD_START in the assembly. Dyld bootstrap::start global search dyLDBootstrap ::start global search dyLDBootstrap ::start global search dyLDBootstrap ::start global search dyLDBootstrap ::start global search dyLDBootstrap ::start global search dyLDBootstrap

dyldbootstrapIs a namespace indyldInitialization.cppSearch in filestartmethods

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

dyld::_main

There are more than 800 lines of code in the dyld::_main method. It is enough to sort out the overall process and master the overall loading and starting process. If you are interested in details, you can explore them by yourself

Configuring environment Variables

uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
		int argc, const char* argv[], const char* envp[], const char* 
                apple[], uintptr_t* startGlue)
{
	// System kernel check
	//Check and see if there are any kernel flags
	dyld3::BootArgs::setFlags(hexToUInt64(_simple_getenv(apple, "dyld_flags"), nullptr)); .// Get the hash value of the main program
	uint8_t mainExecutableCDHashBuffer[20];
	// The main program initializes the hash value to 0
	const uint8_t* mainExecutableCDHash = nullptr;
	if ( const char* mainExeCdHashStr = _simple_getenv(apple, "executable_cdhash")) {... mainExecutableCDHash = mainExecutableCDHashBuffer;/ / assignment
	}
        // Configure the CPU architecture according to the macho header, just some file configurations
	getHostInfo(mainExecutableMH, mainExecutableSlide); .uintptr_t result = 0; // result is the address of main
	sMainExecutableMachHeader = mainExecutableMH;// The header of the executable
        sMainExecutableSlide = mainExecutableSlide;  // Loading into the process system automatically provides ASLR virtual memory offset
        
	{
            __block bool platformFound = false;
            Is it arm64 or x86? Is it 64-bit or 32-bit
            ((dyld3::MachOFile*)mainExecutableMH)->forEachSupportedPlatform
            (^(dyld3::Platform platform, uint32_t minOS, uint32_t sdk) {
                 
                    gProcessInfo->platform = (uint32_t)platform;
                    platformFound = true; }); }...// Setting the context will save the information to gLinkContext
      setContext(mainExecutableMH, argc, argv, envp, apple); .// Whether the file is restricted, AFMI Apple mobile file protection mechanism
      configureProcessRestrictions(mainExecutableMH, envp); .// set again because envp and apple may have changed or moved
      // Update the context information again
      setContext(mainExecutableMH, argc, argv, envp, apple);
      
      // Environment variable configuration, Xcode configuration environment variable console can print information
      if ( sEnv.DYLD_PRINT_OPTS )
            printOptions(argv);
      if ( sEnv.DYLD_PRINT_ENV ) 
            printEnvironmentVariables(envp);
            
Copy the code

Above is some header information for reading macho, saving Settings, and setting environment variables. It’s just preparation

Loading the Shared cache

Shared cacheIOSIt is necessary to have a shared cache, which stores dynamic libraries at the system level. Such asUIKit.CoreFoundationAnd so on. Self-created dynamic libraries or third-party dynamic libraries are not placed in the shared cache

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

The checkSharedRegionDisable method clearly indicates that IOS must have a shared cache. So let’s see how we load a shared cache and one of the mapSharedCache methods calls loadDyldCache to load a shared cache, okay

There are three scenarios for loading a shared cache

  • Forcing private:forcePrivate = YESIs mandatory private. Only the current loadAppProcess, not in the shared cache
  • The shared cache has been loadedIf the library you are relying on is already loaded in the shared cache, you can use it without further action
  • First load; If the library you depend on is not in the shared cache, it will be loaded into the shared cache

I think we now have a clear understanding of the shared cache loading. IOS must have a shared cache and the shared cache only holds system libraries. The purpose of the shared cache is to allow multiple processes to use system libraries together

dyld3ordyld2

Dyld3 has something called closure mode which loads faster and more efficiently. IOS11 after the main program is loaded with DYLD3, IOS13 after the dynamic library and tripartite library with dyLD3 loading

  Start ClosureMode::on use dyLD3; otherwise use dyLD2
  if ( sClosureMode == ClosureMode::Off ) {
    //dyld2
    if ( gLinkContext.verboseWarnings )
            dyld::log("dyld: not using closures\n");
  } else {
    // dyLD3 DYLD_LAUNCH_MODE_USING_CLOSURE uses closure mode
    sLaunchModeUsed = DYLD_LAUNCH_MODE_USING_CLOSURE;
    const dyld3::closure::LaunchClosure* mainClosure = nullptr; dyld3::closure::LoadedFileInfo mainFileInfo; mainFileInfo.fileContent = mainExecutableMH; mainFileInfo.path = sExecPath; .// Check the shared cache to see if there is a mainClosure for dyLD3
    if( sSharedCacheLoadInfo.loadAddress ! =nullptr ) {
            mainClosure = sSharedCacheLoadInfo.loadAddress->findClosure(sExecPath); . }...// If there is one in the shared cache, then verify that closure is valid
    if( (mainClosure ! =nullptr) &&!closureValid(mainClosure mainFileInfo, and mainExecutableCDHash,true, envp) ) {
            mainClosure = nullptr;
            sLaunchModeUsed &= ~DYLD_LAUNCH_MODE_CLOSURE_FROM_OS;
    }
    
    bool allowClosureRebuilds = false;
    if ( sClosureMode == ClosureMode::On ) {
            allowClosureRebuilds = true; }...// If no valid closure is found in the shared cache, a closure is automatically created
    if ( (mainClosure == nullptr) && allowClosureRebuilds ) {
        ...
        if ( mainClosure == nullptr ) { 
        // Create a mainClosure
        mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp, 
        bootToken);
        if( mainClosure ! =nullptr) sLaunchModeUsed |= DYLD_LAUNCH_MODE_BUILT_CLOSURE_AT_LAUNCH; }}// try using launch closure
    // dyLD3 starts
    if( mainClosure ! =nullptr ) {
        CRSetCrashLogMessage("dyld3: launch started"); ./ / start launchWithClosure
        bool launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress,(dyld3::MachOLoaded*)mainExecutableMH,...) ;// Failed to start
        if ( !launched && closureOutOfDate && allowClosureRebuilds ) {
                // closure is out of date, build new one
                // Re-create mainClosure if it fails
                mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, 
                envp, bootToken);
                if( mainClosure ! =nullptr) {...// dyLD3 starts again
                    launched = launchWithClosure(mainClosure,  sSharedCacheLoadInfo.loadAddress,
                    (dyld3::MachOLoaded*)mainExecutableMH,...);
                }
            }
            if ( launched ) {
                    gLinkContext.startedInitializingMainExecutable = true;
                    if (sSkipMain)
                    // Return the address of main on success
                    result = (uintptr_t)&fake_main;
                    return result;
            }
            else {  
            // Failed to start}}}Copy the code

Dyld3 startup process is after a lot of attempts, the system gave a lot of opportunities, generally there will not be a startup failure

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

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

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

Today, WE mainly look at the mode of DYLD3, because this will gradually replace DYLD2 in the future, and we will explore the dyLD2 process later

Summarize the startup process of DYLD3

  • From the shared cachedyld3An instance of themainClosure
  • validationmainClosureThe validity of
  • Then go to the shared cache to find a valid onemainClosureIf there is a direct boot
  • If not, create onemainClosure
  • Start themainClosure, startdyld3
  • After successful startup, the main program is successfully started,resultismainThe address of the function returned todyldbootstrap::startMethod and then entermianfunction

Instantiate the main program

Dyld3 goes through the same process as dyLD2 except dyLD3 uses closure mode. Image often appears in the source code, image does not mean the picture, but the image file, the image file is mapped from disk to memory macho file. Any macho file loaded into memory is called a mirror file

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

Add the instantiated image to the image file array. Note here that the image of the main program is added first to the array. Now explore the ImageLoaderMachO: : instantiateMainExecutable method

Load command information in macho file, and verify. The sniffLoadCommands code fetching part of the important exploration

sniffLoadCommandsIn the loadsegmentandcommodInformation, and some validation

  • segmentThe maximum number of segments is256a
  • commandThe maximum number of alpha is alpha4096a
  • Make sure you have to rely on itlibSystemlibrary

If you’re still a little confused about segment and command and macho headers, use the MachOView tool to learn about executables

The picture is very clearmachoHeader files are schema information, file types, etc.machoThe file consists of three main piecesHeader,Commods,Data

Insert dynamic library

throughloadInsertedDylibInsert dynamic library, at this time the number of dynamic library is all the image files minus one

Link main program

ImageLoader is responsible for loading the image file (main program, dynamic library). Each image corresponds to an instance of the ImageLoader class

void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool 
preflightOnly, bool neverUnload, const RPathChain& loaderRPaths, const char* imagePath{
	 
	...
	// Load all dynamic libraries recursively
	this->recursiveLoadLibraries(context, preflightOnly, loaderRPaths, imagePath); context.notifyBatch(dyld_image_state_dependents_mapped, preflightOnly); . __blockuint64_t t2, t3, t4, t5;
	{
		dyld3::ScopedTimer(DBG_DYLD_TIMING_APPLY_FIXUPS, 0.0.0);
		t2 = mach_absolute_time();
		// Recursive relocation
		this->recursiveRebaseWithAccounting(context);
		context.notifyBatch(dyld_image_state_rebased, false);

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

		t4 = mach_absolute_time();
		if ( !context.linkingMainExecutable )
			/ / weak binding
			this->weakBind(context); t5 = mach_absolute_time(); }...// The link process can count the link dynamic library time, in the environment variable setting can print information
}
Copy the code
  • Load all dynamic libraries recursively
  • recursiveimagerelocation
  • Recursive binding is not lazy loading
  • Weak binding

RecursiveLoadLibraries (recursiveLoadLibraries) is a recursively loaded dynamic library

void ImageLoader::recursiveLoadLibraries(const LinkContext& context, bool 
preflightOnly, const RPathChain& loaderRPaths, const char* loadPath){...// get list of libraries this image needs
    // Get the current image-dependent dynamic library
    DependentLibraryInfo libraryInfos[fLibraryCount]; 
    this->doGetDependentLibraries(libraryInfos);

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

    // load the image-dependent dynamic library
    for(unsigned int i=0; i < fLibraryCount; ++i){
      ...
      dependentLib = context.loadLibrary(requiredLibInfo.name, true.this->getPath(),
      &thisRPaths, cacheIndex);
      // Save the loaded dynamic library
      setLibImage(i, dependentLib, depLibReExported, requiredLibInfo.upward); . `} `// Tell the image-dependent dynamic libraries to load the required dynamic libraries
    for(unsigned int i=0; i < libraryCount(a); ++i) { ImageLoader* dependentImage =libImage(i);
            if( dependentImage ! =NULL ) {
                 dependentImage->recursiveLoadLibraries(context, preflightOnly, thisRPaths, libraryInfos[i].name); }}}Copy the code

A lot of code just captures the whole process

  • Gets the currentimageDependent dynamic library and the file path of the dynamic library
  • loadingimageDependency dynamic library and save it
  • Tell the image-dependent dynamic libraries to load the required dynamic libraries

Linked dynamic library

The logic of linking the dynamic library is basically the same as that of linking the main program. Note that the next loop to fetch the image file starts at 1, because the 0th position is the main program. Let’s verify this

The first data in the image list is the main program

Weakly bound main program

You might wonder if there’s a weak symbol binding when you link the main program. Because the linkingMainExecutable = true of the main program, the weak binding in link will not be called during the main program, and the weak binding will be made on the main program after the weak binding is made on the dynamic library

Run the initialization method

InitializeMainExecutable will not be launched because there is a lot to explore later

returnmainfunction

Once you’ve got the main function, you’re going to go into the main function that you’re familiar with

conclusion

Dyld load process: DYLD ::_main –> configure environment variables –> load shared cache –> instantiate main program –> Insert dynamic library –> Link main program –> Link dynamic library –> Weakly bind main program –> Run initialization method –> return main function

initializeMainExecutable

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

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

Run the initialization method of the dynamic library first, and then run the initialization method of the main program

runInitializers

Search globally for runInitializers, source below

void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo) { uint64_t t1 = mach_absolute_time(); mach_port_t thisThread = mach_thread_self(); ImageLoader::UninitedUpwards up; up.count = 1; up.imagesAndPaths[0] = { this, this->getPath() }; 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

Enter processInitializers source code as follows

// upward dylib initializers can be run too soon
// To handle dangling dylibs which are upward linked but not downward, all upward linked dylibs
// have their initialization postponed until after the recursion through downward dylibs
// has completed.
void ImageLoader::processInitializers(const LinkContext& context, mach_port_t 
thisThread,InitializerTimingList& timingInfo, ImageLoader::UninitedUpwards& images)
{
	uint32_t maxImageCount = context.imageCount() +2;
	ImageLoader::UninitedUpwards upsBuffer[maxImageCount];
	ImageLoader::UninitedUpwards& ups = upsBuffer[0];
	ups.count = 0;
	// Calling recursive init on all images in images list, building a new list of
	// uninitialized upward dependencies.
        // Recurse all 'images' in the list of all images
	for (uintptr_t i=0; i < images.count; ++i) {
		images.imagesAndPaths[i].first->recursiveInitialization(context, 
                thisThread, images.imagesAndPaths[i].second, timingInfo, ups);
	}
	// If any upward dependencies remain, init them.
        // To ensure that all upward dependencies are initialized, uninitialize the image again
	if ( ups.count > 0 )
		processInitializers(context, thisThread, timingInfo, ups);
}
Copy the code

Global search recursiveInitialization, source code as follows

void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t 
this_thread, const char* pathToInitialize,
InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
	recursive_lock lock_info(this_thread);
	recursiveSpinLock(lock_info); .// initialize lower level libraries first
         // Initialize the most dependent library first
        for(unsigned int i=0; i < libraryCount(a); ++i) { ImageLoader* dependentImage =libImage(i);
            if( dependentImage ! =NULL ) {
                // don't try to initialize stuff "above" me yet
                if ( libIsUpward(i) ) {
                        uninitUps.imagesAndPaths[uninitUps.count] = { dependentImage, 
                        libPath(i) };
                        uninitUps.count++;
                }
                else if ( dependentImage->fDepth >= fDepth ) {
                        dependentImage->recursiveInitialization(context, this_thread, 
                        libPath(i), timingInfo, uninitUps); }}}// The image 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);
        // image initialization is complete
        // let anyone know we finished initializing this image
        fState = dyld_image_state_initialized;
        oldState = fState;
        context.notifySingle(dyld_image_state_initialized, this.NULL); .recursiveSpinUnLock(a); }Copy the code
  • Dynamic library that needs to be initializedimagefromlibImage(), whilelibImage()Is when linking dynamic librariesrecursiveLoadLibrariesIn thesetLibImageThe savedimage
  • The system will initialize each library according to its dependency depth. The depth value with the largest value will be initialized first. Each initialization will have oneimagefile
  • imageWill be calledcontext.notifySingleMethod to callload_imagescallloadmethods
  • doInitializationIs to initialize libraries that have no dependencies
  • context.notifySingle(dyld_image_state_initialized, this, NULL)It doesn’t really work,notifySingleThere is no judgment in the method, it could be inobjcRegister callback according to insidedyld_image_state_initializedState to call the system library

Let’s explore notifySingle and doInitialization. Search for notifySingle globally

(*sNotifyObjCInit)(image->getRealPath(), image->machHeader())) This is a function call. Global search sNotifyObjCInit, source code as follows

In registerObjCNotifiers, assign value to sNotifyObjCInit and search registerObjCNotifiers globally. The source code is as follows

A global search of the _dyLD_OBJC_notify_register shows that there is no place in the DYLD source code where this method is called. Now, what do I do? I can’t explore it, but I don’t know if you’ve forgotten that there’s a notation breakpoint in the exploration. Add the _DYLD_OBJC_Notify_register breakpoint to the project. My project runs dyLD2 on Mac and dyLD3 on IOS

  • doInitialization –> doModInitFunctions –> libSystem_initializer –> libdispatch_init –> _os_object_init –> _objc_init –> _dyld_objc_notify_register –> registerObjCNotifiers
  • libSystem_initializerMethods in thelibSystemSystem in the library
  • libdispatch_initand_os_object_initMethods in thelibdispatchSystem in the library
  • _objc_initMethods in thelibobjcIn the system library,objcThe source code library is the most familiar

_dyLD_OBJC_NOTIFy_register is called by _objc_init, so the assignment should be in the _objc_init method. Ok

SNotifyObjCInit = load_images actually calls the load_images method. Let’s explore the load_images method

The call_load_methods method, from its name, calls the load method, and we’ll explore the call_load_methods method

To iterate over the load method, explore the call_class_loads and call_category_loads

The load method is called by both classes and classes, and the order of calls can be inferred from this

  • Of the classloadClassification of thanloadMethod is called first in the classloadMethod is not called until the class is calledloadmethods
  • In the classloadMethods are compiled in the order in which they are compiledloadMethod first calls
  • classifiedloadMethods are compiled in the order in which they are compiledloadMethod first calls
  • Oops, accidentally solved another interview question

The load process is the same as our stack information display process, step by step to verify the stack information

_objc_initprocess

_objC_init in the objC system library, in addition to being the most familiar source library, _objc_init serves as a link between the preceding and the following. So work backwards from _objc_init to the entire process. The _objc_init method is called by the _OS_OBJect_init method, which is searched globally in the libDispatch source library for _OS_OBJect_init

The _OS_OBJect_init method does call the _objc_init method. The _OS_OBJect_init method is called by libdispatch_init

The libdispatch_init method does call the _OS_object_init method. Libdispatch_init is called by libSystem_initializer, which is in the libSystem system library

The libSystem_initializer method does call the libdispatch_init method. The libSystem_initializer method is called by doModInitFunctions, which is in the dyLD source library

libSystemInitializer, must run first, everything else will wait a little bit,doModInitFunctionsCall allC++function

Add a global C++ function below the main function and break it to see the stack information

The stack information shows that the doModInitFunctions method calls the global c++ method, after the load method

doModInitFunctionsMethods are beingdoInitializationCall, seedoInitializationShould be familiar

RecursiveInitialization calls the doInitialization method, which is then recursiveInitialization

conclusion

Load method call flow

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

Procedure for calling the _objc_init method

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

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

mainFunction entry

The above exploration found that the C++ function is executed after the load method, but before the main function, adding a breakpoint in the C++ function step by step debugging, must enter the main function

The breakpoint assembly is displayed in the C++ function, where step by step debugging goes to the next flow to find where the main function is called

dyldbootstrap::startReturned to themainThe address of the function readx86_64Register First register foundraxDeposit ismainThe address of the function, the last step jumps tomainfunction

Note: do not change the name of main in strange ways. If you change the name of main, you will get an error. Main is a fixed function

dyldLoading flow chart

conclusion

Dyld inquiry is boring, tedious and complex, and many problems are encountered in the process of exploration and learning. I have grasped the whole loading process of DYLD basically