iOS:動態(tài)庫的加載順序

在寫 《iOS:load方法能不能被hook?》《iOS啟動優(yōu)化:App啟動耗時在線監(jiān)控與AppDelegate管控》 兩篇文章時都提到了動態(tài)庫的加載,由于主題的原因,沒有詳細(xì)介紹,有同學(xué)對這個比較感興趣,今天我們就來研究下在iOS中動態(tài)庫的加載順序是什么樣子的。

1.實驗篇

我們先通過demo看下幾種Case:

  • 沒有依賴關(guān)系:

    情形一

    我們制作dylibA、dylibB、dylibB這三個動態(tài)庫(不了解動態(tài)庫的制作的請問度娘...),且它們沒有依賴關(guān)系,同時我們在每個庫中添加一個Class,暫且以Class的load方法的調(diào)用順序當(dāng)做是動態(tài)庫的加載順序(先挖一個坑),比如dylibA

    @implementation ClassA
    + (void)load {
        NSLog(@"dylibA loaded");
    }
    @end
    

    將這三個庫加到demo工程中,并且保證Build Phases-Link Binary With Libraries中的順序:dylibA > dylibB > dylibC,運行結(jié)果:

    Demo[53199:17384949] dylibA loaded
    Demo[53199:17384949] dylibB loaded
    Demo[53199:17384949] dylibC loaded
    

    我們調(diào)整下Link Binary With Libraries中的順序:dylibC > dylibB > dylibA,運行結(jié)果:

    Demo[53265:17397552] dylibC loaded
    Demo[53265:17397552] dylibB loaded
    Demo[53265:17397552] dylibA loaded
    

    通過實驗我們知道:在沒有依賴關(guān)系的情況下,動態(tài)庫的加載順序由Link Binary With Libraries中的順序決定,當(dāng)然我們可以通過Link Binary With Libraries來控制動態(tài)庫的加載順序。

  • 單一依賴關(guān)系

    情形二

    dylibA依賴dylibB,dylibB依賴dylibC,我們簡單改造下這三個庫,如在dylibB中import下dylibC的頭文件,dylibA中同理:

    #import "ClassB.h"
    #import <dylibC/dylibC.h>
    
    @implementation ClassB
    + (void)load {
        NSLog(@"dylibB loaded");
    }
    @end
    

    Link Binary With Libraries中的順序:dylibA > dylibB > dylibC,運行結(jié)果:

    Demo[53570:17450857] dylibC loaded
    Demo[53570:17450857] dylibB loaded
    Demo[53570:17450857] dylibA loaded
    

    這次我們發(fā)現(xiàn)三個庫的加載順序是反的,我們修改下順序:dylibC > dylibA > dylibB,運行結(jié)果不變。由實驗結(jié)果可知:動態(tài)庫的加載順序還受依賴關(guān)系影響,被依賴的子節(jié)點優(yōu)先加載。

  • 組合依賴關(guān)系


    組合關(guān)系1

    其中,dylibA、dylibB、dylibB 沒有依賴關(guān)系,dylibA 依賴了dylibDdylibEdylibF。

    Demo[97898:19286936] dylibD loaded
    Demo[97898:19286936] dylibF loaded
    Demo[97898:19286936] dylibE loaded
    Demo[97898:19286936] dylibA loaded
    Demo[97898:19286936] dylibB loaded
    Demo[97898:19286936] dylibC loaded
    

    通過修改dylibA-Link Binary With LibrariesdylibD、dylibE的順序調(diào)整為:

    組合關(guān)系2

    Demo[97982:19305235] dylibF loaded
    Demo[97982:19305235] dylibE loaded
    Demo[97982:19305235] dylibD loaded
    Demo[97982:19305235] dylibA loaded
    Demo[97982:19305235] dylibB loaded
    Demo[97982:19305235] dylibC loaded
    

    通過上面的嘗試,我們可以看出動態(tài)庫的加載順序為:先根據(jù)配置的鏈接順序加載,如有依賴的先遞歸式加載依賴。

2.源碼篇

上面我們通過幾個實驗對動態(tài)庫的加載有個大概的影響,下面我們通過源碼進(jìn)一步了解動態(tài)庫的加載(dyld源碼,本文使用版本:dyld-635.2,篇幅問題,只展示部分代碼)。
為了便于的過程,我們在上面demo中的位于子節(jié)點的dylibF庫的load方法添加一個符號斷點,獲取下這個load方法的調(diào)用棧:

  #0    +[ClassF load]
  #1    load_images ()
  #2    dyld::notifySingle(dyld_image_states, ImageLoader const*, ImageLoader::InitializerTimingList*) ()
  #3    ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) ()
  #4    ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) ()
  #5    ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) ()
  #6    ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) ()
  #7    ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) ()
  #8    ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) ()
  #9    dyld::initializeMainExecutable() 
  #10   dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) ()
  #11   dyldbootstrap::start(macho_header const*, int, char const**, long, macho_header const*, unsigned long*) ()
  #12   _dyld_start ()

_dyld_startdyldbootstrap::start使用匯編實現(xiàn),我們重點看下dyld::_main開始的實現(xiàn)。

  • dyld::_main,動態(tài)鏈接器的入口函數(shù),代碼較多,只展示部分代碼:

    //
    // 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)  {
        //1.配置環(huán)境
        ...很多代碼
        //2.加載共享緩存
        checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
        //3.實例化主程序
        sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
        gLinkContext.mainExecutable = sMainExecutable;
        gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);
        //4.插入動態(tài)庫(使用動態(tài)庫注入代碼就是通過這個實現(xiàn)的)
        if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
              for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) 
                  loadInsertedDylib(*lib);
          }
        //5.鏈接主程序
        gLinkContext.linkingMainExecutable = true;
        link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
        //6.鏈接插入的動態(tài)庫
        ...很多代碼
        //7.初始化主程序
        initializeMainExecutable(); 
        //...快結(jié)束了 
    }
    

    和我們今天主題相關(guān)的為link()initializeMainExecutable()兩個過程,分別負(fù)責(zé)鏈接(符號rebase,binding)和初始化(調(diào)用load,__attribute__((constructor)) 修飾的c函數(shù)等)的工作。

  • link()

    void link(ImageLoader* image, bool forceLazysBound, bool neverUnload, const 
      ImageLoader::RPathChain& loaderRPaths, unsigned cacheIndex)
    {
      // add to list of known images.  This did not happen at creation time for bundles
      if ( image->isBundle() && !image->isLinked() )
          addImage(image);
    
      // we detect root images as those not linked in yet 
      if ( !image->isLinked() )
          addRootImage(image);
      
      // process images
      try {
          const char* path = image->getPath();
    #if SUPPORT_ACCELERATE_TABLES
          if ( image == sAllCacheImagesProxy )
              path = sAllCacheImagesProxy->getIndexedPath(cacheIndex);
    #endif
          image->link(gLinkContext, forceLazysBound, false, neverUnload, loaderRPaths, path);
      }
      catch (const char* msg) {
          garbageCollectImages();
          throw;
      }
    }
    

    link()中又調(diào)用ImageLoader::link方法,ImageLoader是一個基類負(fù)責(zé)加載image文件(主程序,動態(tài)庫)每個image對應(yīng)一個ImageLoader子類的實例,我們繼續(xù)看下它的實現(xiàn):

  • ImageLoader::link

     void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool preflightOnly, bool neverUnload, const RPathChain& loaderRPaths, const char* imagePath)
     {
      //dyld::log("ImageLoader::link(%s) refCount=%d, neverUnload=%d\n", imagePath, fDlopenReferenceCount, fNeverUnload);
      
      // clear error strings
      (*context.setErrorStrings)(0, NULL, NULL, NULL);
    
      uint64_t t0 = mach_absolute_time();
      this->recursiveLoadLibraries(context, preflightOnly, loaderRPaths, imagePath);
      context.notifyBatch(dyld_image_state_dependents_mapped, preflightOnly);
    
      // we only do the loading step for preflights
      if ( preflightOnly )
          return;
    
      uint64_t t1 = mach_absolute_time();
      context.clearAllDepths();
      this->recursiveUpdateDepth(context.imageCount());
    
      //省略符號rebase 和binding過程...
    }
    

    ImageLoader::link中使用ImageLoader::recursiveLoadLibraries加載動態(tài)庫,截取部分代碼:

  • ImageLoader::recursiveLoadLibraries

     void ImageLoader::recursiveLoadLibraries(const LinkContext& context, bool preflightOnly, const RPathChain& loaderRPaths, const char* loadPath)
    {
        //...省略很多代碼
        // 獲取當(dāng)前image依賴的動態(tài)庫
        DependentLibraryInfo libraryInfos[fLibraryCount]; 
        this->doGetDependentLibraries(libraryInfos);
        //...省略很多代碼
    
        for(unsigned int i=0; i < fLibraryCount; ++i){ 
           //加載動態(tài)庫
           DependentLibraryInfo& requiredLibInfo = libraryInfos[i];
           dependentLib = context.loadLibrary(requiredLibInfo.name, true, this->getPath(), &thisRPaths, enforceIOSMac, cacheIndex);
           //...省略很多代碼
        }
        
        //保存加載完成的動態(tài)庫
        setLibImage(i, dependentLib, depLibReExported, requiredLibInfo.upward);
    
       // 告訴此image依賴的動態(tài)庫們遞歸的加載各自依賴的動態(tài)庫
       for(unsigned int i=0; i < libraryCount(); ++i) {
          ImageLoader* dependentImage = libImage(i);
          if ( dependentImage != NULL ) { 
              dependentImage->recursiveLoadLibraries(context, preflightOnly, thisRPaths, libraryInfos[i].name);
          }
       }
       //...省略很多代碼
    }
    

    此方法也比較長,它主要做的事情是:1)獲取當(dāng)前image依賴的的動態(tài)庫;2)循環(huán)加載當(dāng)前image依賴的動態(tài)庫;3)告訴當(dāng)前image依賴的動態(tài)庫們遞歸的加載各自依賴的動態(tài)庫;
    具體的加載方法在dyld.cppImageLoader* load(const char* path, const LoadContext& context, unsigned& cacheIndex),我們不多做介紹。從上面的代碼可以知道,dyld通過doGetDependentLibraries獲取了image依賴的動態(tài)庫信息,然后循環(huán)加載,這個函數(shù)的實現(xiàn)會影響動態(tài)庫的加載順序,我們看下doGetDependentLibraries的實現(xiàn)(doGetDependentLibraries是個虛函數(shù),只有在ImageLoaderMachO.cpp中找到了它的實現(xiàn)):

  • ImageLoaderMachO::doGetDependentLibraries

    void ImageLoaderMachO::doGetDependentLibraries(DependentLibraryInfo libs[])
    {
      if ( needsAddedLibSystemDepency(libraryCount(), (macho_header*)fMachOData) ) {
          DependentLibraryInfo* lib = &libs[0];
          lib->name = LIBSYSTEM_DYLIB_PATH;
          lib->info.checksum = 0;
          lib->info.minVersion = 0;
          lib->info.maxVersion = 0;
          lib->required = false;
          lib->reExported = false;
          lib->upward = false;
      }
      else {
          uint32_t index = 0;
          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;
          for (uint32_t i = 0; i < cmd_count; ++i) {
              switch (cmd->cmd) {
                  case LC_LOAD_DYLIB:
                  case LC_LOAD_WEAK_DYLIB:
                  case LC_REEXPORT_DYLIB:
                  case LC_LOAD_UPWARD_DYLIB:
                  {
                      const struct dylib_command* dylib = (struct dylib_command*)cmd;
                      DependentLibraryInfo* lib = &libs[index++];
                      lib->name = (char*)cmd + dylib->dylib.name.offset;
                      //lib->name = strdup((char*)cmd + dylib->dylib.name.offset);
                      lib->info.checksum = dylib->dylib.timestamp;
                      lib->info.minVersion = dylib->dylib.compatibility_version;
                      lib->info.maxVersion = dylib->dylib.current_version;
                      lib->required = (cmd->cmd != LC_LOAD_WEAK_DYLIB);
                      lib->reExported = (cmd->cmd == LC_REEXPORT_DYLIB);
                      lib->upward = (cmd->cmd == LC_LOAD_UPWARD_DYLIB);
                  }
                  break;
              }
              cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
          }
      }
    }
    

    dyld通過讀取當(dāng)前Mach-O文件的Load Commonds段來獲取到它依賴的動態(tài)庫,涉及到的字段有LC_LOAD_DYLIBLC_LOAD_WEAK_DYLIB、LC_REEXPORT_DYLIB、LC_LOAD_UPWARD_DYLIB,遍歷時通過&libs[]記錄。而影響Load Commonds中動態(tài)庫順序的是Xcode中Link Binary With Libraries的順序。下圖是上文Demo工程的Mach-O中Load Commonds的示例截圖:

    Load Commonds

好了,我們通過實驗和源碼的方式了解到了動態(tài)庫的加載順序:1)先從當(dāng)前image文件(可執(zhí)行文件/動態(tài)庫)的Load Commonds中獲取動態(tài)庫的信息(包括順序,名字,路徑等);2)然后循環(huán)加載它依賴的動態(tài)庫;3)加載完后,再根據(jù)當(dāng)前image依賴的動態(tài)庫列表遞歸加載各自依賴的動態(tài)庫。

3.填坑篇

文章開始我們就挖了一個坑:實驗的方式是建立在“l(fā)oad方法的調(diào)用順序當(dāng)做是動態(tài)庫的加載順序”這一假設(shè)上的,那么這一假設(shè)成立嗎?
從上文中+[ClassF load]的調(diào)用??芍?code>load 方法的調(diào)用是在initializeMainExecutable()中,在link()之后,initializeMainExecutable()后又調(diào)用ImageLoader::runInitializers()ImageLoader::processInitializers(),我們看下代碼:

  • ImageLoader::processInitializers中調(diào)用了ImageLoader::recursiveInitialization,從名字看進(jìn)行了遞歸方式的初始化。
    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.
      for (uintptr_t i=0; i < images.count; ++i) {
          images.images[i]->recursiveInitialization(context, thisThread, images.images[i]->getPath(), timingInfo, ups);
      }
      // If any upward dependencies remain, init them.
      if ( ups.count > 0 )
          processInitializers(context, thisThread, timingInfo, ups);
     }
    
  • ImageLoader::recursiveInitialization,截取部分代碼:
    void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
                                            InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
    {
      
      //優(yōu)先初始化fDepth深的庫
      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.images[uninitUps.count] = dependentImage;
                  uninitUps.count++;
              }
              else if ( dependentImage->fDepth >= fDepth ) {
                  dependentImage->recursiveInitialization(context, this_thread, libPath(i), timingInfo, uninitUps);
              }
          }
      }
    
    
      // 告訴objc我們準(zhǔn)備初始化image了
      uint64_t t1 = mach_absolute_time();
      fState = dyld_image_state_dependents_initialized;
      oldState = fState;
      context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
    
      // 初始化image
      bool hasInitializers = this->doInitialization(context);
    
      // 告訴objc初始化完成
      fState = dyld_image_state_initialized;
      oldState = fState;
      context.notifySingle(dyld_image_state_initialized, this, NULL);
    
     //...
    }
    
    從上面的代碼可以看出,初始化的動態(tài)庫是從libImage()中獲取,而libImage()的數(shù)據(jù)是在加載動態(tài)庫的ImageLoader::recursiveLoadLibraries中的setLibImage()進(jìn)行保存的。通過比較fDepth,優(yōu)先初始化fDepth大的庫,我們再看下fDepth的賦值:
  • ImageLoader::recursiveUpdateDepth
    unsigned int ImageLoader::recursiveUpdateDepth(unsigned int maxDepth)
    {
      // the purpose of this phase is to make the images sortable such that 
      // in a sort list of images, every image that an image depends on
      // occurs in the list before it.
      if ( fDepth == 0 ) {
          // break cycles
          fDepth = maxDepth;
          
          // get depth of dependents
          unsigned int minDependentDepth = maxDepth;
          for(unsigned int i=0; i < libraryCount(); ++i) {
              ImageLoader* dependentImage = libImage(i);
              if ( (dependentImage != NULL) && !libIsUpward(i) ) {
                  unsigned int d = dependentImage->recursiveUpdateDepth(maxDepth);
                  if ( d < minDependentDepth )
                      minDependentDepth = d;
              }
          }
      
          // make me less deep then all my dependents
          fDepth = minDependentDepth - 1;
      }
      
      return fDepth;
    }
    
    在整個依賴樹的關(guān)系中,層級越深,fDepth的值越大,ImageLoader::recursiveUpdateDepth的調(diào)用在ImageLoader::recursiveLoadLibraries之后,通過上面的分析過程我們知道:在加載動態(tài)庫時將動態(tài)庫的信息通過setLibImage()保存,加載完成后dyld根據(jù)依賴關(guān)系遞歸地給各個庫都計算了依賴深度:fDepth,在初始化的時候遞歸地根據(jù)fDepth進(jìn)行初始化,每初始化一個image都會使用 context.notifySingle()通知Objc通過load_images()調(diào)用load方法。從分析結(jié)果看:不同庫中的load方法的調(diào)用順序可以當(dāng)做是動態(tài)庫的加載順序。(dyld過程比較復(fù)雜,本人水平有限,如有錯誤敬請指出~)
dyld_main.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。