1. What does App startup mean?

From the time the user clicks on the App until the user sees the first screen, this is called an App launch.

Generally, App startup can be divided into cold startup and hot startup.

  • Cold start refers to the situation that the process of App is not in the system before the App is clicked to start, and the system needs to create a new process for it to start. This is a complete startup process.

  • Hot startup refers to the process in which the user exits the background of the App after cold startup and restarts the App while the process of the App is still in the system. This process does very little.

In today’s post, we’ll talk about cold startup of apps.

2. App unpacking

When I do not have access to DYLD, I think that App startup is to hand over the.ipa package packaged by Xcode to the system for execution, and the system calls the code to start the App by itself. Later, in order to study how the App was started, I unpacked.ipa (randomly found the wechat package), as shown below:

When you unpack it, you find some resources (including images, JSON files, HTML native files, etc.) and a black file showing exec. This black file is what the code is packaged with, which is a Mach-O file.

3, the Mach – O

What is Mach-O

Mach-O is short for Mach Object file format, which is a file format used for executables, Object code, dynamic libraries, and kernel dumps. The contents of the Mach-O file are mainly code and data: code is the definition of a function; Data is the definition of a global variable, including its initial value.

In fact, the black file showing exec in the.ipa package is the executable binary file formed by Xcode packaging code.

How was Mach-O generated

Since this article focuses on the App startup process, the generation of Mach-O is briefly mentioned.

The developer used Xcode to package the code into a Mach-O file, which was actually compiled using the LLVM compiler. The compiler compiles each project file, generating multiple Mach-O (executable) files, and the linker merges the multiple Mach-O files in the project into one.

Daming explains the main process of compiling in this article:

  • First, after you write your code, LLVM preprocesses your code, such as embedding macros in the appropriate locations.

  • After preprocessing, LLVM performs lexical and syntax analysis on the code to generate AST. AST is an abstract syntax tree that is structurally leaner than code and faster to traverse, so using AST allows for faster static checking and faster generation of IR (intermediate representation).

  • Finally, THE AST generates IR, which is a language that is more similar to machine code, except that it is platform-independent and multiple machine codes can be generated from IR for different platforms. For iOS, the executable generated by IR is Mach-O.

The following figure shows the main process of compiling.

What is a Mach-O file like

Find a CoreFoundation Mach-O and use MachOView to open it as follows:

It can be seen that the code written in Xcode is transformed into data segments after LLVM compilation. The classification stores the classes, methods, strings, etc. The figure above shows the address and name of the pointer to the referenced class.

PS: To extend this, there is a pointer address stating that the class is generated at compile time. The actual address of the class is ASLR + Offset.

4. App startup process

Now that you understand the concept of a Mach-O file, let’s explore the App startup process.

View the dyLD load flow from the call stack

Since the App is started, the main function of the program to the main.m file must be started.

Attempt 1: Hit the break point atmain.mmainfunction

You can’t see anything except that the start function is called in libdyld. Dylib.

Attempt 2: Hit the break point atobjc-runtime-new.mA random function of phi

To open the God view, you should break into the +(void)load {} method

When you look at the stack information, it seems to be the right thing to do, because all methods have the prefix dyld (this is the C++ namespace), so you can start from __dyld_start.

__dyLD_start (dyLD entry)

Click on the call stack to find a compilation:

Call XXX calls a method in the assembly, and you can see the method description behind the assembly, Dyldbootstrap ::start(dyLD3 ::MachOLoaded Const *, int, char const**, dyLD3 ::MachOLoaded Const *, unsigned long*)

dyldbootstrap:: — Code to bootstrap dyLD into a runnable state. — Code to bootstrap dyLD into a runnable state The start method is then found under the dyldBootstrap namespace.

Start at the first step of the App startup.

dyldbootstrap::start(dyld3::MachOLoaded const*, int, char const**, dyld3::MachOLoaded const*, unsigned long*)

Start source code:

// // This is code to bootstrap dyld. This work in normally done for a program by dyld and CRT. This work is usually done by dyLD and CRT programs. // In dyld we have to do this manually. // uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[], const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue) { // Emit kdebug tracepoint to indicate dyld bootstrap has started <rdar://46878536> // Dyld3 :: kDEBUG_trace_DYLD_Marker (DBG_DYLD_TIMING_BOOTSTRAP_START, 0, 0, 0, 0); // if kernel had to slide dyld, We need to fix up load sensitive locations // We have to do this before using any global variables // We need to fix loading sensitive locations and we must do this before using any global variables rebaseDyld(dyldsMachHeader); // kernel sets up env pointer to be just past end of agV array const char** envp = &argv[argc+1]; // the kernel sets up apple pointer to be just past the 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(argc, argv, envp, apple); #endif _subsystem_init(apple); // Now that we are done bootstrapping dyld, call dyld's main Main uintptr_t appsSlide = appsMachHeader->getSlide(); return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue); }Copy the code

The above code is the boot operation of dyld. After the boot, the dyld::_main function is called to return the result, and the call stack can also see the call of dyld::_main method.

The start function calls rebaseDyld(dyldsMachHeader); This function determines the ASLR at boot dyLD.

ASLR: arbitrary offset address generated by the kernel to defend against address-directed attacks.

dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*)

As you can see from the end of the dyLDbootstrap ::start function, the dyLD must be loaded in dyld::_main.

Returns address of main() in target program which __dyLD_start jumps to., Return the main() address of the target program to which __dyLD_start jumped.

With that premise, look at the _main function. (There is too much code, I will not paste, just release the main code)

Tip: Comments in the source code are mostly important information.

// // Entry point for dyld. The kernel loads dyld and jumps to __dyld_start which // sets up some registers and call // // Returns address of main() in target program which __dyLD_start jumps to // Returns __dyLD_start Uintptr_t _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, int argc, const char* argv[], const char* envp[], const char* apple[], Uintptr_t * startGlue) {// Grab the cdHash of the main executable from the environment // cdHash executable from the environment mainExecutableCDHashBuffer[20]; const uint8_t* mainExecutableCDHash = nullptr; if ( const char* mainExeCdHashStr = _simple_getenv(apple, "executable_cdhash") ) { unsigned bufferLenUsed; if ( hexStringToBytes(mainExeCdHashStr, mainExecutableCDHashBuffer, sizeof(mainExecutableCDHashBuffer), bufferLenUsed) ) mainExecutableCDHash = mainExecutableCDHashBuffer; } // Get the main program architecture getHostInfo(mainExecutableMH, mainExecutableSlide); / / save the head of the main program Mach - O sMainExecutableMachHeader = mainExecutableMH; // Save the main program ASLR smainableslide = mainableslide; SetContext (mainExecutableMH, argc, argv, ENvp, apple); / / according to the limited environment variables determine whether the current process configureProcessRestrictions (mainExecutableMH envp); / / testing process environment variable checkEnvironmentVariables (envp); defaultUninitializedFallbackPaths(envp); If (senv.dyLD_PRINt_opts); if (senv.dyLD_PRINt_opts); if ( sEnv.DYLD_PRINT_ENV ) printEnvironmentVariables(envp); CheckSharedRegionDisable ((DyLD3 ::MachOLoaded*)mainExecutableMH, mainExecutableSlide) cannot be disabled on iOS.  // Load shared cache (UIKIT,Fundation) mapSharedCache(mainexecutablide); // instantiate ImageLoader for main executable Judging compatibility sMainExecutable = instantiateFromLoadedImage (mainExecutableMH mainExecutableSlide, sExecPath); gLinkContext.mainExecutable = sMainExecutable; gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH); // Load any inserted libraries // Check whether DYLD_INSERT_LIBRARIES are allowed to load any inserted libraries If (senv. DYLD_INSERT_LIBRARIES! = NULL ) { for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib ! = NULL; ++lib) loadInsertedDylib(*lib); } / / / / cyclic loading began to link the main program / / link the main executable gLinkContext. LinkingMainExecutable = true; Link (sMainExecutable, senV. DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1); // 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(); } // Do weak binding only after all inserted images linked // Weak binding SMainExecutable ->weakBind(gLinkContext); / / link operation end gLinkContext linkingMainExecutable = false; // run all initializers // the load method of the load_images class is called initializeMainExecutable(); // Notify any motoring proccesses that this process is about to enter main() // Tell any monitoring process that the process is about to enter main() notifyMonitoringDyldMain(); Result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN(); // main executable uses LC_UNIXTHREAD, Dyld needs to let "start" in program set up for main() // call main() result = (uintptr_t)sMainExecutable->getEntryFromLC_UNIXTHREAD(); / /... return result; }Copy the code

Having seen the main code flow above, there are a few important parameters to mention:

  • Macho_header * mainExecutableMH: The header of the Mach-O file, which can be found in every Mach-O file and contains information about the operating environment, such as the CPU architecture;

  • Uintptr_t mainExecutableSlide: uintptr_t mainExecutableSlide

  • Senv. DYLD_PRINT_OPTS: When this parameter is set, the console can print the file address of Mach-O.

  • SEnv. DYLD_INSERT_LIBRARIES: This parameter is not necessary for forward development, but it is an important parameter for reverse protection. Dyld determines whether to allow dynamic libraries to be loaded or inserted into any App. It’s not something that changes the App, it’s the system loading the plug library.

Now that you have an idea of the mainline flow, let’s examine some of the important code in the mainline flow.

checkSharedRegionDisable(const dyld3::MachOLoaded* mainExecutableMH, uintptr_t mainExecutableSlide)

CheckSharedRegionDisable: Checks whether shared cache can be disabled.

This method is not much to say, this article mainly explores the App startup process in iOS system, so here is only a sentence.

static void checkSharedRegionDisable(const dyld3::MachOLoaded* mainExecutableMH, uintptr_t mainExecutableSlide)
{
#if TARGET_OS_OSX
	// ...
#endif
	// iOS cannot run without shared region
}
Copy the code

Remove the OSX method, and all that’s left is a comment that shows why iOS can’t ban shared cache libraries.

instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path)

// The kernel maps in main executable before dyld gets control. We need to // make an ImageLoader* for the already Before dyld takes control, the kernel is mapped in the main executable. We need to create an ImageLoader* for the objects already mapped in the main executable. static ImageLoaderMachO* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path) { // try mach-o loader // if ( isCompatibleMachO((const uint8_t*)mh, Path)) {/ / create the object image = ImageLoaderMachO ImageLoader * : : instantiateMainExecutable (mh, slide, path, gLinkContext); // Add addImage(image) to the AllImages environment variable; return (ImageLoaderMachO*)image; // } // throw "main executable not a known format"; }Copy the code

IsCompatibleMachO used to determine whether the CPU architecture was compatible, but it is now removed.

Image is created by the ImageLoader * = ImageLoaderMachO: : instantiateMainExecutable (mh, slide, path, gLinkContext); This is the line of code to create it, and I need to look down here, and there are some interesting parameters that I need to know about.

instantiateMainExecutable

InstantiateMainExecutable source code:

/ / create the image for the main executable / / as the main executable file to create the image ImageLoader * ImageLoaderMachO: : instantiateMainExecutable (const macho_header* mh, uintptr_t slide, const char* path, const LinkContext& context) { // ... // Use this method to instantiate sniffLoadCommands(mh, path, false, &compressed, &segCount, &libCount, context, &codeSigCmd, &encryptCmd); / /... }Copy the code
sniffLoadCommands

SniffLoadCommands source code:

There is too much code here, just a few important parameters:

// 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) { //dlyd_info_only *compressed = false; // Maximum limit // maximum 255 *segCount = 0; //LC_LOAD_DYLD_LIB maximum 4095 *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; // dylibs that use LC_DYLD_CHAINED_FIXUPS have that load command removed when put in the dyld cache if ( ! *compressed && (mh->flags & MH_DYLIB_IN_CACHE) ) *compressed = true; }Copy the code
  • *compressed: Can be seen from the code here*compressedIs the tagLC_DYLD_INFO/LC_DYLD_INFO_ONLYDid you get itcmdsizeTo determinemach - OSome size of the file.
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; *compressed = true; break; / /... }Copy the code

MachOView looks like this:

  • *segCountLC_SEGMENT(orLC_SEGMENT_64) command is the main load command. This command knows how the kernel sets the memory space of the new running process255A.

  • *libCount: The sum of dynamic libraries + tripartite libraries, the maximum allowed to load these libraries4095A. Because it is an empty project, only the system dynamic library, no third party library.

  • codeSigCmd: code signature;
  • encryptCmd: Indicates the encrypted information of the application, which is used to shell the application.

At this point the main program initialization is complete.

ImageLoader: : link the linker

With the main program initialized, the link library and symbol bindings are now ready.

Big Daming forDynamic library linkingThe description is as follows:

The Mach-O file is the result of compilation, whereas the dynamic library is linked at run time and does not participate in compilation and linking of the Mach-O file, so the Mach-O file does not contain the symbol definition of the dynamic library. That is, the symbols are shown as “undefined,” but their names and the path to the corresponding library are recorded. When the runtime imports dynamic libraries through Dlopen and DLSYM, it first finds the corresponding library path according to the record, and then finds the binding address through the record name symbol.

There are two ways to load dynamic libraries using DYLD: binding when the program starts loading and binding when symbols are first used. To reduce startup time, most dynamic libraries bind symbols the first time they are used.

At the beginning of the loading process, address offset will be corrected, iOS will use ASLR address offset to avoid attacks, determine non-lazy Pointer address for symbolic address binding, load all classes, Finally, the load method and the constructor constructor function of the Clang Attribute are executed.

Each function, global variable, and class is defined and used in symbolic form, and when linking the object file into a Mach-O file, the linker parses the symbols between the object file and the dynamic library.

The source code is as follows:

void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool preflightOnly, bool neverUnload, const RPathChain& loaderRPaths, const char* imagePath) { // ... uint64_t t0 = mach_absolute_time(); / /... This ->recursiveLoadLibraries(context, preflightOnly, loaderRPaths, imagePath); context.notifyBatch(dyld_image_state_dependents_mapped, preflightOnly); / / symbol binding, this - > recursiveRebaseWithAccounting (context); / /... }Copy the code
  • RecursiveLoadLibraries: Dynamic libraries do not have symbolic addresses when they are compiled. They only have directories for linked libraries. Dyld needs to parse the unimplemented, non-private symbol identified by LLVM as undefined into a real pointer address through the dynamic library when linking.

  • RecursiveRebaseWithAccounting: put the resolved pointer address and undefined corresponding one by one.

initializeMainExecutable

The main program is instantiated, the dynamic library link is complete, the symbolic address is bound, and it is time to run the main program.

Void initializeMainExecutable() {// record that we've reached this step gLinkContext.startedInitializingMainExecutable = true; / / the run initialzers for any inserted dylibs/run/warehousing 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]); / /... }Copy the code
ImageLoader::runInitializers

The core function of initializeMainExecutable() calls processInitializers(), basically doing nothing else, and moving on.

void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo)
{
	// ...
    
	processInitializers(context, thisThread, timingInfo, up);
	
	// ..
}
Copy the code
ImageLoader::processInitializers

Call recursive init on all images in the images list to build a new list of uninitialized upward dependencies.

void ImageLoader::processInitializers(const LinkContext& context, mach_port_t thisThread, InitializerTimingList& timingInfo, ImageLoader::UninitedUpwards& images) { // Calling recursive init on all images in images list, Building a new list of // uninitialized upward dependencies. // Call recursive init on all images in the images list to build a new list of uninitialized upward dependencies. for (uintptr_t i=0; i < images.count; ++i) { images.imagesAndPaths[i].first->recursiveInitialization(context, thisThread, images.imagesAndPaths[i].second, timingInfo, ups); }}Copy the code
ImageLoader::recursiveInitialization

This is where the images are instantiated, with the notifySingle callback.

The following two things are mainly handled here:

  • notifySingle: invokes the notification registered in runtime
  • doInitialization: instantiates these images
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(); ++i) { ImageLoader* dependentImage = libImage(i); if ( dependentImage ! = NULL ) { // don't try to initialize stuff "above" me yet if ( libIsUpward(i) ) { uninitUps.imagesAndPaths[uninitUps.count] = { dependentImage, libPath(i) }; uninitUps.count++; } else if ( dependentImage->fDepth >= fDepth ) { dependentImage->recursiveInitialization(context, this_thread, libPath(i), timingInfo, uninitUps); } } } // record termination order if ( this->needsTermination() ) context.terminationRecorder(this); Uint64_t t1 = mach_absolute_time(); // Let objc know we are about to initialize this image fState = dyld_image_state_dependents_initialized; oldState = fState; NotifySingle (DYLD_IMAGe_STATE_dependentS_initialized, this, &timingInfo); // Context. notifySingle(DYLD_IMAGe_STATE_dependents_initialized, this, &timingInfo); Bool hasInitializers = this->doInitialization(context); / /... } catch (const char* msg) { // this image is not initialized // ... } } recursiveSpinUnLock(); }Copy the code
notifySingle

This method is called the C++ method callback, and the notification is registered by the _objc_init() function under _dyld_objc_notify_register. (*sNotifyObjCInit)(image->getRealPath(), image->machHeader()); The callback.

When you look at this, you can see how dyLD and Runtime are perfectly linked.

  • sNotifyObjCMapped: Calls the Runtime methodmap_images, process images mapped by DYLD, calculate the number of classes, and adjust the size of various tables according to the total number;
  • sNotifyObjCInit: Calls the Runtime methodload_imagesInitialization of the class.

static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo) { // ... if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit ! = NULL) && image->notifyObjC() ) { // ... // Call the Runtime method (*sNotifyObjCInit)(image->getRealPath(), image->machHeader()); / /... } / /... }Copy the code
doInitialization

DoInitialization does some initialization.

bool ImageLoaderMachO::doInitialization(const LinkContext& context) { CRSetCrashLogMessage2(this->getPath()); // mach-o has -init and static initializers doImageInit(context); // call the global C++ method doModInitFunctions(context); CRSetCrashLogMessage2(NULL); return (fHasDashInit || fHasInitializers); }Copy the code

The two main methods are as follows:

  • DoImageInit: initializes images one by one;

  • DoModInitFunctions: calls global C++ methods. This method is not currently used in forward development, but in reverse, some code injection is done here. Because this method is executed before calling main() and after 95% of the configuration is complete.

doModInitFunctions

LibSystem_initializer/libdispatch_init / _os_object_init/libSystem_initializer/libdispatch_init/os_object_init/libSystem_initializer/libdispatch_init

Continuing with the doModInitFunctions, libSystemInitialized is called at the end of the current function.

bool haveLibSystemHelpersBefore = (dyld::gLibSystemHelpers ! = NULL); { dyld3::ScopedTimer(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)fMachOData, (uint64_t)func, 0); func(context.argc, context.argv, context.envp, context.apple, &context.programVars); } bool haveLibSystemHelpersAfter = (dyld::gLibSystemHelpers ! = NULL); if ( ! haveLibSystemHelpersBefore && haveLibSystemHelpersAfter ) { // now safe to use malloc() and other calls in libSystem.dylib dyld::gProcessInfo->libSystemInitialized = true; }Copy the code
  • libSystem_initializerIn:libSystemIn the library.
  • libdispatch_initIn:libdispatchIn the library.
  • _os_object_initIn:libdispatchIn the library.
libSystem_initializer

LibSystem_initializer was found in the libSystem library and the call to libdispatch_init() was found.

static void
libSystem_initializer(int argc,
		      const char* argv[],
		      const char* envp[],
		      const char* apple[],
		      const struct ProgramVars* vars)
{
	// ...

	_dyld_initializer();
	_libSystem_ktrace_init_func(DYLD);

	libdispatch_init();
	
	// ...
	errno = 0;
}
Copy the code
libdispatch_init()

Os_object_init () is called by libdispatch_init().

void
libdispatch_init(void)
{
	// ...
    
	_os_object_init();
    
	// ...
}
Copy the code
_os_object_init()

Here, the _objc_init call method is finally found, and it matches the call stack perfectly.

void
_os_object_init(void)
{
	// ...
    
	_objc_init();
	
	// ...
}

Copy the code
_objc_init()

This method will be used in the next class loading tutorial, but I won’t go into detail here. Instead, I’ll look for the + (void)load {} method call.

Load_images load_images

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    // ...

    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}
Copy the code

Call call_load_methods();

static void call_class_loads(void) { int i; // Detach current loadable list. struct loadable_class *classes = loadable_classes; int used = loadable_classes_used; loadable_classes = nil; loadable_classes_allocated = 0; loadable_classes_used = 0; // Call all +loads for the detached list. for (i = 0; i < used; i++) { Class cls = classes[i].cls; load_method_t load_method = (load_method_t)classes[i].method; if (! cls) continue; if (PrintLoading) { _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging()); } (*load_method)(cls, @selector(load)); } // Destroy the detached list. if (classes) free(classes); }Copy the code

After looking at this code, (*load_method)(CLS, @selector(load)) is a call to each class load method.

If the PrintLoading environment variable is configured, then all classes that use the load method can be printed. Optimizing startup time would be handy.

(uintptr_t)sMainExecutable->getEntryFromLC_UNIXTHREAD()

Next, the main() function is called.

5, summary

This is how dyLD works when App starts. You can also see how dyLD is linked to object-C Runtime.

The following is a summary of dyLD’s loading process:

Here, the DYLD analysis of App startup process is over, thank you for reading, if it is helpful to you, I hope to give a thumbs up, if there are mistakes, I hope to point out, learn together, progress together, thank you.