系統(tǒng)底層源碼分析(20)——dyld加載流程

  • 概念

Mach-O是一種文件格式,是mac上可執(zhí)行文件的格式样悟。編寫(xiě)的CC++庭猩、swift窟她、OC,最終編譯鏈接生成Mach-O可執(zhí)行文件蔼水。

鏈接的共用庫(kù)分為靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù):靜態(tài)庫(kù)是編譯時(shí)鏈接的庫(kù)震糖,需要鏈接進(jìn)你的 Mach-O 文件里,如果需要更新就要重新編譯一次趴腋,無(wú)法動(dòng)態(tài)加載和更新吊说;而動(dòng)態(tài)庫(kù)是運(yùn)行時(shí)鏈接的庫(kù)论咏,使用 dyld 就可以實(shí)現(xiàn)動(dòng)態(tài)加載。

dyld(the dynamic link editor)是蘋(píng)果的動(dòng)態(tài)鏈接器颁井,是蘋(píng)果操作系統(tǒng)的一個(gè)重要組成部分厅贪,在應(yīng)用被編譯打包成可執(zhí)行文件格式的 Mach-O 文件之后,交由 dyld 負(fù)責(zé)鏈接雅宾,加載程序 养涮。

編譯過(guò)程
  • 加載流程

為了輔助探索底層,我們可以用符號(hào)斷點(diǎn)看一下堆棧信息:

  1. 首先調(diào)用的是_dyld_start眉抬,我們馬上進(jìn)入dyld源碼開(kāi)始探究:
  1. 然后從匯編跳轉(zhuǎn)調(diào)用start
uintptr_t start(const struct macho_header* appsMachHeader, int argc, const char* argv[], 
                intptr_t slide, const struct macho_header* dyldsMachHeader,
                uintptr_t* startGlue)
{
    slide = slideOfMainExecutable(dyldsMachHeader);//通過(guò)一個(gè)隨機(jī)值來(lái)實(shí)現(xiàn)地址空間配置隨機(jī)加載, 防止被攻擊
    bool shouldRebase = slide != 0;
    ...
    // allow dyld to use mach messaging
    mach_init();//開(kāi)放函數(shù)消息使用
    ...
    // set up random value for stack canary
    __guard_setup(apple);//設(shè)置堆棧保護(hù)
    ...
    // now that we are done bootstrapping dyld, call dyld's main
    uintptr_t appsSlide = slideOfMainExecutable(appsMachHeader);
    return dyld::_main(appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);//開(kāi)始鏈接共享對(duì)象
}
  1. 接著進(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)
{
    ...
    {
        checkEnvironmentVariables(envp);//檢查環(huán)境變量
        defaultUninitializedFallbackPaths(envp);
    }
    ...
    // load shared cache
    checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);//驗(yàn)證共享緩存路徑
    ...
    if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
        mapSharedCache();//加載共享緩存
    }
    ...
    try {
        // add dyld itself to UUID list
        addDyldImageToUUIDList();//添加dyld到UUID列表
        ...
reloadAllImages:
        ...
        sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);//初始化主程序;內(nèi)核會(huì)映射到可執(zhí)行文件中
        ...
        // load any inserted libraries
        if  ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
            for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) 
                loadInsertedDylib(*lib);//加載插入的動(dòng)態(tài)庫(kù)
        }
        ...
        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);//鏈接主程序中各動(dòng)態(tài)庫(kù),進(jìn)行符號(hào)綁定
                image->setNeverUnloadRecursive();
            }
            ...
        }
        ...
        ImageLoader::applyInterposingToDyldCache(gLinkContext);//插入任何動(dòng)態(tài)加載的鏡像文件
        ...
        // run all initializers
        initializeMainExecutable(); //運(yùn)行所有初始化程序
        ...
        notifyMonitoringDyldMain();//通知監(jiān)聽(tīng)dyld的main

        // find entry point for main executable
        result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();//找到真正 main 函數(shù)入口并返回
        ...
    }
    ...
}
  • 流程:配置環(huán)境變量 -> 加載共享緩存 -> 實(shí)例化主程序 -> 加載動(dòng)態(tài)庫(kù) -> 鏈接動(dòng)態(tài)庫(kù) -> 運(yùn)行所有初始化程序 -> 通知監(jiān)聽(tīng)dyldmain -> 找到真正 main 函數(shù)入口
  1. 然后就開(kāi)始初始化:
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);//通過(guò)instantiateMainExecutable中的sniffLoadCommands加載主程序其實(shí)就是對(duì)MachO文件中LoadCommons段的一系列加載
        addImage(image);//生成鏡像文件后吐辙,添加到sAllImages全局鏡像中宣决,主程序永遠(yuǎn)是sAllImages的第一個(gè)對(duì)象
        return (ImageLoaderMachO*)image;
    }
    
    throw "main executable not a known format";
}
  1. 接著下一步就是加載插入的動(dòng)態(tài)庫(kù):
static void loadInsertedDylib(const char* path)
{
    ImageLoader* image = NULL;
    unsigned cacheIndex;
    try {
        image = load(path, context, cacheIndex);
    }
    ...
}
  1. 最后就是鏈接動(dòng)態(tài)庫(kù),插入任何動(dòng)態(tài)加載的鏡像文件昏苏。

  2. 然后就開(kāi)始運(yùn)行:

void initializeMainExecutable()
{
    ...
    if ( rootCount > 1 ) {
        for(size_t i=1; i < rootCount; ++i) {
            sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);//先為所有插入并鏈接完成的動(dòng)態(tài)庫(kù)執(zhí)行初始化操作
        }
    }
    
    // run initializers for main executable and everything it brings up 
    sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);//為主要可執(zhí)行文件及其帶來(lái)的一切運(yùn)行初始化程序
    ...
}
void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo)
{
    ...
    processInitializers(context, thisThread, timingInfo, up);//初始化準(zhǔn)備
    ...
}
void ImageLoader::processInitializers(const LinkContext& context, mach_port_t thisThread,
                                     InitializerTimingList& timingInfo, ImageLoader::UninitedUpwards& images)
{
    ...
    // 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) {//遍歷image.count
        images.images[i]->recursiveInitialization(context, thisThread, images.images[i]->getPath(), timingInfo, ups);//遞歸初始化鏡像
    }
    ...
}
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
                                          InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
    ...
        try {
            ...
            context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);//單個(gè)鏡像通知尊沸;獲取到鏡像的回調(diào)
            
            // initialize this image
            bool hasInitializers = this->doInitialization(context);
            ...
        }
    ...
}
bool ImageLoaderMachO::doInitialization(const LinkContext& context)
{
    CRSetCrashLogMessage2(this->getPath());

    // mach-o has -init and static initializers
    doImageInit(context);
    doModInitFunctions(context);//比如會(huì)調(diào)用c++全局構(gòu)造函數(shù)-初始化
    
    CRSetCrashLogMessage2(NULL);
    
    return (fHasDashInit || fHasInitializers);
}
void ImageLoaderMachO::doModInitFunctions(const LinkContext& context)
{
    ...
    if ( type == S_MOD_INIT_FUNC_POINTERS ) {
        Initializer* inits = (Initializer*)(sect->addr + fSlide);
        ...
        for (size_t j=0; j < count; ++j) {
            Initializer func = inits[j];
            ...
            {
                dyld3::ScopedTimer(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)fMachOData, (uint64_t)func, 0);
                func(context.argc, context.argv, context.envp, context.apple, &context.programVars);//就是調(diào)用libSystem_initializer
            }
            ...
        }
        ...
}
  1. 到這里就要跳轉(zhuǎn)到Libsystem源碼
__attribute__((constructor))
static void
libSystem_initializer(int argc,
              const char* argv[],
              const char* envp[],
              const char* apple[],
              const struct ProgramVars* vars)
{
    ...
    libdispatch_init();
    ...
}
  1. 接著又跳轉(zhuǎn)到libdispatch源碼
void
libdispatch_init(void)
{
    ...
    _dispatch_hw_config_init();
    _dispatch_time_init();
    _dispatch_vtable_init();
    _os_object_init();
    _voucher_init();
    _dispatch_introspection_init();
}
void
_os_object_init(void)
{
    _objc_init();
    ...
}
  1. 最后的最后終于調(diào)用了_objc_init,接著又要跳轉(zhuǎn)到objc4源碼
void _objc_init(void)
{
    ...
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

map_images : dyldimage 加載進(jìn)內(nèi)存時(shí) , 會(huì)觸發(fā)該函數(shù).
load_images : dyld 初始化 image 會(huì)觸發(fā)該方法. ( 我們所熟知的 load 方法也是在此處調(diào)用 ) .
unmap_image : dyldimage 移除時(shí) , 會(huì)觸發(fā)該函數(shù) .

  1. 很不幸贤惯,又要跳回到dyld源碼中:
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);
}

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;//load_images函數(shù)
    ...
    for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) {
        ImageLoader* image = *it;
        if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() ) {
            dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
            (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());//獲取鏡像文件真實(shí)地址; 比如會(huì)調(diào)用[ViewController load]
        }
    }
}

這里可以看到調(diào)用的sNotifyObjCInit就是load_images函數(shù)洼专。

(dyld -> libSystem -> libDispatch -> libObjc -> _objc_init)

  • 總結(jié)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市孵构,隨后出現(xiàn)的幾起案子屁商,更是在濱河造成了極大的恐慌,老刑警劉巖颈墅,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蜡镶,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡恤筛,警方通過(guò)查閱死者的電腦和手機(jī)官还,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)毒坛,“玉大人望伦,你說(shuō)我怎么就攤上這事〖逡螅” “怎么了屯伞?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)豪直。 經(jīng)常有香客問(wèn)我劣摇,道長(zhǎng),這世上最難降的妖魔是什么顶伞? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任饵撑,我火速辦了婚禮剑梳,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘滑潘。我一直安慰自己垢乙,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布语卤。 她就那樣靜靜地躺著追逮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪粹舵。 梳的紋絲不亂的頭發(fā)上钮孵,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音眼滤,去河邊找鬼巴席。 笑死,一個(gè)胖子當(dāng)著我的面吹牛诅需,可吹牛的內(nèi)容都是我干的漾唉。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼堰塌,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼赵刑!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起场刑,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤般此,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后牵现,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體铐懊,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年施籍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了居扒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡丑慎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出瓤摧,到底是詐尸還是另有隱情竿裂,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布照弥,位于F島的核電站腻异,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏这揣。R本人自食惡果不足惜悔常,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一影斑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧机打,春花似錦矫户、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至芥挣,卻和暖如春驱闷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背空免。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工空另, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蹋砚。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓扼菠,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親都弹。 傳聞我的和親對(duì)象是個(gè)殘疾皇子娇豫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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