iOS-OC底層10:dyld加載流程分析

前沿

我們實現ViewController的+(void)load方法萌狂,在main函數中添加c++方法

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

//c++方法
__attribute__((constructor)) void kcFunc(){
    printf("來了 : %s \n",__func__);
}
打印結果
2020-09-29 15:13:42.259577+0800 002-應用程加載分析[81605:302871] +[ViewController load]
來了 : kcFunc 
2020-09-29 15:13:42.268476+0800 002-應用程加載分析[81605:302871] 1223333

我們可以看出先load然后c++竿痰,最后main方法凡傅,接下來我們圍繞這個打印順序就行分析角寸。
我們在c++方法處打斷點专筷,然后查看堆棧信息如下

* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
  * frame #0: 0x0000000102858174 002-應用程加載分析`kcFunc at main.m:30:5
    frame #1: 0x00000001028786d9 dyld_sim`ImageLoaderMachO::doModInitFunctions(ImageLoader::LinkContext const&) + 513
    frame #2: 0x0000000102878ace dyld_sim`ImageLoaderMachO::doInitialization(ImageLoader::LinkContext const&) + 40
    frame #3: 0x0000000102873868 dyld_sim`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 456
    frame #4: 0x0000000102871d2c dyld_sim`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 188
    frame #5: 0x0000000102871dcc dyld_sim`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 82
    frame #6: 0x0000000102866270 dyld_sim`dyld::initializeMainExecutable() + 199
    frame #7: 0x000000010286a1bb dyld_sim`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 3662
    frame #8: 0x00000001028651cd dyld_sim`start_sim + 122
    frame #9: 0x0000000108e4e85c dyld`dyld::useSimulatorDyld(int, macho_header const*, char const*, int, char const**, char const**, char const**, unsigned long*, unsigned long*) + 2308
    frame #10: 0x0000000108e4c4f4 dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 837
    frame #11: 0x0000000108e47227 dyld`dyldbootstrap::start(dyld3::MachOLoaded const*, int, char const**, dyld3::MachOLoaded const*, unsigned long*) + 453
    frame #12: 0x0000000108e47025 dyld`_dyld_start + 37

編譯過程

我們寫的oc代碼怎么能顯示到手機上呢于未?


image.png

我們寫的hm文件先要經過預編譯撕攒,預編譯主要是語法分析,語義分析烘浦,編譯是生成中間代碼然后匯編鏈接到可執(zhí)行文件

iOS的庫

靜態(tài)庫通常以.a抖坪,.lib或者.framework結尾,動態(tài)庫以.tbd闷叉,.so擦俐,.framework結尾
1.靜態(tài)庫:鏈接時,靜態(tài)庫會被完整的復制到可執(zhí)行文件中握侧,被多次使用就會有多份冗余拷貝
2.動態(tài)庫:鏈接時不復制蚯瞧,程序運行時由系統(tǒng)動態(tài)加載到內存嘿期,供程序調用,系統(tǒng)只加載一次埋合,多個程序公用备徐,節(jié)省內存


image.png

我們通過一個圖來區(qū)分靜態(tài)庫和動態(tài)庫
在靜態(tài)庫中B和D分別被完整的復制到可執(zhí)行文件,而動態(tài)庫中則由系統(tǒng)動態(tài)加載到內存甚颂。

dyld加載流程

我們要先在官網上下載一份dyld源碼

概念什么是dyld

dyld 是英文 the dynamic link editor 的簡寫蜜猾,翻譯過來就是動態(tài)鏈接器,是蘋果操作系統(tǒng)的一個重要的組成部分振诬。在 iOS/Mac OSX 系統(tǒng)中蹭睡,僅有很少量的進程只需要內核就能完成加載,基本上所有的進程都是動態(tài)鏈接的贷揽,所以 Mach-O 鏡像文件中會有很多對外部的庫和符號的引用棠笑,但是這些引用并不能直接用梦碗,在啟動時還必須要通過這些引用進行內容的填補禽绪,這個填補工作就是由 動態(tài)鏈接器dyld 來完成的,也就是符號綁定洪规。動態(tài)鏈接器dyld 在系統(tǒng)中以一個用戶態(tài)的可執(zhí)行文件形式存在印屁,一般應用程序會在 Mach-O 文件部分指定一個 LC_LOAD_DYLINKER 的加載命令,此加載命令指定了 dyld 的路徑斩例,通常它的默認值是 /usr/lib/dyld 雄人。系統(tǒng)內核在加載 Mach-O 文件時,都需要用 dyld(位于 /usr/lib/dyld )程序進行鏈接念赶。

總括加載流程

image.png

_dyld_start源碼

__dyld_start:
...
  //調用dyldbootstrap:start函數
    # call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
    subl    $L__dyld_start_picbase-__dyld_start, %ebx # ebx = &__dyld_start
    subl    $0x1000, %ebx   # ebx = load address of dyld
    movl    %edx,(%esp) # param1 = app_mh
    movl    4(%ebp),%eax
    movl    %eax,4(%esp)    # param2 = argc
    lea     8(%ebp),%eax
    movl    %eax,8(%esp)    # param3 = argv
    movl    %ebx,12(%esp)   # param4 = dyld load address
    lea 28(%esp),%eax
    movl    %eax,16(%esp)   # param5 = &startGlue
    call    __ZN13dyldbootstrap5startEPKN5dyld311MachOLoadedEiPPKcS3_Pm
    movl    28(%esp),%edx
    cmpl    $0,%edx
    jne Lnew
...
//調用main函數
    # LC_MAIN case, set up stack for call to main()
Lnew:   movl    4(%ebp),%ebx
    movl    %ebx,(%esp) # main param1 = argc
    leal    8(%ebp),%ecx
    movl    %ecx,4(%esp)    # main param2 = argv
    leal    0x4(%ecx,%ebx,4),%ebx
    movl    %ebx,8(%esp)    # main param3 = env
...

_dyld_start=====>dyldbootstrap::start======>main
dyldbootstrap::start最主要的就是調用dyld::_main
dyldbootstrap::start===>dyld::_main

dyld::_main流程

Dyld.png

我們可以根據注釋去了解大致流程础钠,我們最主要研究的是initializeMainExecutable函數

initializeMainExecutable

initializeMainExecutable=====>runInitializers=====>processInitializers=======>recursiveInitialization

recursiveInitialization

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

context.notifySingle中
(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
我們追蹤sNotifyObjCInit發(fā)現賦值

_dyld_objc_notify_register====> dyld::registerObjCNotifiers(mapped, init, unmapped);==>(sNotifyObjCInit = init;)
在objc源碼中搜索_dyld_objc_notify_register,發(fā)現此方法是在_objc_init中調用的
并且sNotifyObjCInit的回調實現為
調用load方法

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
        didInitialAttachCategories = true;
        loadAllCategories();
    }

    // Return without taking locks if there are no +load methods here.
    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}

我們在_objc_init打斷點

* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
  * frame #0: 0x00000001002d461d libobjc.A.dylib`_objc_init at objc-os.mm:920:5
    frame #1: 0x000000010042b0bc libdispatch.dylib`_os_object_init + 13
    frame #2: 0x000000010043bafc libdispatch.dylib`libdispatch_init + 282
    frame #3: 0x00007fff6ad08791 libSystem.B.dylib`libSystem_initializer + 220
    frame #4: 0x000000010002f1d3 dyld`ImageLoaderMachO::doModInitFunctions(ImageLoader::LinkContext const&) + 535
    frame #5: 0x000000010002f5de dyld`ImageLoaderMachO::doInitialization(ImageLoader::LinkContext const&) + 40
    frame #6: 0x0000000100029ffb dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 493
    frame #7: 0x0000000100029f66 dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 344
    frame #8: 0x00000001000280b4 dyld`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 188
    frame #9: 0x0000000100028154 dyld`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 82
    frame #10: 0x0000000100016662 dyld`dyld::initializeMainExecutable() + 129
    frame #11: 0x000000010001bbba dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 6667
    frame #12: 0x0000000100015227 dyld`dyldbootstrap::start(dyld3::MachOLoaded const*, int, char const**, dyld3::MachOLoaded const*, unsigned long*) + 453
    frame #13: 0x0000000100015025 dyld`_dyld_start + 37

系統(tǒng)會優(yōu)先初始化libSystem叉谜,libdispatch旗吁,libdispatch libobjc,然后注冊一個通知sNotifyObjCInit停局,當我們初始化主程序或者我們寫的庫時很钓,會發(fā)通知讓先加載該哭的load方法,然后doInitialization進行調用c++方法


dyld流程分析圖.png
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末董栽,一起剝皮案震驚了整個濱河市码倦,隨后出現的幾起案子,更是在濱河造成了極大的恐慌锭碳,老刑警劉巖袁稽,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異擒抛,居然都是意外死亡推汽,警方通過查閱死者的電腦和手機蝗柔,發(fā)現死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來民泵,“玉大人癣丧,你說我怎么就攤上這事≌蛔保” “怎么了胁编?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長鳞尔。 經常有香客問我嬉橙,道長,這世上最難降的妖魔是什么寥假? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任市框,我火速辦了婚禮,結果婚禮上糕韧,老公的妹妹穿的比我還像新娘枫振。我一直安慰自己,他們只是感情好萤彩,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布粪滤。 她就那樣靜靜地躺著,像睡著了一般雀扶。 火紅的嫁衣襯著肌膚如雪杖小。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天愚墓,我揣著相機與錄音予权,去河邊找鬼。 笑死浪册,一個胖子當著我的面吹牛扫腺,可吹牛的內容都是我干的。 我是一名探鬼主播议经,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼斧账,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了煞肾?” 一聲冷哼從身側響起咧织,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎籍救,沒想到半個月后习绢,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年闪萄,在試婚紗的時候發(fā)現自己被綠了梧却。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡败去,死狀恐怖放航,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情圆裕,我是刑警寧澤广鳍,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站吓妆,受9級特大地震影響赊时,放射性物質發(fā)生泄漏。R本人自食惡果不足惜行拢,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一祖秒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧舟奠,春花似錦竭缝、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽握巢。三九已至晕鹊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間暴浦,已是汗流浹背溅话。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留歌焦,地道東北人飞几。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像独撇,于是被迫代替她去往敵國和親屑墨。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345