ios关联启动_iOS开发 - dyld与objc的关联

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

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环境初始化,unattachedCategories和allocatedClasses两张表的初始化工作

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

}

}

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

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

}

分别分析doImageInit和doModInitFunctions两个方法调用:

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=参数2 到sNotifyObjcInit()调用,形成了一个闭环。

那我们可以这样理解: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)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值