iOS 逆向07 -- Dyld动态加载器

  • 在上篇中我们知道App经过编译之后最终生成一个Mach-O格式的可执行文件,在运行之前它只是硬盘上的一个静态文件,现在我们需要将它加载进入内存,运行形成一个独立的进程,主要包含下面两个步骤:
    • 装载:将磁盘上的Mach-O可执行文件映射到虚拟内存当中;
    • 动态链接(Dynamic Linking):App应用程序启动时,会加载Dyld动态链接器程序,Dyld会加载链接App运行时所需要的系统动态库的过程称为动态链接;

Dyld

  • Dyld:全称为dynamic loader即动态加载器,是苹果系统提供的一个加载与链接动态库的程序,是iOS系统的一个非常重要组成部分;
  • Dyld可加载的Mach-O格式的文件类型有:MH_OBJECTMH_DYLIBMH_BUNDLE等等;
  • Dyld文件路径位 /usr/lib/dyld,其就是用来加载动态库的程序,同理在移动设备下也有这个程序,如下所示:
Snip20210115_4.png
共享缓存技术
  • 首先App程序启动运行时,会依赖很多的系统动态库,例如UIKit,Foundation等等;
  • Dyld程序开始运行,通过Dyld程序去加载链接App所依赖的所有的系统动态库,由于每个App进程启动时都需要使用这些系统动态库,为了优化程序的启动速度,iOS系统采用了共享缓存技术,即将所有的系统动态库编译成一个大的缓存文件,就是 dyld_shared_cache,该缓存文件存储在iOS系统下的 /System/Library/Caches/com.apple.dyld/目录下;
  • 然后App启动时,Dyld直接去动态库共享缓存中加载链接这些系统动态库
动态链接的优势
  • 共享动态库资源,节省磁盘空间与内存空间;
  • 动态库升级时,App应用程序可做到无感知升级动态库;
  • 更方便程序插件(Plug-in)的制作,为程序带来更好的可扩展性和兼容性;
Dyld源码分析
Snip20210115_7.png
  • 运行工程我们发现类Class的load函数调用,是在main函数之前,为什么会出现这种情况?下面我们来详细分析App运行加载的流程,就能解开其中的秘密;
  • 运行工程,进入断点处可以看到汇编代码如下:
Snip20210115_8.png
  • 在控制台上输入bt命令可以看到函数的调用堆栈:
Snip20210302_116.png
  • 打开源码工程,在dyldStartup.s汇编文件中可以看到:
#if __arm64__ && !TARGET_OS_SIMULATOR
    .text
    .align 2
    .globl __dyld_start
__dyld_start:
    mov     x28, sp
    and     sp, x28, #~15       // force 16-byte alignment of stack
    mov x0, #0
    mov x1, #0
    stp x1, x0, [sp, #-16]! // make aligned terminating frame
    mov fp, sp          // set up fp to point to terminating frame
    sub sp, sp, #16             // make room for local variables
#if __LP64__
    ldr     x0, [x28]               // get app's mh into x0
    ldr     x1, [x28, #8]
    add     x2, x28, #16            // get argv into x2
#else
    ldr     w0, [x28]               // get app's mh into x0
    ldr     w1, [x28, #4]
    add     w2, w28, #8             // get argv into x2
#endif
    adrp    x3,___dso_handle@page
    add     x3,x3,___dso_handle@pageoff // get dyld's mh in to x4
    mov x4,sp                   // x5 has &startGlue
    //call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
    bl  __ZN13dyldbootstrap5startEPKN5dyld311MachOLoadedEiPPKcS3_Pm
    mov x16,x0                  // save entry point address in x16
#if __LP64__
    ldr     x1, [sp]
#else
    ldr     w1, [sp]
#endif
    cmp x1, #0
    b.ne    Lnew
  • 在dyld源码工程中全局搜索dyldbootstrap定位到dyldInitialization.cpp文件中,然后 在dyldInitialization.cpp文件中搜索start(,找到start的函数实现如下:
uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],
                const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue)
{
    // Emit kdebug tracepoint to indicate dyld bootstrap has started <rdar://46878536>
    dyld3::kdebug_trace_dyld_marker(DBG_DYLD_TIMING_BOOTSTRAP_START, 0, 0, 0, 0);

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

    //2.栈溢出保护
    __guard_setup(apple);

#if DYLD_INITIALIZER_SUPPORT
    //3.初始化Dyld
    runDyldInitializers(argc, argv, envp, apple);
#endif

    _subsystem_init(apple);

    //4.进入dyld::main() 主函数 (核心)
    uintptr_t appsSlide = appsMachHeader->getSlide();
    return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}
  • 在start方法中主要做了以下操作:
    • Rebase重定位
    • 栈溢出保护
    • 初始化Dyld
    • 调用dyld::main() 主函数,其中传参macho_header是Mach-O文件的头部,由此可见dyld是针对Mach-O文件进行相关处理的;
为什么要执行Rebase重定位
  • App程序启动时,Dyld会将生成的Mach-O文件加载进入内存,Mach-O文件在内存中的默认起始地址为0,苹果为了防止他人破解App的源码,会采用ASLR技术(地址空间布局随机化),给Mach-O文件载入内存的起始地址新增一个随机偏移量
  • Rebase重定位就是用来纠正Mach-O文件在内存中的起始地址的
  • 执行dyld::main()函数 点击直接定位到dyld2.cpp文件中,对源码做了简化处理如下:
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
        int argc, const char* argv[], const char* envp[], const char* apple[], 
        uintptr_t* startGlue){
    
    if (dyld3::kdebug_trace_dyld_enabled(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE)) {
        launchTraceID = dyld3::kdebug_trace_dyld_duration_start(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE, (uint64_t)mainExecutableMH, 0, 0);
    }

    //Check and see if there are any kernel flags
    dyld3::BootArgs::setFlags(hexToUInt64(_simple_getenv(apple, "dyld_flags"), nullptr));

#if __has_feature(ptrauth_calls)
    if ( const char* disableStr = _simple_getenv(apple, "ptrauth_disabled") ) {
        if ( strcmp(disableStr, "1") == 0 )
            sKeysDisabled = true;
    }else {
        // needed until kernel passes ptrauth_disabled for arm64 main executables
        if ( (mainExecutableMH->cpusubtype == CPU_SUBTYPE_ARM64_V8) || (mainExecutableMH->cpusubtype == CPU_SUBTYPE_ARM64_ALL) )
            sKeysDisabled = true;
    }
#endif

    //第一步:环境变量的配置
    uint8_t mainExecutableCDHashBuffer[20];
    const uint8_t* mainExecutableCDHash = nullptr;
    if ( const char* mainExeCdHashStr = _simple_getenv(apple, "executable_cdhash")) {
        unsigned bufferLenUsed;
        if ( hexStringToBytes(mainExeCdHashStr, mainExecutableCDHashBuffer, sizeof(mainExecutableCDHashBuffer), bufferLenUsed))
            //获取主程序的hash
            mainExecutableCDHash = mainExecutableCDHashBuffer;
    }

    //获取主机信息
    getHostInfo(mainExecutableMH, mainExecutableSlide);
#if !TARGET_OS_SIMULATOR
    // Trace dyld's load
    notifyKernelAboutImage((macho_header*)&__dso_handle, _simple_getenv(apple, "dyld_file"));
    // Trace the main executable's load
    notifyKernelAboutImage(mainExecutableMH, _simple_getenv(apple, "executable_file"));
#endif

    uintptr_t result = 0;
    //获取主程序的macho_header
    sMainExecutableMachHeader = mainExecutableMH;
    //获取主程序的slide值
    sMainExecutableSlide = mainExecutableSlide;

    CRSetCrashLogMessage("dyld: launch started");

    //设置上下文信息
    setContext(mainExecutableMH, argc, argv, envp, apple);

    //获取主程序路径
    sExecPath = _simple_getenv(apple, "executable_path");

    //进程的名字
    sExecShortName = ::strrchr(sExecPath, '/');
    if ( sExecShortName != NULL )
        ++sExecShortName;
    else
        sExecShortName = sExecPath;

    //进程的头环境配置
    configureProcessRestrictions(mainExecutableMH, envp);

#if TARGET_OS_OSX
    if ( !gLinkContext.allowEnvVarsPrint && !gLinkContext.allowEnvVarsPath && !gLinkContext.allowEnvVarsSharedCache ) {
        pruneEnvironmentVariables(envp, &apple);
        setContext(mainExecutableMH, argc, argv, envp, apple);
    }else
#endif
    {
        //检测环境变量
        checkEnvironmentVariables(envp);
        defaultUninitializedFallbackPaths(envp);
    }

    //第二步:加载共享缓存
    //检查缓存共享区域是否开启,iOS必须开启
    checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
    if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
#if TARGET_OS_SIMULATOR
        if ( sSharedCacheOverrideDir)
            mapSharedCache(mainExecutableSlide);
#else
        //共享缓存加载
        mapSharedCache(mainExecutableSlide);
#endif
    }

    //第三步:实例化主程序
    sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
    gLinkContext.mainExecutable = sMainExecutable;
    gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);

    //load any inserted libraries
    //第四步:插入的动态库
    if  ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
        for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib)
            loadInsertedDylib(*lib);
    }
        
    //记录插入的动态库的数量
    sInsertedDylibCount = sAllImages.size()-1;

    //link main executable
    gLinkContext.linkingMainExecutable = true;

    //第五步:链接主程序
    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;
    }

    //第六步:链接插入的动态库
    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();
        }
        if ( gLinkContext.allowInterposing ) {
            for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
                ImageLoader* image = sAllImages[i+1];
                image->registerInterposing(gLinkContext);
            }
        }
    }

    //第七步:弱引用绑定主程序
    sMainExecutable->weakBind(gLinkContext);
    gLinkContext.linkingMainExecutable = false;

    sMainExecutable->recursiveMakeDataReadOnly(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
    
    //第八步:执行所有初始化(动态库+主程序)
    initializeMainExecutable();
#endif

    //第九步:查找程序入口函数 main并返回
    notifyMonitoringDyldMain();
        
    catch(const char* message) {
        syncAllImages();
        halt(message);
    }
    catch(...) {
        dyld::log("dyld: launch failed\n");
    }

    CRSetCrashLogMessage("dyld2 mode");
#if !TARGET_OS_SIMULATOR
    if (sLogClosureFailure) {
        // We failed to launch in dyld3, but dyld2 can handle it. synthesize a crash report for analytics
        dyld3::syntheticBacktrace("Could not generate launchClosure, falling back to dyld2", true);
    }
#endif

    if (sSkipMain) {
        notifyMonitoringDyldMain();
        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);
        }
        ARIADNEDBG_CODE(220, 1);
        result = (uintptr_t)&fake_main;
        *startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit;
    }
    return result;
}
  • 其流程可以总结为以下9个步骤:
  • 【第一步:环境变量的配置】:检查设置的环境变量以及获取运行文件的主机信息;
Snip20210302_126.png
  • 【第二步:加载共享缓存中的系统动态库
Snip20210302_117.png
  • 【第三步:实例化主程序】:调用instantiateFromLoadedImage函数,返回了ImageLoaderMachO对象
Snip20210302_118.png
  • 【第四步:插入系统动态库】:遍历DYLD_INSERT_LIBRARIES环境变量,然后调用loadInsertedDylib加载,插入动态库;sInsertedDylibCount用来记录插入动态库的数量。
Snip20210302_120.png
  • 【第五步:链接主程序】主程序sMainExecutable本质是ImageLoader,会将主程序的ImageLoader添加到sAllImages集合中去;
Snip20210302_121.png
  • 【第六步:链接动态库】:会将各动态库的ImageLoader添加到sAllImages集合中去;
Snip20210302_122.png
  • 【第七步:Rebase重定位与符号的绑定bind】:
Snip20210302_123.png
  • Rebase重定位:镜像文件内部指针的重定位,修复正确的指向;

  • 符号的绑定:镜像文件之间存在相互依赖,符号的绑定就是修正镜像外部指针的指向;

  • 【第八步:执行所有初始化 包括动态库与主程序】:

Snip20210302_124.png
  • dyld会优先初始化动态库,其次初始化主程序;

  • 执行初始化的主要内容包括以下两个方面:主要涉及objc运行时

    • 执行map_images函数,完成所有类与分类的注册,实现与初始化,
    • 执行load_images函数,完成所有类与分类的load方法的调用,以及调用C++中的构造函数,创建C++的全局变量等等;
  • 【第九步:返回主程序入口即main函数】:

Snip20210302_125.png
下面重点分析第八步执行所有初始化,即调用initializeMainExecutable()函数
image.png
  • 其内部调用runInitializers()函数,注意调用者为ImageLoader
  • 函数实现如下:
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);
}
  • 核心步骤为processInitializers()函数,其函数实现如下:
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.imagesAndPaths[i].first->recursiveInitialization(context, thisThread, images.imagesAndPaths[i].second, timingInfo, ups);
    }
    // If any upward dependencies remain, init them.
    if ( ups.count > 0 )
        processInitializers(context, thisThread, timingInfo, ups);
}
  • 看到循环遍历ImageLoader执行recursiveInitialization函数,其实现如下:
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.imagesAndPaths[uninitUps.count] = { dependentImage, libPath(i) };
                        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();
}
  • 其核心步骤有两个:
    • notifySingle() ,发现notifySingle()先后调用了两次,第一次在doInitialization()函数之前,第二次在doInitialization()之后。
    • doInitialization()
notifySingle()函数的核心逻辑如下:
Snip20210304_16.png
  • 关键代码 (*sNotifyObjCInit)(image->getRealPath(), image->machHeader())

  • 第一次调用notifySingle()函数时,sNotifyObjCInit是空的,因为sNotifyObjCInit是在doInitialization()调用-->_objc_init()--> _dyld_objc_notify_register --> registerObjCNotifiers中赋值的;

  • 全局搜索sNotifyObjCInit,定位如下:

Snip20210302_129.png
  • 看到只有赋值操作,然后全局搜索registerObjCNotifiers,定位如下:
void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped)
{
    dyld::registerObjCNotifiers(mapped, init, unmapped);
}
  • 最终定位到void _dyld_objc_notify_register()这个函数,此函数不是在dyld中调用的,而是在objc中调用的,这就涉及到两个不同底层库之间的通信;
  • objc调用void _dyld_objc_notify_register()注册了三个回调函数,当dyldimage镜像载入内存时load加载image镜像时image镜像从内存中移除时,分别调用相应的回调函数;
  • 在objc4_781源码工程中全局搜索_dyld_objc_notify_register(,定位如下:
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(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}
  • 看到objc 在_objc_init中调用了_dyld_objc_notify_register 传入的三个参数就是三个回调函数,dyld会保存这三个回调函数,且在合适的时机去调用这三个回调函数;

  • objc中的load_images方法实现如下:

load_images(const char *path __unused, const struct mach_header *mh)
{
    if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
        didInitialAttachCategories = true;
        loadAllCategories();
    }

    // Return without taking locks if there are no +load methods here.
    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}
  • 核心逻辑call_load_methods(),其实现如下:
Snip20210302_130.png
  • call_class_loads实现如下:
Snip20210302_131.png
  • 流程到了这里,也就解释了为什么类的load类方法会比入口main函数率先调用;
  • 同时也验证了上面控制台打印的函数调用堆栈;
  • 先调用类的load方法,然后再调用分类的load方法;
总结:类的load方法调用堆栈:

_dyld_start --> dyldbootstrap::start --> dyld::_main --> dyld::initializeMainExecutable --> ImageLoader::runInitializers --> ImageLoader::processInitializers --> ImageLoader::recursiveInitialization --> dyld::notifySingle --> sNotifyObjCInit --> load_images(libobjc.A.dylib)

  • 在Xcode中加入符号断点 _objc_init
Snip20210118_11.png
  • 当工程执行到符号断点_objc_init 时在控制器台中输入bt,打印出系统函数的调用堆栈:
Snip20210302_1.png
  • 从函数堆栈我们看到中途调用的是recursiveInitialization()函数中的doInitialization()也就是上面所说的核心步骤的第二个步骤,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():镜像初始化
  • doModInitFunctions()
  • 其中doImageInit()doModInitFunctions()实现分别如下所示:
Snip20210304_7.png
Snip20210304_8.png
  • 从上面的两个函数实现并没有看到_objc_init()的调用;
  • 看到注释在初始化时必须要先初始化libSystem,猜测是否在libSystem的初始化中存在_objc_init()的调用;
  • 下载Libsystem-1292.60.1源码工程,全局搜索libSystem_initializer函数,其实现如下所示:
Snip20210304_10.png
  • 其内部也存在dyld的初始化_dyld_initializer(),其次是libdispatch_init(),根据_objc_init()的调用堆栈,知道libdispatch_init()是在libdispatch开源库中,下载它的开源库 libdispatch-1173.0.3打开工程全局搜索libdispatch_init
Snip20210304_12.png
  • 全局搜索_os_object_init(,找到其函数实现如下所示:
Snip20210304_13.png
  • 到了这里终于看到了_objc_init()的调用。
总结_objc_init的调用堆栈:

_dyld_start --> dyldbootstrap::start --> dyld::_main --> dyld::initializeMainExecutable --> ImageLoader::runInitializers --> ImageLoader::processInitializers --> ImageLoader::recursiveInitialization --> doInitialization -->libSystem_initializer(libSystem.B.dylib) --> _os_object_init(libdispatch.dylib) --> _objc_init(libobjc.A.dylib)

总结:
  • Mach-O文件装载进入内存,创建App进程,接下来的工作交给Dyld,其链接动态库与主应用程序,最后返回Application的Main函数入口,其整体的流程如下图所示:
Dyld_Objc.png
  • objc调用_dyld_objc_notify_register(&map_images, load_images, unmap_image)函数,然后dyld接收到这三个方法参数 dyld::registerObjCNotifiers(mapped, init, unmapped),并用全局变量保存,对应关系如下:
  • map_images <==> sNotifyObjCMapped
  • load_images <==> sNotifyObjCInit
  • unmap_images <==> sNotifyObjCUnmapped
  • 最后dyld在合适的时机,分别调用这三个函数方法;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容