美文网首页逆向安全
dyld 调用流程分析

dyld 调用流程分析

作者: 晨曦的简书 | 来源:发表于2021-04-29 22:24 被阅读0次

dyld 简介

dyld(the dynamic link editor)是苹果的动态链接器,用来加载所有的库和可执行文件,是苹果操作系统一个重要组成部分,在系统内核做好程序准备工作之后,交由 dyld 负责余下的工作。而且它是开源的,任何人可以通过苹果官网下载它的源码来阅读理解它的运作方式,那下面我们就通过源码尝试看一下 dyld 的加载流程。

下面我们通过对控制器的 load 方法添加断点,看看 dyld 是从哪里开始执行的:

load之前的方法调用.png

从函数的调用栈我们可以看到,dyld 是从 start 方法开始执行的,那我我们就打开源码找到 start 方法,来一步一步的分析 dyld 的调用过程。这里用到是832.7.3版本。

dyld 入口函数 start

uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],
                const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue)
{

    // 首先发出kdebug消息,告诉dyld已经开始启动了
    dyld3::kdebug_trace_dyld_marker(DBG_DYLD_TIMING_BOOTSTRAP_START, 0, 0, 0, 0);

    // 第一步,重定位dyld地址,把dyld加载进来(虚拟内存下,一个进程启动都需要做地址重定位)
    rebaseDyld(dyldsMachHeader);

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

    // 栈溢出的保护
    __guard_setup(apple);

#if DYLD_INITIALIZER_SUPPORT
    // run all C++ initializers inside dyld
    runDyldInitializers(argc, argv, envp, apple);
#endif
    // 初始化dyld
    _subsystem_init(apple);

    // now that we are done bootstrapping dyld, call dyld's main
    uintptr_t appsSlide = appsMachHeader->getSlide();
    // 调用main(最核心的代码在main函数里面,因为dyld几乎所有的逻辑都在main里面处理)
    return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}

start 方法里面主要是做了一些准备工作,是开始启动阶段,最核心的代码在 main 函数里面,下面我们接着进入 main 函数。

dyld 的 main 函数

现在我们来到 main函数

uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
        int argc, const char* argv[], const char* envp[], const char* apple[], 
        uintptr_t* startGlue)
{
    // 1. 内核检测代码,这里省略......

    // 2.主程序配置相关 
    uint8_t mainExecutableCDHashBuffer[20];
    // 主程序的hash
    const uint8_t* mainExecutableCDHash = nullptr;
    if ( const char* mainExeCdHashStr = _simple_getenv(apple, "executable_cdhash") ) {
        unsigned bufferLenUsed;
        if ( hexStringToBytes(mainExeCdHashStr, mainExecutableCDHashBuffer, sizeof(mainExecutableCDHashBuffer), bufferLenUsed) )
            mainExecutableCDHash = mainExecutableCDHashBuffer;
    }
    // mainExecutableMH :主程序的header,mainExecutableSlide :主程序的slide
    // 设置cpu,cpu的类型等信息
    getHostInfo(mainExecutableMH, mainExecutableSlide);
    // 对header跟slide赋值
    uintptr_t result = 0;
    sMainExecutableMachHeader = mainExecutableMH;
    sMainExecutableSlide = mainExecutableSlide;

    // 3.设置上下文,把配置信息保存到gLinkContext这个变量里面,到这里都是做一些配置相关的操作并保存下来
    setContext(mainExecutableMH, argc, argv, envp, apple);

    // 配置进程是否受限,envp:环境变量
    // AMFI相关(Apple Mobile File Integrity苹果移动文件保护)
    configureProcessRestrictions(mainExecutableMH, envp);
    // 判断是否要强制使用dyld3 (dyld3是iOS11之后新增的一种闭包模式,处理效率更高,加载速度更快)
    if ( dyld3::internalInstall() ) {...}

    // 4. 检测环境变量,并给环境变量设置默认值
    checkEnvironmentVariables(envp);
    defaultUninitializedFallbackPaths(envp);

    // 打印相关的环境变量(在load之前打印)
    if ( sEnv.DYLD_PRINT_OPTS )
        printOptions(argv);
    if ( sEnv.DYLD_PRINT_ENV ) 
        printEnvironmentVariables(envp);

    // 5. 加载共享缓存,UIKit,Foundtion等动态库 (在这之前还没有加载主程序,但是已经拿到主程序的header,开始读主程序了,根据主程序的header可以知道主程序的cup,架构,系统等信息)
    // 共享缓存: 用来存储UIkit等系统库的,保证这些库只加载一份,因为iOS进程之间访问受限,所以要放到共享缓存里面
    checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);

// 6.加载各项我们所依赖的framework,还有三方库

// 7.开始判断是加载`dyld2`还是`dyld3`。

dyld3 的加载流程

    // ClosureMode: 闭包模式,iOS11后引入的,iOS13开始动态库,三方库都用ClosureMode模式加载
    // 判断是否是闭包模式,是的话就先开始创建闭包模式加载器
    if ( sClosureMode == ClosureMode::Off ) {...} else {
    // 给sLaunchModeUsed变量设置一个表示,表示用闭包模式去启动dyld
        sLaunchModeUsed = DYLD_LAUNCH_MODE_USING_CLOSURE;
    // 1. 配置主程序的信息:mainFileInfo,主程序的header:mainExecutableMH
        const dyld3::closure::LaunchClosure* mainClosure = nullptr;
        dyld3::closure::LoadedFileInfo mainFileInfo;
        mainFileInfo.fileContent = mainExecutableMH;
        
        // check for closure in cache first
        if ( sSharedCacheLoadInfo.loadAddress != nullptr ) {

        // 先去共享缓存中取查看 mainClosure 实例
        mainClosure = sSharedCacheLoadInfo.loadAddress->findClosure(sExecPath);
        // 如果实例对象为空就主动创建
        if ( mainClosure == nullptr ) {
            mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp, bootToken);
        }

        // 2. 这里开始进入到启动
        if ( mainClosure != nullptr ) {
            // 开启启动并返回启动结果
            bool launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress, (dyld3::MachOLoaded*)mainExecutableMH,
                                              mainExecutableSlide, argc, argv, envp, apple, diag, &result, startGlue, &closureOutOfDate, &recoverable);
            // 如果启动失败或者closureOutOfDate过期
            if ( !launched && closureOutOfDate && allowClosureRebuilds ) {
                // 再次创建一个mainClosure实例
                mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp, bootToken);
                // 并再次启动
                launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress, (dyld3::MachOLoaded*)mainExecutableMH,
                                                 mainExecutableSlide, argc, argv, envp, apple, diag, &result, startGlue, &closureOutOfDate, &recoverable);
                }
            }
            // 如果启动成功,就保存变量,主程序加载成功
            if ( launched ) {
                gLinkContext.startedInitializingMainExecutable = true;
            // 拿到主程序的main函数,并返回result
                if (sSkipMain)
                    result = (uintptr_t)&fake_main;
                return result;
            }
            // 如果启动失败,就报响应的错误信息
            else {
                if ( gLinkContext.verboseWarnings ) {
                    dyld::log("dyld: unable to use closure %p\n", mainClosure);
                }
                if ( !recoverable )
                    halt(diag.errorMessage());
            }

dyld2 的加载流程

    // 如果不是闭包模式 dyld2模式
    sLaunchModeUsed = 0;

    // 把 notifyGDB 跟 updateAllImages 回调放到 sBatchHandlers 数组
    sBatchHandlers)->push_back(notifyGDB);
    stateToHandlers(dyld_image_state_mapped, sSingleHandlers)->push_back(updateAllImages);

    // 分配一些初始化空间,尽量空间分配的大一点,保证后面够用
    sImageRoots.reserve(16);
    sAddImageCallbacks.reserve(4);
    sRemoveImageCallbacks.reserve(4);
    sAddLoadImageCallbacks.reserve(4);
    sImageFilesNeedingTermination.reserve(16);
    sImageFilesNeedingDOFUnregistration.reserve(8);

    // 把dyld加入到UUIDList列表
    addDyldImageToUUIDList();


    // 开始加载主程序
    // mainExcutableAlreadyRebased:主程序Rebased状态标识
    bool mainExcutableAlreadyRebased = false;
        if ( (sSharedCacheLoadInfo.loadAddress != nullptr) && !dylibsCanOverrideCache() && !sDisableAcceleratorTables && (sSharedCacheLoadInfo.loadAddress->header.accelerateInfoAddr != 0) ) {
            struct stat statBuf;
            if ( dyld3::stat(IPHONE_DYLD_SHARED_CACHE_DIR "no-dyld2-accelerator-tables", &statBuf) != 0 )
                sAllCacheImagesProxy = ImageLoaderMegaDylib::makeImageLoaderMegaDylib(&sSharedCacheLoadInfo.loadAddress->header, sSharedCacheLoadInfo.slide, mainExecutableMH, gLinkContext);
        }

    // 实例化主程序 image:可执行文件(dyld第一个加载的image是主程序)
    sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
    gLinkContext.mainExecutable = sMainExecutable;
    // 代码签名
    gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);

// 检测主程序是否属于当前系统,当前设备版本
        {
            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 (dyld::isTranslated()) {...}
// 设置动态库的版本
checkVersionedPaths();


// 先判断有没有DYLD_INSERT_LIBRARIES这个环境变量,有的话插入所有动态库
        if  ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
            for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) 
                loadInsertedDylib(*lib);
        }

// 记录插入动态库
        gLinkContext.linkingMainExecutable = true;
// 链接主程序,在此函数开始时记录开始时间,然后递归加载主程序依赖的库,加载完毕后会发送通知,修正ASLR,绑定NoLazy符号,绑定弱符号,递归应用插入的动态库,注册,最后记录结束时间
link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);


if ( sInsertedDylibCount > 0 ) {
// 循环遍历把动态库加入到allImage
            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();
            }
            if ( gLinkContext.allowInterposing ) {
                // 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);
                }
            }
        }

// 循环拿出image,绑定插入的动态库
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, nullptr);
            }
        }
// 弱符号绑定
sMainExecutable->weakBind(gLinkContext);

实例化主程序的过程

这里其实就是实例化一个iamge的过程,其他动态库跟三方库的实例化过程跟主程序的实例化过程一样。

    // 实例化一个iamge的过程
    static ImageLoaderMachO* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path) {
        // 加载主程序的MachO文件(header,LoadCommands等)
        ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext);
        // 把iamge加入到allImage数组
        addImage(image);
        return (ImageLoaderMachO*)image;
    }

// 进入到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);
    // 根据compressed的值选择不同的子类来实例化image
    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函数
void ImageLoaderMachO::sniffLoadCommands(const macho_header* mh, const char* path, bool inCache, bool* compressed,
                                            unsigned int* segCount, unsigned int* libCount, const LinkContext& context,
                                            const linkedit_data_command** codeSigCmd,
                                            const encryption_info_command** encryptCmd)
{
        *compressed = false;
    *segCount = 0;
    *libCount = 0;
    *codeSigCmd = NULL; // 代码签名
    *encryptCmd = NULL; // 代码加密

    // segCount,libCount的数量限制
    if ( *segCount > 255 )
        dyld::throwf("malformed mach-o image: more than 255 segments in %s", path);

    // fSegmentsArrayCount is only 8-bits
    if ( *libCount > 4095 )
        dyld::throwf("malformed mach-o image: more than 4095 dependent libraries in %s", path);

    if ( needsAddedLibSystemDepency(*libCount, mh) )
        *libCount = 1;

    // dylibs that use LC_DYLD_CHAINED_FIXUPS have that load command removed when put in the dyld cache
    if ( !*compressed && (mh->flags & MH_DYLIB_IN_CACHE) )
        *compressed = true;
}

initializeMainExecutable 函数

initializeMainExecutable 函数之前都是做一些初始化与加载准备工作,这里才是开始真正进入到主程序。

  1. 进入到 initializeMainExecutable 函数
void initializeMainExecutable()
{
    // record that we've reached this step
    gLinkContext.startedInitializingMainExecutable = true;

    // run initialzers for any inserted dylibs
    ImageLoader::InitializerTimingList initializerTimes[allImagesCount()];
    initializerTimes[0].count = 0;
    const size_t rootCount = sImageRoots.size();
    if ( rootCount > 1 ) {
        for(size_t i=1; i < rootCount; ++i) {
            sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);
        }
    }
    
    // run initializers for main executable and everything it brings up 
    sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
    
    // register cxa_atexit() handler to run static terminators in all loaded images when this process exits
    if ( gLibSystemHelpers != NULL ) 
        (*gLibSystemHelpers->cxa_atexit)(&runAllStaticTerminators, NULL, NULL);

    // dump info if requested
    if ( sEnv.DYLD_PRINT_STATISTICS )
        ImageLoader::printStatistics((unsigned int)allImagesCount(), initializerTimes[0]);
    if ( sEnv.DYLD_PRINT_STATISTICS_DETAILS )
        ImageLoaderMachO::printStatisticsDetails((unsigned int)allImagesCount(), initializerTimes[0]);
}
  1. 通过函数调用栈我们可以看到,initializeMainExecutable函数之后进入到 ImageLoader 函数,不管dyld2还是dyld3都会走到这
    dyld函数调用栈.png
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.imagesAndPaths[0] = { this, this->getPath() };
    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);
}
  1. 同样根据函数调用栈来到 recursiveInitialization 函数,看一下最终是怎么调到 load_images 函数的。
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
                                          InitializerTimingList& timingInfo, UninitedUpwards& uninitUps) 
{
  context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
}
  1. 进到 notifySingle 函数, 但是在这里我们并没有找到 load_images 函数的调用,因为 load_images 是objc这个库里面的,那他们是怎么调用的呢?
static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
{
if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && image->notifyObjC() ) {
        uint64_t t0 = mach_absolute_time();
        dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
        (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
        uint64_t t1 = mach_absolute_time();
        uint64_t t2 = mach_absolute_time();
        uint64_t timeInObjC = t1-t0;
        uint64_t emptyTime = (t2-t1)*100;
        if ( (timeInObjC > emptyTime) && (timingInfo != NULL) ) {
            timingInfo->addTime(image->getShortName(), timeInObjC);
        }
    }
}
  1. load_images 调用过程

在notifySingle函数的1019行我们可以看到这个地方有一个回调指针,在这里会有一个判断,sNotifyObjCInit不为空就会调用这个回调,那sNotifyObjCInit在哪里赋值的呢?

我们在当前文件搜索会在 registerObjCNotifiers 函数看到是在这里赋值的, 赋值为init,那我们来看一下是哪里调用了 registerObjCNotifiers 函数,并传了一个 init。我们全局搜索一下。

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;
}
registerObjCNotifiers函数调用的地方.png

在这里我们看到registerObjCNotifiers在这被调用了,并传入了一个init。

接着再搜索 _dyld_objc_notify_register 会发现找不到了。那我们用符号断点的方式,看看谁调用了 _dyld_objc_notify_register

_dyld_objc_notify_register符号断点.png _dyld_objc_notify_register断点函数调用栈.png

运行之后,通过打印堆栈信息我们可以看到 _dyld_objc_notify_register 函数是被libobjc文件里面的 _objc_init 函数调用了。这里我们需要看objc的源码,找到 _objc_init 函数,并看看这个地方给 _dyld_objc_notify_register 方法传了哪些值。

// _objc_init函数
void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    runtime_init();
    exception_init();
#if __OBJC2__
    cache_t::init();
#endif
    _imp_implementationWithBlock_init();
// 在这里我们看到了_dyld_objc_notify_register函数的调用,且第二个参数是load_images
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

在这里我们找到了 _dyld_objc_notify_register 的调用,并看到了第二个参数是 load_images,也就是init是 load_images,也就找到了在 notifySingle 函数的1019行调用的是 load_images 函数。这里就看到了从 startload_images 的一个完整过程。

call_load_methods 函数

_objc_init 函数进的 load_images 函数,接着进入到 call_load_methods 函数,在这里我们可以看到353行 call_class_loads ,类的 load方法在这个时候被调用了。

// 1.从 `_objc_init` 函数进的 `load_images` 函数
void
load_images(const char *path __unused, const struct mach_header *mh)
{
    call_load_methods();
}

// 2.进入到 `call_load_methods` 函数
void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();

    do {
        // 1. 类的load方法调用
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 2. 分类的load方法调用
        more_categories = call_category_loads();

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}

C++全局构造函数的加载

看完了 load_images 函数的调用过程之后,我们来到Demo,试一个现象。

#import "AppDelegate.h"
#import "ViewController.h"

__attribute__((constructor)) void func1() {
    printf("func1 执行了");
}

__attribute__((constructor)) void func2() {
    printf("func2 执行了");
}

@interface AppDelegate ()
MachO文件结构.png printf打印顺序.png

AppDelegate.m 文件添加 __attribute__((constructor)) void func1__attribute__((constructor)) void func2 函数,这两个函数是全局C++构造函数,工程编译之后,把编译文件拖到MachOView应用之后,可以看到文件结构里面多了__mod_init_func文件。且工程运行的时候,通过 printf 打印顺序我们可以看到,func1func12load 之后 main 之前执行,那是哪块代码来控制 func1func12 类型的函数的加载呢?

现在我们回到 dyld 源码,来的 recursiveInitialization 函数,并最终来到 ImageLoaderMachO 函数。在这里可以看到 doModInitFunctions 函数其实就是负责构造函数的加载。

// 1.进入到ImageLoaderMachO函数
bool ImageLoaderMachO::doInitialization(const LinkContext& context)
{
    CRSetCrashLogMessage2(this->getPath());

    // mach-o has -init and static initializers
    doImageInit(context);
// 加载init构造方法
    doModInitFunctions(context);
    
    CRSetCrashLogMessage2(NULL);
    
    return (fHasDashInit || fHasInitializers);
}

initializeMainExecutable函数之后

现在我们回到 dyld 的 main 函数,在 之后我们找到7114行,在这里调用了 (uintptr_t)gLibSystemHelpers->startGlueToCallExit 函数,获取主程序的 main。把主程序的 main 函数的地址赋值给 result ,并判断 result 是否有值,最后在 dyld main函数的结尾返回 result

{
            // 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;
            }
        }
    }

// dyld main函数的结尾
return result;

最后总结

  • DYLD : 动态链接器,加载所有的库和可执行文件。
  • dyld 加载流程
    • 程序执行从_dyld_start开始 -> dyld`dyldbootstrap::start
    • 进入 dyld: main 函数
    • 配置一些环境 : rebase_dyld
    • 加载共享缓存
    • 判断加载模式 DYLD2/DYLD3(闭包模式)
      • 实例化主程序
      • 加载动态库(首先是插入动态库) (主程序和动态库都会添加到 allImages 里面)
      • 链接主程序、绑定符号(这里绑定的都非懒加载的、弱符号)等等
      • 最关键的: 初始化方法 initializeMainExecutable (初始化主程序)
        • dyld`ImageLoader::runInitializers
          • dyld`ImageLoader::processInitializers:
            • dyld`ImageLoader::recursiveInitialization:
              • dyld`dyld::notifySingle:函数

                • 此函数执行一个回调
                • 通过断点调试: 此函数是_objc_init初始化赋值的一个函数Load_images
                  • Load_images里面执行class_load_methods函数
                    • call_class_loads函数: 循环调用各个类的 load 方法
              • doModInitFunction 函数

                • 内部会调动全局 C++对象的构造函数attribute((constructor))的 C 函数
      • 返回主程序的入口函数,开始进入主程序的 main 函数

不足之处,还请指正......

相关文章

  • Objective-C 类的加载原理(上)

    上篇文章中分析了dyld整个流程以及dyld与objc的交互。这篇文章将继续分析dyld调用map_images究...

  • dyld 调用流程分析

    dyld 简介 dyld(the dynamic link editor)是苹果的动态链接器,用来加载所有的库和可...

  • iOS dyld调用流程

    一、dyld概述 dyld(the dynamic link editor)动态链接器,是苹果操作系统一个重要组成...

  • dyld 流程分析

    前言 在编写一个应用程序时候,我们看到的入口函数都是main.m 里面的 main函数,曾以为这是程序的入口,其实...

  • dyld流程分析

    编译流程 在开始分析dyld之前,我们先看下分析下可执行文件的整个编译流程: 如上图所示,我们编写的源文件,会在预...

  • dyld加载流程

    dyld加载流程 配置环境变量依赖DYLD(dyld)dyld(the dynamic link editor)是...

  • 十、dyld流程分析

    dyld dyld(the dynamic link editor)是苹果的动态链接器,是苹果操作系统一个重要组成...

  • iOS dyld流程分析

    本文的目的主要是分析dyld的加载流程,了解在main函数之前,底层还做了什么 引子 创建一个project,在V...

  • 类的加载(上)-- _objc_init&read_image

    前言 上一篇文章主要分析dyld的整个流程以及dyld与_objc_init之间的交互,_objc_init向dy...

  • iOS dyld与objc的关联

    本文的主要目的是理解dyld与objc是如何关联的 在上一篇文章iOS dyld流程分析[https://www....

网友评论

    本文标题:dyld 调用流程分析

    本文链接:https://www.haomeiwen.com/subject/irnurltx.html