在本篇文章中,主要探索dyld与objc是如何关联的。
objc_init
_objc_init
通过dyld
的消息对我们镜像进行注册,由libSsytem
调用,其实现为:
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(); // 1
tls_init(); // 2
static_init(); // 3
runtime_init(); // 4
exception_init();// 5
cache_init(); // 6
_imp_implementationWithBlock_init(); // 7
_dyld_objc_notify_register(&map_images, load_images, unmap_image); // 8
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
- 1,读取影响运行时的环境变量,可以通过终端命令
export OBJC_HELP = 1
,获得所有的环境变量值。 - 2,关于线程key的绑定,例如线程数据的析构函数。
- 3,运行C++静态构造函数,在
dyld
调用我们的静态析构函数之前,libc会调用_objc_init(),因此我们必须自己做。 - 4,
runtime
运行时环境初始化,里面主要是unattachedCategories
、allocatedClasses
方法,主要用来分类初始化。 - 5,初始化libobjc的异常处理系统,对系统的
crash
信息进行回调处理。 - 6,缓存条件初始化。
- 7,启动回调机制,通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待的加载trampolines dylib。
- 8,
dyld
注册的地方,仅供objc运行时
使用。dyld
将使用包含objc_image_info
的镜像文件数组
,回调mapped
函数。1,map_images
:dyld
将image
镜像文件加载进内存时,会触发该函数。2,load_images
:dyld
初始化image
会触发该函数。3,unman_image
:dyld
将image
移除时会触发该函数。
下面我们一一展开分析
environ_init
其实现如下
for (char **p = *_NSGetEnviron(); *p != nil; p++) {
if (0 == strncmp(*p, "Malloc", 6) || 0 == strncmp(*p, "DYLD", 4) ||
0 == strncmp(*p, "NSZombiesEnabled", 16))
{
maybeMallocDebugging = true;
}
if (0 != strncmp(*p, "OBJC_", 5)) continue;
if (0 == strncmp(*p, "OBJC_HELP=", 10)) {
PrintHelp = true;
continue;
}
if (0 == strncmp(*p, "OBJC_PRINT_OPTIONS=", 19)) {
PrintOptions = true;
continue;
}
const char *value = strchr(*p, '=');
if (!*value) continue;
value++;
for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
const option_t *opt = &Settings[i];
if ((size_t)(value - *p) == 1+opt->envlen &&
0 == strncmp(*p, opt->env, opt->envlen))
{
*opt->var = (0 == strcmp(value, "YES"));
break;
}
}
}
if (PrintHelp || PrintOptions) {
if (PrintHelp) {
_objc_inform("Objective-C runtime debugging. Set variable=YES to enable.");
_objc_inform("OBJC_HELP: describe available environment variables");
if (PrintOptions) {
_objc_inform("OBJC_HELP is set");
}
_objc_inform("OBJC_PRINT_OPTIONS: list which options are set");
}
if (PrintOptions) {
_objc_inform("OBJC_PRINT_OPTIONS is set");
}
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);
}
}
通过对代码进行阅读我们可以知道:分别把OBJC_HELP
和OBJC_PRINT_OPTIONS
设置为YES
,设置步骤为Edit Schemes
--> Environment variable
,添加 上述两个值, 就可以输出所有的环境变量。
objc[10494]: Objective-C runtime debugging. Set variable=YES to enable.
objc[10494]: OBJC_HELP: describe available environment variables
objc[10494]: OBJC_HELP is set
objc[10494]: OBJC_PRINT_OPTIONS: list which options are set
objc[10494]: OBJC_PRINT_OPTIONS is set
objc[10494]: OBJC_PRINT_IMAGES: log image and library names as they are loaded
objc[10494]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps
objc[10494]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods
objc[10494]: OBJC_PRINT_INITIALIZE_METHODS: log calls to class +initialize methods
objc[10494]: OBJC_PRINT_RESOLVED_METHODS: log methods created by +resolveClassMethod: and +resolveInstanceMethod:
objc[10494]: OBJC_PRINT_CLASS_SETUP: log progress of class and category setup
objc[10494]: OBJC_PRINT_PROTOCOL_SETUP: log progress of protocol setup
objc[10494]: OBJC_PRINT_IVAR_SETUP: log processing of non-fragile ivars
objc[10494]: OBJC_PRINT_VTABLE_SETUP: log processing of class vtables
objc[10494]: OBJC_PRINT_VTABLE_IMAGES: print vtable images showing overridden methods
objc[10494]: OBJC_PRINT_CACHE_SETUP: log processing of method caches
objc[10494]: OBJC_PRINT_FUTURE_CLASSES: log use of future classes for toll-free bridging
objc[10494]: OBJC_PRINT_PREOPTIMIZATION: log preoptimization courtesy of dyld shared cache
objc[10494]: OBJC_PRINT_CXX_CTORS: log calls to C++ ctors and dtors for instance variables
objc[10494]: OBJC_PRINT_EXCEPTIONS: log exception handling
objc[10494]: OBJC_PRINT_EXCEPTION_THROW: log backtrace of every objc_exception_throw()
objc[10494]: OBJC_PRINT_ALT_HANDLERS: log processing of exception alt handlers
objc[10494]: OBJC_PRINT_REPLACED_METHODS: log methods replaced by category implementations
objc[10494]: OBJC_PRINT_DEPRECATION_WARNINGS: warn about calls to deprecated runtime functions
objc[10494]: OBJC_PRINT_POOL_HIGHWATER: log high-water marks for autorelease pools
objc[10494]: OBJC_PRINT_CUSTOM_CORE: log classes with custom core methods
objc[10494]: OBJC_PRINT_CUSTOM_RR: log classes with custom retain/release methods
objc[10494]: OBJC_PRINT_CUSTOM_AWZ: log classes with custom allocWithZone methods
objc[10494]: OBJC_PRINT_RAW_ISA: log classes that require raw pointer isa fields
objc[10494]: OBJC_DEBUG_UNLOAD: warn about poorly-behaving bundles when unloaded
objc[10494]: OBJC_DEBUG_FRAGILE_SUPERCLASSES: warn about subclasses that may have been broken by subsequent changes to superclasses
objc[10494]: OBJC_DEBUG_NIL_SYNC: warn about @synchronized(nil), which does no synchronization
objc[10494]: OBJC_DEBUG_NONFRAGILE_IVARS: capriciously rearrange non-fragile ivars
objc[10494]: OBJC_DEBUG_ALT_HANDLERS: record more info about bad alt handler use
objc[10494]: OBJC_DEBUG_MISSING_POOLS: warn about autorelease with no pool in place, which may be a leak
objc[10494]: OBJC_DEBUG_POOL_ALLOCATION: halt when autorelease pools are popped out of order, and allow heap debuggers to track autorelease pools
objc[10494]: OBJC_DEBUG_DUPLICATE_CLASSES: halt when multiple classes with the same name are present
objc[10494]: OBJC_DEBUG_DONT_CRASH: halt the process by exiting instead of crashing
objc[10494]: OBJC_DISABLE_VTABLES: disable vtable dispatch
objc[10494]: OBJC_DISABLE_PREOPTIMIZATION: disable preoptimization courtesy of dyld shared cache
objc[10494]: OBJC_DISABLE_TAGGED_POINTERS: disable tagged pointer optimization of NSNumber et al.
objc[10494]: OBJC_DISABLE_TAG_OBFUSCATION: disable obfuscation of tagged pointers
objc[10494]: OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields
objc[10494]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY: disable safety checks for +initialize after fork
常用的环境变量:
- 1,
DYLD_PRINT_STATISTICS
:设置DYLD_PRINT_STATISTICS
为YES
时,控制台就会打印App的加载时长和动态库的加载时长。即main函数之间的启动时间(查看pre-main耗时)
。 - 2,
OBJC_DISABLE_NONPOINTER_ISA
:杜绝生成相应的nonpointer isa
(nonpointer isa指针地址末尾为1),生成的都是普通的isa。 - 3,
OBJC_PRINT_LOAD_METHODS
:打印Class
及Category
的load
方法的调用信息。 - 4,
NSDoubleLocalizedStrings
:项目做国际化本地化,要想检测国际化翻译好的语言文字UI会变成什么样子,可以指定这个启动项,可以设置NSDoubleLocalizedStrings
为YES
。
环境变量有什么作用呢?
我们以 OBJC_PRINT_LOAD_METHODS
环境变量为例。
我们先配置 OBJC_PRINT_LOAD_METHODS
为YES
,然后我们重写LYPerson
的+(void)load
方法。
@implementation LYPerson
+(void)load{
}
@end
.....
objc[10890]: LOAD: +[NSApplication load]
objc[10890]: LOAD: +[NSBinder load]
objc[10890]: LOAD: +[NSColorSpaceColor load]
objc[10890]: LOAD: +[NSNextStepFrame load]
objc[10890]: LOAD: +[NSColor(NSUIKitSupport) load]
objc[10890]: LOAD: category 'NSError(FPAdditions)' scheduled for +load
objc[10890]: LOAD: +[NSError(FPAdditions) load]
objc[10890]: LOAD: class '_DKEventQuery' scheduled for +load
objc[10890]: LOAD: +[_DKEventQuery load]
objc[10890]: LOAD: class 'LYPerson' scheduled for +load
objc[10890]: LOAD: +[LYPerson load]
我们可以通过该环境变量
来跟踪工程中所有的load方法
。
2、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
}
3、static_init:运行系统级别的C++静态构造函数
主要是运行系统级别的C++静态构造函数,在dyld
调用我们的静态构造函数之前,libc
调用_objc_init
方法,即系统级别的C++构造函数 先于 自定义的C++构造函数 运行
。
static void static_init()
{
size_t count;
auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
for (size_t i = 0; i < count; i++) {
inits[i]();
}
}
4、runtime_init: 运行时环境初始化
主要是运行时的初始化,主要分为两部分:分类的初始化
、类的表初始化
void runtime_init(void)
{
objc::unattachedCategories.init(32);
objc::allocatedClasses.init();
}
5、exception_init:初始化libobjc的异常处理系统
主要是初始化libobjc的异常处理系统,注册异常处理的回调,从而监控异常的处理
,源码如下:
void exception_init(void)
{
old_terminate = std::set_terminate(&_objc_terminate);
}
- 当有
crash
(crash是指系统发生的不允许的指令,然后系统给的一些信号)发生时,会来到_objc_terminate
方法,走到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 _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); // 1
(*old_terminate)(); // 2
} @catch (...) {
// It's not an objc object. Continue to C++ terminate.
(*old_terminate)();
}
}
}
- 1,如果在OC中注册了
exception callback
,就调用我们注册的callback
。 - 2,调用之前的中断程序。
crash分类
crash的主要原因是收到了未处理的信号,主要来源于三个地方:
kernel内核
其他进行
App本身
所以其对应的,crash也分为3种
-
Mach异常
:是指最底层的内核级异常。用户态的开发者可以直接通过Mach API设置thread,task,host的异常端口
,来捕获Mach异常。 -
Unix信号
:又称BSD信号,如果开发者没有捕获Mach异常,则会被host层的方法un_exception()
将异常转换为对应的UNIX信号,并通过方法threadSignal()
将信号投递到出错线程,可以通过方法signal(x, SignalHandler)来捕获Single
。 -
NSException 应用级异常
:它是未被捕获的OC异常,导致程序向自身发送了SIGABRT
信号而崩溃,对于未捕获的OC异常,是可以通过try catch来捕获
的,或者通过NSSetUncaughtExceptionHandler()机制来捕获
。
针对应用级异常
,可以通过注册异常捕获的函数,即NSSetUncaughtExceptionHandler
机制,实现线程保活,收集上传崩溃日志。
6,_dyld_objc_notify_register: dyld注册
_dyld_objc_notify_register方法在dyld
源码中,以下是 _dyld_objc_notify_register
方法的声明
//
// 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运行时
使用 -
注册处理程序
,以便在映射、取消映射和初始化objc图像时调用。 -
dyld
将会通过一个包含objc-image-info 的镜像文件的数组回调mapped
函数。
方法中的三个参数
分别表示的含义如下: -
map_images
: dyld 将 image(镜像文件)加载进内存时,会触发该函数。 -
load_image
: dyld 初始化image会触发该函数 -
unman_image
: dyld将 image移除时,会触发该函数。
总结
dyld
库,通过_dyld_objc_notify_register(&map_images, load_images, unmap_image)
方法,来调用 这个3个回调函数,从而将 dyld与objc关联起来。
网友评论