iOS APP 启动优化(七):dyld 加载流程详细分析

 前面一篇 《iOS APP 启动优化(三):dyld(the dynamic link editor)动态链接器和 dyld 加载流程.md》分析到 dyld::_main 函数的整体流程,还没有分析到每一个细节,那么就由本篇继续分析。

 接上篇。

 下面我们对 initializeMainExecutable 进行分析。

initializeMainExecutable

 上面我们分析了 dyld::_main 函数的总体流程,其中 initializeMainExecutable 函数进行了所有的 initializers,下面我们看一下它的执行过程。

void initializeMainExecutable()
{
    // record that we've reached this step
    // 在 gLinkContext 全局变量中标记现在 main executable 开始执行 Initializers
    gLinkContext.startedInitializingMainExecutable = true;

    // run initialzers for any inserted dylibs(为任何插入的 dylibs 运行 initialzers)
    
    // 创建一个 struct InitializerTimingList 的数组,用来记录 Initializer 所花费的时间
    ImageLoader::InitializerTimingList initializerTimes[allImagesCount()];
    
    initializerTimes[0].count = 0;
    
    // sImageRoots 是一个静态全局变量:static std::vector<ImageLoader*> sImageRoots;  
    const size_t rootCount = sImageRoots.size();
    if ( rootCount > 1 ) {
        for(size_t i=1; i < rootCount; ++i) {
            // ⬇️ 调用 ImageLoader 的 runInitializers 函数
            sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);
        }
    }
    
    // run initializers for main executable and everything it brings up 
    // ⬇️ 为 main executable 及其带来的一切运行 initializers
    sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
    
    // register cxa_atexit() handler to run static terminators in all loaded images when this process exits
    // 注册 cxa_atexit() 处理程序以在此进程退出时在所有加载的 image 中运行静态终止符
    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]);
}
复制代码

gLinkContext 是一个 ImageLoader::LinkContext gLinkContext; 类型的全局变量,LinkContext 是在 class ImageLoader 中定义的一个结构体,其中定义了很多函数指针和成员变量,来记录和处理 Link 的上下文。其中 bool startedInitializingMainExecutable; 则是用来记录标记 Main Executable 开始进行 Initializing 了,这里是直接把它的值置为 true

InitializerTimingList 也是在 class ImageLoader 中定义的一个挺简单的结构体。用来记录 Initializer 所花费的时间。

struct InitializerTimingList
{
    uintptr_t    count;
    struct {
        const char*        shortName;
        uint64_t        initTime;
    } images[1];

    void addTime(const char* name, uint64_t time);
};

// 给指定的 image 追加时间
void ImageLoader::InitializerTimingList::addTime(const char* name, uint64_t time)
{
    for (int i=0; i < count; ++i) {
        if ( strcmp(images[i].shortName, name) == 0 ) {
            images[i].initTime += time;
            return;
        }
    }
    images[count].initTime = time;
    images[count].shortName = name;
    ++count;
}
复制代码

 看到 addTime 函数是为当前记录到的 image 追加时间。

runInitializers

 下面看一下 sImageRoots[i]sMainExecutable 都要调用的 runInitializers 函数,runInitializers 函数定义在 ImageLoader 类中。

void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo)
{
    // 计时开始
    uint64_t t1 = mach_absolute_time();
    
    // 记录当前所处的线程
    mach_port_t thisThread = mach_thread_self();
    
    // UninitedUpwards 是在 ImageLoader 类内部定义的结构体,
    // 它的 imagesAndPaths 成员变量用来记录 image 和 image 的 path
    ImageLoader::UninitedUpwards up;
    up.count = 1;
    up.imagesAndPaths[0] = { this, this->getPath() };
    
    // 核心 ⬇️⬇️⬇️
    processInitializers(context, thisThread, timingInfo, up);
    
    // 通知已经处理完  
    context.notifyBatch(dyld_image_state_initialized, false);
    
    // deallocate 任务
    mach_port_deallocate(mach_task_self(), thisThread);
    
    // 执行结束时的计时
    uint64_t t2 = mach_absolute_time();
    // 统计时长
    fgTotalInitTime += (t2 - t1); 
}
复制代码

 在 runInitializers 函数中我们看到了两个老面孔,在学习 GCD 源码时见过的 mach_absolute_timemach_thread_self 一个用来统计初始化时间,一个用来记录当前线程。

UninitedUpwardsImageLoader 类内部定义的一个超简单的结构体,其中的成员变量 std::pair<ImageLoader*, const char*> imagesAndPaths[1]; 一个值记录 ImageLoader 的地址,另一个值记录该 ImageLoader 的路径。

struct UninitedUpwards
{
    uintptr_t count;
    std::pair<ImageLoader*, const char*> imagesAndPaths[1];
};
复制代码

 可看到 processInitializers(context, thisThread, timingInfo, up); 是其中最重要的函数,下面来一起看看。

processInitializers

 处理初始化过程,看到 processInitializersrecursiveInitialization 函数的递归调用,之所以递归调用,是因为我们的动态库或者静态库会引入其它类库,而且表是个多表结构,所以需要递归实例化。

// <rdar://problem/14412057> upward dylib initializers can be run too soon
// To handle dangling dylibs which are upward linked but not downward, all upward linked dylibs
// have their initialization postponed until after the recursion through downward dylibs
// has completed.
// 为了处理向上链接但不向下链接的悬空控件,所有向上链接的控件都将其初始化推迟到完成通过向下控件的递归完成以后
void ImageLoader::processInitializers(const LinkContext& context, mach_port_t thisThread,
                                     InitializerTimingList& timingInfo, ImageLoader::UninitedUpwards& images)
{
    uint32_t maxImageCount = context.imageCount()+2;
    ImageLoader::UninitedUpwards upsBuffer[maxImageCount];
    ImageLoader::UninitedUpwards& ups = upsBuffer[0];
    ups.count = 0;
    
    // Calling recursive init on all images in images list, building a new list of uninitialized upward dependencies.
    // 在 image 列表中所有 image 调用递归实例化,以建立未初始化的向上依赖关系新列表
    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);
}
复制代码

images.imagesAndPaths[i].firstImageLoader 指针(ImageLoader *),即调用 class ImageLoaderrecursiveInitialization 函数,下面我们看一下 recursiveInitialization 函数的定义。

recursiveInitialization

recursiveInitialization 函数定义较长,其中比较重要的是 // initialize lower level libraries first 下的 for 循环,进行循环判断是否都加载过,没有的话就再执行 dependentImage->recursiveInitialization 因为我们前面说的动态库可能会依赖其它库。

void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
                                          InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
    // 递归锁结构体,会持有当前所在的线程
    // struct recursive_lock {
    //     recursive_lock(mach_port_t t) : thread(t), count(0) {}
    //     mach_port_t        thread;
    //     int                count;
    // };
    
    recursive_lock lock_info(this_thread);
    
    // 递归加锁
    recursiveSpinLock(lock_info);

    // dyld_image_state_dependents_initialized = 45, // Only single notification for this
    
    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
            // 首先初始化较低级别的库
            
            // unsigned int libraryCount() const { return fLibraryCount; } 
            
            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
            // 让 objc 知道我们将要初始化这个 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
            // 初始化 image
            
            // 这里便是最终的执行 initialize,那它内部的内容是什么呢?就是下面两个函数!
            
            // mach-o has -init and static initializers
            // doImageInit(context);
            // doModInitFunctions(context); // __mod_init_func 区的 Initializer 执行
            
            // 核心 ⬇️⬇️⬇️
            bool hasInitializers = this->doInitialization(context);
            // ⬆️⬆️⬆️ 
            
            // let anyone know we finished initializing this image
            // 让任何人知道我们完成了这个 image 的初始化
            fState = dyld_image_state_initialized;
            oldState = fState;
            
            // void (*notifySingle)(dyld_image_states, const ImageLoader* image, InitializerTimingList*);
            
            // 核心 ⬇️⬇️⬇️
            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 函数内会有动态库依赖的递归调用初始化,主要研究的代码是 notifySingledoInitialization

 看到这里的时候,我们可以先稍微停顿一下,回忆文章开头处,在 +load 函数打断点,然后 bt 指令打印函数调用栈,现在正是到达了其中的 recursiveInitializationnotifySingle

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0:  Test_ipa_Simple`+[ViewController load](self=ViewController, _cmd="load") at ViewController.m:17:5
    frame #1:  libobjc.A.dylib`load_images + 944
    frame #2:  dyld`dyld::notifySingle(dyld_image_states, ImageLoader const*, ImageLoader::InitializerTimingList*) + 464
    frame #3:  dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 512
    frame #4:  dyld`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 184
    frame #5:  dyld`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 92
    frame #6:  dyld`dyld::initializeMainExecutable() + 216
    frame #7:  dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 5216
    frame #8:  dyld`dyldbootstrap::start(dyld3::MachOLoaded const*, int, char const**, dyld3::MachOLoaded const*, unsigned long*) + 396
    frame #9:  dyld`_dyld_start + 56
(lldb)
复制代码

 可看到从 _dyld_start -> dyldbootstrap::start -> dyld::_main -> dyld::initializeMainExecutable -> ImageLoader::runInitializers -> ImageLoader::processInitializers -> ImageLoader::recursiveInitialization -> dyld::notifySingle -> libobjc.A.dylib load_images -> +[ViewController load] 的一路调用流程,而我们目前则到了其中的 notifySingle

 下面我们接着分析 recursiveInitialization 函数。

 在 recursiveInitialization 函数内部的 // let objc know we are about to initialize this image 注释往下走,它们才是 recursiveInitialization 函数最重要的部分,我们首先看一下:

context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
复制代码

 函数的调用,首先这里我们一直往上追溯的话可发现 context 参数即在 initializeMainExecutable 函数中传入的 ImageLoader::LinkContext gLinkContext; 这个全局变量。

// notifySingle 是这样的一个函数指针
void (*notifySingle)(dyld_image_states, const ImageLoader* image, InitializerTimingList*);
复制代码

 然后在 dyld/src/dyld2.cpp 文件中的:

static void setContext(const macho_header* mainExecutableMH, int argc, const char* argv[], const char* envp[], const char* apple[]) { ... }
复制代码

 这个静态全局函数中,gLinkContext.notifySingle 被赋值为 &notifySingle;,而这个 notifySingle 函数是在 dyld2.cpp 中定义的一个静态全局函数。看到这里,我们即可确定 recursiveInitialization 函数中调用的 context.notifySinglegLinkContext.notifySingle,即 dyld/src/dyld2.cpp 中的 notifySingle 这个静态全局函数。

notifySingle

 然后我们直接在 dyld2.cpp 中搜索 notifySingle 函数, 它是一个静态全局函数,我们现在看一下它的实现:

 首先我们看一组在 ImageLoader.h 中定义的枚举,它们每个值都表示 dyld 过程中 image 的状态。

enum dyld_image_states
{
    dyld_image_state_mapped                    = 10,        // No batch notification for this
    dyld_image_state_dependents_mapped        = 20,        // Only batch notification for this
    dyld_image_state_rebased                = 30,
    dyld_image_state_bound                    = 40,
    dyld_image_state_dependents_initialized    = 45,        // Only single notification for this
    dyld_image_state_initialized            = 50,
    dyld_image_state_terminated                = 60        // Only single notification for this
};
复制代码
static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
{
    //dyld::log("notifySingle(state=%d, image=%s)\n", state, image->getPath());
    
    // 下面第一个 if 的内容是回调 image 的状态变化(dyld_image_state_change_handler)
    std::vector<dyld_image_state_change_handler>* handlers = stateToHandlers(state, sSingleHandlers);
    if ( handlers != NULL ) {
        dyld_image_info info;
        info.imageLoadAddress    = image->machHeader();
        info.imageFilePath        = image->getRealPath();
        info.imageFileModDate    = image->lastModified();
        for (std::vector<dyld_image_state_change_handler>::iterator it = handlers->begin(); it != handlers->end(); ++it) {
            const char* result = (*it)(state, 1, &info);
            if ( (result != NULL) && (state == dyld_image_state_mapped) ) {
                //fprintf(stderr, "  image rejected by handler=%p\n", *it);
                // make copy of thrown string so that later catch clauses can free it
                const char* str = strdup(result);
                throw str;
            }
        }
    }
    
    // 如果 state 是 dyld_image_state_mapped 时对 Shared Cache 的一些处理
    
    // 已知在 recursiveInitialization 函数,两次调用 context.notifySingle 函数,
    // state 参数分别传入的:dyld_image_state_dependents_initialized 和 dyld_image_state_initialized,
    // 所以这里我们暂时忽略 state 是 dyld_image_state_mapped 的情况
    
    if ( state == dyld_image_state_mapped ) {
        // <rdar://problem/7008875> Save load addr + UUID for images from outside the shared cache
        // <rdar://problem/50432671> Include UUIDs for shared cache dylibs in all image info when using private mapped shared caches
        if (!image->inSharedCache()
            || (gLinkContext.sharedRegionMode == ImageLoader::kUsePrivateSharedRegion)) {
            dyld_uuid_info info;
            if ( image->getUUID(info.imageUUID) ) {
                info.imageLoadAddress = image->machHeader();
                addNonSharedCacheImageUUID(info);
            }
        }
    }
    
    // ⬇️⬇️⬇️ 这里是我们要重点看的
    // sNotifyObjCInit 是一个静态全局变量:static _dyld_objc_notify_init sNotifyObjCInit;
    // 其实是一个函数指针:typedef void (*_dyld_objc_notify_init)(const char* path, const struct mach_header* mh);
    
    // 然后 image->notifyObjC() 默认为 1
    
    if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && image->notifyObjC() ) {
        
        // 统计开始时间
        uint64_t t0 = mach_absolute_time();
        dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
        
        // ⬇️⬇️ 调用 sNotifyObjCInit 函数,那么这个全局变量的函数指针在什么时候赋值的呢?    
        (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
        
        // 统计结束时间,并进行时间追加
        uint64_t t1 = mach_absolute_time();
        uint64_t t2 = mach_absolute_time();
        uint64_t timeInObjC = t1-t0;
        uint64_t emptyTime = (t2-t1)*100;
        if ( (timeInObjC > emptyTime) && (timingInfo != NULL) ) {
            timingInfo->addTime(image->getShortName(), timeInObjC);
        }
    }
    
    // 当 state 是 dyld_image_state_terminated 时的处理操作,我们可忽略
    
    // mach message csdlc about dynamically unloaded images
    if ( image->addFuncNotified() && (state == dyld_image_state_terminated) ) {
    
        notifyKernel(*image, false);
        
        const struct mach_header* loadAddress[] = { image->machHeader() };
        const char* loadPath[] = { image->getPath() };
        
        notifyMonitoringDyld(true, 1, loadAddress, loadPath);
    }
}
复制代码

 可看到 notifySingle 函数的核心便是这个 (*sNotifyObjCInit)(image->getRealPath(), image->machHeader()); 函数的执行!

typedef void (*_dyld_objc_notify_init)(const char* path, const struct mach_header* mh);

static _dyld_objc_notify_init sNotifyObjCInit;
复制代码

sNotifyObjCInit 是一个静态全局的名为 _dyld_objc_notify_init 的函数指针。然后在 dyld2.cpp 文件中搜索,可看到在 registerObjCNotifiers 函数中,有对 sNotifyObjCInit 这个全局变量进行赋值操作。

registerObjCNotifiers

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;

    // call 'mapped' function with all images mapped so far
    try {
        notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);
    }
    catch (const char* msg) {
        // ignore request to abort during registration
    }

    // <rdar://problem/32209809> call 'init' function on all images already init'ed (below libSystem)
    for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) {
        ImageLoader* image = *it;
        if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() ) {
            dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
            
            // ⬇️⬇️⬇️ 
            (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
        }
    }
}
复制代码

 其中进行赋值的三个全局变量定义在一起,即为三个类型不同的函数指针。

typedef void (*_dyld_objc_notify_mapped)(unsigned count, const char* const paths[], const struct mach_header* const mh[]);
typedef void (*_dyld_objc_notify_init)(const char* path, const struct mach_header* mh);
typedef void (*_dyld_objc_notify_unmapped)(const char* path, const struct mach_header* mh);

static _dyld_objc_notify_mapped        sNotifyObjCMapped;
static _dyld_objc_notify_init        sNotifyObjCInit;
static _dyld_objc_notify_unmapped    sNotifyObjCUnmapped;
复制代码

 我们看到 registerObjCNotifiers 函数的 _dyld_objc_notify_init init 参数会直接赋值给 sNotifyObjCInit,并在下面的 for 循环中进行调用。

_dyld_objc_notify_register

 那么什么时候调用 registerObjCNotifiers 函数呢?_dyld_objc_notify_init init 的实参又是什么呢?我们全局搜索 registerObjCNotifiers 函数。(其实看到这里,看到 registerObjCNotifiers 函数的形参我们可能会有一点印象了,之前看 objc 的源码时的 _objc_init 函数中涉及到 image 部分。)

 我们全局搜索 registerObjCNotifiers 函数,在 dyld/src/dyldAPIs.cpp 中的 _dyld_objc_notify_register 函数内部调用了 registerObjCNotifiers 函数(属于 namespace dyld)。

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);
}
复制代码

 这下就连上了,_dyld_objc_notify_register 函数在 objc 源码中也有调用过,并且就在 _objc_init 函数中。下面我们先看一下 _dyld_objc_notify_register 函数的声明。

//
// Note: only for use by objc runtime
// Register handlers to be called when objc images are mapped, unmapped, and initialized.
// Dyld will call back the "mapped" function with an array of images that contain an objc-image-info section.
// Those images that are dylibs will have the ref-counts automatically bumped, so objc will no longer need to call dlopen() on them to keep them from being unloaded.  
// During the call to _dyld_objc_notify_register(), dyld will call the "mapped" function with already loaded objc images.  
// During any later dlopen() call, dyld will also call the "mapped" function.  
// Dyld will call the "init" function when dyld would be called initializers in that image.  
// This is when objc calls any +load methods in that image.

void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped);
复制代码

_dyld_objc_notify_register 函数仅供 objc runtime 使用。注册在 mapped、unmapped 和 initialized objc images 时要调用的处理程序。Dyld 将使用包含 objc-image-info section 的 images 数组回调 mapped 函数。那些 dylib 的 images 将自动增加引用计数,因此 objc 将不再需要对它们调用 dlopen() 以防止它们被卸载。在调用 _dyld_objc_notify_register() 期间,dyld 将使用已加载的 objc images 调用 mapped 函数。在以后的任何 dlopen() 调用中,dyld 还将调用 mapped 函数。当 dyld 在该 image 中调用 initializers 时,Dyld 将调用 init 函数。这是当 objc 调用 image 中的任何 +load 方法时。

Note: only for use by objc runtime 提示我们 _dyld_objc_notify_register 函数仅提供给 objc runtime 使用,那么我们就去 objc4 源码中寻找 _dyld_objc_notify_register 函数的调用。

_objc_init

 下面我们在 objc4-781 中搜一下 _dyld_objc_notify_register 函数,在 _objc_init 中我们看到了它的身影。

void _objc_init(void)
{
    // initialized 作为一个局部静态变量,只能初始化一次,保证 _objc_init 全局只执行一次 
    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();
    cache_init();
    _imp_implementationWithBlock_init();
    
    // ⬇️⬇️⬇️
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}
复制代码

 那么追了这么久,上面的拿着 init 形参对 sNotifyObjCInit 赋值的实参便是 load_images,另外两个则是 &map_images 赋值给 sNotifyObjCMappedunmap_image 赋值给 sNotifyObjCUnmapped

 看到这里我们就能连上了,load_images 会调用 image 中所有父类、子类、分类中的 +load 函数,前面的文章中我们有详细分析过,这里就不展开 +load 函数的执行过程了。

 那么到这里我们就能直接从源码层面连上了:recursiveInitialization -> context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo) -> (*sNotifyObjCInit)(image->getRealPath(), image->machHeader()),而这个 sNotifyObjCInit 便是 _objc_init 函数中调用 _dyld_objc_notify_register 注册进来的 load_images 函数,即在 objc 这层注册了回调函数,然后在 dyld 调用这些回调函数。(当前看的两个苹果开源的库:objc4-781 和 dyld-832.7.3)

 这样我们最开始的 bt 指令的截图中出现的函数就都浏览一遍了:_dyld_start -> dyldbootstrap::start -> dyld::_main -> dyld::initializeMainExecutable -> ImageLoader::runInitializers -> ImageLoader::processInitializers -> ImageLoader::recursiveInitialization -> dyld::notifySingle -> libobjc.A.dylib load_images

_objc_init 中注册回调函数,在 dyld 中调用这些回调函数。

_objc_init 中注册回调函数,在 dyld 中调用这些回调函数。

_objc_init 中注册回调函数,在 dyld 中调用这些回调函数。

doInitialization

 那么看到这里,我们心中不免有一个疑问,既然在 _objc_init 函数内部调用 _dyld_objc_notify_register 函数注册了 dyld 的回调函数,那什么时候调用 _objc_init 呢?_objc_initlibobjc 这个 image 的初始化函数,那么 libobjc 什么时候进行初始化呢?

 我们依然顺着上面的 recursiveInitialization 函数往下,在 context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo); 调用的下面便是:

// initialize this image
// 初始化 image
bool hasInitializers = this->doInitialization(context);
复制代码

 那么下面我们一起来看这个 doInitialization 函数。

bool ImageLoaderMachO::doInitialization(const LinkContext& context)
{
    CRSetCrashLogMessage2(this->getPath());

    // ⬇️⬇️⬇️
    // mach-o has -init and static initializers
    // mach-o 包含 -init 和 static initializers(静态初始化方法)
    // for 循环调用 image 的初始化方法(libSystem 库需要第一个初始化)
    doImageInit(context);
    
    doModInitFunctions(context);
    
    CRSetCrashLogMessage2(NULL);
    
    return (fHasDashInit || fHasInitializers);
}
复制代码

 其中的核心是 doImageInit(context);doModInitFunctions(context); 两个函数调用。

 在 doImageInit(context); 中,核心就是 for 循环调用 image 的初始化函数,但是需要注意的是 libSystem 库需要第一个初始化。

doImageInit

 下面我们看一下 doImageInit 函数的实现:

// rdar://problem/17973316 libSystem initializer must run first

void ImageLoaderMachO::doImageInit(const LinkContext& context)
{
    // fHasDashInit 是 class ImageLoaderMachO 其中一个位域:fHasDashInit : 1,
    if ( fHasDashInit ) {
    
        // load command 的数量
        // fMachOData 是 class ImageLoaderMachO 的一个成员变量:const uint8_t* fMachOData;
        const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
        
        // 从 mach header 直接寻址到 load_command 的位置
        const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
        const struct load_command* cmd = cmds;
        
        // 下面是对 load_command 进行遍历
        for (uint32_t i = 0; i < cmd_count; ++i) {
        
            switch (cmd->cmd) {
            
                // 看到这里只处理 #define LC_ROUTINES_COMMAND LC_ROUTINES_64 类型的 load_command
                //(大概是这种类型的 load_command 就是用来放 Initializer 的吗?)
                case LC_ROUTINES_COMMAND:
                    
                    // __LP64__ 下 macho_routines_command 继承自 routines_command_64,
                    // struct macho_routines_command : public routines_command_64  {};
                    // 在 darwin-xnu/EXTERNAL_HEADERS/mach-o/loader.h 下搜 routines_command_64 可看到如下定义
                    
                    /*
                     * The 64-bit routines command.  Same use as above.
                     */
                    // struct routines_command_64 { /* for 64-bit architectures */
                    //     uint32_t    cmd;        /* LC_ROUTINES_64 */
                    //     uint32_t    cmdsize;    /* total size of this command */
                    //     uint64_t    init_address;    /* address of initialization routine 初始化程序地址 */ 
                    //     uint64_t    init_module;    /* index into the module table that the init routine is defined in */
                    //     uint64_t    reserved1;
                    //     uint64_t    reserved2;
                    //     uint64_t    reserved3;
                    //     uint64_t    reserved4;
                    //     uint64_t    reserved5;
                    //     uint64_t    reserved6;
                    // };
                    
                    // 这里是找到当前这一条 load_command 的 Initializer
                    Initializer func = (Initializer)(((struct macho_routines_command*)cmd)->init_address + fSlide);
                    
#if __has_feature(ptrauth_calls)
                    func = (Initializer)__builtin_ptrauth_sign_unauthenticated((void*)func, ptrauth_key_asia, 0);
#endif
                    // <rdar://problem/8543820&9228031> verify initializers are in image
                    if ( ! this->containsAddress(stripPointer((void*)func)) ) {
                        dyld::throwf("initializer function %p not in mapped image for %s\n", func, this->getPath());
                    }
                    
                    // libSystem 的 initializer 必须首先运行
                    // extern struct dyld_all_image_infos* gProcessInfo; 声明在 dyld 这个命名空间中 
                    // 这里如果 libSystem.dylib 没有初始化(及 link)则抛错
                    if ( ! dyld::gProcessInfo->libSystemInitialized ) {
                        // <rdar://problem/17973316> libSystem initializer must run first
                        dyld::throwf("-init function in image (%s) that does not link with libSystem.dylib\n", this->getPath());
                    }
                    
                    if ( context.verboseInit )
                        dyld::log("dyld: calling -init function %p in %s\n", func, this->getPath());
                    
                    // 执行 initializer 
                    {
                        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);
                    }
                    
                    break;
            }
            
            // cmd 指向下一条 load_command 
            cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
        }
    }
}
复制代码

 在 doImageInit 函数内部看到其中就是遍历当前 image 的 load command,找到其中 LC_ROUTINES_COMMAND 类型的 load command 然后通过内存地址偏移找到 Initializer 函数的位置并执行。(Initializer func = (Initializer)(((struct macho_routines_command*)cmd)->init_address + fSlide);) 其中的 if ( ! dyld::gProcessInfo->libSystemInitialized ) 是判断 libSystem 必须先初始化,否则就直接抛错。

 看了 doImageInit 的函数实现,我们肯定对其中的那行 if ( ! dyld::gProcessInfo->libSystemInitialized ) 记忆犹新,那么为什么 libSystem.dylib 要第一个初始化,是因为 libobjc 库的初始化是在 libDispatch 库执行的,而 libDispatch 库是在 libSystem 库初始化后执行。那么我们怎么验证这个呢?

 我们在 objc4-781 源码的 _objc_init 处打一个断点并运行,可看到如下的堆栈信息。

截屏2021-06-06 上午11.30.09.png

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1

  * frame #0: 0x00000001860964a0 libobjc.A.dylib`_objc_init // ⬅️ 这里
    frame #1: 0x00000001002f5014 libdispatch.dylib`_os_object_init + 24 // ⬅️ 这里
    frame #2: 0x0000000100308728 libdispatch.dylib`libdispatch_init + 476 // ⬅️ 这里
    frame #3: 0x000000018f8777e8 libSystem.B.dylib`libSystem_initializer + 220 // ⬅️ 这里
    
    frame #4: 0x000000010003390c dyld`ImageLoaderMachO::doModInitFunctions(ImageLoader::LinkContext const&) + 868
    frame #5: 0x0000000100033b94 dyld`ImageLoaderMachO::doInitialization(ImageLoader::LinkContext const&) + 56
    frame #6: 0x000000010002d84c dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 620
    frame #7: 0x000000010002d794 dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 436
    frame #8: 0x000000010002b300 dyld`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 192
    frame #9: 0x000000010002b3cc dyld`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 96
    frame #10: 0x00000001000167fc dyld`dyld::initializeMainExecutable() + 140
    frame #11: 0x000000010001cb98 dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 7388
    frame #12: 0x0000000100015258 dyld`dyldbootstrap::start(dyld3::MachOLoaded const*, int, char const**, dyld3::MachOLoaded const*, unsigned long*) + 476
    frame #13: 0x0000000100015038 dyld`_dyld_start + 56
(lldb) 
复制代码

 自 _dyld_startImageLoaderMachO::doModInitFunctions 的函数调用我们都已经比较熟悉了,这里我们主要看的是 libSystem_initializer_objc_init 的调用过程。

  libobjc.A.dylib`_objc_init
  
  ⬆️⬆️⬆️
  
  libdispatch.dylib`_os_object_init
  
  ⬆️⬆️⬆️
  
  libdispatch.dylib`libdispatch_init
  
  ⬆️⬆️⬆️
  
  libSystem.B.dylib`libSystem_initializer
复制代码

 恰好这些函数所处的库都是开源的,下面我们下载源码一探究竟。

libSystem_initializer

 我们看到了 libSystem_initializer 函数的调用,我们去下载源码:Libsystem,打开源码,全局搜索 libSystem_initializer,可在 Libsystem/init.c 中看到 libSystem_initializer 函数的定义如下(只摘录一部分):

// libsyscall_initializer() initializes all of libSystem.dylib
// <rdar://problem/4892197>
__attribute__((constructor))
static void
libSystem_initializer(int argc,
              const char* argv[],
              const char* envp[],
              const char* apple[],
              const struct ProgramVars* vars)
{
    static const struct _libkernel_functions libkernel_funcs = {
        .version = 4,
        // V1 functions
#if !TARGET_OS_DRIVERKIT
        .dlsym = dlsym,
#endif
        .malloc = malloc,
        .free = free,
        .realloc = realloc,
        ._pthread_exit_if_canceled = _pthread_exit_if_canceled,
        ...
复制代码

 下面我们摘录 libSystem_initializer 函数中比较重要的内容:

// 对内核的初始化
__libkernel_init(&libkernel_funcs, envp, apple, vars);

// 对平台的初始化
__libplatform_init(NULL, envp, apple, vars);

// 对线程的初始化(初始化后我们的 GCD 才能使用)
__pthread_init(&libpthread_funcs, envp, apple, vars);

// 对 libc 的初始化
_libc_initializer(&libc_funcs, envp, apple, vars);

// 对 malloc 初始化
// 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);

// 对 dyld 进行初始化(dyld_start 时 dyld 并没有初始化,dyld 也是一个库)
_dyld_initializer();

// 对 libdispatch 进行初始化,上面的堆栈信息中我们也看到了 libdispatch 的初始化
libdispatch_init();

_libxpc_initializer();
复制代码

 在 Libsystem/init.c 文件中我们能看到一组外联函数的声明:

// system library initialisers
extern void mach_init(void);            // from libsystem_kernel.dylib
extern void __libplatform_init(void *future_use, const char *envp[], const char *apple[], const struct ProgramVars *vars);
extern void __pthread_init(const struct _libpthread_functions *libpthread_funcs, const char *envp[], const char *apple[], const struct ProgramVars *vars);    // from libsystem_pthread.dylib
extern void __malloc_init(const char *apple[]); // from libsystem_malloc.dylib
extern void __keymgr_initializer(void);        // from libkeymgr.dylib
extern void _dyld_initializer(void);        // from libdyld.dylib
extern void libdispatch_init(void);        // from libdispatch.dylib
extern void _libxpc_initializer(void);        // from libxpc.dylib
extern void _libsecinit_initializer(void);        // from libsecinit.dylib
extern void _libtrace_init(void);        // from libsystem_trace.dylib
extern void _container_init(const char *apple[]); // from libsystem_containermanager.dylib
extern void __libdarwin_init(void);        // from libsystem_darwin.dylib
复制代码

 看到 libSystem_initializer 函数的内部,会调用其他库的初始化函数,例如 _dyld_initializer(); 这个是 dyld 库的初始化,因为 dyld 也是一个动态库。

 在启动一个可执行文件的时候,系统内核做完环境的初始化,就把控制权交给 dyld 去执行加载和链接。

libdispatch_init

 看到 libSystem_initializer 函数内部调用 libdispatch_init();,同时可看到 libdispatch_init 是在 libdispatch.dylib 中,我们去下载源码:libdispatch,打开源码,全局搜索 libdispatch_init,可在 libdispatch/Dispatch Source/queue.c 中看到 libdispatch_init 函数的定义如下(只摘录一部分):

DISPATCH_EXPORT DISPATCH_NOTHROW
void
libdispatch_init(void)
{
    dispatch_assert(sizeof(struct dispatch_apply_s) <=
            DISPATCH_CONTINUATION_SIZE);

    if (_dispatch_getenv_bool("LIBDISPATCH_STRICT", false)) {
        _dispatch_mode |= DISPATCH_MODE_STRICT;
    }
    ...
...
复制代码

 我们一直沿着 libdispatch_init 的定义往下看,临近定义结束处,会有 _dispatch_thread 的一些 creat 操作(GCD 相关)。

...
_dispatch_hw_config_init();
_dispatch_time_init();
_dispatch_vtable_init();

// ⬇️⬇️⬇️
_os_object_init();

_voucher_init();
_dispatch_introspection_init();
}
复制代码
_os_object_init

 看到其中 _os_object_init 的调用(本身它也属于 libdispatch.dylib),我们全局搜一下 _os_object_init,可在 libdispatch/Dispatch Source/object.m 中看到其定义。

void
_os_object_init(void)
{
    // ⬇️⬇️⬇️
    _objc_init();
    
    Block_callbacks_RR callbacks = {
        sizeof(Block_callbacks_RR),
        (void (*)(const void *))&objc_retain,
        (void (*)(const void *))&objc_release,
        (void (*)(const void *))&_os_objc_destructInstance
    };
    
    _Block_use_RR2(&callbacks);
#if DISPATCH_COCOA_COMPAT
    const char *v = getenv("OBJC_DEBUG_MISSING_POOLS");
    if (v) _os_object_debug_missing_pools = _dispatch_parse_bool(v);
    v = getenv("DISPATCH_DEBUG_MISSING_POOLS");
    if (v) _os_object_debug_missing_pools = _dispatch_parse_bool(v);
    v = getenv("LIBDISPATCH_DEBUG_MISSING_POOLS");
    if (v) _os_object_debug_missing_pools = _dispatch_parse_bool(v);
#endif
}
复制代码

 我们看到 _os_object_init 函数定义第一行就是 _objc_init 调用,也就是从 _os_object_init 跳入到 _objc_init 进入 runtime 的初始化,上面我们讲了 _objc_init 会调用 _dyld_objc_notify_register,对 sNotifyObjCInit 进行赋值。

 所以到这里我们可以总结一下 _objc_init 的调用流程:

  • _dyld_start ->
  • dyldbootstrap::start ->
  • dyld::_main ->
  • dyld::initializeMainExecutable ->
  • ImageLoader::runInitializers ->
  • ImageLoader::processInitializers ->
  • ImageLoader::recursiveInitialization ->
  • doInitialization ->
  • doModInitFunctions ->
  • libSystem_initializer 属于 libSystem.B.dylib ->
  • libdispatch_init 属于 libdispatch.dylib ->
  • _os_object_init 属于 libdispatch.dylib ->
  • _objc_init 属于 libobjc.A.dylib
  1. dyld 加载到开始链接 mainExecutable 的时候,递归调用 recursiveInitialization 函数。
  2. 这个函数第一次运行,会进行 libSystem 的初始化,会走到 doInitialization -> doModInitFunctions -> libSystem_initializer
  3. libSystem 的初始化,会调用 libdispatch_initlibdispatch_init 会调用 _os_object_init_os_object_init 会调用 _objc_init
  4. _objc_init 中调用 dyld_dyld_objc_notify_register 函数注册保存了 map_imagesload_imagesunmap_images 的函数地址。
  5. 注册完回到 dyldrecursiveInitialization 递归下一次调用,例如 libObjc,当 libObjc 来到 recursiveInitialization 调用时,会触发保存的 load_images 回调,就调用了 load_images 函数。

 看到这里时我们还有一个函数没有看,上面我们分析了 void ImageLoaderMachO::doImageInit(const LinkContext& context) 函数的内容,然后在 ImageLoaderMachO::doInitialization 函数定义内部进行 doImageInit(context); 调用,然后下面还有一行 doModInitFunctions(context); 的调用,正是在 doModInitFunctions 的调用过程中,调用了 libSystem 的初始化函数 libSystem_initializer

doModInitFunctions

 下面我们看一下 void ImageLoaderMachO::doModInitFunctions(const LinkContext& context) 函数的定义。

void ImageLoaderMachO::doModInitFunctions(const LinkContext& context)
{
    // 在 ImageLoaderMachO 定的一个位域:fHasInitializers : 1, 标记是否进行过初始化
    if ( fHasInitializers ) {
    
        // 找到当前 image 的 load command 的位置
        const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
        const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
        const struct load_command* cmd = cmds;
        
        // 遍历 load command
        for (uint32_t i = 0; i < cmd_count; ++i) {
        
            // 仅处理类型是 LC_SEGMENT_64 的 load command(#define LC_SEGMENT_COMMAND LC_SEGMENT_64)
            if ( cmd->cmd == LC_SEGMENT_COMMAND ) {
                
                // struct macho_segment_command : public segment_command_64  {};
                // 转化为 segment_command_64 并移动指针,找到 macho_section 的位置
                const struct macho_segment_command* seg = (struct macho_segment_command*)cmd;
                const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command));
                const struct macho_section* const sectionsEnd = &sectionsStart[seg->nsects];
                
                // 对当前的 segment 的 section 进行遍历
                for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
                    
                    // 取出当前 section 的类型
                    const uint8_t type = sect->flags & SECTION_TYPE;
                    
                    // 如果当前是 S_MOD_INIT_FUNC_POINTERS 类型(即 __mod_init_funcs 区)
                    if ( type == S_MOD_INIT_FUNC_POINTERS ) {
                    
                        Initializer* inits = (Initializer*)(sect->addr + fSlide);
                        const size_t count = sect->size / sizeof(uintptr_t);
                        
                        // <rdar://problem/23929217> Ensure __mod_init_func section is within segment
                        // 确认 __mod_init_func 区在当前段内
                        if ( (sect->addr < seg->vmaddr) || (sect->addr+sect->size > seg->vmaddr+seg->vmsize) || (sect->addr+sect->size < sect->addr) )
                            dyld::throwf("__mod_init_funcs section has malformed address range for %s\n", this->getPath());
                        
                        // 遍历当前区的所有的 Initializer
                        for (size_t j=0; j < count; ++j) {
                            
                            // 取出每一个 Initializer
                            Initializer func = inits[j];
                            
                            // <rdar://problem/8543820&9228031> verify initializers are in image
                            // 验证 initializers 是否在 image 中
                            if ( ! this->containsAddress(stripPointer((void*)func)) ) {
                                dyld::throwf("initializer function %p not in mapped image for %s\n", func, this->getPath());
                            }
                            
                            // 确保 libSystem.dylib 首先进行初始化
                            if ( ! dyld::gProcessInfo->libSystemInitialized ) {
                                // <rdar://problem/17973316> libSystem initializer must run first
                                
                                const char* installPath = getInstallPath();
                                
                                // 即如果当前 libSystem.dylib 没有初始化,并且当前 image 的路径为 NULL 或者 当前 image 不是 libSystem.dylib,则进行抛错
                                // 即如果当前 libSystem.dylib 没有进行初始化,并且当前 image 是 libSystem.dylib 之前的 image 则直接抛错,
                                // 即必须保证 libSystem.dylib 第一个进行初始化
                                
                                if ( (installPath == NULL) || (strcmp(installPath, libSystemPath(context)) != 0) )
                                    dyld::throwf("initializer in image (%s) that does not link with libSystem.dylib\n", this->getPath());
                            }
                            
                            // 打印开始调用初始化
                            if ( context.verboseInit )
                                dyld::log("dyld: calling initializer function %p in %s\n", func, this->getPath());
                            
                            // 调用初始化
                            // const struct LibSystemHelpers* gLibSystemHelpers = NULL; 是一个全局变量,用来协助 LibSystem
                            // struct LibSystemHelpers 是装满函数指针的结构体
                            
                            // 如果当前是 libSystem.dylib 则 haveLibSystemHelpersBefore 的值是 NO
                            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);
                            }
                            
                            // 如果当前是 libSystem.dylib 则执行到这里时 haveLibSystemHelpersBefore 的值是 YES
                            bool haveLibSystemHelpersAfter = (dyld::gLibSystemHelpers != NULL);
                            
                            // 如果前置两个条件一个是 NO 一个是 YES,则表示刚刚是进行的是 libSystem.dylib 的初始化,则把 libSystemInitialized 标记为 true  
                            if ( !haveLibSystemHelpersBefore && haveLibSystemHelpersAfter ) {
                            
                                // now safe to use malloc() and other calls in libSystem.dylib
                                // libSystem 初始化完成,现在可以安全地在 libSystem.dylib 中使用 malloc() 和其他调用了
                                
                                dyld::gProcessInfo->libSystemInitialized = true;
                            }
                        }
                        
                    } 
                    
                    // 如果当前是 S_MOD_INIT_FUNC_POINTERS 类型(即 __init_offsets 区)
                    else if ( type == S_INIT_FUNC_OFFSETS ) {
                        // 读出 inits
                        const uint32_t* inits = (uint32_t*)(sect->addr + fSlide);
                        const size_t count = sect->size / sizeof(uint32_t);
                        
                        // Ensure section is within segment
                        // 确保当前 section 在当前 segment 内
                        if ( (sect->addr < seg->vmaddr) || (sect->addr+sect->size > seg->vmaddr+seg->vmsize) || (sect->addr+sect->size < sect->addr) )
                            dyld::throwf("__init_offsets section has malformed address range for %s\n", this->getPath());
                        
                        // 确认当前段是只读的
                        if ( seg->initprot & VM_PROT_WRITE )
                            dyld::throwf("__init_offsets section is not in read-only segment %s\n", this->getPath());
                        
                        // 遍历当前区的所有的 inits
                        for (size_t j=0; j < count; ++j) {
                            uint32_t funcOffset = inits[j];
                            
                            // verify initializers are in image
                            // 验证 initializers 是否在 image 中
                            if ( ! this->containsAddress((uint8_t*)this->machHeader() + funcOffset) ) {
                                dyld::throwf("initializer function offset 0x%08X not in mapped image for %s\n", funcOffset, this->getPath());
                            }
                            
                            // 确保 libSystem.dylib 首先进行初始化
                            if ( ! dyld::gProcessInfo->libSystemInitialized ) {
                                // <rdar://problem/17973316> libSystem initializer must run first
                                
                                // libSystemPath(context) 函数返回的路径,context 参数仅是用来判断是否是 driverKit 环境
                                // #define LIBSYSTEM_DYLIB_PATH "/usr/lib/libSystem.B.dylib"
                                // 看到 libSystem 这个系统动态库在本地的位置是固定的
                                
                                const char* installPath = getInstallPath();
                                
                                // 即如果当前 libSystem.dylib 没有初始化,并且当前 image 的路径为 NULL 或者 当前 image 不是 libSystem.dylib,则进行抛错
                                // 即如果当前 libSystem.dylib 没有进行初始化,并且当前 image 是 libSystem.dylib 之前的 image 则直接抛错,
                                // 即必须保证 libSystem.dylib 第一个进行初始化
                                
                                if ( (installPath == NULL) || (strcmp(installPath, libSystemPath(context)) != 0) )
                                    dyld::throwf("initializer in image (%s) that does not link with libSystem.dylib\n", this->getPath());
                            }
                            
                            // 转换为 Initializer 函数指针 
                            Initializer func = (Initializer)((uint8_t*)this->machHeader() + funcOffset);
                            
                            // 打印开始调用初始化 
                            if ( context.verboseInit )
                                dyld::log("dyld: calling initializer function %p in %s\n", func, this->getPath());
                                
#if __has_feature(ptrauth_calls)
                            func = (Initializer)__builtin_ptrauth_sign_unauthenticated((void*)func, ptrauth_key_asia, 0);
#endif
                            
                            // 调用初始化 
                            
                            // haveLibSystemHelpersBefore 和 haveLibSystemHelpersAfter 两个变量的使用同上
                            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);
                            
                            // 如果前置两个条件一个是 NO 一个是 YES,则表示刚刚是进行的是 libSystem.dylib 的初始化,则把 libSystemInitialized 标记为 true
                            if ( !haveLibSystemHelpersBefore && haveLibSystemHelpersAfter ) {
                            
                                // now safe to use malloc() and other calls in libSystem.dylib
                                // libSystem 初始化完成,现在可以安全地在 libSystem.dylib 中使用 malloc() 和其他调用了
                                
                                dyld::gProcessInfo->libSystemInitialized = true;
                            }
                        }
                        
                    }
                }
            }
            
            // 继续遍历下一条 load command  
            cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
        }
    }
}
复制代码

doModInitFunctions 函数内部是对 __mod_init_func 区和 __init_offsets 两个分区的 Initializer 进行调用。而在 libSystem.dylib 库的 __mod_init_func 区中存放的正是 libSystem.dylib 的初始化函数 libSystem_initializer。(__mod_init_func 区仅用来存放初始化函数。)

 文章开头处我们看到 load 方法是最先执行的,在之前的文章中我们有详细分析过 +load 的执行,如果还有印象的话一定记得它的入口 load_imags 函数。这正和我们上面的分析联系起来了,在 objc-781 源码中,它最先走的是 objc_init,它最后会调用 _dyld_objc_notify_register 传入 load_images,而 load_images 内部的 prepare_load_methodscall_load_methods 完成了整个项目中父类、子类、分类中的所有 +load 函数的调用。

截屏2021-06-06 下午4.19.52.png

doModInitFunctions 函数首先遍历找到类型是 LC_SEGMENT_COMMAND 的 Load command,然后遍历该段中类型是 S_MOD_INIT_FUNC_POINTERSS_INIT_FUNC_OFFSETS 的区,然后便利其中的 Initializer 并执行。

typedef void (*Initializer)(int argc, const char* argv[], const char* envp[], const char* apple[], const ProgramVars* vars);
复制代码

 我们在之前写的 main_front 函数中打一个断点,运行代码,使用 bt 查看其函数调用堆栈。

截屏2021-06-06 下午5.07.34.png

 可看到 main_front 正是在 ImageLoaderMachO::doModInitFunctions(ImageLoader::LinkContext const&) 下执行的,也正说明了 C++ 的静态方法就是在执行 doModInitFunctions 下执行的。

 下面我们用 MachOView 看一下 libSystem.dylib 的结构。(这里没找到 libSystem.dylib 就不看了)

__attribute__((constructor))

__attribute__((constructor))attribute((constructor)) 标记的函数,会在 main 函数之前或动态库加载时执行。在 mach-o 中,被 attribute((constructor)) 标记的函数会在 _DATA 段的 __mod_init_func 区中。当多个被标记 attribute((constructor)) 的方法想要有顺序的执行,怎么办?attribute((constructor)) 是支持优先级的:_attribute((constructor(1)))

 前面我们学习 __attribute__((constructor)) 时,我们知道被 attribute((constructor)) 标记的函数会在 _DATA 段的 __mod_init_func 区中,而在 Libsystem-1292.100.5 中我们搜索 libSystem_initializer 函数时,我们能看到它前面有 attribute((constructor)) 标记,即 libSystem_initializer 位于 libSystem.dylib 的 __mod_init_func 区中,上面 void ImageLoaderMachO::doModInitFunctions 函数调用过程中,查找的正是 __mod_init_func 区,既而当 libSystem.dylib 调用 doModInitFunctions 函数时,正会执行 libSystem_initializer 函数。

 当 libSystem_initializer 被调用时,dyld 会对 gProcessInfo->libSystemInitialized 进行标记,表示 libSystem 已经被初始化。

_dyld_initializer

 这里我们再看一个点,dyld 是怎么知道 libSystem 已经被初始化了,这里用到了 _dyld_initializer 函数:

// called by libSystem_initializer only
extern void _dyld_initializer(void);
复制代码

_dyld_initializer 函数仅由 libSystem_initializer 调用。

//
// during initialization of libSystem this routine will run and call dyld, 
// registering the helper functions.
//
extern "C" void tlv_initializer();

void _dyld_initializer()
{    
   void (*p)(dyld::LibSystemHelpers*);

    // Get the optimized objc pointer now that the cache is loaded
    // 现在缓存已加载,获取优化的 objc 指针
    
    const dyld_all_image_infos* allInfo = _dyld_get_all_image_infos();
    
    if ( allInfo != nullptr  ) {
        const DyldSharedCache* cache = (const DyldSharedCache*)(allInfo->sharedCacheBaseAddress);
        if ( cache != nullptr )
            // 仅为了 gObjCOpt 赋值
            gObjCOpt = cache->objcOpt();
    }

    if ( gUseDyld3 ) {
        // 如果开始使用 dyld3 了,则执行如下,对 gAllImages 中所有 Image 执行初始化
        dyld3::gAllImages.applyInitialImages();
        
#if TARGET_OS_IOS && !TARGET_OS_SIMULATOR

        // For binaries built before 13.0, set the lookup function if they need it
        // 对于 13.0 之前构建的二进制文件,如果需要,请设置查找功能
        
        if (dyld_get_program_sdk_version() < DYLD_PACKED_VERSION(13,0,0))
            setLookupFunc((void*)&dyld3::compatFuncLookup);
            
#endif

    }
    else {
        _dyld_func_lookup("__dyld_register_thread_helpers", (void**)&p);
        if(p != NULL)
            // sHelpers 是一个静态全局结构体变量:static dyld::LibSystemHelpers = {....}
            p(&sHelpers);
    }
    
    // 这里调用了 tlv_initializer 函数 
    tlv_initializer();
}
复制代码

 在 libSystem 初始化期间,此例程将运行并调用 dyld,注册辅助函数(LibSystemHelpers)。这里也对应了 doModInitFunctions 函数内部, bool haveLibSystemHelpersBefore = (dyld::gLibSystemHelpers != NULL);bool haveLibSystemHelpersAfter = (dyld::gLibSystemHelpers != NULL); 两个变量加一起可用来表示 dyld::gProcessInfo->libSystemInitialized = true;,指示 libSystem 初始化完成,现在可以安全地在 libSystem.dylib 中使用 malloc() 和其他调用了。

libSystem 的初始化是一个内部行为,dyld 是如何知道它被初始化的呢?libSystem 是一个特殊的 dylib,默认情况下会被所有可执行文件所依赖,dyld 为它单独提供了一个 API:_dyld_initializer,当 libSystem 被初始化时,会调用该函数,进而 dyld 内部就知道了 libSystem 被初始化了。

在 _dyld_start 中调用 main() 函数

 看到这里我们的 dyld_dyld_start 函数就执行过程中会啊调用 main() 函数,那它是怎么调用到我们的 main.m 的 main 函数呢?

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x00000001045d1ad8 Test_ipa_Simple`main(argc=1, argv=0x000000016b82dc88) at main.mm:89:5
    frame #1: 0x0000000180223cbc libdyld.dylib`start + 4
(lldb) 
复制代码

 此时我们要回忆我们的 dylddyldbootstrap::start 函数,如果我们对前面的函数调用还有印象的话,dyldbootstrap::start 函数的最后是返回 dyld::_main 函数的执行结果:return dyld::_main((macho_header*)mainExecutableMH, appsSlide, argc, argv, envp, apple, startGlue);,而 dyld::_main 函数的返回值就是 main() 函数的地址,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() 的地址。

 下面我们深入 dyld::_main 函数的实现看一下最后的返回结果:

// find entry point for main executable
result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();
...

return result;
复制代码

 下面我们看一下 getEntryFromLC_MAIN 函数实现:

void* ImageLoaderMachO::getEntryFromLC_MAIN() const
{
    // load command 的数量
    const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
    
    // 跳过 macho_header 寻址到 load command 的位置  
    const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
    const struct load_command* cmd = cmds;
    
    // 遍历 load command
    for (uint32_t i = 0; i < cmd_count; ++i) {
        // 找到 LC_MAIN 类型的 load_command
        if ( cmd->cmd == LC_MAIN ) {
            
            // 返回 entry
            entry_point_command* mainCmd = (entry_point_command*)cmd;
            void* entry = (void*)(mainCmd->entryoff + (char*)fMachOData);
            
            // <rdar://problem/8543820&9228031> verify entry point is in image
            if ( this->containsAddress(entry) ) {
            
#if __has_feature(ptrauth_calls)
                // start() calls the result pointer as a function pointer so we need to sign it.
                return __builtin_ptrauth_sign_unauthenticated(entry, 0, 0);
#endif

                return entry;
            }
            else
                throw "LC_MAIN entryoff is out of range";
        }
        cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
    }
    return NULL;
}
复制代码

 即返回 LC_MAIN 的 Entry Point,而它正是当前可执行程序的 main() 地址。

 至此我们可以接着看 dyld__dyld_start 的汇编实现,这里我们摘录 __arm64 下的汇编实现:

// call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
bl    __ZN13dyldbootstrap5startEPKN5dyld311MachOLoadedEiPPKcS3_Pm

// ⬆️ 上面便是 dyldbootstrap::start 调用,执行完成后返回 main() 入口地址,并保存在 x16 中

mov    x16,x0                  // save entry point address in x16

#if __LP64__
ldr     x1, [sp]
#else
ldr     w1, [sp]
#endif

cmp    x1, #0
b.ne    Lnew

// LC_UNIXTHREAD way, clean up stack and jump to result
#if __LP64__
add    sp, x28, #8             // restore unaligned stack pointer without app mh
#else
add    sp, x28, #4             // restore unaligned stack pointer without app mh
#endif

// ⬇️ 跳转到程序的入口,即 main() 函数 

#if __arm64e__
braaz   x16                     // jump to the program's entry point
#else
br      x16                     // jump to the program's entry point
#endif

// LC_MAIN case, set up stack for call to main() 为调用 main() 设置堆栈
Lnew:    mov    lr, x1            // simulate return address into _start in libdyld.dylib 将返回地址模拟到 libdyld.dylib 中的 _start

// ⬇️ 下面是我们熟悉的 main 函数的 argc 和 argv 参数

#if __LP64__
ldr    x0, [x28, #8]       // main param1 = argc
add    x1, x28, #16        // main param2 = argv
add    x2, x1, x0, lsl #3
add    x2, x2, #8          // main param3 = &env[0]
mov    x3, x2
复制代码

 看到这里我们就把 main() 函数之前的流程都看完了,在执行完 dyldbootstrap::start 后,会调用程序的 main() 函数,并且我们也看到了 main() 函数的地址其实是从 LC_MAIN 类型的 load command 读出来的,这也表明了 main() 函数是底层写定函数,

main 函数是被编译到可执行文件中的,而且是固定写死的,编译器找到 main 函数会加载到内存中,如果我们修改 main 函数的名字则会报如下错误: ld: entry point (_main) undefined. for architecture x86_64,告诉我们找不到 main 函数。

 至此 main() 函数之前的流程我们就全部看完了。完结撒花 ???

参考链接

参考链接:?

下面是一些新增的参考链接?:

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享