iOS應(yīng)用程序的加載(一)

調(diào)用在main()之前

一般開發(fā)場景中,我們都是把main()函數(shù)作為程序的入口,但是這里探究一下man()函數(shù)開始之前發(fā)生了什么。

程序運行依賴很多的庫和文件,有動態(tài)庫(.so .framwork)還有靜態(tài)庫(.a .lib)以及很多的.h .m文件 他們?nèi)绾渭虞d 到程序中的呢

編譯鏈接
  • 靜態(tài)庫:鏈接時會被完整的復(fù)制到可執(zhí)行文件中,所以如果兩個程序都用了某個靜態(tài)庫,那么每個二進(jìn)制可執(zhí)行文件里面其實都含有這份靜態(tài)庫的代碼。

  • 動態(tài)庫: 鏈接時不復(fù)制,在程序啟動后用動態(tài)加載,然后再決議符號,所以理論上動態(tài)庫只用存在一份,好多個程序都可以動態(tài)鏈接到這個動態(tài)庫上面,達(dá)到了節(jié)省內(nèi)存,還有另外一個好處,由于動態(tài)庫并不綁定到可執(zhí)行程序上,所以我們想升級這個動態(tài)庫就很容易,windows和linux上面一般插件和模塊機制都是這樣實現(xiàn)的。

動態(tài)鏈接庫包括:

  • iOS 中用到的所有系統(tǒng) framework
  • 加載OC runtime方法的libobjc,
  • 系統(tǒng)級別的libSystem,例如libdispatch(GCD)和libsystem_blocks (Block)
    這些放在內(nèi)存中的共享庫文件會在app啟動后交給dyld動態(tài)連接器來進(jìn)行鏈接管理。
image.png

動靜態(tài)鏈接庫以及對于App本身的可執(zhí)行文件而言,都稱為image。
dyld則是以image為單位將這些可執(zhí)行文件加載到app中。

開始在main()之前

首先做一份斷點信息,iOS中-load()方法會在main()函數(shù)開始之前就調(diào)用,在這里設(shè)置斷點可以很好的追蹤主函數(shù)開始之前的調(diào)用信息。同樣的,使用__attribute__ ((constructor))修飾的C++屬性函數(shù)也會在main()之前被加載調(diào)用.

load方法

C++屬性函數(shù)

這里通過函數(shù)名稱大致也能推測出一些比較重要的函數(shù)名稱

  • _dyld_start
  • dyldbootstrap::start
  • dyld::main
  • dyld::initializeMainExecutable
  • imageLoader::****
  • dyld:: notifySingle
  • load_images

dyld的加載流程

很明顯_dyld_start是作為整個流程的起始位置。
打開一份新鮮的objc756源碼,開始查找_dyld_start。

__dyld_start:
    mov     x28, sp
    and     sp, x28, #~15       // force 16-byte alignment of stack
    //...
    // call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
    bl  __ZN13dyldbootstrap5startEPKN5dyld311MachOLoadedEiPPKcS3_Pm
    //... 

_dyld_start的源碼是用匯編寫的,但是只需要看注釋就能明白多個版本的下的_dyld_start都會來到dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)這個函數(shù)。

來看看dyldbootstrap::start內(nèi)部實現(xiàn):

uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],
                const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue){
    dyld3::kdebug_trace_dyld_marker(DBG_DYLD_TIMING_BOOTSTRAP_START, 0, 0, 0, 0);

    rebaseDyld(dyldsMachHeader);
    const char** envp = &argv[argc+1];
    const char** apple = envp;
    while(*apple != NULL) { ++apple; }
    ++apple;
    __guard_setup(apple);
    uintptr_t appsSlide = appsMachHeader->getSlide();
    return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}

出了一些參數(shù)的處理 最重要的一步就是_main()這個函數(shù)了。

_main()函數(shù)

_main()函數(shù)代碼量很大,分了很多功能,主要完成了上下文的建立,主程序初始化成ImageLoader對象,加載共享的系統(tǒng)動態(tài)庫,加載依賴的動態(tài)庫,鏈接動態(tài)庫,初始化主程序,返回主程序main()函數(shù)地址。

加載共享緩存

mapSharedCache()負(fù)責(zé)將系統(tǒng)中的共享動態(tài)庫加載進(jìn)內(nèi)存空間,比如UIKit就是動態(tài)共享庫,這也是不同的App之間能夠?qū)崿F(xiàn)動態(tài)庫共享的機制。不同App間訪問的共享庫最終都映射到了同一塊物理內(nèi)存,從而實現(xiàn)了共享動態(tài)庫。
內(nèi)部會調(diào)用loadDyldCache()
mapSharedCache()的基本邏輯就是:

  1. 先判斷共享動態(tài)庫是否已經(jīng)映射到內(nèi)存中了。
  2. 如果已經(jīng)存在,則直接返回。
  3. 否則打開緩存文件,并將共享動態(tài)庫映射到內(nèi)存中。
load shared cache
checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
    if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
#if TARGET_OS_SIMULATOR
        if ( sSharedCacheOverrideDir)
            mapSharedCache();
#else
        mapSharedCache();
#endif
    }

生成ImageLoader對象

動靜態(tài)庫以及程序自己的.o文件都是以image的形式加載的。
ImageLoader本身是一個抽象類,用于幫助加載特定格式的可執(zhí)行文件。例如ImageLoaderMachO繼承自ImageLoader.
instantiateFromLoadedImage()會實例化一個ImageLoader對象,然后調(diào)用instantiateMainExecutable()加載文件生成image并進(jìn)行鏈接。

image.png

APP啟動過程中,相關(guān)的庫和主程序都被加載成ImageLoader對象
最后返回一個ImageLoaderMachO對象用于訪問所有的images對象

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

加載所有插入的庫 loadInsertedDylib

遍歷sEnv.DYLD_INSERT_LIBRARIES這個環(huán)境變量中的庫,調(diào)用loadInsertedDylib來加載庫。

// load any inserted libraries
        if  ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
            for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) 
                loadInsertedDylib(*lib);
        }

鏈接主程序

這一步最終調(diào)用的還是ImageLoader::link,內(nèi)部會調(diào)用recursiveLoadLibraries遞歸加載動態(tài)庫。
注意,先鏈接主程序,然后鏈接所有加載的庫文件。

link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
        sMainExecutable->setNeverUnloadRecursive();
        if ( sMainExecutable->forceFlat() ) {
            gLinkContext.bindFlat = true;
            gLinkContext.prebindUsage = ImageLoader::kUseNoPrebinding;
        }
// 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);
                }
            }
        }

執(zhí)行初始化方法 initializeMainExecutable

initializeMainExecutable()里面先對動態(tài)庫進(jìn)行runInitializers(),然后才對主程序進(jìn)行runInitializers()。
runInitializers()內(nèi)部調(diào)用了Imageloader::recursiveInitialization
Imageloader::recursiveInitialization里面調(diào)用了如下內(nèi)容:

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

我們需要注意這幾行代碼:

context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
// initialize this image
bool hasInitializers = this->doInitialization(context);
context.notifySingle(dyld_image_state_initialized, this, NULL);
doInitialization()

doInitialization(context);這里開始做images初始化工作。
注意,在這個函數(shù)里面有一個判斷:

if ( ! dyld::gProcessInfo->libSystemInitialized ) {
  // libSystem initializer must run first
  dyld::throwf("-init function in image (%s) that does not link with libSystem.dylib\n", this->getPath());
}

那就是在所有的images的初始化中,libSystem必須放在第一位。
既然libSystem這么重要,那么就看看libSystem的初始化方法:

static void
libSystem_initializer(int argc,const char* argv[],const char* envp[],const char* apple[],const struct ProgramVars* vars){   
    //內(nèi)核初始化
    __libkernel_init(&libkernel_funcs, envp, apple, vars);
    //平臺初始化
    __libplatform_init(NULL, envp, apple, vars);
    //線程相關(guān)初始化
    __pthread_init(&libpthread_funcs, envp, apple, vars);
    _libc_initializer(&libc_funcs, envp, apple, vars);
    __malloc_init(apple);
    //dyld的初始化,上面dyld_start之后并不代表就開始初始化。
    _dyld_initializer();
    //這里是重點
    libdispatch_init();
    _libxpc_initializer();
}

_objc_init()

libdispatch_init()這個庫的初始化應(yīng)該最熟悉了,內(nèi)部會對GCD底層進(jìn)行一些初始化工作,以及會調(diào)用一個對我們當(dāng)前探索流程很重要的一個初始化函數(shù)_os_object_init(),而_os_object_init內(nèi)部則會直接調(diào)用_objc_init()這個函數(shù),來完成_dyld_objc_notify_register(&map_images, load_images, unmap_image);函數(shù)注冊。

void
libdispatch_init(void)
{
    //...
    _dispatch_hw_config_init();
    _dispatch_time_init();
    _dispatch_vtable_init();
    _os_object_init();
    _voucher_init();
    _dispatch_introspection_init();
}
從_os_object_init()內(nèi)部調(diào)用來到這里
void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

notifySingle()

doInitialization()完成前后,都會有一個通知信號notifySingle()
。
找到它的源碼位置

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

只發(fā)現(xiàn)了(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());這個函數(shù)的調(diào)用。
先看看它的定義:

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

全局搜索它的賦值,只有一處地方能找到:

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;
    //...
}

那么說明一定是在dyld的某個地方,調(diào)用了registerObjCNotifiers,然后給了sNotifyObjCInit賦值。
同樣,全局找到了唯一的調(diào)用位置:

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

那么看到這里就能明白,這里之所以能調(diào)用*sNotifyObjCInit (),就是因為在doInitialization()中完成了libSystem_initializer()的調(diào)用,而libSystem初始化的時候內(nèi)部調(diào)用了objc_init()函數(shù),完成了_dyld_objc_notify_register(&map_images, load_images, unmap_image);函數(shù)的注冊。
因此,在doInitialization()執(zhí)行完畢之后,發(fā)送一條完成的通知notifySingle(),并執(zhí)行回調(diào)函數(shù)。

dyld總體流程總結(jié):
_dyld_start開始

  1. 環(huán)境變量的配置
  2. 共享緩存
  3. 主程序初始化
  4. 加入動態(tài)庫
  5. link主程序
  6. link動態(tài)庫
  7. main()
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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