iOS dyld分析

前言:

动态库和静态库

  我们都知道,一段程序的运行,都会依赖各种各样的库,那么什么是程序依赖的库呢?简而言之,库就是一个可执行的二进制文件,作为程序运行的支撑。通俗的说,当我们需要造一辆汽车的时候,库就是轮子。我们常见windows系列的库就是.dll文件,linux系列库就是.so文件。

库有动态库、静态库之分,那么iOS使用的库有哪些呢?

  • 静态库:.a、.lib
  • 动态库:framework

动态库和静态库加载的区别:

  • 静态库在链接阶段,会将汇编生成的目标与引用的库一起链接打包到可执行的文件中。
  • 动态库并不会链接到目标代码中,在运行时才会被载入。

动态库的优势:

  • 减少打包之后App的大小
  • 共享内容
  • 动态更新

常见的动态库:

  • UIKit
  • libdispatch
  • libobjc.dyld

程序的编译过程

  程序的编译过程其实就是将源文件转化成可执行文件的过程:


image

App加载分析

  当我们运行一个iOS App的时候,它的加载过程是什么呢?当我们点击桌面应用图标的时候,App就会进入启动流程,系统先会加载libSystemruntimedyld注册回调函数,然后加载新的镜像文件(image),执行map_images、load_images,最后调用main函数,这样App就会启动起来。

image

我们创建一个新程序,在ViewController.m文件中实现如下的方法:

+ (void)load{
    NSLog(@"%s",__func__);
}

然后在main函数的入口处打一个断点,当程序停在断点的时候,我们会发现控制台已经输出如下log:

+[ViewController load]

说明,当程序进入main函数之前,已经执行了类的load方法。
查看旁边的调用顺序:

image

我们可以看到,在main函数之前,会调用一个start方法,该方法即

libdyld.dylib`start

那么,dyld是什么呢?

dyld

dyld简介

dyld: the dynamic link editor,苹果系统的动态链接器。它的作用是在需要使用动态库的时候将其加载到内存中。那么dyld是怎样将动态库加载到内存中的?dyld的加载流程又是什么样的呢?

dyld加载流程

在上面的代码中,我们可以看出,在main函数之前,会先调用dyld_start函数,通过查看dyld相关源码,我们来逐步分析其加载流程:

汇编部分

首先进行全局搜索dyld_start,进入dyldStartup.s文件,找到arm64相关部分,如图:

image

如图所示,在进行一系列汇编操作之后,程序会跳转到:

__ZN13dyldbootstrap5startEPK12macho_headeriPPKclS2_Pm

c++部分

也就是调用一个c++方法:

dyldbootstrap::start(app_mh, argc, argv, slide, dyld_mh, &startGlue)

全局搜索“start(”,即可找到该方法对应的实现部分:

uintptr_t start(const struct macho_header* appsMachHeader, int argc, const char* argv[], 
                intptr_t slide, const struct macho_header* dyldsMachHeader,
                uintptr_t* startGlue)
{
    // 忽略其它条件
    return dyld::_main(appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}

该方法最终会调用dyld::_main方法:
由于该方法比较长,我们就不一一贴出代码,择重要的展示:

// Entry point for dyld.  The kernel loads dyld and jumps to __dyld_start which
// sets up some registers and call this function.

uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
        int argc, const char* argv[], const char* envp[], const char* apple[], 
        uintptr_t* startGlue)
{
    // 设置上下文
    setContext(mainExecutableMH, argc, argv, envp, apple);
    
    // 检查环境变量的相关操作
    checkEnvironmentVariables(envp);
    defaultUninitializedFallbackPaths(envp);
    
    // 加载共享缓存
    checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
    mapSharedCache();
    
    // 将dyld添加到UUID列表
    addDyldImageToUUIDList();
    
    // 为主要可执行文件实例化ImageLoader
    sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);

    // 加载任何插入的库
    if  ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
        for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) 
                loadInsertedDylib(*lib);
    }
    
    // 链接库
    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();
        }
        // 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);
        }
    }
    
    // 运行所有的初始化
    initializeMainExecutable(); 

    // 通知任何监听的进程此进程即将进入main()函数
    notifyMonitoringDyldMain();
}

具体
  通过该方法的注释,我们可以知道dyld::_maindyld方法的入口,内核设置了寄存器,并且跳转到__dyld_start,然后调用了该方法。

准备工作:
  • 设置上下文信息
  • 检查环境变量的配置

// 此处只展示较为重要的代码
static void checkEnvironmentVariables(const char* envp[])
{
    if ( !gLinkContext.allowEnvVarsPath && !gLinkContext.allowEnvVarsPrint )
        return;
    const char** p;
    for(p = envp; *p != NULL; p++) {
        ......
        processDyldEnvironmentVariable(key, value, NULL);
    }
    ......
}
加载共享缓存
// 此处只展示较为重要的代码
static void mapSharedCache()
{
    dyld3::SharedCacheOptions opts;
    opts.cacheDirOverride   = sSharedCacheOverrideDir;
    opts.forcePrivate       = (gLinkContext.sharedRegionMode == ImageLoader::kUsePrivateSharedRegion);
    opts.verbose            = gLinkContext.verboseMapping;
    loadDyldCache(opts, &sSharedCacheLoadInfo);
    ....
}

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

if ( options.forcePrivate ) {
        // 仅加载当前进程的缓存
        return mapCachePrivate(options, results);
    }
    else {
        // 缓存已经被加载到共享缓存文件了
        bool hasError = false;
        if ( reuseExistingCache(options, results) ) {
            hasError = (results->errorMessage != nullptr);
        } else {
            // 第一次加载缓存
            hasError = mapCacheSystemWide(options, results);
        }
        return hasError;
    }
    ......
}
加载镜像文件:
  1. 为可执行文件实例化imageLoader
  2. 为主可执行文件创建image
  3. 加载任何插入动态库
  4. 链接动态库

为可执行文件实例化imageLoader

// 此处只展示较为重要的代码
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath)

static ImageLoaderMachO* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path)
{
    // try mach-o loader
    if ( isCompatibleMachO((const uint8_t*)mh, path) ) {
        // 为主可执行文件创建image
        ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext);
        
        // 添加镜像
        addImage(image);
        return (ImageLoaderMachO*)image;
    }
}

为主可执行文件创建image:

// 此处只展示较为重要的代码
ImageLoader* ImageLoaderMachO::instantiateMainExecutable(const macho_header* mh, uintptr_t slide, const char* path, const LinkContext& context)
{
    bool compressed;
    unsigned int segCount;
    unsigned int libCount;
    const linkedit_data_command* codeSigCmd;
    const encryption_info_command* encryptCmd;
    
    // 确定此mach-o文件是否具有经典或压缩的LINKEDIT以及其具有的段数
    sniffLoadCommands(mh, path, false, &compressed, &segCount, &libCount, context, &codeSigCmd, &encryptCmd);
    // instantiate concrete class based on content of load commands
    if ( compressed ) 
        return ImageLoaderMachOCompressed::instantiateMainExecutable(mh, slide, path, segCount, libCount, context);
}

加载任何插入动态库:

// 此处只展示较为重要的代码
static void loadInsertedDylib(const char* path)
{
    ImageLoader* image = NULL;
    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.enforceIOSMac       = true;
        context.origin              = NULL; // can't use @loader_path with DYLD_INSERT_LIBRARIES
        context.rpath               = NULL;
        image = load(path, context, cacheIndex);
    }
    ......
}

链接程序:

// 此处只展示较为重要的代码
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();
    }
        
    for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
        ImageLoader* image = sAllImages[i+1];
        image->registerInterposing(gLinkContext);
    }
}

// 递归加载所有依赖库
void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool preflightOnly, bool neverUnload, const RPathChain& loaderRPaths, const char* imagePath)
{
    (*context.setErrorStrings)(0, NULL, NULL, NULL);

    uint64_t t0 = mach_absolute_time();
    this->recursiveLoadLibraries(context, preflightOnly, loaderRPaths, imagePath);
    ......
}

void ImageLoaderMegaDylib::recursiveLoadLibraries(const LinkContext& context, bool preflightOnly, const RPathChain& loaderRPaths, const char* loadPath)
{
    unsigned index = findImageIndex(context, loadPath);
    recursiveMarkLoaded(context, index);
}
运行所有的初始化:

一步一步递归,初始化任何插入的库。

// 此处只展示较为重要的代码
void initializeMainExecutable()
{
    gLinkContext.startedInitializingMainExecutable = true;

    // 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]);
        }
    }
    
    ......
}
void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo)
{
    uint64_t t1 = mach_absolute_time();
    mach_port_t thisThread = mach_thread_self();
    ImageLoader::UninitedUpwards up;
    up.count = 1;
    up.images[0] = this;
    processInitializers(context, thisThread, timingInfo, up);
    ......
}


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;

    for (uintptr_t i=0; i < images.count; ++i) {
        images.images[i]->recursiveInitialization(context, thisThread, images.images[i]->getPath(), timingInfo, ups);
    }
    
    if ( ups.count > 0 )
        processInitializers(context, thisThread, timingInfo, ups);
}


// 在每一个image初始化完成之后,通知objc和anyone,当前image的初始化工作完成了
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);

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

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

    // mach-o has -init and static initializers
    doImageInit(context);
    doModInitFunctions(context);
    
    CRSetCrashLogMessage2(NULL);
    
    return (fHasDashInit || fHasInitializers);
}

那么当image加载完成之后,它是怎们告诉大家它加载完成了呢?答案是指针回调。加载完成之后,会调用下面的方法:

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

而这两个方法的实现如下:

// 此处只展示较为重要的代码
static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
{
    ......
    if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && image->notifyObjC() ) {
        ......
        
        (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
       
       ......
    }
    ......
}
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 'init' function on all images already init'ed (below libSystem)
    for (std::vector::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());
        }
    }
}

// 该函数的注释大概可以知道:
// _dyld_objc_notify_mapped是dyld已经加载完成image进行映射操作
// _dyld_objc_notify_init 是初始化image操作
// _dyld_objc_notify_unmapped 解除映射操作
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);
}
void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

追踪该函数的调用,我们发现当调用到_objc_init函数时,此处就注册了image加载完成的通知。那么什么时候会调用_objc_init方法呢?在此处打一个断点,运行程序,在lldb查看使用bt指令,就可以看到代码调用的顺序如下:

image

分析得到如下流程图:

image

总结:dyld加载顺序

    1. __dyly_start(汇编)
    1. uintptr_t start()
    1. uintptr_t _main()
    • 3.1 配置上下文
    • 3.2 处理环境变量
    • 3.3 加载共享缓存
    • 3.4 将dyld加入UUID列表
    • 3.5 加载所有image
      • 3.5.1 为可执行文件实例化imageLoader
      • 3.5.2 为主可执行文件创建image
      • 3.5.3 加载任何插入动态库
      • 3.5.4 链接动态库
    • 3.6 初始化所有程序
      • 3.6.1 遍历初始化image
      • 3.6.2 通知image初始化完成
      • 3.6.3 初始化libSystem
      • 3.6.4 初始化libdispatch
      • 3.6.5 初始化libobjc
    • 3.7 进入主程序的main函数

Tips: 共享缓存(dyld_shared_cache)

dyld加载时,为了优化程序启动,使用了共享缓存(dyld_shared_cache)。共享缓存会在进程启动时被dyld映射到内存中,之后,当任何Mach-O映像加载时,dyld首先会检查该Mach-O映像与所需的动态库是否在共享缓存中,如果存在,则直接将它在共享内存中的内存地址映射到进程的内存地址空间。在程序依赖的系统动态库很多的情况下,这种做法对程序启动性能是有明显提升的。

update_dyld_shared_cache程序确保了dyld的共享缓存是最新的,它会扫描 /var/db/dyld/shared_region_roots/目录下paths路径文件,这些paths文件包含了需要加入到共享缓存的Mach-O文件路径列表,update_dyld_shared_cache()会挨个将这些Mach-O文件及其依赖的dylib都加共享缓存中去。

  共享缓存是以文件形式存放在/var/db/dyld/目录下的,生成共享缓存的update_dyld_shared_cache程序位于是/usr/bin/目录下。

Tips: 函数指针

定义:指向函数的指针变量。 因此“函数指针”本身首先应是指针变量,只不过该指针变量指向函数。
用途:调用函数和做函数的参数。

eg:

void (*funcP)(int num);
void printFunc(int num) {
    printf("num:%d\n",num);
}
int main(int argc,char *argv[]) {
    funcP = printFunc;
    (*funcP)(100);
    funcP(100);
    return 0;
}

运行以上程序,控制台就会输出100;100。因为funcP是一个函数指针,当执行funcP = printFunc的时候,其实就是让funcP指向printFunc,所以调用(*funcP)(100)实际上就是调用printFunc(100)。而funcP(100)的结果和(*funcP)(100)是一样的,只是写法不同。

Tips: 回调函数

定义:通过函数指针调用的函数。
如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
eg:

//函数功能:实现求和
int func_sum(int n,int b) {
    return a + b;
}

//这个函数是回调函数,其中第二个参数为一个函数指针,通过该函数指针来调用求和函数,并把结果返回给主调函数
int callback(int m, int n, int (*p)(int))
{
    return p(m, n);
}

int main(void)
{
    printf("the sum is %d\n", callback(1, 2, func_sum));       
    return 0;
}

此处输出控制台输出3,

Tips:指针函数

定义:返回值是指针的函数。
eg:

int * func_sum(int a, int, b)
{
    sum = a + b;
    int *p = ∑
    return p;
}

你可能感兴趣的:(iOS dyld分析)