iOS开发 - dyld与objc的关联

iOS开发 - 程序启动流程探究

上篇文章我们分析了一遍dyld的流程iOS开发 - 程序加载过程之dyld流程分析,这篇文章我们就来探究探究dyldobjc之间的关联

objc_init 源码分析

可调式源码全局搜索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();
    cache_init();
    _imp_implementationWithBlock_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

阅读源码可知,objc_init() 中进行了如下的一系列操作:

  • environ_init(): 读取影响运行时的环境变量
  • tls_init(): 关于线程key的绑定 - 比如每个线程数据的析构函数
  • static_init(): 运行 C++ 静态构造函数系统级别的构造函数)。在dyld 调用我们的静态构造函数之前,libc 会调用 _objc_init()
  • runtime_init(): Runtime环境初始化,unattachedCategoriesallocatedClasses两张表的初始化工作
  • exception_init(): 初始化libobjc异常处理系统
  • cache_init(): cache缓存条件初始化
  • _imp_implementationWithBlock_init(): 启动回调机制。通常不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载trampolines dylib
  • _dyld_objc_notify_register(): dyld的注册工作

下面我们分别对所有操作进行分析:

environ_init():读取运行时环境变量

源码如下,省略一部分,关键部分为for循环打印所有环境变量:

// 省略一部分代码
// Print OBJC_HELP and OBJC_PRINT_OPTIONS output.
    if (PrintHelp  ||  PrintOptions) {
       // 省略一部分代码
       for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
            const option_t *opt = &Settings[i];            
            if (PrintHelp) _objc_inform("%s: %s", opt->env, opt->help);
            if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env);
        }
    }

调试发现PrintHelpPrintOptionsfalse,不会打印结果,所以我们进行一些修改,将打印函数提出来:

for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
          const option_t *opt = &Settings[i];            
          _objc_inform("%s: %s", opt->env, opt->help);
          _objc_inform("%s is set", opt->env);
 }

得到所有的打印结果如下:

环境变量打印结果

这些环境变量,均可以通过target -- Edit Scheme -- Run --Arguments -- Environment Variables配置,例如我们设置一个环境变量OBJC_DISABLE_NONPOINTER_ISA,利用lldb调试打印对象的isa二进制打印对比查看:
添加环境变量

添加了OBJC_DISABLE_NONPOINTER_ISA变量并且设置为YES,打印结果:
设置OBJC_DISABLE_NONPOINTER_ISA打印结果

移除OBJC_DISABLE_NONPOINTER_ISA环境变量后的打印结果:
未设置OBJC_DISABLE_NONPOINTER_ISA变量

对比打印结果,isa末尾的数字由0变成了1,说明OBJC_DISABLE_NONPOINTER_ISA可以控制isa优化开关,从而优化整个内存结构

tls_init(): 关于线程key的绑定

tls_init() 操作本地线程池的初始化及线程的析构

void tls_init(void)
{
#if SUPPORT_DIRECT_THREAD_KEYS
    pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
    _objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}

static_init() 运行 C++ 静态构造函数

主要是运行系统级别的C++静态构造函数,在dyld调用我们的静态构造函数之前,libc调用_objc_init方法,即系统级别的C++构造函数 先于 自定义的C++构造函数运行

/***********************************************************************
* static_init
* Run C++ static constructor functions.
* libc calls _objc_init() before dyld would call our static constructors, 
* so we have to do it ourselves.
**********************************************************************/
static void static_init()
{
    size_t count;
    auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
    for (size_t i = 0; i < count; i++) {
        inits[i]();
    }
}

runtime_init() Runtime环境初始化

runtime_init 主要是unattachedCategories(分类)allocatedClasses(类)两个表的初始化工作:

void runtime_init(void)
{
    objc::unattachedCategories.init(32);
    objc::allocatedClasses.init();
}

exception_init() 初始化libobjc的异常处理系统

初始化 libobjc 的异常处理系统,注册异常处理的回调,从而监控异常的处理,源码:

/***********************************************************************
* exception_init
* Initialize libobjc's exception handling system.
* Called by map_images().
**********************************************************************/
void exception_init(void)
{
    old_terminate = std::set_terminate(&_objc_terminate);
}

当程序crash发生时,程序会来到_objc_terminate,然后在判断造成crash的是否是objc的对象,如果是的话,就会调用uncaught_handler 扔出异常

/***********************************************************************
* _objc_terminate
* Custom std::terminate handler.
*
* The uncaught exception callback is implemented as a std::terminate handler. 
* 1. Check if there's an active exception
* 2. If so, check if it's an Objective-C exception
* 3. If so, call our registered callback with the object.
* 4. Finally, call the previous terminate handler.
**********************************************************************/
static void (*old_terminate)(void) = nil;
static void _objc_terminate(void)
{
    if (PrintExceptions) {
        _objc_inform("EXCEPTIONS: terminating");
    }

    if (! __cxa_current_exception_type()) {
        // No current exception.
        (*old_terminate)();
    }
    else {
        // There is a current exception. Check if it's an objc exception.
        @try {
            __cxa_rethrow();
        } @catch (id e) {
            // It's an objc object. Call Foundation's handler, if any.
            (*uncaught_handler)((id)e);
            (*old_terminate)();
        } @catch (...) {
            // It's not an objc object. Continue to C++ terminate.
            (*old_terminate)();
        }
    }
}

搜索uncaught_handler,会传入一个函数用于处理处理异常,然后回传到app应用层,如下,其中的fn 函数即为异常发生的函数

objc_uncaught_exception_handler 
objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
{
    objc_uncaught_exception_handler result = uncaught_handler;
    uncaught_handler = fn;
    return result;
}

cache_init() cache缓存条件初始化

主要进行缓存cache的初始化工作:

void cache_init()
{
#if HAVE_TASK_RESTARTABLE_RANGES
    mach_msg_type_number_t count = 0;
    kern_return_t kr;

    while (objc_restartableRanges[count].location) {
        count++;
    }
    //为当前的任务注册一段属于当前任务的可重启的缓存空间范围
    kr = task_restartable_ranges_register(mach_task_self(),
                                          objc_restartableRanges, count);
    if (kr == KERN_SUCCESS) return;
    _objc_fatal("task_restartable_ranges_register failed (result 0x%x: %s)",
                kr, mach_error_string(kr));
#endif // HAVE_TASK_RESTARTABLE_RANGES
}

_imp_implementationWithBlock_init() 启动回调机制

启动回调机制。通常不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载trampolines dylib

void
_imp_implementationWithBlock_init(void)
{
#if TARGET_OS_OSX
    // Eagerly load libobjc-trampolines.dylib in certain processes. Some
    // programs (most notably QtWebEngineProcess used by older versions of
    // embedded Chromium) enable a highly restrictive sandbox profile which
    // blocks access to that dylib. If anything calls
    // imp_implementationWithBlock (as AppKit has started doing) then we'll
    // crash trying to load it. Loading it here sets it up before the sandbox
    // profile is enabled and blocks it.
    //
    // This fixes EA Origin (rdar://problem/50813789)
    // and Steam (rdar://problem/55286131)
    if (__progname &&
        (strcmp(__progname, "QtWebEngineProcess") == 0 ||
         strcmp(__progname, "Steam Helper") == 0)) {
        Trampolines.Initialize();
    }
#endif
}

_dyld_objc_notify_register dyld 注册工作

首先我们来看下源码:

// Note: only for use by objc runtime
// Register handlers to be called when objc images are mapped, unmapped, and initialized.
// Dyld will call back the "mapped" function with an array of images that contain an objc-image-info section.
// Those images that are dylibs will have the ref-counts automatically bumped, so objc will no longer need to
// call dlopen() on them to keep them from being unloaded.  During the call to _dyld_objc_notify_register(),
// dyld will call the "mapped" function with already loaded objc images.  During any later dlopen() call,
// dyld will also call the "mapped" function.  Dyld will call the "init" function when dyld would be called
// initializers in that image.  This is when objc calls any +load methods in that image.
//
void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped);

从源码及注释可知,仅运行时可用,在此注册的是objc镜像文件映射反映射初始化方法,dyld 流程中会在不同时机调用_dyld_objc_notify_register中注册的三个回调函数方法

  • map_images = mapped dyld在将Mach-O中的镜像文件映射到内存中时会调用
  • load_images = init dyld加载所有load 方法及所有的分类相关处理
  • unmap_image = unmapped dyld 将镜像文件删除时候,会触发此方法

dyld 与 objc 关联

_dyld_objc_notify_register 方法其实是一个跨层的方法,我们在dyld源码中搜索_dyld_objc_notify_register,得到源码:

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

继续探究,我们查看registerObjCNotifiers源码:

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;

    // call 'mapped' function with all images mapped so far
    try {
        notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);
    }
    catch (const char* msg) {
        // ignore request to abort during registration
    }

    //  call 'init' function on all images already init'ed (below libSystem)
    for (std::vector::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) {
        ImageLoader* image = *it;
        if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() ) {
            dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
            (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
        }
    }
}

从上面registerObjCNotifiers源码我们可以看到,将mapped(map_images)赋值给了sNotifyObjCMapped,将init(load_images)赋值给了sNotifyObjCInit,将unmap_image(unmapped)赋值给了sNotifyObjCUnmapped,那么我们可以通过探索sNotifyObjCMapped及sNotifyObjCInit在什么时候哪里调用来探索dyld和objc之间的关联。

sNotifyObjCMapped
dyld 源码中搜索sNotifyObjCMapped,知道sNotifyObjCMapped只有在一个地方调用:

static void notifyBatchPartial(dyld_image_states state, bool orLater, dyld_image_state_change_handler onlyHandler, bool preflightOnly, bool onlyObjCMappedNotification)
{
  //省略部分代码
  if ( objcImageCount != 0 ) {
      dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_MAP, 0, 0, 0);
      uint64_t t0 = mach_absolute_time();
    (*sNotifyObjCMapped)(objcImageCount, paths, mhs);
    uint64_t t1 = mach_absolute_time();
    ImageLoader::fgTotalObjCSetupTime += (t1-t0);
   }
  //省略部分代码
}

全局搜索notifyBatchPartial ,发现在上面的registerObjCNotifiers 源码中在对sNotifyObjCMapped 进行赋值之后就有调用。可见map_images的调用是在load_images之前的。

sNotifyObjCInit

全局搜索sNotifyObjCInit ,发现在notifySingle() 处调用了,而notifySingle方法正式我们上一篇文章分析的,镜像文件递归初始化时候recursiveInitialization方法中调用了,源码如下:

void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
                                          InitializerTimingList& timingInfo, UninitedUpwards& 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);
            }

}

到这里,我们已经找到了sNotifyObjCInit 在什么时候调用,即objc_init中注册的load_images 方法的调用,得出下方load_images调用链关系:
_dyld_start --> dyldbootstrap::start --> dyld::_main --> dyld::initializeMainExecutable --> ImageLoader::runInitializers --> ImageLoader::processInitializers --> ImageLoader::recursiveInitialization --> dyld::notifySingle(是一个回调处理) --> sNotifyObjCInit --> load_images(libobjc.A.dylib)

那么到了这一步,还是不知道objc_init在哪里调用,我们继续探究。

doInitialization

在上方recursiveInitialization()方法中我们忽略了bool hasInitializers = this->doInitialization(context);这句代码,忽略了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);
}

分别分析doImageInitdoModInitFunctions两个方法调用:
doImageInit 中主要是for循环加载方法的调用,我们不要关注的是libSystem 初始化程序必须先运行这个提示:

doImageInit源码

doModInitFunctions方法中主要是加载所有C++方法:

doModInitFunctions源码

研究到这里,doInitialization方法中也没发现什么实质性调用objc_init方法的地方,只获取到一个信息,libSystem初始化工作。那么objc_init 到底在哪里调用了呢?

我们不妨在objc_init方法里面打下断点,来查看一下堆栈情况:

objc_init方法调用堆栈

从上图objc_init的调用堆栈来看,加上上面一系列的研究我们到了doModInitFunctions,然后查看堆栈,来到libSystem_initializer方法,我们在libSystem中搜索libSystem_initializer:
libSystem_initializer源码

对照objc_init调用堆栈,及libSystem_initializer源码,我们找到libdispatch_init()libSystem_initializer中调用了,继续查看libdispatch_init源码:

libdispatch_init源码

继续os_object_init()方法源码查找:

os_object_init源码

终于我们看到了objc_init方法的调用,功夫不负有心人,终于把它找到了。
【总结】结合上面的分析,从初始化_objc_init注册的_dyld_objc_notify_register的参数2,即load_images,到sNotifySingle --> sNotifyObjCInie=参数2sNotifyObjcInit()调用,形成了一个闭环

那我们可以这样理解:sNotifySingle 相当于作为一个观察者observor,_objc_init中调用_dyld_objc_notify_register相当于发送通知,即push,而sNotifyObjcInit相当于通知的处理函数,即selector

结论

_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)

你可能感兴趣的:(iOS开发 - dyld与objc的关联)