美文网首页
dyld与objc的关联

dyld与objc的关联

作者: Bel李玉 | 来源:发表于2020-10-17 22:21 被阅读0次

    在本篇文章中,主要探索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运行时环境初始化,里面主要是unattachedCategoriesallocatedClasses方法,主要用来分类初始化。
    • 5,初始化libobjc的异常处理系统,对系统的crash信息进行回调处理。
    • 6,缓存条件初始化。
    • 7,启动回调机制,通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待的加载trampolines dylib。
    • 8,dyld注册的地方,仅供objc运行时使用。dyld将使用包含objc_image_info镜像文件数组,回调mapped函数。1,map_images: dyldimage镜像文件加载进内存时,会触发该函数。2,load_imagesdyld初始化image会触发该函数。3,unman_image: dyldimage移除时会触发该函数。

    下面我们一一展开分析

    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_HELPOBJC_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_STATISTICSYES时,控制台就会打印App的加载时长和动态库的加载时长。即main函数之间的启动时间(查看pre-main耗时)
    • 2, OBJC_DISABLE_NONPOINTER_ISA:杜绝生成相应的nonpointer isa(nonpointer isa指针地址末尾为1),生成的都是普通的isa。
    • 3,OBJC_PRINT_LOAD_METHODS:打印ClassCategoryload方法的调用信息。
    • 4,NSDoubleLocalizedStrings:项目做国际化本地化,要想检测国际化翻译好的语言文字UI会变成什么样子,可以指定这个启动项,可以设置 NSDoubleLocalizedStringsYES
    环境变量有什么作用呢?

    我们以 OBJC_PRINT_LOAD_METHODS环境变量为例。
    我们先配置 OBJC_PRINT_LOAD_METHODSYES,然后我们重写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关联起来。

    相关文章

      网友评论

          本文标题:dyld与objc的关联

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