014-类的加载原理(上)

979 阅读7分钟

通过这篇文章可以获得什么

探索概览

类的加载(上).jpeg

_objc_init的引入

根据App的启动流程可以得知dyld通过递归初始化libsystemlibdispatchlibobjc等库,然后会调用libobjc的_objc_init进行类的加载。

对+load方法设置断点

在dyld加载应用程序的过程中就自动加载了类,通过对load方法设置断点式无法找到_objc_init,无法发现类的初始化蛛丝马迹。

load设置断点.png

对_objc_init方法设置断点

通过对_objc_init设置断点找到了类的加载过程

_objc_init设置断点.png

整理类加载过程的dyld调用顺序

  • dyld`_dyld_start -> dyld加载还是标志
  • dyld`dyldbootstrap::start -> dyld引导器
  • dyld`dyld::_main -> dyld的main函数
  • dyld`dyld::initializeMainExecutable() -> 序列化主程序
  • dyld`ImageLoader::runInitializers -> 镜像文件序列化
  • dyld`ImageLoader::processInitializers -> 进出序列化
  • dyld`ImageLoader::recursiveInitialization -> 递归初始化libsystem
  • dyld`ImageLoader::recursiveInitialization -> 递归初始化libdispatch
  • dyld`ImageLoaderMachO::doInitialization ->
  • dyld`ImageLoaderMachO::doModInitFunctions ->
  • libSystem.B.dylib`libSystem_initializer -> libsystem序列化
  • libdispatch.dylib`libdispatch_init -> libdispatch初始化
  • libdispatch.dylib`_os_object_init ->
  • libobjc.A.dylib`_objc_init -> 类的加载

libdispatch的_os_object_init函数

类加载的开始.png

结论:类加载之前dyld会首先加载libsysrtemlibdispatch系统的库,并对其初始化,类的加载是有libdisaptch开启的。由libdisaptch的_os_object_init函数进行_objc_init类加载的开始。

_objc_init

思维导图概览:

_objc_init.png

源码

void _objc_init(void)
{
    //initialized赋值
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    //环境变量的初始化
    environ_init();
    tls_init();
    //全局静态C++函数的调用
    static_init();
    //runtime运行时环境的初始化,里面注册了两张表
    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
}

environ_init()

环境变量的初始化,可以监控在类的加载阶段的诸多指标,在你需要的时候可以通过在工程中设置环境变量的方式在控制台打印其信息

/***********************************************************************
* 环境初始化
* 读取影响运行时的环境变量。
* 如果需要,还打印环境变量帮助。
**********************************************************************/
void environ_init(void) 
{
    if (issetugid()) {
        // 当 setuid 或 setgid 时,所有环境变量都会被静默忽略
        // 这包括 OBJC_HELP 和 OBJC_PRINT_OPTIONS 本身。
        return;
    } 

    bool PrintHelp = false;
    bool PrintOptions = false;
    bool maybeMallocDebugging = false;

    // 直接扫描environ[] 而不是大量调用getenv()。
    // 这优化了没有设置的情况。
    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;
        }
        
        if (0 == strncmp(*p, "OBJC_DEBUG_POOL_DEPTH=", 22)) {
            SetPageCountWarning(*p + 22);
            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;
            }
        }
    }

    // 特殊情况:启用一些自动释放池调试
    // 当某些 malloc 调试被启用时
    // 并且 OBJC_DEBUG_POOL_ALLOCATION 未设置为 NO 以外的值。
    if (maybeMallocDebugging) {
        const char *insert = getenv("DYLD_INSERT_LIBRARIES");
        const char *zombie = getenv("NSZombiesEnabled");
        const char *pooldebug = getenv("OBJC_DEBUG_POOL_ALLOCATION");
        if ((getenv("MallocStackLogging")
             || getenv("MallocStackLoggingNoCompact")
             || (zombie && (*zombie == 'Y' || *zombie == 'y'))
             || (insert && strstr(insert, "libgmalloc")))
            &&
            (!pooldebug || 0 == strcmp(pooldebug, "YES")))
        {
            DebugPoolAllocation = true;
        }
    }

    // 打印 OBJC_HELP 和 OBJC_PRINT_OPTIONS 输出。
    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在控制台可以打印出环境变量

设置步骤: target -> Edit scheme -> Run -> Arguments -> Environments Variables

设置环境变量.png

环境变量的打印结果:

环境变量的打印.png

通过terminal打印出环境变量

命令:

export OBJC_HELP=1

terminal打印环境变量结果:

terminal打印环境变量.png

static_init()

/***********************************************************************
* static_init
* 运行 C++ 静态构造函数。
* libc 在 dyld 调用我们的静态构造函数之前调用 _objc_init(),
**********************************************************************/
static void static_init()
{
    size_t count;
    auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
    for (size_t i = 0; i < count; i++) {
        inits[i]();
    }
    auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count);
    for (size_t i = 0; i < count; i++) {
        UnsignedInitializer init(offsets[i]);
        init();
    }
}

runtime_init

初始化了两张表

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

exception_init

/***********************************************************************
* exception_init
* 初始化 libobjc 的异常处理系统。
* 由 map_images() 调用。
**********************************************************************/
void exception_init(void)
{
    old_terminate = std::set_terminate(&_objc_terminate);
}
/***********************************************************************
* _objc_terminate
* 自定义 std::terminate 处理程序。
*
* 未捕获的异常回调作为 std::terminate 处理程序实现。
* 1. 检查是否有活动异常
* 2. 如果是,检查是否是Objective-C异常
* 3. 如果是,则使用该对象调用我们注册的回调。
* 4. 最后,调用前面的终止处理程序。
**********************************************************************/
static void (*old_terminate)(void) = nil;
static void _objc_terminate(void)
{
    if (PrintExceptions) {
        _objc_inform("EXCEPTIONS: terminating");
    }

    if (! __cxa_current_exception_type()) {
        // 当前没有异常
        (*old_terminate)();
    }
    else {
        // 检查它是否是 objc 异常。
        @try {
            __cxa_rethrow();
        } @catch (id e) {
            // 如果出现了异常,通过uncaught_handler回调
            (*uncaught_handler)((id)e);
            (*old_terminate)();
        } @catch (...) {
            // 它不是一个 objc 对象。
            (*old_terminate)();
        }
    }
}

_dyld_objc_notify_register

注册消息事件

void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped);

map_images

void
map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    mutex_locker_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}

map_images_nolock

void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    static bool firstTime = YES;
    header_info *hList[mhCount];
    uint32_t hCount;
    size_t selrefCount = 0;

    // 如有必要,执行第一次初始化。
    // 此函数在普通库初始化程序之前调用。
    // fixme 延迟初始化,直到找到使用 objc 的镜像?
    if (firstTime) {
        preopt_init();
    }


    // 查找所有带有 Objective-C 元数据的镜像。
    hCount = 0;

    //中间省略了一些逻辑操作......
    
    //当hCount有值的时候执行_read_images
    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

    firstTime = NO;
    
    // 一切设置完毕后调用镜像加载函数。
    for (auto func : loadImageFuncs) {
        for (uint32_t i = 0; i < mhCount; i++) {
            func(mhdrs[i]);
        }
    }
}

_read_images(核心方法)

思维导图概览:

_read_images.png

源码;

/***********************************************************************
* _read_images
* 对链接中的标头执行初始处理
* 以 headerList 开头的列表。
*
* 调用者:map_images_nolock
*
* Locking:map_images获取的runtimeLock
**********************************************************************/
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
    header_info *hi;
    uint32_t hIndex;
    size_t count;
    size_t i;
    Class *resolvedFutureClasses = nil;
    size_t resolvedFutureClassCount = 0;
    static bool doneOnce;
    bool launchTime = NO;
    TimeLogger ts(PrintImageTimes);

    runtimeLock.assertLocked();
    
    //条件控制第一次加载
    if (!doneOnce) {...}
    
    // 修复预编译阶段@selector的混乱问题
    static size_t UnfixedSelectors;
    {
        mutex_locker_t lock(selLock);
        for (EACH_HEADER) {
            if (hi->hasPreoptimizedSelectors()) continue;

            bool isBundle = hi->isBundle();
            SEL *sels = _getObjc2SelectorRefs(hi, &count);
            UnfixedSelectors += count;
            for (i = 0; i < count; i++) {
                const char *name = sel_cname(sels[i]);
                SEL sel = sel_registerNameNoLock(name, isBundle);
                if (sels[i] != sel) {
                    sels[i] = sel;
                }
            }
        }
    }
    ts.log("IMAGE TIMES: fix up selector references");
    
    //错误混乱的类处理
    bool hasDyldRoots = dyld_shared_cache_some_image_overridden();

    for (EACH_HEADER) {
        删除了部分代码...
    }
    ts.log("IMAGE TIMES: discover classes");
    
    
    //修复、重新映射没有被镜像文件加载进来的类
    if (!noClassesRemapped()) {
        for (EACH_HEADER) {
            Class *classrefs = _getObjc2ClassRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[i]);
            }
            // fixme why doesn't test future1 catch the absence of this?
            classrefs = _getObjc2SuperRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[i]);
            }
        }
    }
    ts.log("IMAGE TIMES: remap classes");
    
    // 修复旧的 objc_msgSend 修复调用站点
    for (EACH_HEADER) {
        message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
        if (count == 0) continue;

        if (PrintVtables) {
            _objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
                         "call sites in %s", count, hi->fname());
        }
        for (i = 0; i < count; i++) {
            fixupMessageRef(refs+i);
        }
    }

    ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
    
     // 发现协议
    for (EACH_HEADER) {
        extern objc_class OBJC_CLASS_$_Protocol;
        Class cls = (Class)&OBJC_CLASS_$_Protocol;
        ASSERT(cls);
        NXMapTable *protocol_map = protocols();
        bool isPreoptimized = hi->hasPreoptimizedProtocols();

        bool isBundle = hi->isBundle();

        protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
        for (i = 0; i < count; i++) {
            readProtocol(protolist[i], cls, protocol_map, 
                         isPreoptimized, isBundle);
        }
    }
    ts.log("IMAGE TIMES: discover protocols");
    
    //修复没有加载的协议
    for (EACH_HEADER) {
        if (launchTime && hi->isPreoptimized())
            continue;
        protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
        for (i = 0; i < count; i++) {
            remapProtocolRef(&protolist[i]);
        }
    }
    ts.log("IMAGE TIMES: fix up @protocol references");
    
    //分类的加载
    if (didInitialAttachCategories) {
        for (EACH_HEADER) {
            load_categories_nolock(hi);
        }
    }
    ts.log("IMAGE TIMES: discover categories");
    
    // 实现非懒加载类(用于+load 方法和静态实例)
    for (EACH_HEADER) {
        classref_t const *classlist = hi->nlclslist(&count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            if (!cls) continue;

            addClassTableEntry(cls);

            if (cls->isSwiftStable()) {
                if (cls->swiftMetadataInitializer()) {
                    _objc_fatal("Swift class %s with a metadata initializer "
                                "is not allowed to be non-lazy",
                                cls->nameForLogging());
                }
            }
            realizeClassWithoutSwift(cls, nil);
        }
    }

    ts.log("IMAGE TIMES: realize non-lazy classes");
    
    // 实现没有被处理的类,优化被入侵的类
    if (resolvedFutureClasses) {
        for (i = 0; i < resolvedFutureClassCount; i++) {
            Class cls = resolvedFutureClasses[i];
            if (cls->isSwiftStable()) {
                _objc_fatal("Swift class is not allowed to be future");
            }
            realizeClassWithoutSwift(cls, nil);
            cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
        }
        free(resolvedFutureClasses);
    }

    ts.log("IMAGE TIMES: realize future classes");
    
    //省略了部分代码
}

readClass

读取类,并将其添加进表中

/***********************************************************************
* readClass
* 读取由编译器编写的类和元类。
* 返回新的类指针。 这可能是:
* - cls
* - nil(cls 缺少弱链接超类)
* - 别的东西(这个class的空间被未来的class保留了)
*
* 请注意,此功能执行的所有工作均由
* mustReadClasses()。 不要在不更新该功能的情况下更改该功能。
*
* Locking:由map_images或objc_readClassPair获取的runtimeLock
**********************************************************************/
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    const char *mangledName = cls->nonlazyMangledName();
    //容错处理(情况一):return nil
    if (missingWeakSuperclass(cls)) {
        // 没有父类 (可能是弱链接). 
        // 否认对这个子类的任何了解。
        if (PrintConnecting) {
            _objc_inform("CLASS: IGNORING class '%s' with "
                         "missing weak-linked superclass", 
                         cls->nameForLogging());
        }
        addRemappedClass(cls, nil);
        cls->setSuperclass(nil);
        return nil;
    }
    
    cls->fixupBackwardDeployingStableSwift();

    Class replacing = nil;
    if (mangledName != nullptr) {
        //通常情况下并不会在此处进行rw与ro的处理
        if (Class newCls = popFutureNamedClass(mangledName)) {
          
            if (newCls->isAnySwift()) {
                _objc_fatal("Can't complete future class request for '%s' "
                            "because the real class is too big.",
                            cls->nameForLogging());
            }

            class_rw_t *rw = newCls->data();
            const class_ro_t *old_ro = rw->ro();
            memcpy(newCls, cls, sizeof(objc_class));

            // Manually set address-discriminated ptrauthed fields
            // so that newCls gets the correct signatures.
            newCls->setSuperclass(cls->getSuperclass());
            newCls->initIsa(cls->getIsa());

            rw->set_ro((class_ro_t *)newCls->data());
            newCls->setData(rw);
            freeIfMutable((char *)old_ro->getName());
            free((void *)old_ro);

            addRemappedClass(cls, newCls);

            replacing = cls;
            cls = newCls;
        }
    }
    
    if (headerIsPreoptimized  &&  !replacing) {
        // class list built in shared cache
        // fixme strict assert doesn't work because of duplicates
        // ASSERT(cls == getClass(name));
        ASSERT(mangledName == nullptr || getClassExceptSomeSwift(mangledName));
    } else {
        if (mangledName) { //some Swift generic classes can lazily generate their names
            addNamedClass(cls, mangledName, replacing);
        } else {
            Class meta = cls->ISA();
            const class_ro_t *metaRO = meta->bits.safe_ro();
            ASSERT(metaRO->getNonMetaclass() && "Metaclass with lazy name must have a pointer to the corresponding nonmetaclass.");
            ASSERT(metaRO->getNonMetaclass() == cls && "Metaclass nonmetaclass pointer must equal the original class.");
        }
        addClassTableEntry(cls);
    }

    // for future reference: shared cache never contains MH_BUNDLEs
    if (headerIsBundle) {
        cls->data()->flags |= RO_FROM_BUNDLE;
        cls->ISA()->data()->flags |= RO_FROM_BUNDLE;
    }
    
    return cls;
}

addNamedClass

/***********************************************************************
* addNamedClass
* 将 name => cls 添加到命名的非元类映射。
* 警告重复的类名并保留旧的映射。
* 锁定:runtimeLock 必须由调用者持有
**********************************************************************/
static void addNamedClass(Class cls, const char *name, Class replacing = nil)
{
    runtimeLock.assertLocked();
    Class old;
    if ((old = getClassExceptSomeSwift(name))  &&  old != replacing) {
        inform_duplicate(name, old, cls);

         // getMaybeUnrealizedNonMetaClass 使用名称查找。
         // 名称查找未找到的类必须在
         // 二级元->非元表。
        addNonMetaClass(cls);
    } else {
        NXMapInsert(gdb_objc_realized_classes, name, cls);
    }
    ASSERT(!(cls->data()->flags & RO_META));
}

addClassTableEntry(关键函数)

将类添加到表里面,如果addMeta为真,并且将当前类的元类也添加到所有的表中

/***********************************************************************
* addClassTableEntry
* 将一个类添加到所有类的表中。 如果 addMeta 为真,
* 也会自动添加类的元类。
* 锁定:runtimeLock 必须由调用者持有。
**********************************************************************/
static void
addClassTableEntry(Class cls, bool addMeta = true)
{
    runtimeLock.assertLocked();

    // 该类允许通过共享缓存或数据段成为已知类,但不允许已经在动态表中。
    auto &set = objc::allocatedClasses.get();

    ASSERT(set.find(cls) == set.end());

    if (!isKnownClass(cls))
        set.insert(cls);
    if (addMeta)
        //将元类添加到所有表中
        addClassTableEntry(cls->ISA(), false);
}

类的加载一阶段探索流程图:

类的加载流程图.png

调试readClass

阶段一:打印所有

readClass通过探索源码可以知道,这是个读取类的方法的函数,那么在此函数里面添加打印事件

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    const char *mangledName = cls->nonlazyMangledName();
    
    printf("%s - BBlv - %s\n",__func__, mangledName);
    ......

控制台输出部分结果

readClass - BBlv - __NSStackBlock__
readClass - BBlv - __NSMallocBlock__
readClass - BBlv - __NSAutoBlock__
readClass - BBlv - __NSFinalizingBlock__
readClass - BBlv - __NSGlobalBlock__
readClass - BBlv - __NSBlockVariable__
readClass - BBlv - OS_object
readClass - BBlv - OS_dispatch_object
readClass - BBlv - OS_dispatch_queue
readClass - BBlv - OS_dispatch_channel
readClass - BBlv - OS_dispatch_source
readClass - BBlv - OS_dispatch_mach
readClass - BBlv - OS_dispatch_queue_runloop
readClass - BBlv - OS_dispatch_semaphore
readClass - BBlv - OS_dispatch_group
readClass - BBlv - OS_dispatch_workloop
readClass - BBlv - OS_dispatch_queue_serial
readClass - BBlv - OS_dispatch_queue_concurrent
readClass - BBlv - OS_dispatch_queue_main
readClass - BBlv - OS_dispatch_queue_global
readClass - BBlv - OS_dispatch_queue_pthread_root
readClass - BBlv - OS_dispatch_queue_mgr
readClass - BBlv - OS_dispatch_queue_attr
readClass - BBlv - OS_dispatch_mach_msg
readClass - BBlv - OS_dispatch_io
readClass - BBlv - OS_dispatch_operation
readClass - BBlv - OS_dispatch_disk
省略了巨量的输出信息......
readClass - BBlv - LGTeacher
readClass - BBlv - LGPerson
readClass - BBlv - FFPerson

readClass打印.png

具体操作图解:

readClass打印01.png

阶段二:精确打印

为打印添加条件,我只关心我自己创建的类,不关心系统的类,代码修改如下:

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    const char *mangledName = cls->nonlazyMangledName();
    
    const char *ffPersonName = "FFPerson";
    
    if (strcmp(mangledName, ffPersonName) == 0) {
        printf("%s - BBlv - %s\n",__func__, mangledName);        
    }

打印结果:

readClass - BBlv - FFPerson

具体操作图解

readClass打印02.png