dyld源碼解讀

前言

dyld全稱the dynamic link editor甜熔,即動態(tài)鏈接器,其本質是Mach-O文件,他是專門用來加載動態(tài)庫的庫盗痒。源碼可以從這里下載,本文采用的是| dyld-635.2 |
源碼進行分析低散。dyld位于/usr/lib/dyld俯邓,可以從越獄機或者mac電腦中找到。以mac為例熔号,終端執(zhí)行如下命令:

cd /usr/lib/
file dyld

輸出為:

dyld: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit dynamic linker x86_64] [i386:Mach-O dynamic linker i386]
dyld (for architecture x86_64): Mach-O 64-bit dynamic linker x86_64
dyld (for architecture i386):   Mach-O dynamic linker i386

即看成,dyld是Mach-O類型的通用二進制文件,支持x86_64和i386兩種架構跨嘉。當然川慌,iPhone真機對應的dyld支持的為arm系列架構吃嘿。

  • otool簡介
    otool是專門用來查看Mach-O類型文件的工具,終端輸入otool可以看到很多用法:
$ otool
Usage: /Library/Developer/CommandLineTools/usr/bin/otool [-arch arch_type] [-fahlLDtdorSTMRIHGvVcXmqQjCP] [-mcpu=arg] [--version] <object file> ...
    -f print the fat headers
    -a print the archive header
    -h print the mach header
    -l print the load commands
    -L print shared libraries used
    -D print shared library id name
    -t print the text section (disassemble with -v)
    -p <routine name>  start dissassemble from routine name
    -s <segname> <sectname> print contents of section
    -d print the data section
    -o print the Objective-C segment
    -r print the relocation entries
    -S print the table of contents of a library (obsolete)
    -T print the table of contents of a dynamic shared library (obsolete)
    -M print the module table of a dynamic shared library (obsolete)
    -R print the reference table of a dynamic shared library (obsolete)
    -I print the indirect symbol table
    -H print the two-level hints table (obsolete)
    -G print the data in code table
    -v print verbosely (symbolically) when possible
    -V print disassembled operands symbolically
    -c print argument strings of a core file
    -X print no leading addresses or headers
    -m don't use archive(member) syntax
    -B force Thumb disassembly (ARM objects only)
    -q use llvm's disassembler (the default)
    -Q use otool(1)'s disassembler
    -mcpu=arg use `arg' as the cpu for disassembly
    -j print opcode bytes
    -P print the info plist section as strings
    -C print linker optimization hints
    --version print the version of /Library/Developer/CommandLineTools/usr/bin/otool

比如梦重,可以通過-L來查看當前Mach-O所依賴的動態(tài)庫兑燥。如,常用的gcd依賴以下這些動態(tài)庫:

$ otool -L /usr/lib/system/libdispatch.dylib
/usr/lib/system/libdispatch.dylib:
    /usr/lib/system/libdispatch.dylib (compatibility version 1.0.0, current version 1008.250.7)
    /usr/lib/system/libsystem_darwin.dylib (compatibility version 1.0.0, current version 1.0.0)
    /usr/lib/system/libdyld.dylib (compatibility version 1.0.0, current version 655.1.1)
    /usr/lib/system/libcompiler_rt.dylib (compatibility version 1.0.0, current version 63.4.0)
    /usr/lib/system/libsystem_kernel.dylib (compatibility version 1.0.0, current version 4903.255.45)
    /usr/lib/system/libsystem_platform.dylib (compatibility version 1.0.0, current version 177.250.1)
    /usr/lib/system/libsystem_pthread.dylib (compatibility version 1.0.0, current version 330.250.2)
    /usr/lib/system/libsystem_malloc.dylib (compatibility version 1.0.0, current version 166.250.4)
    /usr/lib/system/libsystem_c.dylib (compatibility version 1.0.0, current version 1272.250.1)
    /usr/lib/system/libsystem_blocks.dylib (compatibility version 1.0.0, current version 73.0.0)
    /usr/lib/system/libunwind.dylib (compatibility version 1.0.0, current version 35.4.0)
    /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)

當然琴拧,也可以通過MachOView更加直觀的查看相關信息

libdispatch.dylib

dyld加載

動態(tài)庫的加載必然在main函數(shù)之前降瞳,而load方法的調用也在main之前,因此從這里入手蚓胸。新建工程挣饥,打上符號斷點[NSObject load],運行程序后如圖所示:

符號斷點

可見沛膳,load的加載是從_dyld_start這個函數(shù)開始的扔枫。_dyld_start對應匯編文件,內部調用dyldbootstrap::start锹安,位于dyldInitialization.cpp中:

//
//  This is code to bootstrap dyld.  This work in normally done for a program by dyld and crt.
//  In dyld we have to do this manually.
//
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);
}

流程很簡單:獲取dyld對應的slide->通過slide對dyld進行rebase->mach初始化->棧溢出保護->獲取應用的slide(appsSlide)->調用dyld的main函數(shù)

slide與rebase

由于apple采用了ASLR(Address space layout randomization)技術短荐,所以Mach-O每次加載到內存中的首地址是變化的,此時想找到代碼在內存中對應的地址需要重定位rebase叹哭。rebase要用到slide值忍宋,那么slide如何計算?

static uintptr_t slideOfMainExecutable(const struct macho_header* mh)
{
    const uint32_t cmd_count = mh->ncmds;
    const struct load_command* const cmds = (struct load_command*)(((char*)mh)+sizeof(macho_header));
    const struct load_command* cmd = cmds;
    for (uint32_t i = 0; i < cmd_count; ++i) {
        if ( cmd->cmd == LC_SEGMENT_COMMAND ) {
            const struct macho_segment_command* segCmd = (struct macho_segment_command*)cmd;
            if ( (segCmd->fileoff == 0) && (segCmd->filesize != 0)) {
                return (uintptr_t)mh - segCmd->vmaddr;
            }
        }
        cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
    }
    return 0;
}
intptr_t _dyld_get_image_vmaddr_slide(uint32_t imageIndex)
{
    log_apis("_dyld_get_image_vmaddr_slide(%d)\n", imageIndex);

    const mach_header* mh = gAllImages.imageLoadAddressByIndex(imageIndex);
    if ( mh != nullptr )
        return dyld3::_dyld_get_image_slide(mh);
    return 0;
}

intptr_t _dyld_get_image_slide(const mach_header* mh)
{
    log_apis("_dyld_get_image_slide(%p)\n", mh);

    const MachOLoaded* mf = (MachOLoaded*)mh;
    if ( !mf->hasMachOMagic() )
        return 0;

    return mf->getSlide();
}

intptr_t MachOLoaded::getSlide() const
{
    Diagnostics diag;
    __block intptr_t slide = 0;
    forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
        if ( cmd->cmd == LC_SEGMENT_64 ) {
            const segment_command_64* seg = (segment_command_64*)cmd;
            if ( strcmp(seg->segname, "__TEXT") == 0 ) {
                slide = (uintptr_t)(((uint64_t)this) - seg->vmaddr);
                stop = true;
            }
        }
        else if ( cmd->cmd == LC_SEGMENT ) {
            const segment_command* seg = (segment_command*)cmd;
            if ( strcmp(seg->segname, "__TEXT") == 0 ) {
                slide = (uintptr_t)(((uint64_t)this) - seg->vmaddr);
                stop = true;
            }
        }
    });
    diag.assertNoError();   // any malformations in the file should have been caught by earlier validate() call
    return slide;
}

由于應用本身的Mach-O及dyld的特殊性风罩,這兩個采用的是slideOfMainExecutable的方式獲取slide糠排,而動態(tài)庫加載采用的是_dyld_get_image_vmaddr_slide的方式獲取slide。

對比后不難發(fā)現(xiàn):
slide = mh首地址 - load_command中__TEXT段中vmaddr的值(slideOfMainExecutable方式中以(segCmd->fileoff == 0) && (segCmd->filesize != 0)為條件超升,對于應用本身Mach-O及dyld乳讥,此時也是對應__TEXT段)

簡單驗證一下,以應用Mach-O為例:

VM Address
image list

可以看到廓俭,Mach-O的地址為0x101321000(16進制)云石,VM Address的地址為4294967296(十進制,對應16進制為0x100000000)研乒。當然汹忠,也可以通過命令行直接獲取slide的值,這里為了方便理解雹熬,采用手動計算

image list -o -f
Calculation

1宽菜、用Mach-O的內存地址減去對應虛擬地址,得到20058112(十進制)為slide的值
2竿报、獲取viewDidLoad函數(shù)在當前內存中的地址
3铅乡、用viewDidLoad內存地址減去slide得到Mach-O中對應的虛擬地址
4、將虛擬地址轉化為16進制

viewDidLoad

可以看到烈菌,最終計算出的值0x100001750與在Mach-O中看到的值一致

dyld::_main

ASLR有個基本認知后阵幸,接著看dyld中的main干了什么花履。由于內部代碼過長,此處先貼出流程再逐步分析:設置運行環(huán)境->加載共享緩存->實例化主程序->加載插入的動態(tài)庫->鏈接主程序->鏈接插入的動態(tài)庫->執(zhí)行弱符號綁定->執(zhí)行初始化方法->查找入口點并返回

  • 設置運行環(huán)境
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
        int argc, const char* argv[], const char* envp[], const char* apple[], 
        uintptr_t* startGlue)
{
  ...
   // Grab the cdHash of the main executable from the environment
    uint8_t mainExecutableCDHashBuffer[20];
    const uint8_t* mainExecutableCDHash = nullptr;
    if ( hexToBytes(_simple_getenv(apple, "executable_cdhash"), 40, mainExecutableCDHashBuffer) )
                //  獲取主程序hash
        mainExecutableCDHash = mainExecutableCDHashBuffer;

    // Trace dyld's load
        // 告知kernel挚赊,dyld已加載
    notifyKernelAboutImage((macho_header*)&__dso_handle, _simple_getenv(apple, "dyld_file"));
#if !TARGET_IPHONE_SIMULATOR
    // Trace the main executable's load
        // 告知kernel诡壁,主程序Mach-O已加載
    notifyKernelAboutImage(mainExecutableMH, _simple_getenv(apple, "executable_file"));
#endif

    uintptr_t result = 0;
    sMainExecutableMachHeader = mainExecutableMH;
    sMainExecutableSlide = mainExecutableSlide;
#if __MAC_OS_X_VERSION_MIN_REQUIRED
    // if this is host dyld, check to see if iOS simulator is being run
        // 獲取dyld路徑
    const char* rootPath = _simple_getenv(envp, "DYLD_ROOT_PATH");
    if ( (rootPath != NULL) ) {
        // look to see if simulator has its own dyld
        char simDyldPath[PATH_MAX]; 
        strlcpy(simDyldPath, rootPath, PATH_MAX);
        strlcat(simDyldPath, "/usr/lib/dyld_sim", PATH_MAX);
        int fd = my_open(simDyldPath, O_RDONLY, 0);
        if ( fd != -1 ) {
                        // 如果是模擬器,并且正確加載`dyld_sim`荠割,則直接返回主程序地址
            const char* errMessage = useSimulatorDyld(fd, mainExecutableMH, simDyldPath, argc, argv, envp, apple, startGlue, &result);
            if ( errMessage != NULL )
                halt(errMessage);
            return result;
        }
    }
#endif

    CRSetCrashLogMessage("dyld: launch started");
        // 設置上下文
    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];
    
        // 獲取Mach-O絕對路徑
    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);

//  再次檢測/設置上下文環(huán)境
#if __MAC_OS_X_VERSION_MIN_REQUIRED
    if ( !gLinkContext.allowEnvVarsPrint && !gLinkContext.allowEnvVarsPath && !gLinkContext.allowEnvVarsSharedCache ) {
        pruneEnvironmentVariables(envp, &apple);
        // set again because envp and apple may have changed or moved
        setContext(mainExecutableMH, argc, argv, envp, apple);
    }
    else
#endif
    {
        checkEnvironmentVariables(envp);
        defaultUninitializedFallbackPaths(envp);
    }

...
        
        // 如果設置了DYLD_PRINT_OPTS妹卿,則打印參數(shù)
    if ( sEnv.DYLD_PRINT_OPTS )
        printOptions(argv);
        // 如果設置了DYLD_PRINT_ENV,則打印環(huán)境變量
    if ( sEnv.DYLD_PRINT_ENV ) 
        printEnvironmentVariables(envp);
        // 獲取主程序架構信息
    getHostInfo(mainExecutableMH, mainExecutableSlide);
}

通過源碼可以看到蔑鹦,如果是模擬器運行的程序夺克,其實是通過dyld_sim來進行后續(xù)加載工作的,與正常真機加載流程略有不同嚎朽,具體實現(xiàn)在useSimulatorDyld這個函數(shù)中铺纽,本文不做進一步解析。不難看出火鼻,模擬器比真機多加載一個dyld_sim

模擬器
真機

這里還有一個知識點雕崩,環(huán)境變量DYLD_PRINT_OPTSDYLD_PRINT_ENV

processDyldEnvironmentVariable

添加這兩個環(huán)境變量魁索,對應的字段會被設置為true,可以看到盼铁,這里并不需要設置value

arguments & environment

輸出如下:


輸出

但是并非每個環(huán)境變量都不需要配置value粗蔚,如:

void processDyldEnvironmentVariable(const char* key, const char* value, const char* mainExecutableDir)
{
    if ( strcmp(key, "DYLD_FRAMEWORK_PATH") == 0 ) {
        appendParsedColonList(value, mainExecutableDir, &sEnv.DYLD_FRAMEWORK_PATH);
    }
    else if ( strcmp(key, "DYLD_FALLBACK_FRAMEWORK_PATH") == 0 ) {
        appendParsedColonList(value, mainExecutableDir, &sEnv.DYLD_FALLBACK_FRAMEWORK_PATH);
    }
    else if ( strcmp(key, "DYLD_LIBRARY_PATH") == 0 ) {
        appendParsedColonList(value, mainExecutableDir, &sEnv.DYLD_LIBRARY_PATH);
    }
    else if ( strcmp(key, "DYLD_FALLBACK_LIBRARY_PATH") == 0 ) {
        appendParsedColonList(value, mainExecutableDir, &sEnv.DYLD_FALLBACK_LIBRARY_PATH);
    }
...
}

這些環(huán)境變量是需要配置value的,更多可配置的環(huán)境變量可從processDyldEnvironmentVariable函數(shù)中找到

  • 加載共享緩存
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
        int argc, const char* argv[], const char* envp[], const char* apple[], 
        uintptr_t* startGlue)
{
...
    // load shared cache
        // 檢測共享緩存是否可用
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 ) {
                // 映射共享緩存到共享區(qū)
        mapSharedCache();
    }

    ...                     

#if !TARGET_IPHONE_SIMULATOR
#ifdef WAIT_FOR_SYSTEM_ORDER_HANDSHAKE
    // <rdar://problem/6849505> Add gating mechanism to dyld support system order file generation process
    WAIT_FOR_SYSTEM_ORDER_HANDSHAKE(dyld::gProcessInfo->systemOrderFlag);
#endif
#endif

    try {
        // add dyld itself to UUID list
                // 添加dyld的UUID到共享緩存UUID列表中
        addDyldImageToUUIDList();

        ...
        }
#endif
...
}

這部分流程也很簡單:檢測共享緩存是否可用->如果可用饶火,映射共享緩存到共享區(qū)->添加dyld的UUID到緩存列表

其中鹏控,檢測共享緩存是否可用checkSharedRegionDisable這個函數(shù)中有兩句注釋:

// if main executable has segments that overlap the shared region, then disable using the shared region
// iOS cannot run without shared region

意思是:如果主程序Mach-O有segments與共享區(qū)重疊,那么共享區(qū)不可用肤寝。并且当辐,iOS不開啟共享區(qū)無法運行。

看下是如何檢測這兩者是否重疊的:

...
// 調用
 mainExecutableMH->intersectsRange(SHARED_REGION_BASE, SHARED_REGION_SIZE) 
...

bool MachOLoaded::intersectsRange(uintptr_t start, uintptr_t length) const
{
    __block bool result = false;
    uintptr_t slide = getSlide();
    forEachSegment(^(const SegmentInfo& info, bool& stop) {
        if ( (info.vmAddr+info.vmSize+slide >= start) && (info.vmAddr+slide < start+length) )
            result = true;
    });
    return result;
}

如果主程序segment中的虛擬地址+虛擬地址+偏移量 >= 共享區(qū)起始地址并且主程序segment中的虛擬地址 + 偏移量 < 共享區(qū)終止地址鲤看,那么認為主程序Mach-O有segments與共享區(qū)重疊缘揪,此時共享區(qū)不可用,從而動態(tài)庫緩存不可用

疑問:可以看到這段檢測代碼在滿足重疊條件后义桂,并沒有將stop設為true找筝,所以源碼其實是檢測Mach-O最后一段segment與共享區(qū)是否重疊,但之前的每個segment都計算了一次慷吊。這里是否在滿足重疊條件后將stop設為true跳出循環(huán)袖裕,或者只檢測最后一段segment都比原方法更好?

加載共享緩存最核心的步驟在mapSharedCache中:

static void mapSharedCache()
{
    dyld3::SharedCacheOptions opts;
    opts.cacheDirOverride   = sSharedCacheOverrideDir;
    opts.forcePrivate       = (gLinkContext.sharedRegionMode == ImageLoader::kUsePrivateSharedRegion);


#if __x86_64__ && !TARGET_IPHONE_SIMULATOR
    opts.useHaswell         = sHaswell;
#else
    opts.useHaswell         = false;
#endif
    opts.verbose            = gLinkContext.verboseMapping;
    loadDyldCache(opts, &sSharedCacheLoadInfo);

    // update global state
    if ( sSharedCacheLoadInfo.loadAddress != nullptr ) {
        gLinkContext.dyldCache                              = sSharedCacheLoadInfo.loadAddress;
        dyld::gProcessInfo->processDetachedFromSharedRegion = opts.forcePrivate;
        dyld::gProcessInfo->sharedCacheSlide                = sSharedCacheLoadInfo.slide;
        dyld::gProcessInfo->sharedCacheBaseAddress          = (unsigned long)sSharedCacheLoadInfo.loadAddress;
        sSharedCacheLoadInfo.loadAddress->getUUID(dyld::gProcessInfo->sharedCacheUUID);
        dyld3::kdebug_trace_dyld_image(DBG_DYLD_UUID_SHARED_CACHE_A, (const uuid_t *)&dyld::gProcessInfo->sharedCacheUUID[0], {0,0}, {{ 0, 0 }}, (const mach_header *)sSharedCacheLoadInfo.loadAddress);
    }

//#if __IPHONE_OS_VERSION_MIN_REQUIRED && !TARGET_IPHONE_SIMULATOR
// RAM disk booting does not have shared cache yet
// Don't make lack of a shared cache fatal in that case
//  if ( sSharedCacheLoadInfo.loadAddress == nullptr ) {
//      if ( sSharedCacheLoadInfo.errorMessage != nullptr )
//          halt(sSharedCacheLoadInfo.errorMessage);
//      else
//          halt("error loading dyld shared cache");
//  }
//#endif
}

bool loadDyldCache(const SharedCacheOptions& options, SharedCacheLoadInfo* results)
{
    results->loadAddress        = 0;
    results->slide              = 0;
    results->errorMessage       = nullptr;

#if TARGET_IPHONE_SIMULATOR
    // simulator only supports mmap()ing cache privately into process
    return mapCachePrivate(options, results);
#else
    if ( options.forcePrivate ) {
        // mmap cache into this process only
        return mapCachePrivate(options, results);
    }
    else {
        // fast path: when cache is already mapped into shared region
        bool hasError = false;
        if ( reuseExistingCache(options, results) ) {
            hasError = (results->errorMessage != nullptr);
        } else {
            // slow path: this is first process to load cache
            hasError = mapCacheSystemWide(options, results);
        }
        return hasError;
    }
#endif
}

可以看到溉瓶,加載緩存分三種情況:
1急鳄、僅加載到當前進程谤民,通過mapCachePrivate加載并返回錯誤信息
2、已經(jīng)加載過的攒岛,僅獲取加載錯誤信息并返回
3赖临、未加載過的,通過mapCacheSystemWide加載并返回錯誤信息

options.forcePrivate為true或者模擬器運行時灾锯,僅加載到當前進程兢榨。而options.forcePrivate的值是這么定義的:

opts.forcePrivate = (gLinkContext.sharedRegionMode == ImageLoader::kUsePrivateSharedRegion)

enum SharedRegionMode { kUseSharedRegion, kUsePrivateSharedRegion, kDontUseSharedRegion, kSharedRegionIsSharedCache };

gLinkContext.sharedRegionMode就是之前檢測共享區(qū)是否可用的標識值,可以看到默認值為kUseSharedRegion

  • 實例化主程序
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
        int argc, const char* argv[], const char* envp[], const char* apple[], 
        uintptr_t* startGlue)
{
...
  try {
  ...
        CRSetCrashLogMessage(sLoadingCrashMessage);
        // instantiate ImageLoader for main executable
                // 實例化主程序
        sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
        gLinkContext.mainExecutable = sMainExecutable;
        gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);

#if TARGET_IPHONE_SIMULATOR
        // check main executable is not too new for this OS
                // 檢測主程序是否支持當前設備版本
        {
            if ( ! isSimulatorBinary((uint8_t*)mainExecutableMH, sExecPath) ) {
                throwf("program was built for a platform that is not supported by this runtime");
            }
            uint32_t mainMinOS = sMainExecutable->minOSVersion();

            // dyld is always built for the current OS, so we can get the current OS version
            // from the load command in dyld itself.
            uint32_t dyldMinOS = ImageLoaderMachO::minOSVersion((const mach_header*)&__dso_handle);
            if ( mainMinOS > dyldMinOS ) {
    #if TARGET_OS_WATCH
                throwf("app was built for watchOS %d.%d which is newer than this simulator %d.%d",
                        mainMinOS >> 16, ((mainMinOS >> 8) & 0xFF),
                        dyldMinOS >> 16, ((dyldMinOS >> 8) & 0xFF));
    #elif TARGET_OS_TV
                throwf("app was built for tvOS %d.%d which is newer than this simulator %d.%d",
                        mainMinOS >> 16, ((mainMinOS >> 8) & 0xFF),
                        dyldMinOS >> 16, ((dyldMinOS >> 8) & 0xFF));
    #else
                throwf("app was built for iOS %d.%d which is newer than this simulator %d.%d",
                        mainMinOS >> 16, ((mainMinOS >> 8) & 0xFF),
                        dyldMinOS >> 16, ((dyldMinOS >> 8) & 0xFF));
    #endif
            }
        }
  }
#endif

    ...
        // dyld_all_image_infos image list does not contain dyld
        // add it as dyldPath field in dyld_all_image_infos
        // for simulator, dyld_sim is in image list, need host dyld added
#if TARGET_IPHONE_SIMULATOR
        // get path of host dyld from table of syscall vectors in host dyld
        void* addressInDyld = gSyscallHelpers;
#else
        // get path of dyld itself
        void*  addressInDyld = (void*)&__dso_handle;
#endif
              // 獲取dyld路徑并與gProcessInfo->dyldPath對比
              // 如果不同將獲取到的路徑復制給gProcessInfo->dyldPath
        char dyldPathBuffer[MAXPATHLEN+1];
        int len = proc_regionfilename(getpid(), (uint64_t)(long)addressInDyld, dyldPathBuffer, MAXPATHLEN);
        if ( len > 0 ) {
            dyldPathBuffer[len] = '\0'; // proc_regionfilename() does not zero terminate returned string
            if ( strcmp(dyldPathBuffer, gProcessInfo->dyldPath) != 0 )
                gProcessInfo->dyldPath = strdup(dyldPathBuffer);
        }
...

}

這一步流程為:實例化主程序->檢測主程序是否支持當前設備版本->檢測進程信息中dyld路徑是否與dyld路徑相符顺饮,若不符則重新賦值

源碼中有這樣一段注釋:

// dyld_all_image_infos image list does not contain dyld
// add it as dyldPath field in dyld_all_image_infos
// for simulator, dyld_sim is in image list, need host dyld added

就是說吵聪,dyld加載的image_infos并不包含dyld本身,他被放到dyld_all_image_infosdyldPath字段中去了兼雄。而對于模擬器吟逝,dyld加載的image_infos是包含dyld_sim的。

dyld_all_image_infos是個結構體赦肋,同樣分為32位及64位兩個版本块攒,分別對應dyld_all_image_infos_32dyld_all_image_infos_64,由于獲取dyld_all_image_infos需要用到一些未開源信息佃乘,這里為了方便囱井,從側面驗證一下這條注釋信息:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    for (uint32_t i = 0; i < _dyld_image_count(); ++i) {
        NSLog(@"%s", _dyld_get_image_name(i));
    }
}
真機
模擬器

可以看到,真機打印出的加載image中并沒有dyld趣避,第0個image是主程序庞呕。同樣,模擬器對應打印的image也沒有dyld程帕,第0個image是dyld_sim住练,第一個image才是主程序

回到最核心的instantiateFromLoadedImage函數(shù),實例化主程序:

// The kernel maps in main executable before dyld gets control.  We need to 
// make an ImageLoader* for the already mapped in main executable.
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";
}

從注釋及源碼可以看到愁拭,kernel在dyld之前已經(jīng)加載了主程序Mach-O讲逛,dyld判斷Mach-O的兼容性后,實例化成ImageLoader加載到內存中交給dyld管理

// create image for main executable
ImageLoader* ImageLoaderMachO::instantiateMainExecutable(const macho_header* mh, uintptr_t slide, const char* path, const LinkContext& context)
{
    //dyld::log("ImageLoader=%ld, ImageLoaderMachO=%ld, ImageLoaderMachOClassic=%ld, ImageLoaderMachOCompressed=%ld\n",
    //  sizeof(ImageLoader), sizeof(ImageLoaderMachO), sizeof(ImageLoaderMachOClassic), sizeof(ImageLoaderMachOCompressed));
    bool compressed;
    unsigned int segCount;
    unsigned int libCount;
    const linkedit_data_command* codeSigCmd;
    const encryption_info_command* encryptCmd;
    sniffLoadCommands(mh, path, false, &compressed, &segCount, &libCount, context, &codeSigCmd, &encryptCmd);
    // instantiate concrete class based on content of load commands
    if ( compressed ) 
        return ImageLoaderMachOCompressed::instantiateMainExecutable(mh, slide, path, segCount, libCount, context);
    else
#if SUPPORT_CLASSIC_MACHO
        return ImageLoaderMachOClassic::instantiateMainExecutable(mh, slide, path, segCount, libCount, context);
#else
        throw "missing LC_DYLD_INFO load command";
#endif
}

instantiateMainExecutable內部調用sniffLoadCommands岭埠,這個函數(shù)會對主程序Mach-O進行一系列的校驗妆绞。從函數(shù)名也可以看出,這里的校驗并不包括對主程序Mach-O的解密操作枫攀,這個操作是由xnu完成的括饶。

可以看到,當compressed為true時来涨,通過ImageLoaderMachOCompressed::instantiateMainExecutable初始化图焰,否則通過ImageLoaderMachOClassic::instantiateMainExecutable初始化。其實兩者內部的邏輯相同蹦掐,只是返回類型一個是ImageLoaderMachOCompressed一個是ImageLoaderMachOClassic而已技羔。

以ImageLoaderMachOCompressed為例:

// create image for main executable
ImageLoaderMachOCompressed* ImageLoaderMachOCompressed::instantiateMainExecutable(const macho_header* mh, uintptr_t slide, const char* path, 
                                                                        unsigned int segCount, unsigned int libCount, const LinkContext& context)
{
        // 初始化image
    ImageLoaderMachOCompressed* image = ImageLoaderMachOCompressed::instantiateStart(mh, path, segCount, libCount);

    // set slide for PIE programs
        // 設置image偏移量
    image->setSlide(slide);

    // for PIE record end of program, to know where to start loading dylibs
    if ( slide != 0 )
                // 設置動態(tài)庫起始地址
        fgNextPIEDylibAddress = (uintptr_t)image->getEnd();

        // 禁用段覆蓋檢測
    image->disableCoverageCheck();
        // 結束image上下文
    image->instantiateFinish(context);
        // 設置image加載狀態(tài)為dyld_image_state_mapped
    image->setMapped(context);

    if ( context.verboseMapping ) {
        dyld::log("dyld: Main executable mapped %s\n", path);
        for(unsigned int i=0, e=image->segmentCount(); i < e; ++i) {
            const char* name = image->segName(i);
            if ( (strcmp(name, "__PAGEZERO") == 0) || (strcmp(name, "__UNIXSTACK") == 0)  )
                dyld::log("%18s at 0x%08lX->0x%08lX\n", name, image->segPreferredLoadAddress(i), image->segPreferredLoadAddress(i)+image->segSize(i));
            else
                dyld::log("%18s at 0x%08lX->0x%08lX\n", name, image->segActualLoadAddress(i), image->segActualEndAddress(i));
        }
    }

    return image;
}

此處流程為:初始化image->設置image偏移量->設置動態(tài)庫起始地址->禁用段覆蓋檢測->結束image上下文->設置image加載狀態(tài)為dyld_image_state_mapped僵闯,并調用notifySingle進行處理

其中,結束image上下文內部做了:解析loadCmds藤滥、設置動態(tài)庫連接信息鳖粟、設置符號表相關信息等

  • 加載插入的動態(tài)庫
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
        int argc, const char* argv[], const char* envp[], const char* apple[], 
        uintptr_t* startGlue)
{
...
        // load any inserted libraries
                // 插入動態(tài)庫
        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.
                // 記錄插入的動態(tài)庫個數(shù)
        sInsertedDylibCount = sAllImages.size()-1;
...
}

如果配置了DYLD_INSERT_LIBRARIES環(huán)境變量,通過loadInsertedDylib插入配置的動態(tài)庫拙绊。對于越獄插件而言向图,其實就是添加DYLD_INSERT_LIBRARIES這個環(huán)境變量達到加載插件的目的

static void loadInsertedDylib(const char* path)
{
    ImageLoader* image = NULL;
    unsigned cacheIndex;
    try {
        LoadContext context;
        context.useSearchPaths      = false;
        context.useFallbackPaths    = false;
        context.useLdLibraryPath    = false;
        context.implicitRPath       = false;
        context.matchByInstallName  = false;
        context.dontLoad            = false;
        context.mustBeBundle        = false;
        context.mustBeDylib         = true;
        context.canBePIE            = false;
        context.enforceIOSMac       = true;
        context.origin              = NULL; // can't use @loader_path with DYLD_INSERT_LIBRARIES
        context.rpath               = NULL;
        image = load(path, context, cacheIndex);
    }
    catch (const char* msg) {
        if ( gLinkContext.allowInsertFailures )
            dyld::log("dyld: warning: could not load inserted library '%s' into hardened process because %s\n", path, msg);
        else
            halt(dyld::mkstringf("could not load inserted library '%s' because %s\n", path, msg));
    }
    catch (...) {
        halt(dyld::mkstringf("could not load inserted library '%s'\n", path));
    }
}

內部構建context后調用load函數(shù)生成image

ImageLoader* load(const char* path, const LoadContext& context, unsigned& cacheIndex)
{

...
    // try all path permutations and check against existing loaded images

    ImageLoader* image = loadPhase0(path, orgPath, context, cacheIndex, NULL);
    if ( image != NULL ) {
        CRSetCrashLogMessage2(NULL);
        return image;
    }

    // try all path permutations and try open() until first success
    std::vector<const char*> exceptions;
    image = loadPhase0(path, orgPath, context, cacheIndex, &exceptions);
#if !TARGET_IPHONE_SIMULATOR
    // <rdar://problem/16704628> support symlinks on disk to a path in dyld shared cache
    if ( image == NULL)
        image = loadPhase2cache(path, orgPath, context, cacheIndex, &exceptions);
#endif
    CRSetCrashLogMessage2(NULL);
    if ( image != NULL ) {
        // <rdar://problem/6916014> leak in dyld during dlopen when using DYLD_ variables
        for (std::vector<const char*>::iterator it = exceptions.begin(); it != exceptions.end(); ++it) {
            free((void*)(*it));
        }
        // if loaded image is not from cache, but original path is in cache
        // set gSharedCacheOverridden flag to disable some ObjC optimizations
        if ( !gSharedCacheOverridden && !image->inSharedCache() && image->isDylib() && cacheablePath(path) && inSharedCache(path) ) {
            gSharedCacheOverridden = true;
        }
        return image;
    }
...
}

可以看到,先調用了loadPhase0查找image标沪,如果沒找到再調用loadPhase2cache查找image榄攀。其實內部有一整套loadPhase0~loadPhase6的流程來查找及加載image,如果在共享緩存中找到則直接調用instantiateFromCache 實例化image金句,否則通過loadPhase5open打開文件并調用loadPhase6檩赢,內部通過instantiateFromFile實例化image,最后再調用checkandAddImage將image加載進內存

  • 鏈接主程序
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
        int argc, const char* argv[], const char* envp[], const char* apple[], 
        uintptr_t* startGlue)
{
...
        // 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這個函數(shù)完成的:

void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool preflightOnly, bool neverUnload, const RPathChain& loaderRPaths, const char* imagePath)
{
    //dyld::log("ImageLoader::link(%s) refCount=%d, neverUnload=%d\n", imagePath, fDlopenReferenceCount, fNeverUnload);
    
    // clear error strings
    (*context.setErrorStrings)(0, NULL, NULL, NULL);

    uint64_t t0 = mach_absolute_time();
        // 遞歸加載主程序依賴的庫
    this->recursiveLoadLibraries(context, preflightOnly, loaderRPaths, imagePath);
    context.notifyBatch(dyld_image_state_dependents_mapped, preflightOnly);

    // we only do the loading step for preflights
    if ( preflightOnly )
        return;

    uint64_t t1 = mach_absolute_time();
        // 清空image層級關系
    context.clearAllDepths();
        // 遞歸更新image層級關系
    this->recursiveUpdateDepth(context.imageCount());

    __block uint64_t t2, t3, t4, t5;
    {
        dyld3::ScopedTimer(DBG_DYLD_TIMING_APPLY_FIXUPS, 0, 0, 0);
        t2 = mach_absolute_time();
                // 遞歸進行rebase
        this->recursiveRebase(context);
        context.notifyBatch(dyld_image_state_rebased, false);

        t3 = mach_absolute_time();
        if ( !context.linkingMainExecutable )
                        // 遞歸綁定符號表
            this->recursiveBindWithAccounting(context, forceLazysBound, neverUnload);

        t4 = mach_absolute_time();
        if ( !context.linkingMainExecutable )
                        // 綁定弱符號表
            this->weakBind(context);
        t5 = mach_absolute_time();
    }

    if ( !context.linkingMainExecutable )
        context.notifyBatch(dyld_image_state_bound, false);
    uint64_t t6 = mach_absolute_time(); 

    std::vector<DOFInfo> dofs;
        // 遞歸獲取dof信息
    this->recursiveGetDOFSections(context, dofs);
        // 注冊dofs信息
    context.registerDOFs(dofs);
    uint64_t t7 = mach_absolute_time(); 

    // interpose any dynamically loaded images
    if ( !context.linkingMainExecutable && (fgInterposingTuples.size() != 0) ) {
        dyld3::ScopedTimer timer(DBG_DYLD_TIMING_APPLY_INTERPOSING, 0, 0, 0);
                // 遞歸應用插入的動態(tài)庫
        this->recursiveApplyInterposing(context);
    }

    // clear error strings
    (*context.setErrorStrings)(0, NULL, NULL, NULL);

    fgTotalLoadLibrariesTime += t1 - t0;
    fgTotalRebaseTime += t3 - t2;
    fgTotalBindTime += t4 - t3;
    fgTotalWeakBindTime += t5 - t4;
    fgTotalDOF += t7 - t6;
    
    // done with initial dylib loads
    fgNextPIEDylibAddress = 0;
}

內部加載動態(tài)庫贞瞒、rebase、綁定符號表趁曼、注冊dofs信息等军浆,同時還計算各步驟的耗時。如果想獲取這些耗時彰阴,只需要在環(huán)境變量中添加DYLD_PRINT_STATISTICS就可以了瘾敢,同樣拍冠,這個環(huán)境變量也不需要value

  • 鏈接插入的動態(tài)庫
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
        int argc, const char* argv[], const char* envp[], const char* apple[], 
        uintptr_t* startGlue)
{
...
        // 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);
            }
        }
...
    // 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);
            }
        }
...
}

這里很簡單尿这,從加載的images中取出image,重復前面的link操作進行連接庆杜。registerInterposing內部會加載loadCmds并查找__interpose__DATA段,讀取段信息保存到fgInterposingTuples中,然后調用applyInterposing预厌,內部調用recursiveApplyInterposing建炫,通過這個函數(shù)調用到doInterpose,同樣以ImageLoaderMachOCompressed為例:

void ImageLoaderMachOCompressed::doInterpose(const LinkContext& context)
{
    if ( context.verboseInterposing )
        dyld::log("dyld: interposing %lu tuples onto image: %s\n", fgInterposingTuples.size(), this->getPath());

    // update prebound symbols
    eachBind(context, ^(const LinkContext& ctx, ImageLoaderMachOCompressed* image,
                        uintptr_t addr, uint8_t type, const char* symbolName,
                        uint8_t symbolFlags, intptr_t addend, long libraryOrdinal,
                        ExtraBindData *extraBindData,
                        const char* msg, LastLookup* last, bool runResolver) {
        return ImageLoaderMachOCompressed::interposeAt(ctx, image, addr, type, symbolName, symbolFlags,
                                                       addend, libraryOrdinal, extraBindData,
                                                       msg, last, runResolver);
    });
    eachLazyBind(context, ^(const LinkContext& ctx, ImageLoaderMachOCompressed* image,
                            uintptr_t addr, uint8_t type, const char* symbolName,
                            uint8_t symbolFlags, intptr_t addend, long libraryOrdinal,
                            ExtraBindData *extraBindData,
                            const char* msg, LastLookup* last, bool runResolver) {
        return ImageLoaderMachOCompressed::interposeAt(ctx, image, addr, type, symbolName, symbolFlags,
                                                       addend, libraryOrdinal, extraBindData,
                                                       msg, last, runResolver);
    });
}

這里eachBind與eachLazyBind都調用了interposeAt断盛,interposeAt通過interposedAddress在上文提到的fgInterposingTuples中找到需要替換的符號地址進行替換

  • 弱符號綁定
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
        int argc, const char* argv[], const char* envp[], const char* apple[], 
        uintptr_t* startGlue)
{
...
        // <rdar://problem/12186933> do weak binding only after all inserted images linked
        sMainExecutable->weakBind(gLinkContext);
...
}

void ImageLoader::weakBind(const LinkContext& context)
{
...
// get set of ImageLoaders that participate in coalecsing
    ImageLoader* imagesNeedingCoalescing[fgImagesRequiringCoalescing];
    unsigned imageIndexes[fgImagesRequiringCoalescing];
// 合并所有動態(tài)庫的弱符號到列表中
    int count = context.getCoalescedImages(imagesNeedingCoalescing, imageIndexes);

    // count how many have not already had weakbinding done
    int countNotYetWeakBound = 0;
    int countOfImagesWithWeakDefinitionsNotInSharedCache = 0;
    for(int i=0; i < count; ++i) {
        if ( ! imagesNeedingCoalescing[i]->weakSymbolsBound(imageIndexes[i]) )
                        // 獲取未進行綁定的弱符號的個數(shù)
            ++countNotYetWeakBound;
        if ( ! imagesNeedingCoalescing[i]->inSharedCache() )
                    // 獲取在共享緩存中已綁定的弱符號個數(shù)
            ++countOfImagesWithWeakDefinitionsNotInSharedCache;
    }

    // don't need to do any coalescing if only one image has overrides, or all have already been done
    if ( (countOfImagesWithWeakDefinitionsNotInSharedCache > 0) && (countNotYetWeakBound > 0) ) {
        // make symbol iterators for each
        ImageLoader::CoalIterator iterators[count];
        ImageLoader::CoalIterator* sortedIts[count];
        for(int i=0; i < count; ++i) {
                        // 對需要綁定的弱符號排序
            imagesNeedingCoalescing[i]->initializeCoalIterator(iterators[i], i, imageIndexes[i]);
            sortedIts[i] = &iterators[i];
            if ( context.verboseWeakBind )
                dyld::log("dyld: weak bind load order %d/%d for %s\n", i, count, imagesNeedingCoalescing[i]->getIndexedPath(imageIndexes[i]));
        }

    // walk all symbols keeping iterators in sync by 
        // only ever incrementing the iterator with the lowest symbol 
        int doneCount = 0;
        while ( doneCount != count ) {
            //for(int i=0; i < count; ++i)
            //  dyld::log("sym[%d]=%s ", sortedIts[i]->loadOrder, sortedIts[i]->symbolName);
            //dyld::log("\n");
            // increment iterator with lowest symbol
                        // 計算弱符號偏移量及大小罗洗,綁定弱符號
            if ( sortedIts[0]->image->incrementCoalIterator(*sortedIts[0]) )
                ++doneCount; 
...
        }
}

主要流程:合并所有動態(tài)庫的弱符號到列表中->對需要綁定的弱符號排序->計算弱符號偏移量及大小,綁定弱符號

  • 初始化主程序
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
        int argc, const char* argv[], const char* envp[], const char* apple[], 
        uintptr_t* startGlue)
{
...
 
        CRSetCrashLogMessage("dyld: launch, running initializers");
    #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();

...
}

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) {
                        // 初始化動態(tà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]);
}

先初始化動態(tài)庫钢猛,然后初始化主程序伙菜。上文提到的DYLD_PRINT_STATISTICS環(huán)境變量在這里也出現(xiàn)了,除此之外還有個detail版的環(huán)境變量DYLD_PRINT_STATISTICS_DETAILS

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

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

動態(tài)庫和主程序的初始化是調用runInitializers命迈,內部通過processInitializers調用recursiveInitialization遞歸初始化當前image鎖依賴的庫

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 ) {
        uint8_t oldState = fState;
        // break cycles
        fState = dyld_image_state_dependents_initialized-1;
        try {
            // initialize lower level libraries first
            for(unsigned int i=0; i < libraryCount(); ++i) {
                ImageLoader* dependentImage = libImage(i);
                if ( dependentImage != NULL ) {
                    // don't try to initialize stuff "above" me yet
                    if ( libIsUpward(i) ) {
                        uninitUps.images[uninitUps.count] = dependentImage;
                        uninitUps.count++;
                    }
                    else if ( dependentImage->fDepth >= fDepth ) {
                        dependentImage->recursiveInitialization(context, this_thread, libPath(i), timingInfo, uninitUps);
                    }
                }
            }
            
            // record termination order
            if ( this->needsTermination() )
                context.terminationRecorder(this);
            
            // let objc know we are about to initialize this image
            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);
            
            if ( hasInitializers ) {
                uint64_t t2 = mach_absolute_time();
                timingInfo.addTime(this->getShortName(), t2-t1);
            }
        }
        catch (const char* msg) {
            // this image is not initialized
            fState = oldState;
            recursiveSpinUnLock();
            throw;
        }
    }
    
    recursiveSpinUnLock();
}

遞歸初始化很簡單沒啥好說的贩绕,注意內部有個調用context.notifySingle(dyld_image_state_initialized, this, NULL)火的,其實每次image狀態(tài)改變都會調用notifySingle這個方法:

static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
{
    //dyld::log("notifySingle(state=%d, image=%s)\n", state, image->getPath());
    std::vector<dyld_image_state_change_handler>* handlers = stateToHandlers(state, sSingleHandlers);
    if ( handlers != NULL ) {
        dyld_image_info info;
        info.imageLoadAddress   = image->machHeader();
        info.imageFilePath      = image->getRealPath();
        info.imageFileModDate   = image->lastModified();
        for (std::vector<dyld_image_state_change_handler>::iterator it = handlers->begin(); it != handlers->end(); ++it) {
            const char* result = (*it)(state, 1, &info);
            if ( (result != NULL) && (state == dyld_image_state_mapped) ) {
                //fprintf(stderr, "  image rejected by handler=%p\n", *it);
                // make copy of thrown string so that later catch clauses can free it
                const char* str = strdup(result);
                throw str;
            }
        }
    }
    if ( state == dyld_image_state_mapped ) {
        // <rdar://problem/7008875> Save load addr + UUID for images from outside the shared cache
        if ( !image->inSharedCache() ) {
            dyld_uuid_info info;
            if ( image->getUUID(info.imageUUID) ) {
                info.imageLoadAddress = image->machHeader();
                addNonSharedCacheImageUUID(info);
            }
        }
    }
    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);
        }
    }
    // mach message csdlc about dynamically unloaded images
    if ( image->addFuncNotified() && (state == dyld_image_state_terminated) ) {
        notifyKernel(*image, false);
        const struct mach_header* loadAddress[] = { image->machHeader() };
        const char* loadPath[] = { image->getPath() };
        notifyMonitoringDyld(true, 1, loadAddress, loadPath);
    }
}

可見,當state == dyld_image_state_mapped時淑倾,將image對應的UUID存起來馏鹤,當state == dyld_image_state_dependents_initialized并且有sNotifyObjCInit回調時調用sNotifyObjCInit函數(shù)。

搜索回調函數(shù)賦值入口:

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;

...
}

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

發(fā)現(xiàn)是通過_dyld_objc_notify_register這個函數(shù)注冊回調的娇哆,回到文章開頭符號斷點[NSObject load]截圖中的堆棧:

* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.2
  * frame #0: 0x000000010944f3b1 libobjc.A.dylib`+[NSObject load]
    frame #1: 0x000000010943d317 libobjc.A.dylib`call_load_methods + 691
    frame #2: 0x000000010943e814 libobjc.A.dylib`load_images + 77
    frame #3: 0x0000000108b73b97 dyld_sim`dyld::registerObjCNotifiers(void (*)(unsigned int, char const* const*, mach_header const* const*), void (*)(char const*, mach_header const*), void (*)(char const*, mach_header const*)) + 260
    frame #4: 0x000000010b779bf3 libdyld.dylib`_dyld_objc_notify_register + 113
    frame #5: 0x000000010944ca12 libobjc.A.dylib`_objc_init + 115
    frame #6: 0x000000010b7015c0 libdispatch.dylib`_os_object_init + 13
    frame #7: 0x000000010b70f4e5 libdispatch.dylib`libdispatch_init + 300
    frame #8: 0x0000000109e05a78 libSystem.B.dylib`libSystem_initializer + 164
    frame #9: 0x0000000108b82b96 dyld_sim`ImageLoaderMachO::doModInitFunctions(ImageLoader::LinkContext const&) + 506
    frame #10: 0x0000000108b82d9c dyld_sim`ImageLoaderMachO::doInitialization(ImageLoader::LinkContext const&) + 40
    frame #11: 0x0000000108b7e3fc dyld_sim`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 324
    frame #12: 0x0000000108b7e392 dyld_sim`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 218
    frame #13: 0x0000000108b7d5d3 dyld_sim`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 133
    frame #14: 0x0000000108b7d665 dyld_sim`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 73
    frame #15: 0x0000000108b71333 dyld_sim`dyld::initializeMainExecutable() + 129
    frame #16: 0x0000000108b75434 dyld_sim`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 4384
    frame #17: 0x0000000108b70630 dyld_sim`start_sim + 136
    frame #18: 0x00000001155c1234 dyld`dyld::useSimulatorDyld(int, macho_header const*, char const*, int, char const**, char const**, char const**, unsigned long*, unsigned long*) + 2238
    frame #19: 0x00000001155bf0ce dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 522
    frame #20: 0x00000001155ba503 dyld`dyldbootstrap::start(macho_header const*, int, char const**, long, macho_header const*, unsigned long*) + 1167
    frame #21: 0x00000001155ba036 dyld`_dyld_start + 54

可以看到湃累,_dyld_objc_notify_register是在初始化libobjc.A.dylib這個動態(tài)庫時調用的,然后_objc_init內部調用了load_images迂尝,進而調用call_load_methods脱茉,從而調用各個類中的load方法,感興趣可以下載源碼查看具體流程

notifySingle調用完畢后垄开,開始真正初始化工作doInitialization

bool ImageLoaderMachO::doInitialization(const LinkContext& context)
{
    CRSetCrashLogMessage2(this->getPath());

    // mach-o has -init and static initializers
    doImageInit(context);
    doModInitFunctions(context);
    
    CRSetCrashLogMessage2(NULL);
    
    return (fHasDashInit || fHasInitializers);
}

doImageInit執(zhí)行LC_ROUTINES_COMMAND segment中保存的函數(shù)琴许,doModInitFunctions執(zhí)行__DATA,__mod_init_func section中保存的函數(shù)。這個section中保存的是C++的構造函數(shù)及帶有attribute((constructor))的C函數(shù)溉躲,簡單驗證一下:

class Test {
public:
    Test();
};

Test::Test(){
    NSLog(@"%s", __func__);
}

Test test;

__attribute__((constructor)) void testConstructor() {
    NSLog(@"%s", __func__);
}
__mod_init_func

同樣榜田,通過MachOView也可以看到:


MachOView

顯然,__mod_init_func中的函數(shù)在類對應的load方法之后調用锻梳。

  • 查找主程序入口函數(shù)指針并返回
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
        int argc, const char* argv[], const char* envp[], const char* apple[], 
        uintptr_t* startGlue)
{
... 
        // 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;
}

此處兩步走:調用getEntryFromLC_MAIN獲取入口箭券,如果沒有入口則調用getEntryFromLC_UNIXTHREAD獲取入口。這個入口就是主程序main函數(shù)的地址

void* ImageLoaderMachO::getEntryFromLC_MAIN() const
{
    const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
    const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
    const struct load_command* cmd = cmds;
    for (uint32_t i = 0; i < cmd_count; ++i) {
        if ( cmd->cmd == LC_MAIN ) {
            entry_point_command* mainCmd = (entry_point_command*)cmd;
            void* entry = (void*)(mainCmd->entryoff + (char*)fMachOData);
            // <rdar://problem/8543820&9228031> verify entry point is in image
            if ( this->containsAddress(entry) )
                return entry;
            else
                throw "LC_MAIN entryoff is out of range";
        }
        cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
    }
    return NULL;
}

void* ImageLoaderMachO::getEntryFromLC_UNIXTHREAD() const
{
    const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
    const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
    const struct load_command* cmd = cmds;
    for (uint32_t i = 0; i < cmd_count; ++i) {
        if ( cmd->cmd == LC_UNIXTHREAD ) {
    #if __i386__
            const i386_thread_state_t* registers = (i386_thread_state_t*)(((char*)cmd) + 16);
            void* entry = (void*)(registers->eip + fSlide);
            // <rdar://problem/8543820&9228031> verify entry point is in image
            if ( this->containsAddress(entry) )
                return entry;
    #elif __x86_64__
            const x86_thread_state64_t* registers = (x86_thread_state64_t*)(((char*)cmd) + 16);
            void* entry = (void*)(registers->rip + fSlide);
            // <rdar://problem/8543820&9228031> verify entry point is in image
            if ( this->containsAddress(entry) )
                return entry;
    #elif __arm64__ && !__arm64e__
            // temp support until <rdar://39514191> is fixed
            const uint64_t* regs64 = (uint64_t*)(((char*)cmd) + 16);
            void* entry = (void*)(regs64[32] + fSlide); // arm_thread_state64_t.__pc
            // <rdar://problem/8543820&9228031> verify entry point is in image
            if ( this->containsAddress(entry) )
                return entry;
    #endif
        }
        cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
    }
    throw "no valid entry point";
}

可以看到疑枯,入口是在load_command的LC_MAIN或者LC_UNIXTHREAD

LC_MAIN

受能力所限辩块,可能存在部分理解偏差,歡迎指正荆永。

Have fun!

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末废亭,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子具钥,更是在濱河造成了極大的恐慌豆村,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件骂删,死亡現(xiàn)場離奇詭異掌动,居然都是意外死亡,警方通過查閱死者的電腦和手機宁玫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門粗恢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人欧瘪,你說我怎么就攤上這事眷射。” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵凭迹,是天一觀的道長罚屋。 經(jīng)常有香客問我,道長嗅绸,這世上最難降的妖魔是什么脾猛? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮鱼鸠,結果婚禮上猛拴,老公的妹妹穿的比我還像新娘。我一直安慰自己蚀狰,他們只是感情好愉昆,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著麻蹋,像睡著了一般跛溉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上扮授,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天芳室,我揣著相機與錄音,去河邊找鬼刹勃。 笑死堪侯,一個胖子當著我的面吹牛,可吹牛的內容都是我干的荔仁。 我是一名探鬼主播伍宦,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼乏梁!你這毒婦竟也來了次洼?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤掌呜,失蹤者是張志新(化名)和其女友劉穎滓玖,沒想到半個月后坪哄,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體质蕉,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年翩肌,在試婚紗的時候發(fā)現(xiàn)自己被綠了模暗。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡念祭,死狀恐怖兑宇,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情粱坤,我是刑警寧澤隶糕,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布瓷产,位于F島的核電站,受9級特大地震影響枚驻,放射性物質發(fā)生泄漏濒旦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一再登、第九天 我趴在偏房一處隱蔽的房頂上張望尔邓。 院中可真熱鬧,春花似錦锉矢、人聲如沸梯嗽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽灯节。三九已至,卻和暖如春绵估,著一層夾襖步出監(jiān)牢的瞬間显晶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工壹士, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留磷雇,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓躏救,卻偏偏與公主長得像唯笙,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子盒使,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內容