九 OC底层原理 APP 的加载流程

2021-01-02  本文已影响0人  可乐冒气

前言

当我们的应用程序被打开的时候,kernel(内核)就会将应用程序加载到内存中,同时kernel 又回加载另一个程序,就是我们的dyld(动态链接器), 不管是应用程序,还是dyld 都是一个可执行文件,在MacOS系统中称做Mach-o

一. Mach-o 介绍

1.1 mach-o 文件结构

image.png

1.2 Segment

一般Mach-O文件有多个段(Segement),每个段有不同的功能,一般包括:

二 APP 加载

2.1 __dyld_start

我们在开源代码中的 dyldStartup.s 找到了 __dyld_start的函数入口,在函数实现中找到了这样一段注释,这样我们可以得知 __dyld_start是一个汇编函数,其内调用了 dyldbootstrap::start

image.png

2.2 dyldbootstrap::start

image.png

这个函数注释表示,这段代码是用来启动 dyld的,一般情况下程序的启动工作都是 dyld和crt完成的,在dyld中需要我们手动完成

这段代码主要执行的三个事情

2.3 rebaseDyld

image.png
方法注释表示:在磁盘中,在dyld 的 Data segment中所有的指针都是链在一起的,需要修复执行正确的指针,所有的镜像的基地址都是0,所以偏移量slide 就是加载地址

2.4 __guard_setup

// set up random value for stack canary
就是 设置栈 溢出保护

2.5 dyld::_main

执行了dyldmain 函数

三 dyld::_main

3.1 dyld::_main 入口解析

image.png
注释:表示这是 dyld的入口,内核加载 dyld 并且跳转到了 __dyld_start函数中,设置一些注册,并且回掉了这个方法

//
// Entry point for dyld.  The kernel loads dyld and jumps to __dyld_start which
// sets up some registers and call this function.
//
// Returns address of main() in target program which __dyld_start jumps to
//
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
        int argc, const char* argv[], const char* envp[], const char* apple[], 
        uintptr_t* startGlue)
{
    ······
    // 准备dyld的启动工作,配置相关的环境变量
    ······
    CRSetCrashLogMessage("dyld: launch started");
    // 设置上下文
    setContext(mainExecutableMH, argc, argv, envp, apple);
    ······
    // 检测线程是否受限
    configureProcessRestrictions(mainExecutableMH, envp);

    {
        // 检测环境变量
        checkEnvironmentVariables(envp);
        defaultUninitializedFallbackPaths(envp);
    }
    ······
    // 打印opts
    if ( sEnv.DYLD_PRINT_OPTS )
        printOptions(argv);
    // 打印环境变量
    if ( sEnv.DYLD_PRINT_ENV ) 
        printEnvironmentVariables(envp);
    ······
    // 获取程序架构
    getHostInfo(mainExecutableMH, mainExecutableSlide);

    // load shared cache 检测共享缓存禁用状态
    checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
    if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
#if TARGET_OS_SIMULATOR
        if ( sSharedCacheOverrideDir)
            mapSharedCache();
#else
        // 加载共享缓存库
        mapSharedCache();
#endif
    }
    ······
        // add dyld itself to UUID list 将dyld 添加到UUID列表中
        addDyldImageToUUIDList();
    ······
        // instantiate ImageLoader for main executable  实例化主程序
        sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
    ······
        // load any inserted libraries 加载插入的动态库
        if  ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
            for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) 
                loadInsertedDylib(*lib);
        }
    ······
        // 链接主程序
        link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
    ······
        // 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);
                }
            }
        }
        ······
        // <rdar://problem/12186933> do weak binding only after all inserted images linked
        // //弱符号绑定
        sMainExecutable->weakBind(gLinkContext);

        sMainExecutable->recursiveMakeDataReadOnly(gLinkContext);

        CRSetCrashLogMessage("dyld: launch, running initializers");
        // run all initializers   执行所有初始化方法
        initializeMainExecutable(); 

        // notify any montoring proccesses that this process is about to enter main()
        // notifyMonitoringDyldMain监听dyld的main
        notifyMonitoringDyldMain();     
            // find entry point for main executable
            // 找到主程序的入口
        result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();
    return result;
}

3.2 主要过程-简述

  1. 设置运行环境,为可执行文件的加载做准备工作
  2. 映射共享缓存到当前进程的逻辑内存空间
  3. 实例话主程序
  4. 加入插入的动态库
  5. 链接主程序
  6. 链接插入的动态库
  7. 执行弱符号绑定
  8. 执行初始化方法
  9. 查找程序入口并返回main()

3.3.1 checkSharedRegionDisable 检测共享缓存

函数底部的注释,表示iOS不能在共享区域以外运行

static void checkSharedRegionDisable(const dyld3::MachOLoaded* mainExecutableMH, uintptr_t mainExecutableSlide)
{
#if __MAC_OS_X_VERSION_MIN_REQUIRED
    // if main executable has segments that overlap the shared region,
    // then disable using the shared region
    if ( mainExecutableMH->intersectsRange(SHARED_REGION_BASE, SHARED_REGION_SIZE) ) {
        gLinkContext.sharedRegionMode = ImageLoader::kDontUseSharedRegion;
        if ( gLinkContext.verboseMapping )
            dyld::warn("disabling shared region because main executable overlaps\n");
    }
#if __i386__
    if ( !gLinkContext.allowEnvVarsPath ) {
        // <rdar://problem/15280847> use private or no shared region for suid processes
        gLinkContext.sharedRegionMode = ImageLoader::kUsePrivateSharedRegion;
    }
#endif
#endif
    // iOS cannot run without shared region
}

3.3.2 mapSharedCache() 函数的核心就是 loadDyldCache()

共享区域的共享的实现

bool loadDyldCache(const SharedCacheOptions& options, SharedCacheLoadInfo* results)
{
    results->loadAddress        = 0;
    results->slide              = 0;
    results->errorMessage       = nullptr;

#if TARGET_OS_SIMULATOR
    // simulator only supports mmap()ing cache privately into process
    return mapCachePrivate(options, results);
#else
    if ( options.forcePrivate ) {
        // 如果为强制私有类型 缓存就只映射到当前进程
        // mmap cache into this process only
        return mapCachePrivate(options, results);
    }
    else {
        // 缓存已经映射到共享区域的时候
        // fast path: when cache is already mapped into shared region
        bool hasError = false;
        // 复制已有的缓存
        if ( reuseExistingCache(options, results) ) {
            hasError = (results->errorMessage != nullptr);
        } else {
            // 当前进程就是第一个加载缓存的进程
            // slow path: this is first process to load cache
            hasError = mapCacheSystemWide(options, results);
        }
        return hasError;
    }
#endif
}

3.3.3 instantiateFromloadedImage

isCompatibleMachO 是检查Mach-O的subtype是否是当前cpu可以支持; 内核会映射到主可执行文件中,我们需要为映射到主可执行文件的文件,创建ImageLoader
instantiateMainExecutable 就是实例化可执行文件, 这个期间会解析LoadCommand, 这个之后会发送 dyld_image_state_mapped 通知; 在此方法中,读取image,然后addImage() 到镜像列表。
addImage() 就是将image 添加到 imageList。因此我们可以lldb 调试image list 命令,就可以看到第一个就是macho

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";
}

3.3.4 loadInsertedDylib

加载插入的动态库

3.3.5 link(sMainExecutable,...) 链接主程序 和 link(image,...) 链接所有插入的动态库

对上面生成的 Image 进行链接。这个过程就是将加载进来的二进制变为可用状态的过程。其主要做的事有对image进行 load(加载),rebase(基地址复位)bind(外部符号绑定),我们可以查看源码:

void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool preflightOnly, bool neverUnload, const RPathChain& loaderRPaths, const char* imagePath)
{
    ......
    this->recursiveLoadLibraries(context, preflightOnly, loaderRPaths, imagePath);  
    ......
    this->recursiveRebaseWithAccounting(context);
    ......
    this->recursiveBindWithAccounting(context, forceLazysBound, neverUnload);
}

recursiveLoadLibraries: 递归加载所有依赖库进内存。
recursiveRebase: 递归对自己以及依赖库进行rebase操作
recursiveBindWithAccounting: 对库中所有nolazy的符号进行bind,一般的情况下多数符号都是lazybind的,他们在第一次使用的时候才进行bind

3.3.6 initializeMainExecutable()

void initializeMainExecutable()
{
    // record that we've reached this step
    gLinkContext.startedInitializingMainExecutable = true;
    // 调用每个image 的 initalizer 方法进行初始化,
    // run initialzers for any inserted dylibs
    ImageLoader::InitializerTimingList initializerTimes[allImagesCount()];
    initializerTimes[0].count = 0;
    const size_t rootCount = sImageRoots.size();
    if ( rootCount > 1 ) {
        for(size_t i=1; i < rootCount; ++i) {
            sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);
        }
    }
    // 为主程序可执行文件执行初始化
    // run initializers for main executable and everything it brings up 
    sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
    
    // register cxa_atexit() handler to run static terminators in all loaded images when this process exits
    if ( gLibSystemHelpers != NULL ) 
        (*gLibSystemHelpers->cxa_atexit)(&runAllStaticTerminators, NULL, NULL);

    // dump info if requested
    if ( sEnv.DYLD_PRINT_STATISTICS )
        ImageLoader::printStatistics((unsigned int)allImagesCount(), initializerTimes[0]);
    if ( sEnv.DYLD_PRINT_STATISTICS_DETAILS )
        ImageLoaderMachO::printStatisticsDetails((unsigned int)allImagesCount(), initializerTimes[0]);
}

3.3.7 ImageLoader::runInitializers 流程

void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
                                          InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
    recursive_lock lock_info(this_thread);
    recursiveSpinLock(lock_info);

    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();
}

在这个函数中 我们主要关注

3.3.7.1 context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);

通知objc我们要初始化这个镜像,这里 通过 notifySingle 函数对sNotifyObjCInit 进行函数调用。

static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo){
...
 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)(image->getRealPath(), image->machHeader());
            ...
        }
    } 
...
}

获取镜像文件的真实地址 (*sNotifyObjCInit)(image->getRealPath(), image->machHeader() , 但是根据最开始的堆栈信息 可以看到 下一步就应该是调用 load_images

image.png

主要是因为 (*sNotifyObjCInit)(image->getRealPath(), image->machHeader() 这是一个回调函数

在if判断条件中对sNotifyObjCInit进行了非空判断,也就是有值,在本文件搜索,发现它在 registerObjCNotifiers 中被赋值

3.3.7.2 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;
      ···

接着 全局搜索 registerObjCNotifiers, 发现 只有 _dyld_objc_notify_register 中调用到了

void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped)
{
    dyld::registerObjCNotifiers(mapped, init, unmapped);
}

参数:

我们在 下一个符号断点 _dyld_objc_notify_register

image.png
可以看到 _dyld_objc_notify_register 之前调用了 _objc_init

3.3.7.3 _objc_init

_dyld_objc_notify_register函数是供 objc runtime 使用的,当objc镜像被映射,取消映射,和初始化时 被调用的注册处理器。我们可以在libobjc.A.dylib库里,_objc_init函数中找到其调用。


/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init(); // 环境变量
    tls_init(); //线程 key 的绑定
    static_init(); //初始化系统内置的 C++ 静态构造函数
    runtime_init(); //主要是运行时的初始化,主要分为两部分:分类初始化和类的表初始化
    exception_init(); // 初始化libobjc异常处理
    cache_init(); //缓存初始化
    _imp_implementationWithBlock_init(); //启动机制回调

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

3.3.8 doInitialization

我们从最新的堆栈信息 可以看到 recursiveInitialization() 函数,之后就调用了doInitialization()

image.png

在 doModInitFunctions之后 会 先执行 libSystem_initializer,保证系统库优先初始化完毕,在这里初始化 libdispatch_init,进而在_os_object_init 中 调用 _objc_init。

由于 runtime 向 dyld 绑定了回调,当 image 加载到内存后,dyld 会通知 runtime 进行处理
runtime 接手后调用 map_images 做解析和处理,接下来 load_images 中调用 call_load_methods 方法,遍历所有加载进来的 Class,按继承层级依次调用 Class 的 +load 方法和其 Category 的 +load 方法。
至此,可执行文件和动态库中所有的符号(Class,Protocol,Selector,IMP,…)都已经按格式成功加载到内存中,被 runtime 所管理,在这之后,runtime 的那些方法(动态添加 Class、swizzle 等等才能生效)

四 总结- 流程

image.png
上一篇 下一篇

猜你喜欢

热点阅读