In the process of app development, it is generally considered that main function is the entrance of program execution, but before main function, the system has done a lot of work, today we will study it.

Now that we know that load is handled before main, we implement load in our custom class and break the point. Using the bt command to look at the stack information, we see that the first execution is _dyLD_START in the dyLD library

Dyld is Apple’s dynamic linker. After the kernel is ready, control is handed over to DYLD to do the rest of the work. He is open source

The source code to explore

_dyld_start

#if __arm64__ && ! TARGET_OS_SIMULATOR .text .align 2 .globl __dyld_start __dyld_start: ...... // call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, & startGlue) / / according to the annotation know here call dyldbootstrap: : start __ZN13dyldbootstrap5startEPKN5dyld311MachOLoadedEiPPKcS3_Pm / / bl Store the return value of dyLDBootstrap ::start in x16 mov x16,x0...... // Execute the function address returned in x16 br x16...... #endif // __arm64__ && ! TARGET_OS_SIMULATORCopy the code

Analysis shows that dyLDBootstrap ::start is called and the return value is executed

dyldbootstrap::start

// // This is code to bootstrap dyld. This work in normally done for a program by dyld and crt. // In dyld we have to do This manually. // // we can see that // appsMachHeader is the Mach header of the application and // dyldsMachHeader is the Mach header of dyld 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 // Dyld is also an application for system, This step must be done before using any global variables // We see that the argument passed is dyld's Mach header rebaseDyld(dyldsMachHeader); // 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; // setup 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, Uintptr_t appsSlide = appsMachHeader->getSlide(); // return dyld's _main function, // appsMachHeader is MachHeader. // appsSlide is ASLR or something related to ASLR dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue); }Copy the code

There are three main steps involved here

  • dyldAddress relocation
  • Stack protection
  • returndyldthe_mainfunction

rebaseDyld

// // On disk, all pointers in dyld's DATA segment are chained together. // They need to be fixed up to be real pointers to run. // All Pointers to DATA segment on disk point to base address 0, which needs to be calibrated against the offset slide, Static void rebaseDyld(const dyLD3 ::MachOLoaded* dyldMH) {walk all fixups chains and rebase DYLD const dyld3::MachOAnalyzer* ma = (dyld3::MachOAnalyzer*)dyldMH; assert(ma->hasChainedFixups()); Uintptr_t slide = (long) uintptr_t slide = (long); // all fixup chain based images have a base address of zero, so slide == load address __block Diagnostics diag; ma->withChainStarts(diag, 0, ^(const dyld_chained_starts_in_image* starts) { ma->fixupAllChainedFixups(diag, starts, slide, dyld3::Array<const void*>(), nullptr); }); diag.assertNoError(); // now that rebasing done, initialize mach/syscall layer mach_init(); // <rdar://47805386> mark __DATA_CONST segment in dyld as read-only (once fixups are done) ma->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& info, bool& stop) { if ( info.readOnlyData ) { ::mprotect(((uint8_t*)(dyldMH))+info.vmAddr, (size_t)info.vmSize, VM_PROT_READ); }}); }Copy the code

dyld::_main

This function is very long, more than a thousand lines, let’s pick some major code analysis

// // Entry point for dyld. The kernel loads dyld and jumps to __dyld_start which // sets up some registers and call this function. // // Returns address of main() in target program which __dyld_start jumps to // // Uintptr_t _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, int argc, const char* argv[], const char* envp[], const char* apple[], SetContext (mainExecutableMH, argc, argv, ENVP, apple); checkEnvironmentVariables(envp); defaultUninitializedFallbackPaths(envp); If (senv.dyLD_PRINt_opts) printOptions(argv); if ( sEnv.DYLD_PRINT_ENV ) printEnvironmentVariables(envp); CheckSharedRegionDisable ((DyLD3 ::MachOLoaded*)mainExecutableMH, mainExecutableSlide); } mapSharedCache(mainExecutableSlide); / / 3, instantiation of the main program sMainExecutable = instantiateFromLoadedImage (mainExecutableMH mainExecutableSlide, sExecPath); // Load any inserted libraries if (senv. DYLD_INSERT_LIBRARIES! = NULL ) { for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib ! = NULL; ++lib) loadInsertedDylib(*lib); } // 4, 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(); }... // Bind and notify for the main executable now that interposture 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 interposture 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, nullptr); // <rdar://problem/12186933> do weak binding only after all inserted images sMainExecutable->weakBind(gLinkContext); // Run all Initializers initializeMainExecutable();Copy the code

This function is very long and is the core method of this study. It is difficult for us to analyze it from beginning to end. The result is assigned by the sMainExecutable after the reverse deduction, so we focus on the analysis of the sMainExecutable

Here is the main process dyLD uses to load applications:

  • 1. Configure and check the context
  • 2. Load the shared cache
  • 3. Instantiate the main program
  • Load the inserted dynamic library
  • 5. Link the main program
  • 6. Link inserted dynamic library
  • Recursively bind the main program and the library that the main program depends on
  • 8. Bind the inserted image file recursively
  • Weak symbol binding
  • 10. Perform all initialization operations

_dyLD_start ->start->_main; _dyLD_start = _main

1. Configure and check the context

Here we know that we can print environment variables by configuring DYLD_PRINT_ENV

After running, you can see a number of environment variables

For example, you can see here that three dynamic libraries are inserted

  • libBacktraceRecording.dylib
  • libMainThreadChecker.dylib
  • libViewDebuggerSupport.dylib
2. Load the shared cache
static void checkSharedRegionDisable(const dyld3::MachOLoaded* mainExecutableMH, Uintptr_t mainexecutablide) {#if TARGET_OS_OSX // uintptr_t mainexecutablide #endif // iOS cannot run without shared region // iOS cannot run without shared region}Copy the code

Although there is no code for the ARM64 architecture in this method, the last comment makes it clear that iOS cannot run without a shared cache, which is enough to illustrate its importance

static void mapSharedCache(uintptr_t mainExecutableSlide) { ...... loadDyldCache(opts, &sSharedCacheLoadInfo); . } bool loadDyldCache(const SharedCacheOptions& options, SharedCacheLoadInfo* results) { #if TARGET_OS_SIMULATOR // simulator only supports mmap()ing cache privately into process return mapCachePrivate(options, results); If (options.forcePrivate) {// mmap cache into this process only return mapCachePrivate(options, results); } else { // fast path: when cache is already mapped into shared region bool hasError = false; If (reuseExistingCache(options, results)) {hasError = (results->errorMessage! = nullptr); } else { // slow path: HasError = mapCacheSystemWide(options, results); this is the first process to load the cache. } return hasError; } #endif }Copy the code

Shared cache libraries can be shared across multiple processes, saving memory space

3. Instantiate the main program
// 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 into the main executable. // We need to create an ImageLoader* for the object 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) ) { ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext); AddImage (image); // addImage from MachO to imageList return (ImageLoaderMachO*)image; // } // throw "main executable not a known format"; }Copy the code

Three arguments are passed in

  • 1. Applicationmach headerPointer to the
  • 2. Offsetslide
  • 3, pathpath

With this information loader can load the MachO file of the application. MachO operations are involved here, which is the process of reading content from MachO based on information such as Mach headers. This will be described in more detail when we focus on MachO files later

Load the inserted dynamic library
// 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

DYLD_INSERT_LIBRARIES we saw it in the first step of configuring environment variables, which is to load the dynamic libraries used

5. Link the main program
void link(ImageLoader* image, bool forceLazysBound, bool neverUnload, const ImageLoader::RPathChain& loaderRPaths, unsigned cacheIndex) { // add to list of known images. This did not happen at creation time for bundles // If (image->isBundle() &&! image->isLinked() ) addImage(image); // We detect root images as those not linked in yet // If (! image->isLinked() ) addRootImage(image); // process images try { const char* path = image->getPath(); #if SUPPORT_ACCELERATE_TABLES if ( image == sAllCacheImagesProxy ) path = sAllCacheImagesProxy->getIndexedPath(cacheIndex); Image ->link(gLinkContext, forceLazysBound, false, neverUnload, loaderRPaths, path); } catch (const char* msg) { garbageCollectImages(); throw; }}Copy the code

Link the root image before you link the rest of the content

void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool preflightOnly, bool neverUnload, const RPathChain& loaderRPaths, const char* imagePath) { ...... This ->recursiveLoadLibraries(context, preflightOnly, loaderRPaths, imagePath); . }Copy the code
6. Link inserted dynamic library
// 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 //  ( 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(); }... }Copy the code
Recursively bind the main program and the library that the main program depends on
// Bind and notify for the main executable now that interposing has been registered uint64_t bindMainExecutableStartTime  = mach_absolute_time(); / / recursive binding main program and the main program rely on other libraries 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);Copy the code
8. Bind the inserted image file recursively
// 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, NULlptr); }}Copy the code
Weak symbol binding
    // <rdar://problem/12186933> do weak binding only after all inserted images linked
    sMainExecutable->weakBind(gLinkContext);
Copy the code
10. Perform all initialization operations
void initializeMainExecutable() { // record that we've reached this step gLinkContext.startedInitializingMainExecutable = true; / / the run initialzers for any inserted dylibs / / perform all plugged into the Treasury's first initialization 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

This function focuses on initialization, after runInitializers->processInitializers->recursiveInitialization

Look at the recursiveInitialization

void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize, InitializerTimingList& timingInfo, UninitedUpwards& uninitUps) { // 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

Look at the notifySingle

static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo) { ...... (*sNotifyObjCInit)(image->getRealPath(), image->machHeader()); . }Copy the code

The assignment operation is performed in

// _dyld_objc_notify_init void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped) { ...... // record functions to call sNotifyObjCInit = init; . }Copy the code

Be _dyld_objc_notify_register tuning up

// _dyld_objc_notify_register 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

The location of the _DYLD_OBJC_notify_register register is not found in the dyLD source code. It is found in _objC_init in the OBJC4 library through the symbol breakpoint

/*********************************************************************** * _objc_init * Bootstrap initialization. Registers our image notifier with dyld. * Called by libSystem BEFORE library initialization time **********************************************************************/ void _objc_init(void) { static bool initialized = false; if (initialized) return; initialized = true; // fixme defer initialization until an objc-using image is found? environ_init(); tls_init(); static_init(); runtime_init(); exception_init(); #if __OBJC2__ cache_t::init(); #endif _imp_implementationWithBlock_init(); _dyLD_OBJC_notify_register (&map_images, load_images, unmap_image); #if __OBJC2__ didCallDyldNotifyRegister = true; #endif }Copy the code

So far I have only got a general idea, many details are still vague, the big process can be understood as

  • Start thedyld
  • Instantiate the main program
  • Link main program
  • Bind the main program and its dependent libraries
  • Initialize the
  • Send a notification

Refer to the article

# dyld load process