dyld源码分析-动态加载main的流程和load函数执行的流程

动态加载main

Windows系统的动态库是DLL文件,Linux系统是so文件,macOS系统的动态库则使用dylib文件作为动态库。
dylib本质上是一个Mach-O格式的文件,它与普通的Mach-O执行文件几乎使用一样的结构,只是在文件类型上一个是MH_DYLIB,一个是MH_EXECUTE。
在系统的/usr/lib目录下,存放了大量供系统与应用程序调用的动态库文件,使用file命令查看系统动态库libobjc.dylib的信息,输出如下:

$ file /usr/lib/libobjc.dylib
/usr/lib/libobjc.dylib: Mach-O universal binary with 3 architectures
/usr/lib/libobjc.dylib (for architecture i386):    Mach-O dynamically linked shared library i386
/usr/lib/libobjc.dylib (for architecture x86_64):    Mach-O 64-bit dynamically linked shared library x86_64
/usr/lib/libobjc.dylib (for architecture x86_64h):    Mach-O 64-bit dynamically linked shared library x86_64

从上面的输出信息可以看出,libobjc.dylib是一个通用的二进制文件,包含了三种cpu架构的Mach-O。另外,
可以使用Mach-O格式文件管理工具otool查看dylib的信息,如查看动态库的依赖库信息如下:

$ otool -L /usr/lib/libobjc.dylib
/usr/lib/libobjc.dylib:
    /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
    /usr/lib/libauto.dylib (compatibility version 1.0.0, current version 1.0.0)
    /usr/lib/libc++abi.dylib (compatibility version 1.0.0, current version 125.0.0)
    /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 120.1.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1225.0.0)

0x1 构建动态库

XCode环境提供了创建动态库的工程模板,创建动态库的方法比较简单,在XCode中选择File->New->Project,在打开的工程模选择对话框中,选择标签macOS->Framework & Library,在右侧选择Library,点击Next按钮,在新页面中输入项目名称mylib,Type选择Dynamic,单击Next按钮选择项目保存的路径后,工程就创建好了。接着修改工程文件内容:

//mylib.h
#import <Foundation/Foundation.h>

@interface mylib : NSObject
-(void) hello;
@end

//mylib.m
#import "mylib.h"

@implementation mylib
-(void) hello {
    NSLog(@"hello world");
}
@end

保存后。觇击菜单Product->Build,或者按键般的COMMAND+B键就编译成功了。命令执行完后,就会生成mylib.dylib文件。
XCode创建的项目是xcodeproj文件,可以使用XCode提供的工具xcodebuild在命令行下编译,在命令行下切换到工程文件所在的目录后,执行xcodebuild会有如下输出:

$ xcodebuild
=== BUILD TARGET mylib OF PROJECT mylib WITH THE DEFAULT CONFIGURATION (Release) ===

Check dependencies

Write auxiliary files
write-file /Users/macbook/Documents/macbook/macbook/code/chapter4/mylib/build/mylib.build/Release/mylib.build/mylib-own-target-headers.hmap
write-file /Users/macbook/Documents/macbook/macbook/code/chapter4/mylib/build/mylib.build/Release/mylib.build/mylib-all-non-framework-target-headers.hmap
......
/bin/mkdir -p /Users/macbook/Documents/macbook/macbook/code/chapter4/mylib/build/mylib.build/Release/mylib.build/Objects-normal/x86_64
write-file /Users/macbook/Documents/macbook/macbook/code/chapter4/mylib/build/mylib.build/Release/mylib.build/Objects-normal/x86_64/mylib.LinkFileList

CompileC build/mylib.build/Release/mylib.build/Objects-normal/x86_64/mylib.o mylib/mylib.m normal x86_64 objective-c com.apple.compilers.llvm.clang.1_0.compiler
    cd /Users/macbook/Documents/macbook/macbook/code/chapter4/mylib
    export LANG=en_US.US-ASCII
    /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -x objective-c -arch x86_64 -fmessage-length=94 -fdiagnostics-show-note-include-stack -fmacro-backtrace-limit=0 -fcolor-diagnostics -std=gnu99 -fobjc-arc -fmodules -gmodules -fmodules-prune-interval=86400 -fmodules-prune-after=345600 -fbuild-session-file=/var/folders/rd/mts0362j0n92rq0z1cnmdb580000gn/C/org.llvm.clang/ModuleCache/Session.modulevalidation -fmodules-validate-once-per-build-session -Wnon-modular-include-in-framework-module -Werror=non-modular-include-in-framework-module -Wno-trigraphs -fpascal-strings -Os -fno-common -Wno-missing-field-initializers -Wno-missing-prototypes -Werror=return-type -Wunreachable-code -Wno-implicit-atomic-properties -Werror=deprecated-objc-isa-usage -Werror=objc-root-class -Wno-arc-repeated-use-of-weak -Wduplicate-method-match -Wno-missing-braces -Wparentheses -Wswitch -Wunused-function -Wno-unused-label -Wno-unused-parameter -Wunused-variable -Wunused-value -Wempty-body -Wconditional-uninitialized -Wno-unknown-pragmas -Wno-shadow -Wno-four-char-constants -Wno-conversion -Wconstant-conversion -Wint-conversion -Wbool-conversion -Wenum-conversion -Wshorten-64-to-32 -Wpointer-sign -Wno-newline-eof -Wno-selector -Wno-strict-selector-match -Wundeclared-selector -Wno-deprecated-implementations -DNS_BLOCK_ASSERTIONS=1 -DOBJC_OLD_DISPATCH_PROTOTYPES=0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk -fasm-blocks -fstrict-aliasing -Wprotocol -Wdeprecated-declarations -mmacosx-version-min=10.11 -g -Wno-sign-conversion -iquote /Users/macbook/Documents/macbook/macbook/code/chapter4/mylib/build/mylib.build/Release/mylib.build/mylib-generated-files.hmap -I/Users/macbook/Documents/macbook/macbook/code/chapter4/mylib/build/mylib.build/Release/mylib.build/mylib-own-target-headers.hmap -I/Users/macbook/Documents/macbook/macbook/code/chapter4/mylib/build/mylib.build/Release/mylib.build/mylib-all-target-headers.hmap -iquote /Users/macbook/Documents/macbook/macbook/code/chapter4/mylib/build/mylib.build/Release/mylib.build/mylib-project-headers.hmap -I/Users/macbook/Documents/macbook/macbook/code/chapter4/mylib/build/Release/include -I/Users/macbook/Documents/macbook/macbook/code/chapter4/mylib/build/mylib.build/Release/mylib.build/DerivedSources/x86_64 -I/Users/macbook/Documents/macbook/macbook/code/chapter4/mylib/build/mylib.build/Release/mylib.build/DerivedSources -F/Users/macbook/Documents/macbook/macbook/code/chapter4/mylib/build/Release -MMD -MT dependencies -MF /Users/macbook/Documents/macbook/macbook/code/chapter4/mylib/build/mylib.build/Release/mylib.build/Objects-normal/x86_64/mylib.d --serialize-diagnostics /Users/macbook/Documents/macbook/macbook/code/chapter4/mylib/build/mylib.build/Release/mylib.build/Objects-normal/x86_64/mylib.dia -c /Users/macbook/Documents/macbook/macbook/code/chapter4/mylib/mylib/mylib.m -o /Users/macbook/Documents/macbook/macbook/code/chapter4/mylib/build/mylib.build/Release/mylib.build/Objects-normal/x86_64/mylib.o

Ld build/Release/libmylib.dylib normal x86_64
    cd /Users/macbook/Documents/macbook/macbook/code/chapter4/mylib
    export MACOSX_DEPLOYMENT_TARGET=10.11
    /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -arch x86_64 -dynamiclib -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk -L/Users/macbook/Documents/macbook/macbook/code/chapter4/mylib/build/Release -F/Users/macbook/Documents/macbook/macbook/code/chapter4/mylib/build/Release -filelist /Users/macbook/Documents/macbook/macbook/code/chapter4/mylib/build/mylib.build/Release/mylib.build/Objects-normal/x86_64/mylib.LinkFileList -install_name /usr/local/lib/libmylib.dylib -mmacosx-version-min=10.11 -fobjc-arc -fobjc-link-runtime -single_module -compatibility_version 1 -current_version 1 -Xlinker -dependency_info -Xlinker /Users/macbook/Documents/macbook/macbook/code/chapter4/mylib/build/mylib.build/Release/mylib.build/Objects-normal/x86_64/mylib_dependency_info.dat -o /Users/macbook/Documents/macbook/macbook/code/chapter4/mylib/build/Release/libmylib.dylib

GenerateDSYMFile build/Release/libmylib.dylib.dSYM build/Release/libmylib.dylib
    cd /Users/macbook/Documents/macbook/macbook/code/chapter4/mylib
    /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/dsymutil /Users/macbook/Documents/macbook/macbook/code/chapter4/mylib/build/Release/libmylib.dylib -o /Users/macbook/Documents/macbook/macbook/code/chapter4/mylib/build/Release/libmylib.dylib.dSYM

CodeSign build/Release/libmylib.dylib
    cd /Users/macbook/Documents/macbook/macbook/code/chapter4/mylib
    export CODESIGN_ALLOCATE=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate

Signing Identity:     "-"

    /usr/bin/codesign --force --sign - --timestamp=none /Users/macbook/Documents/macbook/macbook/code/chapter4/mylib/build/Release/libmylib.dylib

** BUILD SUCCEEDED **

从上面的日志中可以看出,整个编译过程分为:
检查依赖(Check dependencies)、生成辅助文件(Write auxiliary files)、编译(CompileC)、链接(Ld)、生成调试符号(GenerateDSYMFile)、代码签名(CodeSign)等几步。
编译代码时,使用的编译器是clang,这是苹果公司开发的用来替代gcc的现代化编译器,该编译器目前也广泛用于安卓、Linux平台上的软件开发工作;链接时使用clang前端传入参数给链接器ld,链接完成后dylib动态库就编译成功了;生成调试符号这一步主要用于生成符号的调试信息,供调试器使用;最后一步是代码签名,在没有指定签名证书的情况下,XCode默认使用的adhoc签名。

编译好的动态库可以被其它程序通过头文件声明隐式的调用,也可以像Linux系统那样,使用系统函数dlopen()、dlsym()手动进行调用。

0x2 dyld

动态库不能直接运行,而是需要通过系统的动态链接加载器进行加载到内存后执行,动态链接加载器在系统中以一个用户态的可执行文件形式存在,一般应用程序会在Mach-O文件部分指定一个LC_LOAD_DYLINKER的加载命令,此加载命令指定了dyld的路径,通常它的默认值是“/usr/lib/dyld”。系统内核在加载Mach-O文件时,会使用该路径指定的程序作为动态库的加载器来加载dylib。

dyld加载时,为了优化程序启动,启用了共享缓存(shared cache)技术。共享缓存会在进程启动时被dyld映射到内存中,之后,当任何Mach-O映像加载时,dyld首先会检查该Mach-O映像与所需的动态库是否在共享缓存中,如果存在,则直接将它在共享内存中的内存地址映射到进程的内存地址空间。在程序依赖的系统动态库很多的情况下,这种做法对程序启动性能是有明显提升的。

update_dyld_shared_cache程序确保了dyld的共享缓存是最新的,它会扫描/var/db/dyld/shared_region_roots/目录下paths路径文件,这些paths文件包含了需要加入到共享缓存的Mach-O文件路径列表,update_dyld_shared_cache()会挨个将这些Mach-O文件及其依赖的dylib都加共享缓存中去。

共享缓存是以文件形式存放在/var/db/dyld/目录下的,生成共享缓存的update_dyld_shared_cache程序位于是/usr/bin/目录下,该工具会为每种系统加构生成一个缓存文件与对应的内存地址map表,如下所示:

ls -l /var/db/dyld/
total 1741296
-rw-r--r--   1 root  wheel  333085108 Apr 22 15:02 dyld_shared_cache_i386
-rw-r--r--   1 root  wheel      65378 Apr 22 15:02 dyld_shared_cache_i386.map
-rw-r--r--   1 root  wheel  558259294 Apr 25 16:18 dyld_shared_cache_x86_64h
-rw-r--r--   1 root  wheel     129633 Apr 25 16:18 dyld_shared_cache_x86_64h.map
drwxr-xr-x  10 root  wheel        340 Apr  7 09:19 shared_region_roots

生成的共享缓存可以使用工具dyld_shared_cache_util查看它的信息,该工具位于dyld源码中的 launch-cache\dyld_shared_cache_util.cpp 文件,需要自己手动编译。另外,也可以使用dyld提供的两个函数dyld_shared_cache_extract_dylibs()与dyld_shared_cache_extract_dylibs_progress()来自己解开cache文件,代码位于dyld源码的launch-cache\dsc_extractor.cpp文件中。

update_dyld_shared_cache通常它只在系统的安装器安装软件与系统更新时调用,当然,可以手动运行“sudo update_dyld_shared_cache”来更新共享缓存。新的共享缓存会在系统下次启动后自动更新。

0x3 动态库的加载过程分析

dyld是苹果操作系统一个重要组成部分,而且令人兴奋的是,它是开源的,任何人可以通过苹果官网下载它的源码来阅读理解它的运作方式(下载地址:Source Browser),了解系统加载动态库的细节。

系统内核在加载动态库前,会加载dyld,然后调用去执行__dyld_start(),该函数会执行dyldbootstrap::start(),后者会执行_main()函数,dyld的加载动态库的代码就是从_main()开始执行的。下面以dyld源码的360.18版本为蓝本进行分析:

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;
    sMainExecutableMachHeader = mainExecutableMH;

    ......

    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];

    ......

    sExecShortName = ::strrchr(sExecPath, '/');
    if ( sExecShortName != NULL )
        ++sExecShortName;
    else
        sExecShortName = sExecPath;
    sProcessIsRestricted = processRestricted(mainExecutableMH, &ignoreEnvironmentVariables, &sProcessRequiresLibraryValidation);
    if ( sProcessIsRestricted ) {
#if SUPPORT_LC_DYLD_ENVIRONMENT
        checkLoadCommandEnvironmentVariables();
#endif     
        pruneEnvironmentVariables(envp, &apple);
        setContext(mainExecutableMH, argc, argv, envp, apple);
    }
    else {
        if ( !ignoreEnvironmentVariables )
            checkEnvironmentVariables(envp);
        defaultUninitializedFallbackPaths(envp);
    }
    if ( sEnv.DYLD_PRINT_OPTS )
        printOptions(argv);
    if ( sEnv.DYLD_PRINT_ENV )
        printEnvironmentVariables(envp);
    getHostInfo(mainExecutableMH, mainExecutableSlide);

    ......

    //第二步,初始化主程序
    try {
        // add dyld itself to UUID list
        addDyldImageToUUIDList();
        CRSetCrashLogMessage(sLoadingCrashMessage);
        // instantiate ImageLoader for main executable
        sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
        gLinkContext.mainExecutable = sMainExecutable;
        gLinkContext.processIsRestricted = sProcessIsRestricted;
        gLinkContext.processRequiresLibraryValidation = sProcessRequiresLibraryValidation;
        gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);

        ......

        //第三步,加载共享缓存
        checkSharedRegionDisable();
    #if DYLD_SHARED_CACHE_SUPPORT
        if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion )
            mapSharedCache();
    #endif

        // Now that shared cache is loaded, setup an versioned dylib overrides
    #if SUPPORT_VERSIONED_PATHS
        checkVersionedPaths();
    #endif

        //第四步,加载插入的动态库
        if    ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
            for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib)
                loadInsertedDylib(*lib);
        }
        sInsertedDylibCount = sAllImages.size()-1;

        //第五步,链接主程序
        gLinkContext.linkingMainExecutable = true;
        link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL));
        sMainExecutable->setNeverUnloadRecursive();
        if ( sMainExecutable->forceFlat() ) {
            gLinkContext.bindFlat = true;
            gLinkContext.prebindUsage = ImageLoader::kUseNoPrebinding;
        }

        //第六步,链接插入的动态库
        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));
                image->setNeverUnloadRecursive();
            }
            // only INSERTED libraries can interpose
            for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
                ImageLoader* image = sAllImages[i+1];
                image->registerInterposing();
            }
        }

        // <rdar://problem/19315404> dyld should support interposition even without DYLD_INSERT_LIBRARIES
        for (int i=sInsertedDylibCount+1; i < sAllImages.size(); ++i) {
            ImageLoader* image = sAllImages[i];
            if ( image->inSharedCache() )
                continue;
            image->registerInterposing();
        }

        // apply interposing to initial set of images
        for(int i=0; i < sImageRoots.size(); ++i) {
            sImageRoots[i]->applyInterposing(gLinkContext);
        }

        //第七步,执行弱符号绑定
        gLinkContext.linkingMainExecutable = false;
        // <rdar://problem/12186933> do weak binding only after all inserted images linked
        sMainExecutable->weakBind(gLinkContext);

        //第八步,执行初始化方法
        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

        //第九步,查找入口点并返回
        result = (uintptr_t)sMainExecutable->getThreadPC();
        if ( result != 0 ) {
            // main executable uses LC_MAIN, needs to return to glue in libdyld.dylib
            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->getMain();
            *startGlue = 0;
        }
    }
    catch(const char* message) {
        syncAllImages();
        halt(message);
    }
    catch(...) {
        dyld::log("dyld: launch failed\n");
    }

    CRSetCrashLogMessage(NULL);

    return result;
}

整个方法的代码比较长,将它按功能分成九个步骤进行讲解:

第一步,设置运行环境,处理环境变量

代码在开始时候,将传入的变量mainExecutableMH赋值给了sMainExecutableMachHeader,这是一个macho_header类型的变量,其结构体内容就是本章前面介绍的mach_header结构体,表示的是当前主程序的Mach-O头部信息,有了头部信息,加载器就可以从头开始,遍历整个Mach-O文件的信息。
接着执行了setContext(),此方法设置了全局一个链接上下文,包括一些回调函数、参数与标志设置信息,代码片断如下:

static void setContext(const macho_header* mainExecutableMH, int argc, const char* argv[], const char* envp[], const char* apple[])
{
    gLinkContext.loadLibrary            = &libraryLocator;
    gLinkContext.terminationRecorder    = &terminationRecorder;
    gLinkContext.flatExportFinder        = &flatFindExportedSymbol;
    gLinkContext.coalescedExportFinder    = &findCoalescedExportedSymbol;
    gLinkContext.getCoalescedImages        = &getCoalescedImages;

    ......

    gLinkContext.bindingOptions            = ImageLoader::kBindingNone;
    gLinkContext.argc                    = argc;
    gLinkContext.argv                    = argv;
    gLinkContext.envp                    = envp;
    gLinkContext.apple                    = apple;
    gLinkContext.progname                = (argv[0] != NULL) ? basename(argv[0]) : "";
    gLinkContext.programVars.mh            = mainExecutableMH;
    gLinkContext.programVars.NXArgcPtr    = &gLinkContext.argc;
    gLinkContext.programVars.NXArgvPtr    = &gLinkContext.argv;
    gLinkContext.programVars.environPtr    = &gLinkContext.envp;
    gLinkContext.programVars.__prognamePtr=&gLinkContext.progname;
    gLinkContext.mainExecutable            = NULL;
    gLinkContext.imageSuffix            = NULL;
    gLinkContext.dynamicInterposeArray    = NULL;
    gLinkContext.dynamicInterposeCount    = 0;
    gLinkContext.prebindUsage            = ImageLoader::kUseAllPrebinding;
#if TARGET_IPHONE_SIMULATOR
    gLinkContext.sharedRegionMode        = ImageLoader::kDontUseSharedRegion;
#else
    gLinkContext.sharedRegionMode        = ImageLoader::kUseSharedRegion;
#endif
}

设置的回调函数都是dyld本模块实现的,如loadLibrary方法就是本模块的libraryLocator()方法,负责加载动态库。
在设置完这些信息后,执行processRestricted()方法判断进程是否受限。代码如下:

static bool processRestricted(const macho_header* mainExecutableMH, bool* ignoreEnvVars, bool* processRequiresLibraryValidation)
{
#if TARGET_IPHONE_SIMULATOR
    gLinkContext.codeSigningEnforced = true;
#else
    // ask kernel if code signature of program makes it restricted
    uint32_t flags;
    if ( csops(0, CS_OPS_STATUS, &flags, sizeof(flags)) != -1 ) {
        if (flags & CS_REQUIRE_LV)
            *processRequiresLibraryValidation = true;

  #if __MAC_OS_X_VERSION_MIN_REQUIRED
        if ( flags & CS_ENFORCEMENT ) {
            gLinkContext.codeSigningEnforced = true;
        }
        if ( ((flags & CS_RESTRICT) == CS_RESTRICT) && (csr_check(CSR_ALLOW_TASK_FOR_PID) != 0) ) {
            sRestrictedReason = restrictedByEntitlements;
            return true;
        }
  #else
        if ((flags & CS_ENFORCEMENT) && !(flags & CS_GET_TASK_ALLOW)) {
            *ignoreEnvVars = true;
        }
        gLinkContext.codeSigningEnforced = true;
  #endif
    }
#endif

    // all processes with setuid or setgid bit set are restricted
    if ( issetugid() ) {
        sRestrictedReason = restrictedBySetGUid;
        return true;
    }

    // <rdar://problem/13158444&13245742> Respect __RESTRICT,__restrict section for root processes
    if ( hasRestrictedSegment(mainExecutableMH) ) {
        // existence of __RESTRICT/__restrict section make process restricted
        sRestrictedReason = restrictedBySegment;
        return true;
    }
    return false;
}

进程受限会是以下三种可能:
restrictedByEntitlements:在macOS系统上,在需要验证代码签名(Gatekeeper开启)的情况下,且csr_check(CSR_ALLOW_TASK_FOR_PID)返回为真(表示Rootless开启了TASK_FOR_PID标志)时,进程才不会受限,在macOS版本10.12系统上,默认Gatekeeper是开启的,并且Rootless是关闭了CSR_ALLOW_TASK_FOR_PID标志位的,这意味着,默认情况下,系统上运行的进程是受限的。
restrictedBySetGUid:当进程的setuid与setgid位被设置时,进程会被设置成受限。这样做是出于安全的考虑,受限后的进程无法访问DYLD_开头的环境变量,一种典型的系统攻击就是针对这种情况而发生的,在macOS版本10.10系统上,一个由DYLD_PRINT_TO_FILE环境变量引发的系统本地提权漏洞,就是通过向DYLD_PRINT_TO_FILE环境变量传入拥有SUID权限的受限文件,而系统没做安全检测,而这些文件是直接有向系统创建与写入文件权限的。关于漏洞的具体细节可以参看:OS X 10.10 DYLD_PRINT_TO_FILE Local Privilege Escalation Vulnerability
restrictedBySegment:段名受限。当Mach-O包含一个__RESTRICT/__restrict段时,进程会被设置成受限。

在进程受限后,执行了以下三个方法:
checkLoadCommandEnvironmentVariables():遍历Mach-O中所有的LC_DYLD_ENVIRONMENT加载命令,然后调用processDyldEnvironmentVariable()对不同的环境变量做相应的处理。
pruneEnvironmentVariables():删除进程的LD_LIBRARY_PATH与所有以DYLD_开头的环境变量,这样以后创建的子进程就不包含这些环境变量了。
setContext():重新设置链接上下文。这一步执行的主要目的是由于环境变量发生变化了,需要更新进程的envp与apple参数。

第二步,初始化主程序

这一步主要执行了instantiateFromLoadedImage()。它的代码如下:

static ImageLoader* 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 image;
    }

    throw "main executable not a known format";
}

isCompatibleMachO()主要检查Mach-O的头部的cputype与cpusubtype来判断程序与当前的系统是否兼容。如果兼容接下来就调用instantiateMainExecutable()实例化主程序,代码如下:

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
}

sniffLoadCommands()主要获取了加载命令中的如下信息:
compressed:判断Mach-O的Compressed还是Classic类型。判断的依据是Mach-O是否包含LC_DYLD_INFO或LC_DYLD_INFO_ONLY加载命令。这2个加载命令记录了Mach-O的动态库加载信息,使用结构体dyld_info_command表示:

struct dyld_info_command {
   uint32_t   cmd;        /* LC_DYLD_INFO or LC_DYLD_INFO_ONLY */
   uint32_t   cmdsize;        /* sizeof(struct dyld_info_command) */
   uint32_t   rebase_off;    /* file offset to rebase info  */
   uint32_t   rebase_size;    /* size of rebase info   */
   uint32_t   bind_off;    /* file offset to binding info   */
   uint32_t   bind_size;    /* size of binding info  */
   uint32_t   weak_bind_off;    /* file offset to weak binding info   */
   uint32_t   weak_bind_size;  /* size of weak binding info  */
   uint32_t   lazy_bind_off;    /* file offset to lazy binding info */
   uint32_t   lazy_bind_size;  /* size of lazy binding infs */
   uint32_t   export_off;    /* file offset to lazy binding info */
   uint32_t   export_size;    /* size of lazy binding infs */
};

rebase_off与大小rebase_size存储了rebase(重设基址)相关信息,当Mach-O加载到内存中的地址不是指定的首选地址时,就需要对当前的映像数据进行rebase(重设基址)。
bind_off与bind_size存储了进程的符号绑定信息,当进程启动时必须绑定这些符号,典型的有dyld_stub_binder,该符号被dyld用来做迟绑定加载符号,一般动态库都包含该符号。
weak_bind_off与weak_bind_size存储了进程的弱绑定符号信息。弱符号主要用于面向对旬语言中的符号重载,典型的有c++中使用new创建对象,默认情况下会绑定ibstdc++.dylib,如果检测到某个映像使用弱符号引用重载了new符号,dyld则会重新绑定该符号并调用重载的版本。
lazy_bind_off与lazy_bind_size存储了进程的延迟绑定符号信息。有些符号在进程启动时不需要马上解析,它们会在第一次调用时被解析,这类符号叫延迟绑定符号(Lazy Symbol)。
export_off与export_size存储了进程的导出符号绑定信息。导出符号可以被外部的Mach-O访问,通常动态库会导出一个或多个符号供外部使用,而可执行程序由导出_main与_mh_execute_header符号供dyld使用。

segCount:段的数量。sniffLoadCommands()通过遍历所有的LC_SEGMENT_COMMAND加载命令来获取段的数量。

libCount:需要加载的动态库的数量。Mach-O中包含的每一条LC_LOAD_DYLIB、LC_LOAD_WEAK_DYLIB、LC_REEXPORT_DYLIB、LC_LOAD_UPWARD_DYLIB加载命令,都表示需要加载一个动态库。

codeSigCmd:通过解析LC_CODE_SIGNATURE来获取代码签名的加载命令。

encryptCmd:通过LC_ENCRYPTION_INFO与LC_ENCRYPTION_INFO_64来获取段加密信息。

获取compressed后,根据Mach-O是否compressed来分别调用ImageLoaderMachOCompressed::instantiateMainExecutable()与ImageLoaderMachOClassic::instantiateMainExecutable()。ImageLoaderMachOCompressed::instantiateMainExecutable()代码如下:

// 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)
{
    ImageLoaderMachOCompressed* image = ImageLoaderMachOCompressed::instantiateStart(mh, path, segCount, libCount);

    // set slide for PIE programs
    image->setSlide(slide);

    // for PIE record end of program, to know where to start loading dylibs
    if ( slide != 0 )
        fgNextPIEDylibAddress = (uintptr_t)image->getEnd();

    image->disableCoverageCheck();
    image->instantiateFinish(context);
    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;
}

ImageLoaderMachOCompressed::instantiateStart()使用主程序Mach-O信息构造了一个ImageLoaderMachOCompressed对象。disableCoverageCheck()禁用覆盖率检查。instantiateFinish()调用parseLoadCmds()解析其它所有的加载命令,后者会填充完ImageLoaderMachOCompressed的一些保护成员信息,最后调用setDyldInfo()设置动态库链接信息,然后调用setSymbolTableInfo()设置符号表信息。

instantiateFromLoadedImage()调用完了ImageLoaderMachO::instantiateMainExecutable()后,接着调用addImage(),代码如下

static void addImage(ImageLoader* image)
{
    // add to master list
    allImagesLock();
        sAllImages.push_back(image);
    allImagesUnlock();

    // update mapped ranges
    uintptr_t lastSegStart = 0;
    uintptr_t lastSegEnd = 0;
    for(unsigned int i=0, e=image->segmentCount(); i < e; ++i) {
        if ( image->segUnaccessible(i) )
            continue;
        uintptr_t start = image->segActualLoadAddress(i);
        uintptr_t end = image->segActualEndAddress(i);
        if ( start == lastSegEnd ) {
            // two segments are contiguous, just record combined segments
            lastSegEnd = end;
        }
        else {
            // non-contiguous segments, record last (if any)
            if ( lastSegEnd != 0 )
                addMappedRange(image, lastSegStart, lastSegEnd);
            lastSegStart = start;
            lastSegEnd = end;
        }        
    }
    if ( lastSegEnd != 0 )
        addMappedRange(image, lastSegStart, lastSegEnd);

    if ( sEnv.DYLD_PRINT_LIBRARIES || (sEnv.DYLD_PRINT_LIBRARIES_POST_LAUNCH && (sMainExecutable!=NULL) && sMainExecutable->isLinked()) ) {
        dyld::log("dyld: loaded: %s\n", image->getPath());
    }

}

这段代码将实例化好的主程序添加到全局主列表sAllImages中,最后调用addMappedRange()申请内存,更新主程序映像映射的内存区。做完这些工作,第二步初始化主程序就算完成了。

第三步,加载共享缓存

这一步主要执行mapSharedCache()来映射共享缓存。该函数先通过_shared_region_check_np()来检查缓存是否已经映射到了共享区域了,如果已经映射了,就更新缓存的slide与UUID,然后返回。反之,判断系统是否处于安全启动模式(safe-boot mode)下,如果是就删除缓存文件并返回,正常启动的情况下,接下来调用openSharedCacheFile()打开缓存文件,该函数在sSharedCacheDir路径下,打开与系统当前cpu架构匹配的缓存文件,也就是/var/db/dyld/dyld_shared_cache_x86_64h,接着读取缓存文件的前8192字节,解析缓存头dyld_cache_header的信息,将解析好的缓存信息存入mappings变量,最后调用_shared_region_map_and_slide_np()完成真正的映射工作。部分代码片断如下:

static void mapSharedCache()
{
    uint64_t cacheBaseAddress = 0;
    if ( _shared_region_check_np(&cacheBaseAddress) == 0 ) {
        sSharedCache = (dyld_cache_header*)cacheBaseAddress;
#if __x86_64__
        const char* magic = (sHaswell ? ARCH_CACHE_MAGIC_H : ARCH_CACHE_MAGIC);
#else
        const char* magic = ARCH_CACHE_MAGIC;
#endif
        if ( strcmp(sSharedCache->magic, magic) != 0 ) {
            sSharedCache = NULL;
            if ( gLinkContext.verboseMapping ) {
                dyld::log("dyld: existing shared cached in memory is not compatible\n");
                return;
            }
        }
        const dyld_cache_header* header = sSharedCache;
        ......
        // if cache has a uuid, copy it
        if ( header->mappingOffset >= 0x68 ) {
            memcpy(dyld::gProcessInfo->sharedCacheUUID, header->uuid, 16);
        }
        ......
    }
    else {
#if __i386__ || __x86_64__
        uint32_t    safeBootValue = 0;
        size_t        safeBootValueSize = sizeof(safeBootValue);
        if ( (sysctlbyname("kern.safeboot", &safeBootValue, &safeBootValueSize, NULL, 0) == 0) && (safeBootValue != 0) ) {
            struct stat dyldCacheStatInfo;
            if ( my_stat(MACOSX_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME, &dyldCacheStatInfo) == 0 ) {
                struct timeval bootTimeValue;
                size_t bootTimeValueSize = sizeof(bootTimeValue);
                if ( (sysctlbyname("kern.boottime", &bootTimeValue, &bootTimeValueSize, NULL, 0) == 0) && (bootTimeValue.tv_sec != 0) ) {
                    if ( dyldCacheStatInfo.st_mtime < bootTimeValue.tv_sec ) {
                        ::unlink(MACOSX_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME);
                        gLinkContext.sharedRegionMode = ImageLoader::kDontUseSharedRegion;
                        return;
                    }
                }
            }
        }
#endif
        // map in shared cache to shared region
        int fd = openSharedCacheFile();
        if ( fd != -1 ) {
            uint8_t firstPages[8192];
            if ( ::read(fd, firstPages, 8192) == 8192 ) {
                dyld_cache_header* header = (dyld_cache_header*)firstPages;
        #if __x86_64__
                const char* magic = (sHaswell ? ARCH_CACHE_MAGIC_H : ARCH_CACHE_MAGIC);
        #else
                const char* magic = ARCH_CACHE_MAGIC;
        #endif
                if ( strcmp(header->magic, magic) == 0 ) {
                    const dyld_cache_mapping_info* const fileMappingsStart = (dyld_cache_mapping_info*)&firstPages[header->mappingOffset];
                    const dyld_cache_mapping_info* const fileMappingsEnd = &fileMappingsStart[header->mappingCount];
                    shared_file_mapping_np    mappings[header->mappingCount+1]; // add room for code-sig

                    ......

                        if (_shared_region_map_and_slide_np(fd, mappingCount, mappings, codeSignatureMappingIndex, cacheSlide, slideInfo, slideInfoSize) == 0) {
                            sSharedCache = (dyld_cache_header*)mappings[0].sfm_address;
                            sSharedCacheSlide = cacheSlide;
                            dyld::gProcessInfo->sharedCacheSlide = cacheSlide;
                            ......
                        }
                        else {
#if __IPHONE_OS_VERSION_MIN_REQUIRED
                            throw "dyld shared cache could not be mapped";
#endif
                            if ( gLinkContext.verboseMapping )
                                dyld::log("dyld: shared cached file could not be mapped\n");
                        }
                    }
                }
                else {
                    if ( gLinkContext.verboseMapping )
                        dyld::log("dyld: shared cached file is invalid\n");
                }
            }
            else {
                if ( gLinkContext.verboseMapping )
                    dyld::log("dyld: shared cached file cannot be read\n");
            }
            close(fd);
        }
        else {
            if ( gLinkContext.verboseMapping )
                dyld::log("dyld: shared cached file cannot be opened\n");
        }
    }
    ......
}

共享缓存加载完毕后,接着进行动态库的版本化重载,这主要通过函数checkVersionedPaths()完成。该函数读取DYLD_VERSIONED_LIBRARY_PATH与DYLD_VERSIONED_FRAMEWORK_PATH环境变量,将指定版本的库比当前加载的库的版本做比较,如果当前的库版本更高的话,就使用新版本的库来替换掉旧版本的。

第四步,加载插入的动态库

这一步循环遍历DYLD_INSERT_LIBRARIES环境变量中指定的动态库列表,并调用loadInsertedDylib()将其加载。该函数调用load()完成加载工作。load()会调用loadPhase0()尝试从文件加载,loadPhase0()会向下调用下一层phase来查找动态库的路径,直到loadPhase6(),查找的顺序为DYLD_ROOT_PATH->LD_LIBRARY_PATH->DYLD_FRAMEWORK_PATH->原始路径->DYLD_FALLBACK_LIBRARY_PATH,找到后调用ImageLoaderMachO::instantiateFromFile()来实例化一个ImageLoader,之后调用checkandAddImage()验证映像并将其加入到全局映像列表中。如果loadPhase0()返回为空,表示在路径中没有找到动态库,就尝试从共享缓存中查找,找到就调用ImageLoaderMachO::instantiateFromCache()从缓存中加载,否则就抛出没找到映像的异常。部分代码片断如下:

ImageLoader* load(const char* path, const LoadContext& context)
{
    ......
    if ( context.useSearchPaths && ( gLinkContext.imageSuffix != NULL) ) {
        if ( realpath(path, realPath) != NULL )
            path = realPath;
    }

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

    ......
    image = loadPhase0(path, orgPath, context, &exceptions);
#if __IPHONE_OS_VERSION_MIN_REQUIRED && DYLD_SHARED_CACHE_SUPPORT && !TARGET_IPHONE_SIMULATOR
    // <rdar://problem/16704628> support symlinks on disk to a path in dyld shared cache
    if ( (image == NULL) && cacheablePath(path) && !context.dontLoad ) {
        ......
        if ( (myerr == ENOENT) || (myerr == 0) )
        {
            const macho_header* mhInCache;
            const char*            pathInCache;
            long                slideInCache;
            if ( findInSharedCacheImage(resolvedPath, false, NULL, &mhInCache, &pathInCache, &slideInCache) ) {
                struct stat stat_buf;
                bzero(&stat_buf, sizeof(stat_buf));
                try {
                    image = ImageLoaderMachO::instantiateFromCache(mhInCache, pathInCache, slideInCache, stat_buf, gLinkContext);
                    image = checkandAddImage(image, context);
                }
                catch (...) {
                    image = NULL;
                }
            }
        }
    }
#endif
    ......
    else {
        const char* msgStart = "no suitable image found.  Did find:";
        ......
        throw (const char*)fullMsg;
    }
}
第五步,链接主程序

这一步执行link()完成主程序的链接操作。该函数调用了ImageLoader自身的link()函数,主要的目的是将实例化的主程序的动态数据进行修正,达到让进程可用的目的,典型的就是主程序中的符号表修正操作,它的代码片断如下

void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool preflightOnly, bool neverUnload, const RPathChain& loaderRPaths)
{
    ......
    this->recursiveLoadLibraries(context, preflightOnly, loaderRPaths);

    ......

    context.clearAllDepths();
    this->recursiveUpdateDepth(context.imageCount());

     this->recursiveRebase(context);

    ......

     this->recursiveBind(context, forceLazysBound, neverUnload);

    if ( !context.linkingMainExecutable )
        this->weakBind(context);    //现在是链接主程序,这里现在不会执行

    ......

    std::vector<DOFInfo> dofs;
    this->recursiveGetDOFSections(context, dofs);
    context.registerDOFs(dofs);

    ......

    if ( !context.linkingMainExecutable && (fgInterposingTuples.size() != 0) ) {
        this->recursiveApplyInterposing(context);    //现在是链接主程序,这里现在不会执行
    }
    ......
}

recursiveLoadLibraries()采用递归的方式来加载程序依赖的动态库,加载的方法是调用context的loadLibrary指针方法,该方法在前面看到过,是setContext()设置的libraryLocator(),该函数只是调用了load()来完成加载,load()加载动态库的过程在上一步已经分析过了。

接着调用recursiveUpdateDepth()对映像及其依赖库按列表方式进行排序。recursiveRebase()则对映像完成递归rebase操作,该函数只是调用了虚函数doRebase(),doRebase()被ImageLoaderMachO重载,实际只是将代码段设置成可写后调用了rebase(),在ImageLoaderMachOCompressed中,该函数读取映像动态链接信息的rebase_off与rebase_size来确定需要rebase的数据偏移与大小,然后挨个修正它们的地址信息。

recursiveBind()完成递归绑定符号表的操作。此处的符号表针对的是非延迟加载的符号表,它的核心是调用了doBind(),在ImageLoaderMachOCompressed中,该函数读取映像动态链接信息的bind_off与bind_size来确定需要绑定的数据偏移与大小,然后挨个对它们进行绑定,绑定操作具体使用bindAt()函数,它主要通过调用resolve()解析完符号表后,调用bindLocation()完成最终的绑定操作,需要绑定的符号信息有三种:
BIND_TYPE_POINTER:需要绑定的是一个指针。直接将计算好的新值屿值即可。
BIND_TYPE_TEXT_ABSOLUTE32:一个32位的值。取计算的值的低32位赋值过去。
BIND_TYPE_TEXT_PCREL32:重定位符号。需要使用新值减掉需要修正的地址值来计算出重定位值。

recursiveGetDOFSections()与registerDOFs()主要注册程序的DOF节区,供dtrace使用。

第六步,链接插入的动态库

链接插入的动态库与链接主程序一样,都是使用的link(),插入的动态库列表是前面调用addImage()保存到sAllImages中的,之后,循环获取每一个动态库的ImageLoader,调用link()对其进行链接,注意:sAllImages中保存的第一项是主程序的映像。接下来调用每个映像的registerInterposing()方法来注册动态库插入与调用applyInterposing()应用插入操作。registerInterposing()查找__DATA段的__interpose节区,找到需要应用插入操作(也可以叫作符号地址替换)的数据,然后做一些检查后,将要替换的符号与被替换的符号信息存入fgInterposingTuples列表中,供以后具体符号替换时查询。applyInterposing()调用了虚方法doInterpose()来做符号替换操作,在ImageLoaderMachOCompressed中实际是调用了eachBind()与eachLazyBind()分别对常规的符号与延迟加载的符号进行应用插入操作,具体使用的是interposeAt(),该方法调用interposedAddress()在fgInterposingTuples中查找要替换的符号地址,找到后然后进行最终的符号地址替换。

第七步,执行弱符号绑定

weakBind()函数执行弱符号绑定。首先通过调用context的getCoalescedImages()将sAllImages中所有含有弱符号的映像合并成一个列表,合并完后调用initializeCoalIterator()对映像进行排序,排序完成后调用incrementCoalIterator()收集需要进行绑定的弱符号,后者是一个虚函数,在ImageLoaderMachOCompressed中,该函数读取映像动态链接信息的weak_bind_off与weak_bind_size来确定弱符号的数据偏移与大小,然后挨个计算它们的地址信息。之后调用getAddressCoalIterator(),按照映像的加载顺序在导出表中查找符号的地址,找到后调用updateUsesCoalIterator()执行最终的绑定操作,执行绑定的是bindLocation(),前面有讲过,此处不再赘述。

第八步,执行初始化方法

执行初始化的方法是initializeMainExecutable()。该函数主要执行runInitializers(),后者调用了ImageLoader的runInitializers()方法,最终迭代执行了ImageLoaderMachO的doInitialization()方法,后者主要调用doImageInit()与doModInitFunctions()执行映像与模块中设置为init的函数与静态初始化方法,代码如下:

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

    doImageInit(context);
    doModInitFunctions(context);

    CRSetCrashLogMessage2(NULL);

    return (fHasDashInit || fHasInitializers);
}
第九步,查找入口点并返回

这一步调用主程序映像的getThreadPC()函数来查找主程序的LC_MAIN加载命令获取程序的入口点,没找到就调用getMain()到LC_UNIXTHREAD加载命令中去找,找到后就跳到入口点指定的地址并返回了。

到这里,dyld整个加载动态库的过程就算完成了。

另外再讨论下延迟符号加载的技术细节。在所有拥有延迟加载符号的Mach-O文件里,它的符号表中一定有一个dyld_stub_helper符号,它是延迟符号加载的关键!延迟绑定符号的修正工作就是由它完成的。绑定符号信息可以使用XCode提供的命令行工具dyldinfo来查看,执行以下命令可以查看python的绑定信息:

xcrun dyldinfo -bind /usr/bin/python
for arch i386:
bind information:
segment section          address        type    addend dylib            symbol
__DATA  __cfstring       0x000040F0    pointer      0 CoreFoundation   ___CFConstantStringClassReference
__DATA  __cfstring       0x00004100    pointer      0 CoreFoundation   ___CFConstantStringClassReference
__DATA  __nl_symbol_ptr  0x00004010    pointer      0 CoreFoundation   _kCFAllocatorNull
__DATA  __nl_symbol_ptr  0x00004008    pointer      0 libSystem        ___stack_chk_guard
__DATA  __nl_symbol_ptr  0x0000400C    pointer      0 libSystem        _environ
__DATA  __nl_symbol_ptr  0x00004000    pointer      0 libSystem        dyld_stub_binder
bind information:
segment section          address        type    addend dylib            symbol
__DATA  __cfstring       0x1000031D8    pointer      0 CoreFoundation   ___CFConstantStringClassReference
__DATA  __cfstring       0x1000031F8    pointer      0 CoreFoundation   ___CFConstantStringClassReference
__DATA  __got            0x100003010    pointer      0 CoreFoundation   _kCFAllocatorNull
__DATA  __got            0x100003000    pointer      0 libSystem        ___stack_chk_guard
__DATA  __got            0x100003008    pointer      0 libSystem        _environ
__DATA  __nl_symbol_ptr  0x100003018    pointer      0 libSystem        dyld_stub_binder

所有的延迟绑定符号都存储在_TEXT段的stubs节区(桩节区),编译器在生成代码时创建的符号调用就生成在此节区中,该节区被称为“桩”节区,桩只是一小段临时使用的指令,在stubs中只是一条jmp跳转指令,跳转的地址位于__DATA段__la_symbol_ptr节区中,指向的是一段代码,类似于如下的语句:

push xxx
jmp yyy

其中xxx是符号在动态链接信息中延迟绑定符号数据的偏移值,yyy则是跳转到_TEXT段的stub_helper节区头部,此处的代码通常为:

lea        r11, qword [ds:zzz]
push       r11
jmp        qword [ds:imp___nl_symbol_ptr_dyld_stub_binder]

jmp跳转的地址是__DATA段中__nl_symbol_ptr节区,指向的是符号dyld_stub_binder(),该函数由dyld导出,实现位于dyld源码的“dyld_stub_binder.s”文件中,它调用dyld::fastBindLazySymbol()来绑定延迟加载的符号,后者是一个虚函数,实际调用ImageLoaderMachOCompressed的doBindFastLazySymbol(),后者调用bindAt()解析并返回正确的符号地址,dyld_stub_binder()在最后跳转到符号地址去执行。这一步完成后,__DATA段__la_symbol_ptr节区中存储的符号地址就是修正后的地址,下一次调用该符号时,就直接跳转到真正的符号地址去执行,而不用dyld_stub_binder()来重新解析该符号了,




1

load函数摘要

OS X或者IOS上运行一个程序时,dyld除了需要加载主要的执行程序之外,还需要加载需要的库文件以及库文件依赖的库文件。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 iOS 中,`_dyld_get_image_vmaddr_slide` 函数法直接获取某个特定动态库的加载地址,它只能用来获取当进程中加载动态库的偏移量(Slide)。然而,我们可以结合其他方法来计算出特定动态库的加载地址。 以下是一种获取特定动态加载地址的方法: ```objective-c #include <mach-o/dyld.h> uintptr_t getLibraryLoadAddress(const char *libraryName) { uint32_t count = _dyld_image_count(); for (uint32_t i = 0; i < count; i++) { const char *imageName = _dyld_get_image_name(i); if (strstr(imageName, libraryName) != NULL) { uintptr_t slide = _dyld_get_image_vmaddr_slide(i); uintptr_t loadAddress = _dyld_get_image_header(i)->vmaddr + slide; return loadAddress; } } return 0; } ``` 在上述代码中,我们定义了一个名为 `getLibraryLoadAddress` 的函数,它接受一个参数 `libraryName` 表示要获取加载地址的动态库的名称。 在函数内部,我们使用 `_dyld_image_count` 函数获取当进程中加载动态库数量。然后使用 `_dyld_get_image_name` 函数获取每个动态库的名称,与目标动态库名称进行匹配。 一旦找到目标动态库,我们使用 `_dyld_get_image_vmaddr_slide` 函数获取偏移量(Slide),并使用 `_dyld_get_image_header` 函数获取动态库的头部信息。通过将偏移量与动态库的虚拟内存基地址相加,即可得到目标动态库的加载地址。 如果成功获取到目标动态库的加载地址,则该地址将被返回。否则,返回 0。 使用示例: ```objective-c const char *libraryName = "libYourLibrary.dylib"; uintptr_t loadAddress = getLibraryLoadAddress(libraryName); if (loadAddress != 0) { printf("The load address of %s is 0x%lx\n", libraryName, loadAddress); } else { printf("Failed to get the load address of %s\n", libraryName); } ``` 请确保提供正确的动态库名称,并链接 `mach-o/dyld.h` 头文件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值