iOS应用程序加载

前言

我们都知道iOS应用程序首先是执行到main函数然后到AppDelegate,那么main之前是做了什么工作呢?今天抱着这么一个疑问来探索一下ios应用程序加载流程。

准备

1.浅谈一下ios的编译流程

我绘画了一张流程图来了解一下iOS的编译流程如下:


例如下面的代码:


当你编译的时候,初始就是源代码,当你源代码放到编译器中时会生成一个IR(intermediate representation 中间状态)


继续编译会产生一个MIR状态(Machine IR)


然后汇编产生如下图的代码

2.iOS的动态库与静态库

静态库:在链接阶段,会随着汇编生成的目标.o文件与引用的库一起链接打包到可执行文件当中,被多次使用就会产生多份拷贝,如下图所示


动态库:在链接阶段,不会链接到目标代码中,而是在程序运行时由系统动态的加载到内存,并且系统只加载一次,不会产生多份拷贝。系统提供的基本库可以多个程序共用,节省内存。


动态库的优势:

  • 减少打包之后的App大小
  • 同享内存,节约资源
  • 可以更新动态库,达到更新程序的目的

静态库文件格式:.a和.framework
动态库文件格式:.dylib和.framework

开始探究

1.引出dyld的_dyld_start函数

首先我们先来看一下main函数执行之前的堆栈信息


发现在mian函数之前调用了libdyld.dylib库的start函数,这个时候你去下载libdyld源代码搜索start函数,发现有3千多个结果,根本无从探究!
换一种思路,我们都知道类方法load是在mian之前调用的,所以我们在load方法去打一个断点,看看堆栈信息,打印结果如下

* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1
  * frame #0: 0x000000010c162f0c ios应用程序加载`+[ViewController load](self=ViewController, _cmd="load") at ViewController.m:19:1
    frame #1: 0x00007fff5125b477 libobjc.A.dylib`load_images + 1386
    frame #2: 0x000000010c173e34 dyld_sim`dyld::notifySingle(dyld_image_states, ImageLoader const*, ImageLoader::InitializerTimingList*) + 418
    frame #3: 0x000000010c181856 dyld_sim`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 438
    frame #4: 0x000000010c17fd2c dyld_sim`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 188
    frame #5: 0x000000010c17fdcc dyld_sim`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 82
    frame #6: 0x000000010c174270 dyld_sim`dyld::initializeMainExecutable() + 199
    frame #7: 0x000000010c1781bb dyld_sim`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 3662
    frame #8: 0x000000010c1731cd dyld_sim`start_sim + 122
    frame #9: 0x0000000112173c27 dyld`dyld::useSimulatorDyld(int, macho_header const*, char const*, int, char const**, char const**, char const**, unsigned long*, unsigned long*) + 2093
    frame #10: 0x000000011217115e dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 1191
    frame #11: 0x000000011216b224 dyld`dyldbootstrap::start(dyld3::MachOLoaded const*, int, char const**, dyld3::MachOLoaded const*, unsigned long*) + 450
    frame #12: 0x000000011216b025 dyld`_dyld_start + 37

从上面的打印结果,我们可以得知在main函数之前首先调用了dyld库的_dyld_start函数,在_dyld_start函数中x86-64或者arm架构中,我们可以发现下面一个注释

说明在_dyld_start中首先调用dyldbootstrap的start方法,其实从上面打印的堆栈中也可以看出这点。

uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],
               const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue)
{

   //发出 kdebug 跟踪点以指示 dyld 引导程序已启动
   dyld3::kdebug_trace_dyld_marker(DBG_DYLD_TIMING_BOOTSTRAP_START, 0, 0, 0, 0);

   // 如果内核不得不移动dyld,我们需要修复加载敏感位置
   // 我们必须在使用任何全局变量之前这样做
   //因为在磁盘上,dyld的DATA segment中的所有指针都被链在一起。它们需要被固定为真正的指针来运行。
   rebaseDyld(dyldsMachHeader);

   // kernel sets up env pointer to be just past end of agv array
   //内核将env指针设置为agv数组参数的末端
   const char** envp = &argv[argc+1];
   
   //内核将apple指针设置为envp数组的末尾
   const char** apple = envp;
   while(*apple != NULL) { ++apple; }
   ++apple;

   // set up random value for stack canary
   __guard_setup(apple);

#if DYLD_INITIALIZER_SUPPORT
   //在dyld中运行所有c++初始化器
   runDyldInitializers(argc, argv, envp, apple);
#endif

   _subsystem_init(apple);

   //现在我们完成了引导dyld,调用dyld的main
   uintptr_t appsSlide = appsMachHeader->getSlide();
   return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}

在start函数中完成dyld的引导,然后调用dyld的main函数,
apple对这个dyld的_main函数的注释是

// 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
//输入dyld指针。内核加载dyld并跳转到__dyld_start
//设置一些寄存器并调用这个函数。
//返回__dyld_start跳转到的目标程序中的main()的地址

1.1 macho_header

函数的参数中我们看到有一个macho_header的参数,这是一个什么东西呢?Mach-O其实是Mach Object文件格式的缩写,是mac以及iOS中的可执行文件格式,并且有自己的文件格式目录,苹果给出的mach文件如下图:


首先我们点击进入macho_header这个结构体看它的定义如下:

struct mach_header_64 {
    uint32_t    magic;      /* 区分系统架构版本 */
    cpu_type_t  cputype;    /*CPU类型 */
    cpu_subtype_t   cpusubtype; /* CPU具体类型 */
    uint32_t    filetype;   /* 文件类型 */
    uint32_t    ncmds;      /* loadcommands 条数,即依赖库数量*/
    uint32_t    sizeofcmds; /* 依赖库大小 */
    uint32_t    flags;      /* 标志位 */
    uint32_t    reserved;   /* 保留字段,暂没有用到*/
};

这里macho_header就是读取macho文件的头部信息,header里面会包含该二进制文件的一些信息:如字节顺序、架构类型、加载指令的数量等。可以用来快速确认一些信息,比如当前文件用于32位还是64位、文件的类型等。

2.探究dyld的核心函数_main

我画了一个流程图,dyld:_main函数主要工作如下

2.1 条件准备

2.2.1 环境变量准备
// 检查我们是否需要覆盖平台
const char* forcedPlatform = _simple_getenv(envp, "DYLD_FORCE_PLATFORM");
//获取dyld根路径
const char* rootPath = _simple_getenv(envp, "DYLD_ROOT_PATH");
//是否使用闭包
const char* useClosures = _simple_getenv(envp, "DYLD_USE_CLOSURES")
checkEnvironmentVariables(envp)
 .....

下面附上一个dyld所使用的环境变量

// 
// state of all environment variables dyld uses
//
struct EnvironmentVariables {
    const char* const *         DYLD_FRAMEWORK_PATH;
    const char* const *         DYLD_FALLBACK_FRAMEWORK_PATH;
    const char* const *         DYLD_LIBRARY_PATH;
    const char* const *         DYLD_FALLBACK_LIBRARY_PATH;
    const char* const *         DYLD_INSERT_LIBRARIES;
    const char* const *         LD_LIBRARY_PATH;            // for unix conformance
    const char* const *         DYLD_VERSIONED_LIBRARY_PATH;
    const char* const *         DYLD_VERSIONED_FRAMEWORK_PATH;
    bool                        DYLD_PRINT_LIBRARIES_POST_LAUNCH;
    bool                        DYLD_BIND_AT_LAUNCH;
    bool                        DYLD_PRINT_STATISTICS;
    bool                        DYLD_PRINT_STATISTICS_DETAILS;
    bool                        DYLD_PRINT_OPTS;
    bool                        DYLD_PRINT_ENV;
    bool                        DYLD_DISABLE_DOFS;
    bool                        hasOverride;
                            //  DYLD_SHARED_CACHE_DIR           ==> sSharedCacheOverrideDir
                            //  DYLD_ROOT_PATH                  ==> gLinkContext.rootPaths
                            //  DYLD_IMAGE_SUFFIX               ==> gLinkContext.imageSuffix
                            //  DYLD_PRINT_OPTS                 ==> gLinkContext.verboseOpts
                            //  DYLD_PRINT_ENV                  ==> gLinkContext.verboseEnv
                            //  DYLD_FORCE_FLAT_NAMESPACE       ==> gLinkContext.bindFlat
                            //  DYLD_PRINT_INITIALIZERS         ==> gLinkContext.verboseInit
                            //  DYLD_PRINT_SEGMENTS             ==> gLinkContext.verboseMapping
                            //  DYLD_PRINT_BINDINGS             ==> gLinkContext.verboseBind
                            //  DYLD_PRINT_WEAK_BINDINGS        ==> gLinkContext.verboseWeakBind
                            //  DYLD_PRINT_REBASINGS            ==> gLinkContext.verboseRebase
                            //  DYLD_PRINT_DOFS                 ==> gLinkContext.verboseDOF
                            //  DYLD_PRINT_APIS                 ==> gLogAPIs
                            //  DYLD_IGNORE_PREBINDING          ==> gLinkContext.prebindUsage
                            //  DYLD_PREBIND_DEBUG              ==> gLinkContext.verbosePrebinding
                            //  DYLD_NEW_LOCAL_SHARED_REGIONS   ==> gLinkContext.sharedRegionMode
                            //  DYLD_SHARED_REGION              LinkContext.sharedRegionMode
                            //  DYLD_PRINT_WARNINGS             ==> gLinkContext.verboseWarnings
                            //  DYLD_PRINT_RPATHS               ==> gLinkContext.verboseRPaths
                            //  DYLD_PRINT_INTERPOSING          ==> gLinkContext.verboseInterposing
                            //  DYLD_PRINT_LIBRARIES            ==> gLinkContext.verboseLoading
};
2.2.2 主机信息

设置环境变量之后,接下来会调用getHostInfo()从machO头部获取当前运行架构的信息

static void getHostInfo(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide)
{
#if CPU_SUBTYPES_SUPPORTED
#if __ARM_ARCH_7K__
    sHostCPU        = CPU_TYPE_ARM;
    sHostCPUsubtype = CPU_SUBTYPE_ARM_V7K;
#elif __ARM_ARCH_7A__
    sHostCPU        = CPU_TYPE_ARM;
    sHostCPUsubtype = CPU_SUBTYPE_ARM_V7;
#elif __ARM_ARCH_6K__
    sHostCPU        = CPU_TYPE_ARM;
    sHostCPUsubtype = CPU_SUBTYPE_ARM_V6;
#elif __ARM_ARCH_7F__
    sHostCPU        = CPU_TYPE_ARM;
    sHostCPUsubtype = CPU_SUBTYPE_ARM_V7F;
#elif __ARM_ARCH_7S__
    sHostCPU        = CPU_TYPE_ARM;
    sHostCPUsubtype = CPU_SUBTYPE_ARM_V7S;
#elif __ARM64_ARCH_8_32__
    sHostCPU        = CPU_TYPE_ARM64_32;
    sHostCPUsubtype = CPU_SUBTYPE_ARM64_32_V8;
#elif __arm64e__
    sHostCPU        = CPU_TYPE_ARM64;
    sHostCPUsubtype = CPU_SUBTYPE_ARM64E;
#elif __arm64__
    sHostCPU        = CPU_TYPE_ARM64;
    sHostCPUsubtype = CPU_SUBTYPE_ARM64_V8;
#else
    struct host_basic_info info;
    mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT;
    mach_port_t hostPort = mach_host_self();
    kern_return_t result = host_info(hostPort, HOST_BASIC_INFO, (host_info_t)&info, &count);
    if ( result != KERN_SUCCESS )
        throw "host_info() failed";
    sHostCPU        = info.cpu_type;
    sHostCPUsubtype = info.cpu_subtype;
    mach_port_deallocate(mach_task_self(), hostPort);
  #if __x86_64__
      // host_info returns CPU_TYPE_I386 even for x86_64.  Override that here so that
      // we don't need to mask the cpu type later.
      sHostCPU = CPU_TYPE_X86_64;
    #if !TARGET_OS_SIMULATOR
      sHaswell = (sHostCPUsubtype == CPU_SUBTYPE_X86_64_H);
      //  x86_64h: Fall back to the x86_64 slice if an app requires GC.
      if ( sHaswell ) {
        if ( isGCProgram(mainExecutableMH, mainExecutableSlide) ) {
            // When running a GC program on a haswell machine, don't use and 'h slices
            sHostCPUsubtype = CPU_SUBTYPE_X86_64_ALL;
            sHaswell = false;
            gLinkContext.sharedRegionMode = ImageLoader::kDontUseSharedRegion;
        }
      }
    #endif
  #endif
#endif
#endif

2.2 加载共享缓存

条件准备好之后,它会去加载共享缓存库,这个共享缓存库是个什么东西呢? 其实我们可以理解为是系统公用的动态库,如我们最常用的UIKit/AppKit框架就在共享缓存库中,举个例子,微信、QQ、支付宝、天猫等APP都会使用到UIKit这个框架,如果每个应用都加载UIKit,势必会导致内存紧张。所以实际是这些APP都会共享一套UIKit框架,应用中用到了对应了UIKit框架中的方法,dyld就会去拿对应的资源供给这些APP使用。

// 加载共享缓存
checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
   if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
#if TARGET_OS_SIMULATOR
       if ( sSharedCacheOverrideDir)
           mapSharedCache(mainExecutableSlide);
#else
       mapSharedCache(mainExecutableSlide);
#endif

       // If this process wants a different __DATA_CONST state from the shared region, then override that now
       //如果这个进程想要一个与共享区域不同的 __DATA_CONST 状态,那么现在覆盖它
       if ( (sSharedCacheLoadInfo.loadAddress != nullptr) && (gEnableSharedCacheDataConst != sharedCacheDataConstIsEnabled) ) {
           uint32_t permissions = gEnableSharedCacheDataConst ? VM_PROT_READ : (VM_PROT_READ | VM_PROT_WRITE);
           sSharedCacheLoadInfo.loadAddress->changeDataConstPermissions(mach_task_self(), permissions,
                                                                        (gLinkContext.verboseMapping ? &dyld::log : nullptr));
       }
   }

2.3 实例化ImageLoader

实例化主程序,调用instantiateFromLoadedImage完毕后会返回一个ImageLoaderMachO镜像加载类,这是一个抽象类,用于加载特定可执行文件格式的类,对于程序中需要的依赖库、插入库,会创建一个对应的image对象,对这些image进行链接,调用各image的初始化方法等等,包括对runtime的初始化。

//实例化ImageLoader
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
gLinkContext.mainExecutable = sMainExecutable;
gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);             
}                
// The kernel maps in main executable before dyld gets control.  We need to 
// make an ImageLoader* for the already mapped in main executable.
//在dyld获得控制之前,kernel映射到main executable。我们需要
//创建一个ImageLoader*为已经映射在主可执行文件。
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);
       return (ImageLoaderMachO*)image;
//  }
   
//  throw "main executable not a known format";

2.4 加载插入的库

// load any inserted libraries
if  ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
   for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) 
       loadInsertedDylib(*lib);
}
static void loadInsertedDylib(const char* path)
{
   unsigned cacheIndex;
   try {
       LoadContext context;
       context.useSearchPaths      = false;
       context.useFallbackPaths    = false;
       context.useLdLibraryPath    = false;
       context.implicitRPath       = false;
       context.matchByInstallName  = false;
       context.dontLoad            = false;
       context.mustBeBundle        = false;
       context.mustBeDylib         = true;
       context.canBePIE            = false;
       context.origin              = NULL; // can't use @loader_path with DYLD_INSERT_LIBRARIES
       context.rpath               = NULL;
       load(path, context, cacheIndex);
   }
   catch (const char* msg) {
       if ( gLinkContext.allowInsertFailures )
           dyld::log("dyld: warning: could not load inserted library '%s' into hardened process because %s\n", path, msg);
       else
           halt(dyld::mkstringf("could not load inserted library '%s' because %s\n", path, msg));
   }
   catch (...) {
       halt(dyld::mkstringf("could not load inserted library '%s'\n", path));
   }
}

根据编译环境条件去调用ImageLoader* load(const char* path, const LoadContext& context, unsigned& cacheIndex)方法去加载插入的库文件。

2.5 链接主程序

 // link main executable
gLinkContext.linkingMainExecutable = true;
link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);

在link函数中会调用recursiveLoadLibraries函数递归加载所有库文件()

2.6 链接插入的库

// 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
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();
   }
   if ( gLinkContext.allowInterposing ) {
       // only INSERTED libraries can interpose
       // register interposing info after all inserted libraries are bound so chaining works
       for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
           ImageLoader* image = sAllImages[i+1];
           image->registerInterposing(gLinkContext);
       }
   }
}

先链接要使用的动态库,也是跟链接主程序一样调用的是link函数

2.7 将镜像绑定到主程序

// Bind and notify for the main executable now that interposing 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 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);
   }
}
sMainExecutable->weakBind(gLinkContext);

2.8 实例化mainExecutable

2.8.1 runInitializers

run all initializers
initializeMainExecutable()
void initializeMainExecutable() {
   //运行加入的库的初始化器
   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]);
       }
   }
   //为主要可执行文件及其带来的一切运行初始化程序
   sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
   //注册 cxa_atexit() 处理程序以在此进程退出时在所有加载的图像中运行静态终止符
   if ( gLibSystemHelpers != NULL ) 
       (*gLibSystemHelpers->cxa_atexit)(&runAllStaticTerminators, NULL, NULL);
   .....
}

调用initializeMainExecutable函数去运行所有可执行文件的初始化。内部的实现流程是调用runInitializers->processInitializers(递归初始化准备工作)->recursiveInitialization(递归初始化)

2.8.2 recursiveInitialization

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

            // let objc know we are about to initialize this image
            uint64_t t1 = mach_absolute_time();
            fState = dyld_image_state_dependents_initialized;
            oldState = fState;
            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);
            
            if ( hasInitializers ) {
                uint64_t t2 = mach_absolute_time();
                timingInfo.addTime(this->getShortName(), t2-t1);
            }
        }
        catch (const char* msg) {
            // this image is not initialized
            fState = oldState;
            recursiveSpinUnLock();
            throw;
        }
    }    
    recursiveSpinUnLock();
}

在recursiveInitialization函数中主要做了三件事,

  • 1.context.notifySingle 单个通知注入
  • 2.doInitialization 调用init方法
  • 3.context.notifySingle 通知初始化完成
    在doInitialization方法中主要是初始化mach_o以及初始化运行libsystem库
2.8.3 doModInitFunctions
bool ImageLoaderMachO::doInitialization(const LinkContext& context)
{ 
    // mach-o has -init and static initializers
    doImageInit(context);
    doModInitFunctions(context);    
    return (fHasDashInit || fHasInitializers);
}

在doModInitFunctions函数中会去初始化libSystem

void ImageLoaderMachO::doModInitFunctions(const LinkContext& context) {
    Initializer* inits = (Initializer*)(sect->addr + fSlide);
    const size_t count = sect->size / sizeof(uintptr_t);
    for (size_t j=0; j < count; ++j) {
    Initializer func = inits[j];
    //libSystem initializer must run first
    //必须首先运行libSystem初始化器
    func(context.argc, context.argv, context.envp, context.apple, &context.programVars);
  }
}

现在的流程到了加载libSystem的initializer,下载libSystem看一下initializer做了什么工作,在libSystem的libSystem_initializer函数中
初始化了所有的libSystem.dylib库,
在/Applications/Xcode_11.6.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/lib/system文件里面很多system库

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

    __libkernel_init(&libkernel_funcs, envp, apple, vars);

    __libplatform_init(NULL, envp, apple, vars);

    __pthread_init(&libpthread_funcs, envp, apple, vars);

    _libc_initializer(&libc_funcs, envp, apple, vars);
    // TODO: Move __malloc_init before __libc_init after breaking malloc's upward link to Libc
    // Note that __malloc_init() will also initialize ASAN when it is present
    __malloc_init(apple);
    
    libdispatch_init();
    __libdarwin_init();
    ....
}

下面看一些libdispatch_init,下载libdispatch源码,搜索libdispatch_init,里面会调用_os_object_init函数

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

_objc_init是在libobjc.dylib中的,是由libSystem调用的初始化器


2.8.4 _objc_init

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();//关于线程key的绑定 - 比如线程数据的析构函数
   static_init();//运行C ++静态构造函数。在dyld调用我们的静态构造函数之前,`libc` 会调用 _objc_init(), 因此我们必须自己做
   runtime_init(); //运行时环境初始化
   exception_init(); //初始化libobjc的异常处理系统
#if __OBJC2__
   cache_t::init(); //缓存条件初始化
#endif
   _imp_implementationWithBlock_init();//启动回调机制。通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载trampolines dylib

   _dyld_objc_notify_register(&map_images, load_images, unmap_image);   
#if __OBJC2__
   didCallDyldNotifyRegister = true;
#endif
}

2.8.5 _dyld_objc_notify_register

给dyld注册一个回调函数,这个函数是在dyld源码里面,进入dyld源码查看

void AllImages::setObjCNotifiers(_dyld_objc_notify_mapped map, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmap)
{
    _objcNotifyMapped   = map;
    _objcNotifyInit     = init;
    _objcNotifyUnmapped = unmap;
}

sNotifyObjCInit是在什么地方被调用呢,在recursiveInitialization函数里面的context.notifySingle调用的,也就是镜像文件初始化完成之后调用

if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && image->notifyObjC() ) {
 (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
}

为了方便理解,我画了一个流程图如下

2.9 通知dyld进入到main函数(notifyMonitoringDyldMain)

dyld的_main函数

uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
       int argc, const char* argv[], const char* envp[], const char* apple[], 
       uintptr_t* startGlue) {
// notify any montoring proccesses that this process is about to enter main()
       notifyMonitoringDyldMain();
}

最后在放一张堆栈图方便理解

你可能感兴趣的:(iOS应用程序加载)