iOS探索 淺嘗輒止dyld加載流程

歡迎閱讀iOS探索系列(按序閱讀食用效果更加)c++

寫在前面

咱們平時編寫的程序的入口函數都是main.m文件裏面的main函數,可是這就是App的生命起點了嗎?玩過逆向的iOSer都知道能夠往+load方法注入代碼來進行安全攻防,而+load方法先於main函數執行,那麼main函數以前都發生了哪些有趣的事呢?本文就將帶着你們來揭開這片神祕面紗!程序員

本文偏新手向,只會過一遍主要流程!!!bootstrap

1、靜態庫與動態庫

1.編譯過程

在平常開發過程當中,開發者會使用成千上萬次的Command + B/R進行開發調試,但可能不多有人關注過這個過程當中 Xcode幫咱們作了哪些事情(iOS開發者每每會吐槽Xcode愈來愈難用了,但不得不認可它愈來愈強了)緩存

事實上,這個過程分解爲4個步驟,分別是預處理(Prepressing)、編譯(Compilation)、彙編(Assembly)和連接(Linking).------ 摘自《程序員的自我修養-- 連接、裝載與庫》安全

在以上4個步驟中,IDE主要作了如下幾件事:架構

  • 預編譯:處理代碼中的# 開頭的預編譯指令,好比刪除#define並展開宏定義,將#include包含的文件插入到該指令位置等
  • 編譯:對預編譯處理過的文件進行詞法分析、語法分析和語義分析,並進行源代碼優化,而後生成彙編代碼;
  • 彙編:經過彙編器將彙編代碼轉換爲機器能夠執行的指令,並生成目標文件.o文件
  • 連接:將目標文件連接成可執行文件。這一過程當中,連接器將不一樣的目標文件連接起來,由於不一樣的目標文件之間可能有相互引用的變量或調用的函數,如咱們常常調用Foundation框架和UIKit 框架中的方法和變量,可是這些框架跟咱們的代碼並不在一個目標文件中,這就須要連接器將它們與咱們本身的代碼連接起來

FoundationUIKit這種能夠共享代碼、實現代碼的複用統稱爲——它是可執行代碼的二進制文件,能夠被操做系統寫入內存,它又分爲靜態庫動態庫app

2.靜態庫

靜態庫是指連接時完整的拷貝到可執行文件,屢次使用屢次拷貝,形成冗餘,使包變的更大框架

.a.lib都是靜態庫dom

3.動態庫

動態庫是指連接時不復制,程序運行時由系統加在到內存中,供系統調用,系統只需加載一次,屢次使用,共用節省內存。ide

.dylib.framework都是動態庫

系統的framework是動態的,開發者建立的framework是靜態的

那麼連接器又是什麼呢?它是怎麼連接不一樣的目標文件的呢?

2、dyld

1.dyld簡介

dyld(The dynamic link editor)是蘋果的動態連接器,負責程序的連接及加載工做,是蘋果操做系統的重要組成部分,存在於MacOS系統的(/usr/lib/dyld)目錄下。在應用被編譯打包成可執行文件格式的Mach-O文件以後 ,交由dyld負責連接,加載程序

2.dyld_shared_cache

因爲不止一個程序須要使用UIKit系統動態庫,因此不可能在每一個程序加載時都去加載全部的系統動態庫。爲了優化程序啓動速度和利用動態庫緩存,蘋果從iOS3.1以後,將全部系統庫(私有與公有)編譯成一個大的緩存文件,這就是dyld_shared_cache,該緩存文件存在iOS系統下的/System/Library/Caches/com.apple.dyld/目錄下

3、dyld加載流程

新建空工程,寫下load方法,並在main方法load方法分別下斷點

點擊函數調用棧/使用LLVM——bt指令打印,都能看到最初的起點_dyld_start

接下來怎麼去研究dyld呢,咱們將經過dyld源碼展開分析

1._dyld_start

在源碼中全局搜索_dyld_start,會發現它是由彙編實現的

arm64中,_dyld_start調用了一個看不懂的方法

從註釋中得出多是dyldbootstrap::start方法(其實在「函數調用棧」那張圖中彙編代碼已經把這個方法暴露出來了)

2.dyldbootstrap::start

全局搜索dyldbootstrap::start並無任何有意義結果,那麼只能根據經驗來瞎蒙一下了——全局搜索空格start(「僥倖」獲得告終果

其實dyldbootstrap::start是指dyldbootstrap這個命名空間做用域裏的 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)
{
	// if kernel had to slide dyld, we need to fix up load sensitive locations
	// we have to do this before using any global variables
    slide = slideOfMainExecutable(dyldsMachHeader);
    bool shouldRebase = slide != 0;
#if __has_feature(ptrauth_calls)
    shouldRebase = true;
#endif
    if ( shouldRebase ) {
        rebaseDyld(dyldsMachHeader, slide);
    }

	// allow dyld to use mach messaging
	mach_init();

	// 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(dyldsMachHeader, slide, argc, argv, envp, apple);
#endif

	// 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);
}
複製代碼

start()函數中主要作了一下幾件事:

  • 根據dyldsMachHeader計算出slide, 經過slide斷定是否須要重定位;這裏的slide是根據ASLR技術 計算出的一個隨機值,使得程序每一次運行的偏移值都不同,防止攻擊者經過固定地址發起惡意攻擊
  • mach_init()初始化(容許dyld使用mach消息傳遞)
  • 棧溢出保護
  • 計算appsMachHeader的偏移,調用dyld::_main()函數

3.dyld::_main()

點擊進入dyld::_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)
{
    ......
    uintptr_t result = 0;
    //保存傳入的可執行文件的頭部(是一個struct macho_header結構體),後面根據頭部訪問信息
    sMainExecutableMachHeader = mainExecutableMH;
    ......
    //根據可執行文件頭部,參數等設置上下文信息
    setContext(mainExecutableMH, argc, argv, envp, apple);

    // Pickup the pointer to the exec path.
    //獲取可執行文件路徑
    sExecPath = _simple_getenv(apple, "executable_path");

    // <rdar://problem/13868260> Remove interim apple[0] transition code from dyld
    if (!sExecPath) sExecPath = apple[0];
    //將相對路徑轉換成絕對路徑
    if ( sExecPath[0] != '/' ) {
        // have relative path, use cwd to make absolute
        char cwdbuff[MAXPATHLEN];
        if ( getcwd(cwdbuff, MAXPATHLEN) != NULL ) {
            // maybe use static buffer to avoid calling malloc so early...
            char* s = new char[strlen(cwdbuff) + strlen(sExecPath) + 2];
            strcpy(s, cwdbuff);
            strcat(s, "/");
            strcat(s, sExecPath);
            sExecPath = s;
        }
    }

    // Remember short name of process for later logging
    //獲取可執行文件的名字
    sExecShortName = ::strrchr(sExecPath, '/');
    if ( sExecShortName != NULL )
        ++sExecShortName;
    else
        sExecShortName = sExecPath;
    //配置進程是否受限
    configureProcessRestrictions(mainExecutableMH);
    ......
    {
        //檢查設置環境變量
        checkEnvironmentVariables(envp);
        //若是DYLD_FALLBACK爲nil,將其設置爲默認值
        defaultUninitializedFallbackPaths(envp);
    }
    ......
    //若是設置了DYLD_PRINT_OPTS環境變量,則打印參數
    if ( sEnv.DYLD_PRINT_OPTS )
        printOptions(argv);
    //若是設置了DYLD_PRINT_ENV環境變量,則打印環境變量
    if ( sEnv.DYLD_PRINT_ENV ) 
        printEnvironmentVariables(envp);
    //根據Mach-O頭部獲取當前運行架構信息
    getHostInfo(mainExecutableMH, mainExecutableSlide);

    // load shared cache
    //檢查共享緩存是否開啓,iOS中必須開啓
    checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
#if TARGET_IPHONE_SIMULATOR
    // <HACK> until <rdar://30773711> is fixed
    gLinkContext.sharedRegionMode = ImageLoader::kUsePrivateSharedRegion;
    // </HACK>
#endif
    if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
        //檢查共享緩存是否映射到了共享區域
        mapSharedCache();
    }
    ......
    

    // instantiate ImageLoader for main executable
    //加載可執行文件並生成一個ImageLoader實例對象
    sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
    gLinkContext.mainExecutable = sMainExecutable;
    gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);

    ......

        // Now that shared cache is loaded, setup an versioned dylib overrides
    #if SUPPORT_VERSIONED_PATHS
        //檢查庫的版本是否有更新,有則覆蓋原有的
        checkVersionedPaths();
    #endif
    ......
        // load any inserted libraries
        //加載全部DYLD_INSERT_LIBRARIES指定的庫
        if  ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
            for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) 
                loadInsertedDylib(*lib);
        }
        // record count of inserted libraries so that a flat search will look at 
        // inserted libraries, then main, then others.
        sInsertedDylibCount = sAllImages.size()-1;

        // link main executable
        //連接主程序
        gLinkContext.linkingMainExecutable = true;
#if SUPPORT_ACCELERATE_TABLES
        if ( mainExcutableAlreadyRebased ) {
            // previous link() on main executable has already adjusted its internal pointers for ASLR
            // work around that by rebasing by inverse amount
            sMainExecutable->rebase(gLinkContext, -mainExecutableSlide);
        }
#endif
        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();
            }
            // 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);
            }
        }

        // <rdar://problem/19315404> dyld should support interposition even without DYLD_INSERT_LIBRARIES
        for (long i=sInsertedDylibCount+1; i < sAllImages.size(); ++i) {
            ImageLoader* image = sAllImages[i];
            if ( image->inSharedCache() )
                continue;
            image->registerInterposing(gLinkContext);
        }
    #if SUPPORT_ACCELERATE_TABLES
        if ( (sAllCacheImagesProxy != NULL) && ImageLoader::haveInterposingTuples() ) {
            // Accelerator tables cannot be used with implicit interposing, so relaunch with accelerator tables disabled
            ImageLoader::clearInterposingTuples();
            // unmap all loaded dylibs (but not main executable)
            for (long i=1; i < sAllImages.size(); ++i) {
                ImageLoader* image = sAllImages[i];
                if ( image == sMainExecutable )
                    continue;
                if ( image == sAllCacheImagesProxy )
                    continue;
                image->setCanUnload();
                ImageLoader::deleteImage(image);
            }
            // note: we don't need to worry about inserted images because if DYLD_INSERT_LIBRARIES was set we would not be using the accelerator table
            sAllImages.clear();
            sImageRoots.clear();
            sImageFilesNeedingTermination.clear();
            sImageFilesNeedingDOFUnregistration.clear();
            sAddImageCallbacks.clear();
            sRemoveImageCallbacks.clear();
            sAddLoadImageCallbacks.clear();
            sDisableAcceleratorTables = true;
            sAllCacheImagesProxy = NULL;
            sMappedRangesStart = NULL;
            mainExcutableAlreadyRebased = true;
            gLinkContext.linkingMainExecutable = false;
            resetAllImages();
            goto reloadAllImages;
        }
    #endif

        // apply interposing to initial set of images
        for(int i=0; i < sImageRoots.size(); ++i) {
            //應用符號插入
            sImageRoots[i]->applyInterposing(gLinkContext);
        }
        ImageLoader::applyInterposingToDyldCache(gLinkContext);
        gLinkContext.linkingMainExecutable = false;

        // Bind and notify for the main executable now that interposing has been registered
        uint64_t bindMainExecutableStartTime = mach_absolute_time();
        sMainExecutable->recursiveBindWithAccounting(gLinkContext, sEnv.DYLD_BIND_AT_LAUNCH, true);
        uint64_t bindMainExecutableEndTime = mach_absolute_time();
        ImageLoaderMachO::fgTotalBindTime += bindMainExecutableEndTime - bindMainExecutableStartTime;
        gLinkContext.notifyBatch(dyld_image_state_bound, false);

        // Bind and notify for the inserted images now interposing has been registered
        if ( sInsertedDylibCount > 0 ) {
            for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
                ImageLoader* image = sAllImages[i+1];
                image->recursiveBind(gLinkContext, sEnv.DYLD_BIND_AT_LAUNCH, true);
            }
        }
        
        // <rdar://problem/12186933> do weak binding only after all inserted images linked
        //弱符號綁定
        sMainExecutable->weakBind(gLinkContext);
        ......
#if SUPPORT_OLD_CRT_INITIALIZATION
        // Old way is to run initializers via a callback from crt1.o
        if ( ! gRunInitializersOldWay ) 
            initializeMainExecutable(); 
    #else
        // run all initializers
        //執行初始化方法
        initializeMainExecutable(); 
    #endif
        // notify any montoring proccesses that this process is about to enter main()
        if (dyld3::kdebug_trace_dyld_enabled(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE)) {
            dyld3::kdebug_trace_dyld_duration_end(launchTraceID, DBG_DYLD_TIMING_LAUNCH_EXECUTABLE, 0, 0, 2);
        }
        notifyMonitoringDyldMain();

        // find entry point for main executable
        //尋找目標可執行文件入口並執行
        result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();
        if ( result != 0 ) {
            // main executable uses LC_MAIN, we need to use helper in libdyld to call into main()
            if ( (gLibSystemHelpers != NULL) && (gLibSystemHelpers->version >= 9) )
                *startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit;
            else
                halt("libdyld.dylib support not present for LC_MAIN");
        }
        else {
            // main executable uses LC_UNIXTHREAD, dyld needs to let "start" in program set up for main()
            result = (uintptr_t)sMainExecutable->getEntryFromLC_UNIXTHREAD();
            *startGlue = 0;
        }
#if __has_feature(ptrauth_calls)
        // start() calls the result pointer as a function pointer so we need to sign it.
        result = (uintptr_t)__builtin_ptrauth_sign_unauthenticated((void*)result, 0, 0);
#endif
    }
    catch(const char* message) {
        syncAllImages();
        halt(message);
    }
    catch(...) {
        dyld::log("dyld: launch failed\n");
    }

    CRSetCrashLogMessage("dyld2 mode");

    if (sSkipMain) {
        if (dyld3::kdebug_trace_dyld_enabled(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE)) {
            dyld3::kdebug_trace_dyld_duration_end(launchTraceID, DBG_DYLD_TIMING_LAUNCH_EXECUTABLE, 0, 0, 2);
        }
        result = (uintptr_t)&fake_main;
        *startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit;
    }
    
    return result;
}
複製代碼

dyld::_main()主要流程爲:

  • 設置上下文信息,檢測進程是否受限
  • 配置環境變量,獲取當前運行架構
  • 檢查是否開啓共享緩存,並加載共享緩存庫
  • 將dyld自己添加到UUID列表
  • 實例化主程序
  • 加載插入動態庫
  • 連接主程序和插入的庫,執行符號替換
  • 執行初始化方法
  • 尋找主程序入口

3.1 設置上下文信息,檢測進程是否受限

  • 調用setContext函數,傳入Mach-O頭部以及一些參數設置上下文
  • configureProcessRestrictions檢測進程是否受限,在上下文中作出對應處理
/// _main函數中
setContext(mainExecutableMH, argc, argv, envp, apple);
...
configureProcessRestrictions(mainExecutableMH);
複製代碼

3.2 配置環境變量,獲取當前運行架構

  • 從環境變量中獲取主要可執行文件的cdHash
  • checkEnvironmentVariables(envp)檢查設置環境變量
  • defaultUninitializedFallbackPaths(envp)DYLD_FALLBACK爲空時設置默認值
  • getHostInfo(mainExecutableMH, mainExecutableSlide)獲取程序架構
/// _main函數中
//若是設置了DYLD_PRINT_OPTS環境變量,則打印參數
if ( sEnv.DYLD_PRINT_OPTS )
    printOptions(argv);
//若是設置了DYLD_PRINT_ENV環境變量,則打印環境變量
if ( sEnv.DYLD_PRINT_ENV ) 
    printEnvironmentVariables(envp);
複製代碼

只要設置了這兩個環境變量參數,在App啓動時就會打印相關參數、環境變量信息(自行嘗試研究)

3.3 檢查是否開啓共享緩存,並加載共享緩存庫

  • checkSharedRegionDisable檢查是否開啓共享緩存(iOS 下不會被禁用)
  • mapSharedCache加載共享緩存庫,其中調用loadDyldCache函數有這麼幾種狀況:
    • 僅加載到當前進程mapCachePrivate(模擬器僅支持加載到當前進程)
    • 共享緩存是第一次被加載,就去作加載操做mapCacheSystemWide
    • 共享緩存不是第一次被加載,那麼就不作任何處理

3.4 將dyld自己添加到UUID列表

addDyldImageToUUIDList將dyld自己添加到UUID列表

接下來是最重要的部分reloadAllImages

3.5 實例化主程序

  • isCompatibleMachO檢測可執行程序格式,主要判斷Mach-O文件的Magic number、cputype、cpusubtype等是否兼容
  • instantiateMainExecutable實例化主程序
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";
}
複製代碼

instantiateMainExecutable中調用 ImageLoaderMachO::sniffLoadCommands,這纔是真正實例化主程序的函數

// determine if this mach-o file has classic or compressed LINKEDIT and number of segments it has
void ImageLoaderMachO::sniffLoadCommands(const macho_header* mh, const char* path, bool inCache, bool* compressed,
											unsigned int* segCount, unsigned int* libCount, const LinkContext& context,
											const linkedit_data_command** codeSigCmd,
											const encryption_info_command** encryptCmd)
{
    *compressed = false;
    *segCount = 0;
    *libCount = 0;
    *codeSigCmd = NULL;
    *encryptCmd = NULL;
    /* ... */
    // fSegmentsArrayCount is only 8-bits
    if ( *segCount > 255 )
    	dyld::throwf("malformed mach-o image: more than 255 segments in %s", path);
    
    // fSegmentsArrayCount is only 8-bits
    if ( *libCount > 4095 )
    	dyld::throwf("malformed mach-o image: more than 4095 dependent libraries in %s", path);
    
    if ( needsAddedLibSystemDepency(*libCount, mh) )
    	*libCount = 1;
    ...
}
複製代碼

這裏幾個字段都與MachO有關:

  • compressed:根據LC_DYLD_INFO_ONYL來決定
  • segCount: MachO文件中segment數量,最多不超過255個
  • libCount: MachO文件中依賴的動態庫的數量
  • codeSigCmd: 簽名信息
  • encryptCmd: 加密信息,如cryptid等

3.6 加載插入動態庫

/// _main函數中
// load any inserted libraries
if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
    for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) 
        loadInsertedDylib(*lib);
}
複製代碼

遍歷DYLD_INSERT_LIBRARIES環境變量,調用loadInsertedDylib加載,經過該環境變量咱們能夠注入自定義的一些動態庫代碼從而完成安全攻防,loadInsertedDylib內部會從DYLD_ROOT_PATHLD_LIBRARY_PATHDYLD_FRAMEWORK_PATH等路徑查找dylib而且檢查代碼簽名,無效則直接拋出異常

3.7 連接主程序和插入的庫,執行符號替換

  • 經過ImageLoader::link()函數連接主程序和插入的庫
  • 連接完畢後還會進行recursiveBindWithAccounting()遞歸綁定符號表、weakBind()弱綁定
/// _main函數中
// link main executable
gLinkContext.linkingMainExecutable = true;
#if SUPPORT_ACCELERATE_TABLES
if ( mainExcutableAlreadyRebased ) {
	// previous link() on main executable has already adjusted its internal pointers for ASLR
	// work around that by rebasing by inverse amount
	sMainExecutable->rebase(gLinkContext, -mainExecutableSlide);
}
#endif
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();
	}
	// 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);
	}
}
複製代碼

接下來是重中之重

3.8 執行初始化方法

回顧一下函數調用棧

initializeMainExecutable方法調用runInitializers

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]);
}
複製代碼

runInitializers調用processInitializers爲初始化作準備

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.images[0] = this;
	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);
}
複製代碼

③遍歷image,recursiveInitialization遞歸初始化鏡像

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) {
		images.images[i]->recursiveInitialization(context, thisThread, images.images[i]->getPath(), timingInfo, ups);
	}
	// If any upward dependencies remain, init them.
	if ( ups.count > 0 )
		processInitializers(context, thisThread, timingInfo, ups);
}
複製代碼

點進去卻只有聲明,shift+cmd+O搜索recursiveInitialization

recursiveInitialization獲取到鏡像的初始化

void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
										  InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
    ...
    uint64_t t1 = mach_absolute_time();
	fState = dyld_image_state_dependents_initialized;
	oldState = fState;
	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);
    ...
}
複製代碼

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);
    	}
    }
    ...
}
複製代碼

notifySingle中並無找到函數調用棧中的load_images,其實這是一個回調函數的調用

sNotifyObjCInitregisterObjCNotifiers函數中賦值

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_dyld_objc_notify_register函數中被調用,這個函數只在運行時提供給objc使用

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);
}
複製代碼

_objc_init調用_dyld_objc_notify_register,並調用load_image

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();
    tls_init();
    static_init();
    lock_init();
    exception_init();
    
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
複製代碼

咱們能夠經過objc源碼下符號斷點來_dyld_objc_notify_register驗證

這裏又出現了libSystem...(好吧,是我太天真了,dyld過程真複雜)

context.notifySingle以後,調用ImageLoaderMachO::doInitialization,內部調用

  • doImageInit
  • ImageLoaderMachO::doModInitFunctions

doImageInit->libSystemInitialized->libdispatch_init->_os_object_init,內部調用_objc_init

doModInitFunctions內部調用__mod_init_funcs section,也就是constructor方法——C++構造方法

initializeMainExecutable總結:

  • runInitializers->processInitializers中,遍歷recursiveInitialization
  • 第一次執行時,進行libsystem初始化——doInitialization->doImageInit-> libSystemInitialized
  • libsystem的初始化,會調用起libdispatch_initlibdispatch初始化會調用_os_object_init, 內部調用了_objc_init
  • _objc_init中註冊並保存了map_imagesload_imagesunmap_image函數地址
  • 註冊完畢繼續回到recursiveInitialization遞歸下一次調用

3.9 尋找主程序入口

// find entry point for main executable
result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();
複製代碼

4、dyld加載過程示意圖

寫在後面

dyld加載流程代碼較多,第一次看大概瞭解這個過程便可

相關文章
相關標籤/搜索