preface
In the normal development process, the entry function of app is main(), but before the main() function is called, how does the system do? What exactly did you do? The following is a detailed analysis of the app loading process.
The preparatory work
Before analyzing the app loading process, let’s understand a few concepts.
- The build process
Compilation process: the system generates the corresponding assembly code by precompiling and compiling the source file (.h. m, etc.), and then loads the library file into memory through DYLD, and then links the assembly and library file into an executable file.
-
Dynamic and static libraries
The dynamic library
: Dynamic link. There will only be one copy, shared in memory.The dynamic library
Reduced package volume size. For example, the system’s dynamic library:Foundation
,UIKit
And so on.Static library
: Static link.Static library
It will repeat during loading, wasting space.
-
Download dyLD source code
-
Download the Libsystem source code
-
Download the libDispatch source code
dyld
Dyld (Dynamic linker) : Is an important part of apple’s operating system, loading all libraries and executables.
Since load() is called earlier than main(), create a Project, add the system load() function to the controller, and run with a breakpoint:
- Through stack analysis: in execution
_dyld_start
After that, calldyldbootstrap::start
- in
Dyld source
In the search_dyld_start
And found that it was implemented regardless of the architecturedyldbootstrap::start
So the dyLD Bootstrap ::start method is an entry point into the overall dyLD process.
Dyld loading process analysis
Search dyldbootstrap for its start method. (Project choice DyLD_executables)
Start function
uintptr_t start(const dyld3::MachOLoaded* appsMachHeader,
int argc,
const char* argv[],
const dyld3::MachOLoaded* dyldsMachHeader,
uintptr_t* startGlue)
{
// Issue the kdebug flag, dyLD is started
dyld3::kdebug_trace_dyld_marker(DBG_DYLD_TIMING_BOOTSTRAP_START, 0.0.0.0);
// Redirect dyld
rebaseDyld(dyldsMachHeader);
// Sets a stack random value for stack protection
__guard_setup(apple);
#if DYLD_INITIALIZER_SUPPORT
// run all C++ initializers
runDyldInitializers(argc, argv, envp, apple);
#endif
// Initialize the stack pin apple
_subsystem_init(apple);
// Get the app header offset ASLR
uintptr_t appsSlide = appsMachHeader->getSlide(a);// Call dyld's main function.
return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}
Copy the code
RebaseDyld (redefine dyLD)
static void rebaseDyld(const dyld3::MachOLoaded* dyldMH)
{
// Go through all the header files and correct the dyld address according to the offset
const dyld3::MachOAnalyzer* ma = (dyld3::MachOAnalyzer*)dyldMH;
assert(ma->hasChainedFixups());
// Since all fixup chaining images have a zero base address, slide is the loading address
uintptr_t slide = (long)ma;
__block Diagnostics diag;
ma->withChainStarts(diag, 0And ^ (const dyld_chained_starts_in_image* starts) {
ma->fixupAllChainedFixups(diag, starts, slide, dyld3::Array<const void* > (),nullptr);
});
diag.assertNoError(a);// The redirect is complete and Mach is initialized
mach_init(a);// Once the fix is complete, mark the data constant segment in dyLD as read-only
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
- According to the offset, the correction
dyld
address- Redirect complete, set
dyld
Constant read only
The main function
Main is the main function of dyld, not the common main function of APP programs. (Due to the main function, only the main code is analyzed.)
The preparatory work
// Get the cdHash of the main program
uint8_t 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;
}
// Set architecture (CPU) information
getHostInfo(mainExecutableMH, mainExecutableSlide);
// Set the main program header and memory offset
uintptr_t result = 0;
sMainExecutableMachHeader = mainExecutableMH;
sMainExecutableSlide = mainExecutableSlide;
// Set the context and put the information into the gLinkContext (notifySingle is assigned to it)
setContext(mainExecutableMH, argc, argv, envp, apple);
// Whether the configuration process is restricted
configureProcessRestrictions(mainExecutableMH, envp);
// Check whether dyLD3 should be mandatory. AMFI related (Apple Mobile File Integrity)
if ( dyld3::internalInstall()) {if (const char* useClosures = _simple_getenv(envp, "DYLD_USE_CLOSURES")) {
if ( strcmp(useClosures, "0") = =0 ) {
// Close closure mode
sClosureMode = ClosureMode::Off;
} else if ( strcmp(useClosures, "1") = =0 ) {
#if! __i386__// don't support dyld3 for 32-bit macOS
// If not i386, enable closure mode
sClosureMode = ClosureMode::On;
sClosureKind = ClosureKind::full;
#endif
} else if ( strcmp(useClosures, "2") = =0 ) {
// Enable closure modesClosureMode = ClosureMode::On; sClosureKind = ClosureKind::minimal; }}}#if TARGET_OS_OSX
// Check whether the environment variable has changed (the main program is not loaded at this time)
if(! gLinkContext.allowEnvVarsPrint && ! gLinkContext.allowEnvVarsPath && ! gLinkContext.allowEnvVarsSharedCache ) {pruneEnvironmentVariables(envp, &apple);
// Set again (because enVP and Apple may have changed or moved at this point)
setContext(mainExecutableMH, argc, argv, envp, apple);
}
else
#endif
{
// Check environment variables
checkEnvironmentVariables(envp);
// Set the default path
defaultUninitializedFallbackPaths(envp);
}
// Load the shared cache, UIKit, Foundation dynamic library. (The main program is not loaded at this point, but the MachO header has been read)
checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
if( gLinkContext.sharedRegionMode ! = ImageLoader::kDontUseSharedRegion ) {#if TARGET_OS_SIMULATOR
if ( sSharedCacheOverrideDir)
// Set the shared cache offset address
mapSharedCache(mainExecutableSlide);
#else
// Set the shared cache offset address
mapSharedCache(mainExecutableSlide);
#endif
Copy the code
- Of the main program
hash
address- Set up the
CPU
Schema information, setting the default context- Determine whether or not
dyld3
- Checking Environment Variables
- Loading the Shared cache
Closure mode judgment
if ( sClosureMode == ClosureMode::Off ) {
// Do not use closures
} else {
// Use closures. (Whether it is actually used depends on the closure address.)
sLaunchModeUsed = DYLD_LAUNCH_MODE_USING_CLOSURE;
const dyld3::closure::LaunchClosure* mainClosure = nullptr;
// Set the main information required for loading
dyld3::closure::LoadedFileInfo mainFileInfo;
mainFileInfo.fileContent = mainExecutableMH;
mainFileInfo.path = sExecPath;
mainFileInfo.sliceOffset = 0;
mainFileInfo.sliceLen = - 1;
// Determine if the closure cache address is empty
if( sSharedCacheLoadInfo.loadAddress ! =nullptr ) {
// If not empty, set closure (loaded from cache)
mainClosure = sSharedCacheLoadInfo.loadAddress->findClosure(sExecPath);
if( mainClosure ! =nullptr )
// If the closure is not empty, prove the closure mode
sLaunchModeUsed |= DYLD_LAUNCH_MODE_CLOSURE_FROM_OS;
}
// sClosureMode determines whether closure mode is used
if ( sClosureMode == ClosureMode::On ) {
// Closure mode
allowClosureRebuilds = true;
} else if( (sClosureMode == ClosureMode::PreBuiltOnly) && (mainClosure ! =nullptr) ) {
allowClosureRebuilds = true;
}
// Validate closures
if( (mainClosure ! =nullptr) &&!closureValid(mainClosure, mainFileInfo,
mainExecutableCDHash, true, envp) ) {
// If the authentication fails, clear the closure address and reset the mode
mainClosure = nullptr;
sLaunchModeUsed &= ~DYLD_LAUNCH_MODE_CLOSURE_FROM_OS;
}
// Check whether the closure address is empty and whether creation is allowed
if ( (mainClosure == nullptr) && allowClosureRebuilds ) {
if ( !sForceInvalidSharedCacheClosureFormat )
// Cache lookup closure
mainClosure = findCachedLaunchClosure(mainExecutableCDHash, mainFileInfo, envp, bootToken);
if ( mainClosure == nullptr ) {
// Create a new closure
mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp, bootToken);
if( mainClosure ! =nullptr) sLaunchModeUsed |= DYLD_LAUNCH_MODE_BUILT_CLOSURE_AT_LAUNCH; }}}Copy the code
- Determine whether or not
closure
model- Determine the cache
closure
Address, and find.- validation
closure
- judge
closure
Address, if not, is created from the cache, if not, a new one is created.
Dyld3 (closure) load
// If the closure address exists, load it using dyLD3
if( mainClosure ! =nullptr ) {
// Start the closure
bool launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress,
(dyld3::MachOLoaded*)mainExecutableMH,
mainExecutableSlide,
argc, argv, envp, apple, diag,
&result, startGlue, &closureOutOfDate,
&recoverable);
if ( !launched && closureOutOfDate && allowClosureRebuilds ) {
// If the closure fails to start and times out, rebuilding is also allowed. The closure is created again
mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp, bootToken);
if( mainClosure ! =nullptr ) {
// If the closure is created successfully
sLaunchModeUsed |= DYLD_LAUNCH_MODE_BUILT_CLOSURE_AT_LAUNCH;
// Start again
launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress, (dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide, argc, argv, envp, apple, diag, &result, startGlue, &closureOutOfDate, &recoverable); }}// If the startup succeeds
if ( launched ) {
// Update the main program loading success flag
gLinkContext.startedInitializingMainExecutable = true;
if (sSkipMain)
// Get the main program address and return it.
result = (uintptr_t)&fake_main;
return result;
} else {
// If the startup fails, save the error information
if ( !recoverable )
halt(diag.errorMessage()); }}Copy the code
Start the
closure- If startup fails, rebuild
closure
, if the reconstruction is successfulOnce again,
Start.- Start the
successful
, gets the main program address, and returns.- Start the
failure
To save the error information.
MainExecutable instantiation (dyld2
)
// instantiate the main program
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
// Update the main program
gLinkContext.mainExecutable = sMainExecutable;
// Update code signature status
gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);
Copy the code
instantiateFromLoadedImage
static ImageLoaderMachO* instantiateFromLoadedImage(const macho_header* mh,
uintptr_t slide,
const char* path)
{
// Load the image file
ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path,
gLinkContext);
// Add to allImages in the main application
addImage(image);
return (ImageLoaderMachO*)image;
}
Copy the code
MainExecutable links (dyld2
)
// Link the main program
link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL.NULL), - 1);
// Link the dynamic library
if ( sInsertedDylibCount > 0 ) {
for (unsigned int i = 0; i < sInsertedDylibCount; ++i) {
// The shared cache already has a main program, so it starts at 1
ImageLoader* image = sAllImages[i+1];
// Link the image file
link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL.NULL), - 1);
// Load recursively
image->setNeverUnloadRecursive(a); }}}Copy the code
Link main program
Linked dynamic library
: loads the dynamic library from1
Start.Recursive loading
The image file is not loaded
link
void link(ImageLoader* image, bool forceLazysBound, bool neverUnload,
const ImageLoader::RPathChain& loaderRPaths, unsigned cacheIndex)
{
// Add itself to allImages first
if ( image->isBundle() && !image->isLinked())addImage(image);
try {
// Mirror address
const char* path = image->getPath(a);// Link to the image file
image->link(gLinkContext, forceLazysBound, false, neverUnload, loaderRPaths, path);
}
catch (const char* msg) {
// Garbage collection
garbageCollectImages(a);throw; }}Copy the code
image->link
void ImageLoader::link(const LinkContext& context, bool forceLazysBound,
bool preflightOnly, bool neverUnload,
const RPathChain& loaderRPaths, const char* imagePath)
{
// Recursively load the library and implement the notification (recursive completion)
this->recursiveLoadLibraries(context, preflightOnly, loaderRPaths, imagePath);
context.notifyBatch(dyld_image_state_dependents_mapped, preflightOnly);
// Clear all Images
context.clearAllDepths(a);this->updateDepth(context.imageCount());
{
// Recursively redirect ASLR and implement notification (redirect complete)
this->recursiveRebaseWithAccounting(context);
context.notifyBatch(dyld_image_state_rebased, false);
// Recursively bind lazy loading symbols
if ( !context.linkingMainExecutable )
this->recursiveBindWithAccounting(context, forceLazysBound, neverUnload);
// Bind the weak reference symbol
if ( !context.linkingMainExecutable )
this->weakBind(context);
}
// Recursive insertion
if ( !context.linkingMainExecutable && (fgInterposingTuples.size() != 0)) {this->recursiveApplyInterposing(context);
}
// Recursively set to read-only
if ( !context.linkingMainExecutable )
this->recursiveMakeDataReadOnly(context);
// When all is done, implement notification (binding is done)
if ( !context.linkingMainExecutable )
context.notifyBatch(dyld_image_state_bound, false);
}
Copy the code
- Recursive loading
The dynamic library
- Recursive redirection
ASLR
- Recursive binding
Lazy loading
symbol- The binding
A weak reference
symbol- Recursive application
insert
- A recursive set
read-only
- All done, implement notification (status: binding complete)
reloadAllImages
// Whether to reload the image file
if( (sAllCacheImagesProxy ! =NULL) && ImageLoader::haveInterposingTuples()) {// Cancel all loaded images except the main program, so start at 1
for (long i = 1; i < sAllImages.size(a); ++i) { ImageLoader* image = sAllImages[i];if ( image == sMainExecutable )
continue;
/ / not loaded
image->setCanUnload(a);/ / delete
ImageLoader::deleteImage(image);
}
// Cancel all Images
resetAllImages(a);// Reload
goto reloadAllImages;
}
Copy the code
MainExecutable binding (dyld2
)
// Bind the main program
sMainExecutable->recursiveBindWithAccounting(gLinkContext, sEnv.DYLD_BIND_AT_LAUNCH, true);
// Implement notification (binding complete)
gLinkContext.notifyBatch(dyld_image_state_bound, false);
// Recursively bind inserted Images, starting at 1 by default
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); }}// Bind the weak reference symbol of the main program
sMainExecutable->weakBind(gLinkContext);
// Set read-only data for the main program
sMainExecutable->recursiveMakeDataReadOnly(gLinkContext);
Copy the code
MainExecutable initialization (dyld2
)
void initializeMainExecutable(a) {
// Start initializing the main program tag
gLinkContext.startedInitializingMainExecutable = true;
// Initialize all rootImages
ImageLoader::InitializerTimingList initializerTimes[allImagesCount()];
initializerTimes[0].count = 0;
const size_t rootCount = sImageRoots.size(a);if ( rootCount > 1 ) {
for (size_t i = 1; i < rootCount; ++i) {
sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]); }}// Initializes the loader for the main program and all content
sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
}
Copy the code
runInitializers
void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo) {
ImageLoader::UninitedUpwards up;
up.count = 1;
up.imagesAndPaths[0] = { this.this->getPath() };
// Process initialization
processInitializers(context, thisThread, timingInfo, up);
// Implement notification (initialization is complete)
context.notifyBatch(dyld_image_state_initialized, false);
}
Copy the code
processInitializers
void ImageLoader::processInitializers(const LinkContext& context,
mach_port_t thisThread,
InitializerTimingList& timingInfo,
ImageLoader::UninitedUpwards& images)
{
for (uintptr_t i=0; i < images.count; ++i) {
// Recursively initializes all images in the images list, building a new dependency.
images.imagesAndPaths[i].first->recursiveInitialization(context, thisThread, images.imagesAndPaths[i].second, timingInfo, ups); }}Copy the code
recursiveInitialization
// Initialize the child dynamic library
for (unsigned int i = 0; i < libraryCount(a); ++i) { ImageLoader* dependentImage =libImage(i);
if( dependentImage ! =NULL ) {
// Get the upper-level lib
if ( libIsUpward(i) ) {
uninitUps.imagesAndPaths[uninitUps.count] = { dependentImage, libPath(i) };
uninitUps.count++;
} else if ( dependentImage->fDepth >= fDepth ) {
// If the child is not loaded, perform deep recursive initialization
dependentImage->recursiveInitialization(context, this_thread, libPath(i), timingInfo, uninitUps); }}}// Send notification (all attached images have been loaded)
context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
// Initialize the overall context
bool hasInitializers = this->doInitialization(context);
// Send notification (initialization completed)
context.notifySingle(dyld_image_state_initialized, this.NULL);
Copy the code
doInitialization
bool ImageLoaderMachO::doInitialization(const LinkContext& context)
{
// Initialize the image
doImageInit(context);
// initializes the global C++ object constructor, internally initializing the libSystem library
doModInitFunctions(context);
}
Copy the code
notifySingle
RecursiveInitialization sends a notification to DYLD_IMAGe_STATE_dependentS_INITIALIZED, so the sNotifyObjCInit callback is implemented.
static void notifySingle(dyld_image_states state,
const ImageLoader* image,
ImageLoader::InitializerTimingList* timingInfo)
{
// The state is DYLD_IMAGe_STATE_dependentS_INITIALIZED and
// sNotifyObjCInit the callback exists and
// The loading image tag exists
if( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit ! =NULL) &&
image->notifyObjC()) {// Implement sNotifyObjCInit notification callback
(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
}
Copy the code
registerObjCNotifiers
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
/ / register map_images
sNotifyObjCMapped = mapped;
/ / register load_images
sNotifyObjCInit = init;
/ / register unmap_images
sNotifyObjCUnmapped = unmapped;
}
Copy the code
_dyld_objc_notify_register
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
_dyld_objc_notify_init init,
_dyld_objc_notify_unmapped unmapped)
{
// Register notification
dyld::registerObjCNotifiers(mapped, init, unmapped);
}
Copy the code
At this point, the _DYLD_OBJC_Notify_register method is not in the DYLD source library.
Later, the process is combed by using the backward method.
notifyMonitoringDyldMain
// Tell the monitor to enter the main() function of dyld
notifyMonitoringDyldMain(a);// Get the address of the main program entry
result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN(a);Copy the code
Backstepping method
Check the stack information using the symbol breakpoint _dyLD_OBJC_notify_register:
_objc_init
_objc_init belongs to libobjc.a.dylib
_objc_init source code:
_os_object_init
Os_object_init belongs to libdispatch. Dylib.
_os_object_init source code:
libdispatch_init
Libdispatch_init belongs to libdispatch. Dylib
Libdispatch_init source code:
libSystem_initializer
LibSystem_initializer belongs to libsystem.b.dilib.
LibSystem_initializer source code:
doModInitFunctions
As analyzed above, doModInitFunctions source code contains initialization of the libSystem library
summary
Through analysis, when the main program initialization is complete, register the notification first, and then perform initialization. The specific process is as follows:
doModInitFunctions
(libSystem
Load) –libSystem_initializer
→libdispatch_init
→_os_object_init
→_objc_init
→_dyld_objc_notify_register
Dyld loading flowchart
supplement
map_images
The first parameter map_images to be registered with the _dyLD_OBJC_notify_register is:
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
/ / register map_images
sNotifyObjCMapped = mapped;
/ / register load_images
sNotifyObjCInit = init;
/ / register unmap_images
sNotifyObjCUnmapped = unmapped;
try {
// Call the mapped function and map all the images
notifyBatchPartial(dyld_image_state_bound, true.NULL.false.true);
} catch (const char* msg) {
// Ignore the request}}Copy the code
(sNotifyObjCMapped) (notifyBatchPartial) (sNotifyObjCMapped)
NotifyBatchPartial is called after the registerObjCNotifiers have been registered.
conclusion
Map_images is called in the following order: _DYLD_OBJC_notify_register → registerObjCNotifiers → notifyBatchPartial → sNotifyObjCMapped (MAP_images)
Dyld2 and dyld3
dyld2
dyld2
isdyld1
A completely rewritten version of.- The right support
c++
Initializer, extendedmach-o
The format has been updateddyld
. - With integrity
dlopen
anddlsym
Implementation, discarding the old versionAPI
(the old versionAPI
Still only locatedmacOS
). dyld2
Is designed to increase speed, so only limited health checks are performed.dyld1
There are some security issues,dyld2
Some functional improvements have been made to improve security.- Speed significantly improves performance and reduces the amount of pre-binding. Different from the
dyld1
Compile your program data,dyld2
Only system libraries are compiled. You can do this only during software updates. So you might see it in software updatesOptimize system performance
“, which is updating the prebinding.
dyld3
Dyld3 is a new dynamic connector, iOS11 above the system procedures are used by default dyLD3, in iOS13 completely replace DYLD2. Dyld3 is mainly improved in the following three aspects:
- Performance. Improve startup speed.
dyld3
Can help us get the program up and running faster. - Security.
dyld2
Increased security is hard to follow in the real world. - Testability and reliability.
Dyld2 and DYLD3 loading comparison
Dyld2 process:
Parse mach-o headers & Find dependencies
Analysis:macho headers
, identify which libraries are required. Recursively analyze the dependent libraries until you get all of themdylib
Library. ordinaryiOS
Application needs to3-600.
adylib
Data is huge and requires a lot of processing.Map mach-o files
: Mapping allmacho
The file puts them into the address space (mapped into memory).Perform symbol lookups
: Performs symbol lookup. Such as the useprintf
Function, will findprintf
If it is in the library system, then find its address and copy it to the function pointer in the application.Bind and rebase
: Binding and redirection. Copy these Pointers. All Pointers must use base addresses (ASLR
The existence of.Run initializers
: Runs all initializers. After that, preparations for execution beginmain
Function.
Dyld3 process:
-
Dyld3 is an out-of-process Macho parser and compiler
- Parsing all search paths,
rpaths
, environment variables. - Analysis of the
macho
Binary data. - Performs all symbol lookups.
- Create closures.
- Normal tests can be performed.
- Most programs start using caching and never need to call out of process
macho
Analyzer or compiler. - Start closure ratio
macho
Even simpler, they are memory-mapped files that don’t need to be parsed in a complicated way and can be easily validated for speed purposes.
- Parsing all search paths,
-
Dyld3 is also an in-process engine
- Check that closures are correct.
- Use closures to map all
dylibs
. - Binding and redirection.
- Run all initializers, then jump to main program
main()
.
-
Start the closure cache service
- system
app
The closure pattern is built into a shared cache. - Third-party applications are built at install time and rebuilt with software updates.
- in
macOS
The upper daemon engine can be called in the daemon, which is not required on other platforms.
- system
dyld3
No need to analyzemacho Headers
Or perform a symbol lookup. inApp
There is no such process at startup, which greatly improves the startup speed of the program.
Please refer to the official video for details.