1. Preparation

Before you start exploring, provide the required source code and extend your knowledge

1.1 the source code

In exploring the iOS application startup process, need to use the source code

  • Objc source
  • Dyld source
  • LibSystem
  • ibdispatch

1.2 Expanding knowledge

  • Applications do not run independently from each other during the run and rely on other libraries
  • What is a library? Binary files that can be loaded into memory
  • In the iOS is divided intoStatic libraryandThe dynamic library, thenStatic libraryandThe dynamic libraryWhat is the difference, mainly in the way of linking, in the static libraryStatic link, the dynamic library isDynamic link

The following graph summarizes static and dynamic libraries:

  • What is themacho? It’s essentiallyExecutable binary files, the dyld will be loaded in its specific formatparsingit
  • What is theimage(Image file)? Which is oursBinary files are mapped into memory
  • What is thedyld? In factDynamic link loaderThe reason why our app can load is by passingdyldTo operate the

2

We all know that our application is loaded from the main function, but who called the main function?

Found fromlibdyldthestartFunction, we continue through symbolic breakpointsstart, found an unable break point. We know from development experienceloadMethod, actually earlier thanmainMethod call, try to view the call stack at the load method break point:

It turns out that there is a call_dyld_startMethod. Now let’s go fromdyldThe source code for this method continues to be explored

3. Dyld main process

  • Due to thedyldThere are too many dependencies to compile, so you have to find a global search through a function_dyld_startfunction
  • Search foundi386,x86_64,armDifferent architectures are implemented, the process is similar, we will focus onarmOf, also be real machine
// call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)

bl	__ZN13dyldbootstrap5startEPKN5dyld311MachOLoadedEiPPKcS3_Pm
Copy the code

Jump to dyldbootstrap::start by commenting and assembling the BL

  1. Issue a KDEBUG trace point to indicate that dyLD boot has started
  2. Rebind to fix dyld location
  3. Set a random value for the stack canary
  4. _subsystem_init System-related initialization
  5. calldyld mainFunction, passed to appmachoASLR

Dyld: : the main function

The main flow of main function is also mentioned in my previous blog. You can refer to the source code analysis of App loading process in Dyld. There are many details of DYLD, and if every point is analyzed, the length will be very long

4. InitializeMainExecutable

void initializeMainExecutable(){
// 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
  • callrunInitializersMethod to initialize all dynamic library image files
  • callrunInitializersMethod to initialize the main program
  • registeredcxa_atexitMethod to terminate all images when the program exits
  • Environment variables, print Settings

Enter the ImageLoader: : runInitializers method call

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

Call processInitializers, initialize, and continue tracking processInitializers by notifyBatch.

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.
if ( ups.count > 0 )
	processInitializers(context, thisThread, timingInfo, ups);
Copy the code
  • For all imagesrecursiveInitialization,

Continue to enter the ImageLoader: : recursiveInitialization method:

void ImageLoader::recursiveInitialization {
1.// initialize lower level libraries first
for(unsigned int i=0; i < libraryCount(); ++i){
     dependentImage->recursiveInitialization(context, this_thread, libPath(i), timingInfo, uninitUps);
}
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
  • Initialize the underlying dependency files first
  • Dependency library initialization notificationcontext.notifySingle(dyld_image_state_dependents_initialized
  • Image file initializationdoInitialization
  • Call notifications againcontext.notifySingle(dyld_image_state_initialized

Let’s go ahead and look at notifySingle, you can see that it calls sNotifyObjCInit, keep looking and find that registerObjCNotifiers has an assignment,

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

Continue looking for the upper level call to the registerObjCNotifiers and find that it is called by _dyLD_OBJC_NOTIFy_register. So when is this method called? Find objc source, open it, search


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();
    runtime_init();
    exception_init();
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
Copy the code

This method will be prescribed at _objc_init, which is load_images

  • sNotifyObjCMapped ->map_images
  • sNotifyObjCInit->load_images
  • sNotifyObjCUnmapped ->unmapped

So when does _objc_init get turned up, the break point, and then start pushing back based on the stack information

_objc_init(objc) ->_os_object_init(libdispathc)->libdispatch_init(libdispathc)->libSystem_initializer->doModInitFunctionsvalidationlibSystem_initializer->doModInitFunctions

if(! dyld::gProcessInfo->libSystemInitialized ) {// <rdar://problem/17973316> libSystem initializer must run first
const char* installPath = getInstallPath();
if( (installPath == NULL) || (strcmp(installPath, libSystemPath(context)) ! =0))dyld::throwf("initializer in image (%s) that does not link with libSystem.dylib\n".this->getPath());
}
Copy the code
  • Through herelibSystem initializer must run firstWe know it’s calledlibSystem_initializer.
  • To continue the pushdoModInitFunctionsThe method is actually made up ofdoInitializationMethod call, if this method is familiar, this is the method that we derived from above
  1. So the actual mountain isdoModInitFunctions->_objc_init->_dyld_objc_notify_register, it will pass here_dyld_objc_notify_registerMethods,!objctheload_imagesAssigned todyldSource codesNotifyObjCInit.
  2. And then by callingcontext.notifySingle, the callsNotifyObjCInit, will actually callobjctheload_imagesmethods

Summarize the main program initialization process with a diagram:

Examples (load, c++ constructor, main)

Create a new Demo project and print it separately after running:

__attribute__((constructor)) void qhFunc(){
    printf("Coming: %s \n",__func__);
}

+ (void)load{
    NSLog(@"%s",__func__);
}
Copy the code

It is found that the order of run is load->c++ constructor ->main, so this is why, since there is the source code all easy to say, below we through the source analysis. The address of the main function is yes, and dyld::main returns. So finally execute.

  • At the constructor break point:

Through the call stack,C++ constructorsbydoInitializationThe call. Then look at:recursiveInitializationfunction

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 calls back to the load_image method, which in turn calls the load method. We can’t call load if sNotifyObjCInit is empty without initializing load_image.

Let’s demonstrate this by adding c++ constructors to the objc source code.

And then in our main program also addloadMethods andC++ constructorsRun,

Print result:

Now the printThe C++ constructor that depends on the libraryAnd then afterThe main program load.Main program C++ constructor.

  • Because the first recursion calls the dependent imagedoInitializationAt this point, it has been initialized. That issNotifyObjCInitWill be assigned. And then it comes in a second timecontext.notifySingleIt will call the load method and then call the main programdoInitializationMethods.