iOS - 探索dyld

一、背景知識

1.靜態(tài)庫和動態(tài)庫

1.1.庫

首先來看什么是庫,庫(Library)說白了就是一段編譯好的二進(jìn)制代碼,加上頭文件就可以供別人使用。
什么時候我們會用到庫呢?

  • 一種情況是某些代碼需要給別人使用,但是我們不希望別人看到源碼,就需要以庫的形式進(jìn)行封裝,只暴露出頭文件。 (一些引入的第三方庫)
  • 對于某些不會進(jìn)行大的改動的代碼,我們想減少編譯的時間,就可以把它打包成庫,因?yàn)閹焓且呀?jīng)編譯好的二進(jìn)制了,編譯的時候只需要 Link 一下,不會浪費(fèi)編譯時間。 (一些比較穩(wěn)定的工具類,或者很穩(wěn)定的功能模塊)

1.2.framework

framework并不是庫,它只是一種打包方式,它既可以是動態(tài)庫也可以是靜態(tài)庫。將庫的二進(jìn)制文件,頭文件和有關(guān)的資源文件打包到一起,方便管理和分發(fā),和靜態(tài)庫、動態(tài)庫的本質(zhì)是沒有什么關(guān)系。

1.3.靜態(tài)庫

靜態(tài)庫 (靜態(tài)鏈接庫) 以.a.framework結(jié)尾 。之所以稱之為【靜態(tài)庫】,是因?yàn)樵阪溄与A段,會將匯編生成的目標(biāo)文件.o與 引用的庫一起鏈接到可執(zhí)行文件中。對應(yīng)的鏈接方式稱為 靜態(tài)鏈接。 靜態(tài)庫在編譯的時候會被直接拷貝一份,復(fù)制到目標(biāo)程序里,這段代碼在程序里就不會在改變。
舉個??:

靜態(tài)庫.png

靜態(tài)庫優(yōu)點(diǎn):

  • 編譯完成之后,庫文件沒有作用了,目標(biāo)沒有外部依賴,直接可以運(yùn)行
  • 靜態(tài)庫對函數(shù)庫的鏈接是在編譯期完成的。執(zhí)行期間代碼裝載速度快。

靜態(tài)庫缺點(diǎn):

  • 使可執(zhí)行文件變大,浪費(fèi)空間和資源(占空間)
  • 維護(hù)成本高,某一個靜態(tài)庫更新了,所有使用它的應(yīng)用程序都需要重新編譯、發(fā)布給用戶。

1.4.動態(tài)庫

動態(tài)庫(動態(tài)鏈接庫) 以 .dylib或者.framework后綴結(jié)尾。與靜態(tài)庫相反,動態(tài)庫在編譯時并不會被拷貝到目標(biāo)程序中,目標(biāo)程序中只會存儲指向動態(tài)庫的引用。等到程序運(yùn)行時,動態(tài)庫才會被真正加載進(jìn)來。不同的應(yīng)用程序如果調(diào)用相同的庫,那么在內(nèi)存中只需要有一份該共享庫的實(shí)例,避免了空間浪費(fèi)問題。同時也解決了靜態(tài)庫對程序的更新的依賴,用戶只需更新動態(tài)庫即可。
再舉個??:

動態(tài)庫.png

動態(tài)庫優(yōu)點(diǎn):

  • 不會影響目標(biāo)程序的體積,而且同一份庫可以被多個程序使用
  • 動態(tài)性,運(yùn)行時才載入的特性,也可以讓我們隨時對庫進(jìn)行替換,而不需要重新編譯代碼,
  • 動態(tài)庫把對一些庫函數(shù)的鏈接載入推遲到程序運(yùn)行時期

動態(tài)庫缺點(diǎn):

  • 動態(tài)載入會帶來一部分性能損失,使用動態(tài)庫也會使得程序依賴于外部環(huán)境。缺少動態(tài)庫或者庫的版本不正確,就會導(dǎo)致程序無法運(yùn)行
  • 維護(hù)成本高,某一個靜態(tài)庫更新了,所有使用它的應(yīng)用程序都需要重新編譯、發(fā)布給用戶。

2.虛擬內(nèi)存與物理內(nèi)存

2.1.物理內(nèi)存

在早期的計(jì)算機(jī)中 , 并沒有虛擬內(nèi)存的概念 , 任何應(yīng)用被從磁盤中加載到運(yùn)行內(nèi)存中時 , 都是完整加載和按序排列的 .
那么因此 , 就會出現(xiàn)兩個問題 :

使用物理內(nèi)存時遺留的問題

  • 安全問題 : 由于在內(nèi)存條中使用的都是真實(shí)物理地址 , 而且內(nèi)存條中各個應(yīng)用進(jìn)程都是按順序依次排列的 . 那么在 進(jìn)程1 中通過地址偏移就可以訪問到 其他進(jìn)程 的內(nèi)存 .
  • 效率問題 : 隨著軟件的發(fā)展 , 一個軟件運(yùn)行時需要占用的內(nèi)存越來越多 , 但往往用戶并不會用到這個應(yīng)用的所有功能 , 造成很大的內(nèi)存浪費(fèi) , 而后面打開的進(jìn)程往往需要排隊(duì)等待 .
    為了解決上述兩個問題 , 虛擬內(nèi)存應(yīng)運(yùn)而生 .

2.2.虛擬內(nèi)存

引用了虛擬內(nèi)存后 , 在我們進(jìn)程中認(rèn)為自己有一大片連續(xù)的內(nèi)存空間實(shí)際上是虛擬的 , 也就是說從0x000000 ~ 0xffffff我們是都可以訪問的 . 但是實(shí)際上這個內(nèi)存地址只是一個虛擬地址 , 而這個虛擬地址通過一張映射表映射后才可以獲取到真實(shí)的物理地址 .

什么意思呢 ?
實(shí)際上我們可以理解為 , 系統(tǒng)對真實(shí)物理內(nèi)存訪問做了一層限制 , 只有被寫到映射表中的地址才是被認(rèn)可可以訪問的 .
例如 , 虛擬地址 0x000000 ~ 0xffffff這個范圍內(nèi)的任意地址我們都可以訪問 , 但是這個虛擬地址對應(yīng)的實(shí)際物理地址是計(jì)算機(jī)來隨機(jī)分配到內(nèi)存頁上的 .
這里提到了實(shí)際物理內(nèi)存分頁的概念 , 下面會詳細(xì)講述 .
可能大家也有注意到 , 我們在一個工程中獲取的地址 , 同時在另一個工程中去訪問 , 并不能訪問到數(shù)據(jù) , 其原理就是虛擬內(nèi)存 .

3.dyld

dyldthe dynamic link editor),【動態(tài)鏈接器】是蘋果操作系統(tǒng)一個重要部分,在 iOS / macOS 系統(tǒng)中,僅有很少的進(jìn)程只需內(nèi)核就可以完成加載,基本上所有的進(jìn)程都是動態(tài)鏈接的,所以 Mach-O 鏡像文件中會有很多對外部的庫和符號的引用,但是這些引用并不能直接用,在啟動時還必須要通過這些引用進(jìn)行內(nèi)容填充,這個填充的工作就是由 dyld 來完成的。
【動態(tài)鏈接加載器】在系統(tǒng)中以一個用戶態(tài)的可執(zhí)行文件形式存在,一般應(yīng)用程序會在Mach-O文件部分指定一個 LC_LOAD_DYLINKER 的加載命令,此加載命令指定了dyld的路徑,通常它的默認(rèn)值是“/usr/lib/dyld”。系統(tǒng)內(nèi)核在加載Mach-O文件時,會使用該路徑指定的程序作為動態(tài)庫的加載器來加載dylib。

4.共享緩存

dyld加載時,為了優(yōu)化程序啟動,啟用了共享緩存(shared cache)技術(shù)。共享緩存會在進(jìn)程啟動時被dyld映射到內(nèi)存中,之后,當(dāng)任何Mach-O鏡像加載時,dyld首先會檢查該Mach-O鏡像與所需的動態(tài)庫是否在共享緩存中,如果存在,則直接將它在共享內(nèi)存中的內(nèi)存地址映射到進(jìn)程的內(nèi)存地址空間。在程序依賴的系統(tǒng)動態(tài)庫很多的情況下,這種做法對程序啟動性能是有明顯提升的。

二、dyld流程

本文中會涉及到dyld的源碼,所以需要的同學(xué)可以點(diǎn)擊下載dyld 源碼。
首先創(chuàng)建一個工程,并且在main函數(shù)前打上斷點(diǎn):

image.png

運(yùn)行工程,來到斷點(diǎn)處,打開匯編模式:


image.png

我們會發(fā)現(xiàn),在main之前,調(diào)用了start函數(shù),這個start是來自于libdyld.dylib庫執(zhí)行的。

我們再在Viewcontroller中重寫load方法,并在load方法前打上斷點(diǎn):

image.png

運(yùn)行:
image.png

我們會發(fā)現(xiàn):

  • load的斷點(diǎn)比main的斷點(diǎn)先執(zhí)行,那說明+load方法是在main之前執(zhí)行的。
  • load之前,dyld還執(zhí)行了很多流程

接下來我們需要來到dyld源碼中,從_dyld_start開始探索:

1._dyld_start

在源碼中搜索_dyld_start并找到下圖所示的地方:

image.png

會發(fā)現(xiàn)_dyld_start是匯編編寫,可能匯編我們看不懂,但是沒關(guān)系,我們可以看下注釋。
盡管在不同架構(gòu)下有所區(qū)別,但都是會調(diào)用dyldbootstrap 命名空間下的start方法,這和上面的堆棧順序也是相同的。

image.png

1.dyldbootstrap::start:

繼續(xù)搜索start,發(fā)現(xiàn)很多不好找,我們再次搜索start(,結(jié)果如下:

image.png

start函數(shù)源碼如下:

//  This is code to bootstrap dyld.  This work in normally done for a program by dyld and crt.
//  In dyld we have to do this manually.
//
uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],
                const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue)
{

    // Emit kdebug tracepoint to indicate dyld bootstrap has started <rdar://46878536>
    dyld3::kdebug_trace_dyld_marker(DBG_DYLD_TIMING_BOOTSTRAP_START, 0, 0, 0, 0);

    // if kernel had to slide dyld, we need to fix up load sensitive locations
    // we have to do this before using any global variables
    rebaseDyld(dyldsMachHeader);

    // kernel sets up env pointer to be just past end of agv array
    const char** envp = &argv[argc+1];
    
    // kernel sets up apple pointer to be just past end of envp array
    const char** apple = envp;
    while(*apple != NULL) { ++apple; }
    ++apple;

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

#if DYLD_INITIALIZER_SUPPORT
    // run all C++ initializers inside dyld
    runDyldInitializers(argc, argv, envp, apple);
#endif

    // now that we are done bootstrapping dyld, call dyld's main
    uintptr_t appsSlide = appsMachHeader->getSlide();
    return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}

看了下源碼,發(fā)現(xiàn)只有一句關(guān)鍵代碼,就是return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);,調(diào)用了_main函數(shù),所以我們接著看_main。

3、dyld::_main

直接command進(jìn)去,看到_main的源碼,這里面源碼非常多,將重要的代碼提煉出來:

uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
        int argc, const char* argv[], const char* envp[], const char* apple[], 
        uintptr_t* startGlue)
{
    ......

    // 設(shè)置運(yùn)行環(huán)境,可執(zhí)行文件準(zhǔn)備工作
    ......

    // load shared cache   加載共享緩存
    mapSharedCache();
    ......
///加載所有的庫,image就是庫
reloadAllImages:

    ......
    // instantiate ImageLoader for main executable 加載可執(zhí)行文件并生成一個ImageLoader實(shí)例對象
    sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);

    ......

    // load any inserted libraries   加載插入的動態(tài)庫
    if  ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
        for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) 
            loadInsertedDylib(*lib);
    }
        
    // link main executable  鏈接主程序
    link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);

    ......
    // link any inserted libraries   鏈接所有插入的動態(tài)庫
    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);
            }
        }
    }

    ......
    //弱符號綁定
    sMainExecutable->weakBind(gLinkContext);
        
    sMainExecutable->recursiveMakeDataReadOnly(gLinkContext);

    ......
    // run all initializers   執(zhí)行初始化方法
    initializeMainExecutable(); 

    // notify any montoring proccesses that this process is about to enter main()
    notifyMonitoringDyldMain();

    return result;
}


_main中做了非常多的時候事情,我們可以對照我們前面的短線堆棧圖來觀察:

重要函數(shù)解釋:

3.1 sMainExecutable = instantiateFromLoadedImage(....)loadInsertedDylib(...)

這一步 dyld將我們可執(zhí)行文件以及插入的 lib 加載進(jìn)內(nèi)存,生成對應(yīng)的image。
sMainExecutable 對應(yīng)著我們的可執(zhí)行文件,里面包含了我們項(xiàng)目中所有新建的類。
InsertDylib 一些插入的庫,他們配置在全局的環(huán)境變量 sEnv 中,我們可以在項(xiàng)目中設(shè)置環(huán)境變量 DYLD_PRINT_ENV 為1來打印該 sEnv 的值。

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

isCompatibleMachO 是檢查Mach-Osubtype是否是當(dāng)前cpu可以支持;
內(nèi)核會映射到主可執(zhí)行文件中,我們需要為映射到主可執(zhí)行文件的文件,創(chuàng)建ImageLoader
instantiateMainExecutable 就是實(shí)例化可執(zhí)行文件, 這個期間會解析LoadCommand, 這個之后會發(fā)送 dyld_image_state_mapped 通知; 在此方法中,讀取image,然后addImage()到鏡像列表。

3.2:link(sMainExecutable,...)link(image,....)

對上面生成的 Image 進(jìn)行鏈接。這個過程就是將加載進(jìn)來的二進(jìn)制變?yōu)榭捎脿顟B(tài)的過程。其主要做的事有對image進(jìn)行 load(加載),rebase(基地址復(fù)位),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);
}
3.3: recursiveLoadLibraries(context, preflightOnly, loaderRPaths)

遞歸加載所有依賴庫進(jìn)內(nèi)存。

3.4:recursiveRebase(context)

遞歸對自己以及依賴庫進(jìn)行rebase操作。在以前,程序每次加載其在內(nèi)存中的堆?;刂范际且粯拥模@意味著你的方法,變量等地址每次都一樣的,這使得程序很不安全,后面就出現(xiàn)ASLR(Address space layout randomization,地址空間布局隨機(jī)化),程序每次啟動后地址都會隨機(jī)變化,這樣程序里所有的代碼地址都是錯的,需要重新對代碼地址進(jìn)行計(jì)算修復(fù)才能正常訪問。

3.5:recursiveBindWithAccounting(context, forceLazysBound, neverUnload);

對庫中所有nolazy的符號進(jìn)行bind,一般的情況下多數(shù)符號都是lazybind的,他們在第一次使用的時候才進(jìn)行bind。

3.6 initializeMainExecutable()
void initializeMainExecutable()
{
    // record that we've reached this step
    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]);
        }
    }
    
    // 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]);
}

這一步主要是調(diào)用所有imageInitalizer方法進(jìn)行初始化。先為所有插入并鏈接完成的動態(tài)庫執(zhí)行初始化操作
sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);

再為主程序可執(zhí)行文件執(zhí)行初始化操作
sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);

具體流程為:ImageLoader::runInitializers -->ImageLoader::processInitializers --> ImageLoader::recursiveInitialization
詳細(xì)代碼如下:

3.7 ImageLoader::runInitializers
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.imagesAndPaths[0] = { this, this->getPath() };
      // 重點(diǎn)
    processInitializers(context, thisThread, timingInfo, up);
    context.notifyBatch(dyld_image_state_initialized, false);
    mach_port_deallocate(mach_task_self(), thisThread);
    uint64_t t2 = mach_absolute_time();
    fgTotalInitTime += (t2 - t1);
}

調(diào)用processInitializers

3.7 ImageLoader::processInitializers
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) {
      // 重點(diǎn)
        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);
}

在這里,對鏡像表中的所有鏡像執(zhí)行recursiveInitialization ,創(chuàng)建一個未初始化的向上依賴新表。如果依賴中未初始化完畢,則繼續(xù)執(zhí)行processInitializers,直到全部初始化完畢。

3.8 ImageLoader::recursiveInitialization
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);

            // 重點(diǎn) 1: 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);
            
            // 重點(diǎn) 2: initialize this image   
            bool hasInitializers = this->doInitialization(context);

            // 重點(diǎn) 3: let anyone know we finished initializing this image
            fState = dyld_image_state_initialized;
            oldState = fState;
            context.notifySingle(dyld_image_state_initialized, this, NULL);
            
            if ( hasInitializers ) {
                uint64_t t2 = mach_absolute_time();
                timingInfo.addTime(this->getShortName(), t2-t1);
            }
        }
        catch (const char* msg) {
            // this image is not initialized
            fState = oldState;
            recursiveSpinUnLock();
            throw;
        }
    }
    
    recursiveSpinUnLock();
}

recursiveInitialization 函數(shù)中,我們重點(diǎn)關(guān)注

context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);,
doInitialization(context)
context.notifySingle(dyld_image_state_initialized, this, NULL);

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

通知objc我們要初始化這個鏡像,這里 通過 notifySingle函數(shù)對sNotifyObjCInit進(jìn)行函數(shù)調(diào)用。

3.9 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() ) {
        uint64_t t0 = mach_absolute_time();
        
        (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
        
    }
    ......  
}

獲取鏡像文件的真實(shí)地址 【*sNotifyObjCInit)(image->getRealPath(), image->machHeader()】,而sNotifyObjCInit 是通過 registerObjCNotifiers 中傳遞的參數(shù)(_dyld_objc_notify_init)進(jìn)行賦值的。

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的 拉起函數(shù) _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 函數(shù)是供 objc runtime 使用的,當(dāng)objc鏡像被映射,取消映射,和初始化時 被調(diào)用的注冊處理器。我們可以在libobjc.A.dylib庫里,_objc_init函數(shù)中找到其調(diào)用。

/***********************************************************************
* _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(); // 環(huán)境變量
    tls_init();
    static_init(); // C++
    runtime_init(); // runtime 初始化
    exception_init(); // 異常初始化
    cache_init(); // 緩存初始化
    _imp_implementationWithBlock_init(); //

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

runtime初始化后,在_objc_init中注冊了幾個通知,從dyld這里接手了幾個活,其中包括負(fù)責(zé)初始化相應(yīng)依賴庫里的類結(jié)構(gòu),調(diào)用依賴庫里所有的load方法等。
就拿sMainExcuatable來說,它的initializer方法是最后調(diào)用的,當(dāng)initializer方法被調(diào)用前dyld會通知runtime進(jìn)行類結(jié)構(gòu)初始化,然后再通知調(diào)用load方法,這些目前還發(fā)生在main函數(shù)前,但由于lazy bind機(jī)制,依賴庫多數(shù)都是在使用時才進(jìn)行bind,所以這些依賴庫的類結(jié)構(gòu)初始化都是發(fā)生在程序里第一次使用到該依賴庫時才進(jìn)行的。
當(dāng)所有的依賴庫的lnitializer都調(diào)用完后,dyld::main 函數(shù)會返回程序的main()函數(shù)地址,main函數(shù)被調(diào)用,從而代碼來到了我們熟悉的程序入口。
那么_objc_init 又是如何被調(diào)用的呢?

還記得之前ImageLoader::recursiveInitialization中的重點(diǎn)2嗎?我們command進(jìn)去:

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

這里調(diào)用了doModInitFunctions,在 doModInitFunctions之后 會 先執(zhí)行 libSystem_initializer,保證系統(tǒng)庫優(yōu)先初始化完畢,在這里初始化 libdispatch_init,進(jìn)而在_os_object_init中 調(diào)用_objc_init
由于runtimedyld 綁定了回調(diào),當(dāng)image加載到內(nèi)存后,dyld會通知 runtime 進(jìn)行處理
runtime 接手后調(diào)用 map_images 做解析和處理,接下來load_images中調(diào)用 call_load_methods方法,遍歷所有加載進(jìn)來的 Class,按繼承層級依次調(diào)用Class+load方法和其 Category+load方法。
至此,可執(zhí)行文件和動態(tài)庫中所有的符號(Class,Protocol,Selector,IMP,…)都已經(jīng)按格式成功加載到內(nèi)存中,被runtime所管理,在這之后,runtime的那些方法(動態(tài)添加 Class、swizzle 等等才能生效)

dyld流程圖:

image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容