iOS底層-16:應(yīng)用程序的加載

今天我們研究的是應(yīng)用程序的加載過程驯遇,先做一下準(zhǔn)備工作。
新建一個(gè)iphone工程侧甫,添加下面代碼:

  • ViewController.m
+ (void)load{
    NSLog(@"%s",__func__);
}
  • main.m
int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    
    NSLog(@"333333");
    
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

__attribute__((constructor)) void lyFunc(){
    printf("來了 : %s \n",__func__);
}

運(yùn)行查看這三個(gè)打印的順序:

+[ViewController load]
來了 : lyFunc 
333333

程序加載過程這些方法的調(diào)用順序是:
load ——> C++ ——> main
main函數(shù)作為程序的入口,為什確實(shí)最后執(zhí)行的,我們需要研究的是在main函數(shù)之前诺核,程序到底做了什么抄肖?

程序編譯過程

編譯過程

預(yù)編譯:預(yù)編譯又稱為預(yù)處理,主要做些代碼文本的替換工作窖杀,處理#開頭的指令漓摩。比如拷貝#include、#import包含的文件代碼入客,#define宏定義的替換管毙,條件編譯等,就是為編譯做預(yù)備工作的階段桌硫,主要處理#開始的預(yù)編譯指令锅风。生成.i文件

編譯:將高級(jí)語言轉(zhuǎn)換為機(jī)器能識(shí)別的匯編語言。生成.s文件

匯編:將匯編文件轉(zhuǎn)換成機(jī)器碼文件鞍泉。生成.o文件

鏈接:對(duì).o文件中引用的其他的庫進(jìn)行引入皱埠,生成可執(zhí)行文件。

動(dòng)態(tài)庫 和 靜態(tài)庫

動(dòng)靜態(tài)庫鏈接
  • 靜態(tài)庫:在鏈接階段咖驮,將可匯編生成目標(biāo)程序與它所引用的庫一起鏈接打包到可執(zhí)行文件中边器。此時(shí)靜態(tài)庫就不會(huì)改變了,靜態(tài)庫是直接拷貝托修,復(fù)制到目標(biāo)程序忘巧。

    • 優(yōu)點(diǎn):編譯完成,庫文件就沒有作用了睦刃,運(yùn)行時(shí)可以直接使用
    • 缺點(diǎn):不同的靜態(tài)庫中引用了相同的文件砚嘴,如上圖中的B、D涩拙,他就會(huì)拷貝兩份相同的庫文件际长,導(dǎo)致目標(biāo)程序體積增大,對(duì)性能兴泥、內(nèi)存會(huì)有一定的影響工育。
  • 動(dòng)態(tài)庫:編譯時(shí)相同的庫并不會(huì)拷貝到目標(biāo)程序,而是在目標(biāo)程序載入的時(shí)候搓彻,把那些相同的庫用一份共享實(shí)例加載進(jìn)來如绸。

    • 優(yōu)點(diǎn)
      • 減少打包的體積:共享內(nèi)存,節(jié)約資源
      • 更新動(dòng)態(tài)庫可以直接更新程序:由于運(yùn)行時(shí)才載入的特性旭贬,因此可以隨時(shí)對(duì)下層庫進(jìn)行更換怔接,而我們的代碼卻不用改變
    • 缺點(diǎn):動(dòng)態(tài)載入會(huì)帶來一部分性能損失,如果當(dāng)前環(huán)境缺少動(dòng)態(tài)庫稀轨,或者版本不正確扼脐,會(huì)導(dǎo)致程序無法運(yùn)行

dyld加載流程

dyld是蘋果的動(dòng)態(tài)連接器,加載到程序中的動(dòng)態(tài)庫靶端,主要靠dyld來鏈接管理谎势。

下面我們通過源碼分析dyld的加載流程凛膏,首先下載一份dyld源碼,這篇文章使用的是750.6脏榆。

拿到了源碼猖毫,那么我們從哪里開始分析呢?借助一下上面的工程须喂,在load方法處加上斷點(diǎn)吁断,打印堆棧信息:

  • bt

image.png

我們可以看到堆棧的第一個(gè)信息是_dyld_start,也可以直接在左邊窗口查看坞生。

  • 打開dyld源碼仔役,搜索_dyld_start找到__arm64__環(huán)境下的源碼

往下查找發(fā)現(xiàn)它調(diào)起了dyldbootstrap::start方法

  • 搜索dyldbootstrap找到start方法
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);
}

其核心就是dyld::_main,返回調(diào)用了dyldmain函數(shù)是己。從左邊的堆棧又兵,也可以清晰的看到。其中macho_header就是Mach-o的頭部卒废。

  • 點(diǎn)擊跳轉(zhuǎn)到dyld::_main函數(shù)
    我們發(fā)現(xiàn)這個(gè)main函數(shù)有600多行代碼沛厨,在這里就不詳細(xì)解釋,感興趣的同學(xué)可以自行研究摔认。_main函數(shù)主要做了以下事情:

1.環(huán)境變量配置
2.共享緩存:(UIKit逆皮、CoreFoundation等)
3.主程序的初始化
4.加入動(dòng)態(tài)庫
5.link主程序
6.link動(dòng)態(tài)庫
7.綁定弱引用
8.initialize初始化
9.main()

-環(huán)境變量配置

  • 共享緩存

  • 主程序初始化

  • 加載動(dòng)態(tài)庫

  • link主程序

  • link動(dòng)態(tài)庫

  • 弱引用綁定主程序

  • run initialize初始化方法

    image.png

  • 進(jìn)入main()函數(shù)

    image.png

initialize

我們主要分析第八步,看看run all initializers里面做了什么参袱?

  • 點(diǎn)擊進(jìn)入initializeMainExecutable源碼

  • 點(diǎn)擊runInitializers

    image.png

    主要是其中的processInitializers方法

  • 點(diǎn)擊進(jìn)入processInitializers

  • 進(jìn)入recursiveInitialization源碼


    重點(diǎn)查看notifySingledoInitialization方法

notifySingle
  • 進(jìn)入notifySingle源碼
    根據(jù)傳入?yún)?shù)dyld_image_state_dependents_initializeddyld_image_state_initialized找到下段代碼

  • sNotifyObjCInit
    直接搜索sNotifyObjCInit沒有找到相應(yīng)的源碼电谣,只找到一段賦值代碼。我們繼續(xù)跟registerObjCNotifiers流程

  • 搜索_dyld_objc_notify_register
    只找到一處調(diào)用的地方抹蚀,接著我們搜索查看_dyld_objc_notify_register剿牺,并沒有發(fā)現(xiàn)調(diào)用的地方。_dyld_objc_notify_register實(shí)際上是在objc源碼中調(diào)用的.

  • objc-781源碼中搜索_dyld_objc_notify_register


    _objc_init中找到况鸣,從這可知賦給sNotifyObjCInit的值其實(shí)就是load_images

  • 接著查看load_images源碼


    主要放大是call_load_methods牢贸,調(diào)用所有+load方法

  • 點(diǎn)擊call_load_methods

void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();

    do {
        //遍歷所有的類,調(diào)用 + load 方法
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            call_class_loads();
        }
        //加載所有的category
        // 2. Call category +loads ONCE
        more_categories = call_category_loads();

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}

看到這里想必大家都已經(jīng)明白了镐捧,sNotifyObjCInit實(shí)際上調(diào)用的是libobjc里的load_images方法,作用是調(diào)用所有類的+load方法和加載category臭增。

doInitialization
  • 點(diǎn)擊進(jìn)入doInitialization

這里的doImageInitdoModInitFunctions這兩個(gè)方法是關(guān)鍵懂酱。我們先看doImageInit

  • 點(diǎn)擊進(jìn)入doImageInit


    這里主要進(jìn)行動(dòng)態(tài)庫的初始化工作誊抛,值得注意的是libSystem.dylib必須第一個(gè)初始化列牺。

  • 點(diǎn)擊進(jìn)入doModInitFunctions


    這里主要是加載C++方法,我們可以在上面的demo中打一個(gè)斷點(diǎn)進(jìn)行驗(yàn)證拗窃。

    C++方法調(diào)用之前瞎领,確實(shí)調(diào)用了doModInitFunctions

至此我們整個(gè)的runInitializers執(zhí)行完畢了泌辫。大致流程是initializeMainExecutable ——> runInitializers ——> processInitializers ——> processInitializers ——> recursiveInitialization ——> notifySingle ——> load_images ——> call_load_methods ——> call_class_loads ——> call_category_loads ——> doInitialization ——> doImageInit ——> doModInitFunctions ——> main()

不知道大家有沒有注意到notifySingle調(diào)用load_images時(shí),必須先由_objc_init ——> _dyld_objc_notify_register ——> registerObjCNotifiers中才給了notifySingle賦值load_images

這個(gè)賦值必須要在notifySingle調(diào)用之前九默,那么_objc_init在什么時(shí)候調(diào)用了呢震放?這個(gè)我們并沒有找到。

探索_objc_init調(diào)用時(shí)機(jī)

我們?cè)趏bjc-781源碼中_objc_init方法打上斷點(diǎn)驼修。查看堆棧情況


我們發(fā)現(xiàn)在_objc_init之前殿遂,調(diào)用了libSystem_initializerlibdispatch_initializer方法。


接下來我們要分別查看libSystemlibdispatch的源碼乙各。開源庫地址我下載的是Libsystem-1281.100.1libdispatch-1173.100.2

libSystem

  • 打開libSystem源碼墨礁,搜索libSystem_initializer
static void
libSystem_initializer(int argc,
              const char* argv[],
              const char* envp[],
              const char* apple[],
              const struct ProgramVars* vars)
{

    _libSystem_ktrace0(ARIADNE_LIFECYCLE_libsystem_init | DBG_FUNC_START);

    __libkernel_init(&libkernel_funcs, envp, apple, vars);
    _libSystem_ktrace_init_func(KERNEL);

    __libplatform_init(NULL, envp, apple, vars);
    _libSystem_ktrace_init_func(PLATFORM);

    __pthread_init(&libpthread_funcs, envp, apple, vars);
    _libSystem_ktrace_init_func(PTHREAD);

    _libc_initializer(&libc_funcs, envp, apple, vars);
    _libSystem_ktrace_init_func(LIBC);

    // TODO: Move __malloc_init before __libc_init after breaking malloc's upward link to Libc
    __malloc_init(apple);
    _libSystem_ktrace_init_func(MALLOC);

#if TARGET_OS_OSX
    /* <rdar://problem/9664631> */
    __keymgr_initializer();
    _libSystem_ktrace_init_func(KEYMGR);
#endif

    // No ASan interceptors are invoked before this point. ASan is normally initialized via the malloc interceptor:
    // _dyld_initializer() -> tlv_load_notification -> wrap_malloc -> ASanInitInternal

    _dyld_initializer();
    _libSystem_ktrace_init_func(DYLD);

    libdispatch_init();
    _libSystem_ktrace_init_func(LIBDISPATCH);

       ....已省略部分代碼.....
}

發(fā)現(xiàn)在其中調(diào)用了__malloc_init、 _dyld_initializer耳峦、 libdispatch_init恩静。

libdispatch

  • 打開libdispatch源碼,搜索libdispatch_init
void
libdispatch_init(void)
{
      ...已省略部分代碼....
    dispatch_assert(sizeof(struct dispatch_apply_s) <=
            DISPATCH_CONTINUATION_SIZE);

    if (_dispatch_getenv_bool("LIBDISPATCH_STRICT", false)) {
        _dispatch_mode |= DISPATCH_MODE_STRICT;
    }

#if DISPATCH_USE_THREAD_LOCAL_STORAGE
    _dispatch_thread_key_create(&__dispatch_tsd_key, _libdispatch_tsd_cleanup);
#else
    _dispatch_thread_key_create(&dispatch_priority_key, NULL);
    _dispatch_thread_key_create(&dispatch_r2k_key, NULL);
    _dispatch_thread_key_create(&dispatch_queue_key, _dispatch_queue_cleanup);
    _dispatch_thread_key_create(&dispatch_frame_key, _dispatch_frame_cleanup);
    _dispatch_thread_key_create(&dispatch_cache_key, _dispatch_cache_cleanup);
    _dispatch_thread_key_create(&dispatch_context_key, _dispatch_context_cleanup);
    _dispatch_thread_key_create(&dispatch_pthread_root_queue_observer_hooks_key,
            NULL);
    _dispatch_thread_key_create(&dispatch_basepri_key, NULL);
#if DISPATCH_INTROSPECTION
    _dispatch_thread_key_create(&dispatch_introspection_key , NULL);
#elif DISPATCH_PERF_MON
    _dispatch_thread_key_create(&dispatch_bcounter_key, NULL);
#endif
    _dispatch_thread_key_create(&dispatch_wlh_key, _dispatch_wlh_cleanup);
    _dispatch_thread_key_create(&dispatch_voucher_key, _voucher_thread_cleanup);
    _dispatch_thread_key_create(&dispatch_deferred_items_key,
            _dispatch_deferred_items_cleanup);
#endif

#if DISPATCH_USE_RESOLVERS // rdar://problem/8541707
    _dispatch_main_q.do_targetq = _dispatch_get_default_queue(true);
#endif

    _dispatch_queue_set_current(&_dispatch_main_q);
    _dispatch_queue_set_bound_thread(&_dispatch_main_q);

#if DISPATCH_USE_PTHREAD_ATFORK
    (void)dispatch_assume_zero(pthread_atfork(dispatch_atfork_prepare,
            dispatch_atfork_parent, dispatch_atfork_child));
#endif
    _dispatch_hw_config_init();
    _dispatch_time_init();
    _dispatch_vtable_init();
    _os_object_init();
    _voucher_init();
    _dispatch_introspection_init();
}

最后我們看到libdispatch調(diào)用了_os_object_init蹲坷。

  • 繼續(xù)搜索_os_object_init

    果然_objc_init就在其中驶乾,所有的猜想都得到了驗(yàn)證。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末冠句,一起剝皮案震驚了整個(gè)濱河市轻掩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌懦底,老刑警劉巖唇牧,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異聚唐,居然都是意外死亡丐重,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門杆查,熙熙樓的掌柜王于貴愁眉苦臉地迎上來扮惦,“玉大人,你說我怎么就攤上這事亲桦⊙旅郏” “怎么了?”我有些...
    開封第一講書人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵客峭,是天一觀的道長豫领。 經(jīng)常有香客問我,道長舔琅,這世上最難降的妖魔是什么等恐? 我笑而不...
    開封第一講書人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上课蔬,老公的妹妹穿的比我還像新娘囱稽。我一直安慰自己,他們只是感情好二跋,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開白布战惊。 她就那樣靜靜地躺著,像睡著了一般同欠。 火紅的嫁衣襯著肌膚如雪样傍。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,246評(píng)論 1 308
  • 那天铺遂,我揣著相機(jī)與錄音衫哥,去河邊找鬼。 笑死襟锐,一個(gè)胖子當(dāng)著我的面吹牛撤逢,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播粮坞,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼蚊荣,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了莫杈?” 一聲冷哼從身側(cè)響起互例,我...
    開封第一講書人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎筝闹,沒想到半個(gè)月后媳叨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡关顷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年糊秆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片议双。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡痘番,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出平痰,到底是詐尸還是另有隱情汞舱,我是刑警寧澤,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布宗雇,位于F島的核電站兵拢,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏逾礁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望嘹履。 院中可真熱鬧腻扇,春花似錦、人聲如沸砾嫉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽焕刮。三九已至舶沿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間配并,已是汗流浹背括荡。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留溉旋,地道東北人畸冲。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像观腊,于是被迫代替她去往敵國和親邑闲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 本文的目的主要是分析dyld的加載流程 首先我們先運(yùn)行個(gè)代碼 來引入我們今天的主題~~ 運(yùn)行結(jié)果: 運(yùn)行程序梧油,查看...
    北京_小海閱讀 570評(píng)論 1 3
  • 前言 我們都知道苫耸,每個(gè)項(xiàng)目都會(huì)有一個(gè)入口 main() 函數(shù)儡陨,是不是應(yīng)用程序加載時(shí)第一個(gè)調(diào)用的就是 main 函數(shù)...
    遠(yuǎn)方竹葉閱讀 673評(píng)論 0 6
  • 首先我們來看一個(gè)案例迄委,viewController添加load方法叙身,main.m文件添加一個(gè)c++函數(shù)信轿,思考一下l...
    HardCabbage閱讀 531評(píng)論 0 0
  • 1. 理論基礎(chǔ)速成 1.1 靜態(tài)庫與動(dòng)態(tài)庫 庫是已寫好的倘核、供使用的 可復(fù)用代碼紧唱,每個(gè)程序都要依賴很多基礎(chǔ)的底層庫漏益。...
    賣饃工程師閱讀 687評(píng)論 0 5
  • 表情是什么绰疤,我認(rèn)為表情就是表現(xiàn)出來的情緒。表情可以傳達(dá)很多信息癣猾。高興了當(dāng)然就笑了纷宇,難過就哭了龙屉。兩者是相互影響密不可...
    Persistenc_6aea閱讀 125,305評(píng)論 2 7