类加载原理(上)

1.jpeg

前言

前面的应用程序加载我们已经探究了从dyld->libSystem->libDispatch->Objc_init的整体流程。从上面的流程我们知道dyld主要是链接了程序需要的镜像文件images(macho格式的文件),并将其映射到了程序里面,到这一步这些镜像images还没有加载到内存里面,接下来我们就来探究这样一个images->内存的过程。接着上一篇文章也是回归我们熟悉的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();
#if __OBJC2__
    cache_t::init();
#endif
    _imp_implementationWithBlock_init();

    /* ******* 这个通知非常重要 这里的几个参数 非常重要  ******/
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

Objc_init()方法里一共调用了8个方法。我们来捋一遍:

environ_init()

这个方法主要是获取运行过程中的一些环境变量,我们可以利用这些环境变量来进行调试和使用。具体的操作请看文章结尾处的 补充内容。

environ_init() :读取影响运⾏时的环境变量。如果需要,还可以打印环境变量帮助。

tls_init()

tls_init() : 关于线程key的绑定 - ⽐如每线程数据的析构函数 。

static_init()

static_init() :运⾏C++静态构造函数。在dyld调⽤我们的静态构造函数之前,libc 会调⽤_objc_init(),因此我们必须⾃⼰做。

runtime_init()

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

点击unattachedCategories再进去看看:

static UnattachedCategories unattachedCategories;

} // namespace objc

点击allocatedClasses再进去看看:

namespace objc {
static ExplicitInitDenseSet allocatedClasses;
}

再分别点击这两个方法的init:

template 
class ExplicitInit {
    alignas(Type) uint8_t _storage[sizeof(Type)];

public:
    template 
    void init(Ts &&... Args) {
        new (_storage) Type(std::forward(Args)...);
    }

    Type &get() {
        return *reinterpret_cast(_storage);
    }
};

发现最后都是一张表在初始化,只是暂时不知道储存什么。

runtime_init() : runtime运⾏时环境初始化,⾥⾯主要是unattachedCategoriesallocatedClasses

lock_init()

lock_init() :没有重写,采⽤C++的特性。

exception_init()

我们点击进入看看实现:

void exception_init(void)
{
    old_terminate = std::set_terminate(&_objc_terminate);
}

点击_objc_terminate

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

从上面代码我们看到当正常情况只会走__cxa_rethrow();当出现异常的时候就走下面的(*uncaught_handler)((id)e);(*old_terminate)(); 。这里就是一个回调。这个回调就是底层程序在运行的过程中出现了不符合底层运行规矩的情况,这时候就会利用系统提前在底层下的句柄回调到上层,抛出异常的信号。这也就是我们常说的抛出异常。既然如此我们就可以考虑利用这个地方的这个回调做一些异常拦截的事情,这个我们放在最后的补充部分。

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

cache_t::init()

cache_t::init() : 缓存条件初始化。

_imp_implementationWithBlock_init()

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

_dyld_objc_notify_register(&map_images, load_images, unmap_image)

_dyld_objc_notify_register(&map_images, load_images, unmap_image) :消息注册为dyld里的回调准备map_imagesload_imagesunmap_image等信息。

ps:在上面的通知注册里携带的&map_images&符号的作用是这个map_images为指针传递,为了实现map_images变化能够达到同步。因为这个map_images太过重要必须保持实时更新。所以采取这种方法。

上一篇文章我们知道objc_init()方法里进行了dyld回调通知的注册,并且我们发现它带回了一些东西,那么我们就接着这一步先去看看_dyld_objc_notify_register(&map_images, load_images, unmap_image) 看看他到底带回了些什么,这些东西又是怎么出现的。我们直接查看这个方法里的第一个参数map_images

map_images探索

点击进入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;

    /*
     *  省略前面的初始化 以及一些细节处理的代码
     */

    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

    firstTime = NO;
    
    // Call image load funcs after everything is set up.
    for (auto func : loadImageFuncs) {
        for (uint32_t i = 0; i < mhCount; i++) {
            func(mhdrs[I]);
        }
    }
}

从上面方法主要在于_read_images()这个方法的调用去读取images

_read_images方法分析:

这个方法我们可以分为10个部分去分析。根据内容和注释我们可以根据每个ts.log来划分。同时我们的目标是要寻找关于类的加载操作,所以不是对类进行操作的步骤我们暂时忽略

• 1: 条件控制进⾏⼀次的加载
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
   //省略部分代码
    /* **************条件控制进行一次的加载*****************/
    
    if (!doneOnce) {
        doneOnce = YES;
        launchTime = YES;

#if SUPPORT_NONPOINTER_ISA
        // Disable non-pointer isa under some conditions.

# if SUPPORT_INDEXED_ISA
        // Disable nonpointer isa if any image contains old Swift code
        for (EACH_HEADER) {
            if (hi->info()->containsSwift()  &&
                hi->info()->swiftUnstableVersion() < objc_image_info::SwiftVersion3)
            {
                DisableNonpointerIsa = true;
                if (PrintRawIsa) {
                    _objc_inform("RAW ISA: disabling non-pointer isa because "
                                 "the app or a framework contains Swift code "
                                 "older than Swift 3.0");
                }
                break;
            }
        }
# endif

# if TARGET_OS_OSX
        // Disable non-pointer isa if the app is too old
        // (linked before OS X 10.11)
//        if (!dyld_program_sdk_at_least(dyld_platform_version_macOS_10_11)) {
//            DisableNonpointerIsa = true;
//            if (PrintRawIsa) {
//                _objc_inform("RAW ISA: disabling non-pointer isa because "
//                             "the app is too old.");
//            }
//        }

        // Disable non-pointer isa if the app has a __DATA,__objc_rawisa section
        // New apps that load old extensions may need this.
        for (EACH_HEADER) {
            if (hi->mhdr()->filetype != MH_EXECUTE) continue;
            unsigned long size;
            if (getsectiondata(hi->mhdr(), "__DATA", "__objc_rawisa", &size)) {
                DisableNonpointerIsa = true;
                if (PrintRawIsa) {
                    _objc_inform("RAW ISA: disabling non-pointer isa because "
                                 "the app has a __DATA,__objc_rawisa section");
                }
            }
            break;  // assume only one MH_EXECUTE image
        }
# endif

#endif

        if (DisableTaggedPointers) {
            disableTaggedPointers();
        }
        
        initializeTaggedPointerObfuscator();

        if (PrintConnecting) {
            _objc_inform("CLASS: found %d classes during launch", totalClasses);
        }

        // namedClasses
        // Preoptimized classes don't go in this table.
        // 4/3 is NXMapTable's load factor
        int namedClassesSize = 
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

        ts.log("IMAGE TIMES: first time tasks");
    }
}

这个方法前面都是一些_objc_inform和环境变量控制。下面这个地方我们需要留意下:initializeTaggedPointerObfuscator();这个小对象类型的处理和混淆。NXCreateMapTable是创建了一个表,这张表是一张总表,(可以gdb_objc_realized_classes跳转去查看下注释):

NXMapTable *NXCreateMapTable(NXMapTablePrototype prototype, unsigned capacity) {
    return NXCreateMapTableFromZone(prototype, capacity, malloc_default_zone());
}
NXMapTable *NXCreateMapTableFromZone(NXMapTablePrototype prototype, unsigned capacity, void *z) {
    NXMapTable          *table = (NXMapTable *)malloc_zone_malloc((malloc_zone_t *)z, sizeof(NXMapTable));
    NXMapTablePrototype     *proto;
    if (! prototypes) prototypes = NXCreateHashTable(protoPrototype, 0, NULL);
    if (! prototype.hash || ! prototype.isEqual || ! prototype.free || prototype.style) {
    _objc_inform("*** NXCreateMapTable: invalid creation parameters\n");
    return NULL;
    }
    proto = (NXMapTablePrototype *)NXHashGet(prototypes, &prototype); 
    if (! proto) {
    proto = (NXMapTablePrototype *)malloc(sizeof(NXMapTablePrototype));
    *proto = prototype;
        (void)NXHashInsert(prototypes, proto);
    }
    table->prototype = proto; table->count = 0;
    table->nbBucketsMinusOne = exp2u(log2u(capacity)+1) - 1;
    table->buckets = allocBuckets(z, table->nbBucketsMinusOne + 1);
    return table;
}

这个表的扩容点也是3/4。利用一个数学算法来计算当前添加的值x是否超过了总容量。来判断是否扩容。

• 2: 修复预编译阶段的 @selector 的混乱问题
    // Fix up @selector references
    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");

这个方法主要是修复一些在预编译之后@selector的混乱问题,因为可能很多方法名字相同,比如retain名字一样但是地址不同。所以这个就需要处理,因为对应每个镜像文件位置不同。(获取两个方法的位置不一样一个是registerNameNoLockdyld链接后的)获取的,一个是从_getObjc2SelectorRefs表里获取的,可以利用跑objc源码在这个sels[i] = sel;`地方断点然后利用打印的方式来查看)

• 3: 错误混乱的类处理
// Discover classes. Fix up unresolved future classes. Mark bundle classes.
    bool hasDyldRoots = dyld_shared_cache_some_image_overridden();

    for (EACH_HEADER) {
        if (! mustReadClasses(hi, hasDyldRoots)) {
            // Image is sufficiently optimized that we need not call readClass()
            continue;
        }

        classref_t const *classlist = _getObjc2ClassList(hi, &count);

        bool headerIsBundle = hi->isBundle();
        bool headerIsPreoptimized = hi->hasPreoptimizedClasses();

        for (i = 0; i < count; i++) {
            Class cls = (Class)classlist[I];
            Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);

            if (newCls != cls  &&  newCls) {
                // Class was moved but not deleted. Currently this occurs 
                // only when the new class resolved a future class.
                // Non-lazily realize the class below.
                resolvedFutureClasses = (Class *)
                    realloc(resolvedFutureClasses, 
                            (resolvedFutureClassCount+1) * sizeof(Class));
                resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
            }
        }
    }

    ts.log("IMAGE TIMES: discover classes");

这个方法主要是处理一些被处理的类比如被删除的类(因为类的位置发生了变化所以系统会把原来地址的类删除),但是系统没有清理完全,导致了野指针所以需要处理。也叫未来类。我们来看看这个地方是否是我们要找的关于类加载到内存rorw的过程。

3.1:readClass

在这个循环比较过程中有一步readClass

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    const char *mangledName = cls->nonlazyMangledName();
    
    if (missingWeakSuperclass(cls)) {
        // No superclass (probably weak-linked). 
        // Disavow any knowledge of this subclass.
        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) {
        if (Class newCls = popFutureNamedClass(mangledName)) {
            // This name was previously allocated as a future class.
            // Copy objc_class to future class's struct.
            // Preserve future's rw data block.

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

这个方法我们看到有关于rwro的一些处理。那我们我们来探索下是否真的是在这里执行了rwro的处理呢?首先我们直接在mangledName后面打印读取的方法和mangledName如图2:

2.png

既然我们可以全部打印那我们是否可以控制打印呢?我们只打印我们自己的类。利用判断来实现。然后跟踪我们自己的类 看他是如何加载进入内存的。如图3:

3.png

然后我们在if (mangledName != nullptr) {...}里添加我们的打印,查看我们自己的类是否进入了下面的if (mangledName != nullptr) {...}并且进行rorw的读取。并且在下方的addNamedClass(cls, mangledName, replacing)addClassTableEntry(cls)两个方法加上断点看看是否会进入这两个方法。如图4、5、6、7:

4.png
5.png

从上面的打印截图我们可以看到并没有进入到我们期待的if判断语句也没有去进行rwro的处理。

3.2:addNamedClass():
6.png

有进入addNamedClass这个方法,不过这个方法只是把我们的类加到表的一个操作所以可以忽略。

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 uses name lookups.
        // Classes not found by name lookup must be in the
        // secondary meta->nonmeta table.
        addNonMetaClass(cls);
    } else {
        NXMapInsert(gdb_objc_realized_classes, name, cls);
    }
    ASSERT(!(cls->data()->flags & RO_META));

    // wrong: constructed classes are already realized when they get here
    // ASSERT(!cls->isRealized());
}
3.3addClassTableEntry():
7.png

进入了addClassTableEntry(cls)方法

static void
addClassTableEntry(Class cls, bool addMeta = true)
{
    runtimeLock.assertLocked();

    // This class is allowed to be a known class via the shared cache or via
    // data segments, but it is not allowed to be in the dynamic table already.
    auto &set = objc::allocatedClasses.get();

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

    if (!isKnownClass(cls))
        set.insert(cls);
    if (addMeta)
        addClassTableEntry(cls->ISA(), false);
}

在这个方法我们看到和其元类加载到一个,但是到此我们还没看到对这个类有加载到rorw的操作。

从上面的分析我们可得知类加载到rorw的操作并不在readClass方法里。这个方法只是把类加载到表的操作

• 4:修复重映射⼀些没有被镜像⽂件加载进来的 类
// Fix up remapped classes
    // Class list and nonlazy class list remain unremapped.
    // Class refs and super refs are remapped for message dispatching.
    
    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");
• 5: 修复⼀些消息!
// Fix up old objc_msgSend_fixup call sites
    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");
• 6: 当我们类⾥⾯有协议的时候 : readProtocol
// Discover protocols. Fix up protocol refs.
    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();

        // Skip reading protocols if this is an image from the shared cache
        // and we support roots
        // Note, after launch we do need to walk the protocol as the protocol
        // in the shared cache is marked with isCanonical() and that may not
        // be true if some non-shared cache binary was chosen as the canonical
        // definition
        if (launchTime && isPreoptimized) {
            if (PrintProtocols) {
                _objc_inform("PROTOCOLS: Skipping reading protocols in image: %s",
                             hi->fname());
            }
            continue;
        }

        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");
• 7: 修复没有被加载的协议
// Fix up @protocol references
    // Preoptimized images may have the right 
    // answer already but we don't know for sure.
    for (EACH_HEADER) {
        // At launch time, we know preoptimized image refs are pointing at the
        // shared cache definition of a protocol.  We can skip the check on
        // launch, but have to visit @protocol refs for shared cache images
        // loaded later.
        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");
• 8: 分类处理
// Discover categories. Only do this after the initial category
    // attachment has been done. For categories present at startup,
    // discovery is deferred until the first load_images call after
    // the call to _dyld_objc_notify_register completes. rdar://problem/53119145
    if (didInitialAttachCategories) {
        for (EACH_HEADER) {
            load_categories_nolock(hi);
        }
    }

    ts.log("IMAGE TIMES: discover categories");
• 9: 类的加载处理
// Category discovery MUST BE Late to avoid potential races
    // when other threads call the new category code before
    // this thread finishes its fixups.

    // +load handled by prepare_load_methods()

    // Realize non-lazy classes (for +load methods and static instances)
    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());
                }
                // fixme also disallow relocatable classes
                // We can't disallow all Swift classes because of
                // classes like Swift.__EmptyArrayStorage
            }
            realizeClassWithoutSwift(cls, nil);
        }
    }

    ts.log("IMAGE TIMES: realize non-lazy classes");

这部分代码我们可以重点分析下,因为刚好是针对类的处理,看是否真的是我们找的对类的加载处理以及rorw处理。
我们看到这段代码主要是做一个遍历然后调用两个方法addClassTableEntry(cls);realizeClassWithoutSwift(cls, nil);我们前面看过addClassTableEntry(cls);只是把类添加到表,这里就不看了我们直接看后面这个方法。(考虑到文章的内容过长问题我们利用下一篇文章来分析这里的内容)

• 10 : 没有被处理的类 优化那些被污染的类
// Realize newly-resolved future classes, in case CF manipulates them
    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");

_read_images方法总结:
• 1: 条件控制进⾏⼀次的加载
• 2: 修复预编译阶段的 @selector 的混乱问题
• 3: 错误混乱的类处理
• 4:修复重映射⼀些没有被镜像⽂件加载进来的 类
• 5: 修复⼀些消息!
• 6: 当我们类⾥⾯有协议的时候 : readProtocol
• 7: 修复没有被加载的协议
• 8: 分类处理
• 9: 类的加载处理
• 10 : 没有被处理的类 优化那些被污染的类

补充

1:环境变量

void environ_init(void) 
{
    if (issetugid()) {
        // All environment variables are silently ignored when setuid or setgid
        // This includes OBJC_HELP and OBJC_PRINT_OPTIONS themselves.
        return;
    } 

    // Turn off autorelease LRU coalescing by default for apps linked against
    // older SDKs. LRU coalescing can reorder releases and certain older apps
    // are accidentally relying on the ordering.
    // rdar://problem/63886091
//    if (!dyld_program_sdk_at_least(dyld_fall_2020_os_versions))
//        DisableAutoreleaseCoalescingLRU = true;

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

    // Scan environ[] directly instead of calling getenv() a lot.
    // This optimizes the case where none are set.
    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;
            }
        }
    }

    // Special case: enable some autorelease pool debugging
    // when some malloc debugging is enabled 
    // and OBJC_DEBUG_POOL_ALLOCATION is not set to something other than 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;
        }
    }

//    if (!os_feature_enabled_simple(objc4, preoptimizedCaches, true)) {
//        DisablePreoptCaches = true;
//    }

    // Print OBJC_HELP and OBJC_PRINT_OPTIONS output.
    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);
        }
    }
}

在上面的方法我们看到最后打印这些环境变量是在PrintHelpPrintOptions条件满足下才能打印那我们直接把打印代码搬出去避免这两个条件的限制然后运行打印看看。(这里是一种方法还有一种打印环境变量的方法就是直接去终端输入命令export OBJC_HELP=1就能看到下图调试栏同样的信息了)

补充1.png

发现确实打印出了很多环境变量里面就有我们比较熟悉的OBJC_DISABLE_NONPOINTER_ISA,是否禁止NONPOINTER_ISA。下面我们来试验一下,把这个环境变量添加到我们的项目里看下打开和关闭的效果:

先设置环境变量OBJC_DISABLE_NONPOINTER_ISA但是不打开

补充2.png
补充3.png

我们发现打印的isa确实不是纯的isa

下面我们把环境变量OBJC_DISABLE_NONPOINTER_ISA打开

补充4.png
补充5.png

打开OBJC_DISABLE_NONPOINTER_ISA后我们发现isa发生了变化,变成了纯isa了只剩下class部分的信息了。这就是环境变量的作用。

2:异常拦截

我们看这样一段代码,点击按钮我们看到会崩溃:

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic, copy) NSArray *dataArray;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.dataArray = @[@"zy1",@"zy2",@"zy3",@"zy4",@"zy5"];
}

- (IBAction)exceptionAction:(UIButton *)sender {
    
    NSLog(@"%@",self.dataArray[5]);
}
@end
2021-07-29 15:32:39.500204+0800 ZYProjectFourteenth000[60961:3517949] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndexedSubscript:]: index 5 beyond bounds [0 .. 4]'
*** First throw call stack:
(
    0   CoreFoundation                      0x00007fff20422fba __exceptionPreprocess + 242
    1   libobjc.A.dylib                     0x00007fff20193ff5 objc_exception_throw + 48
    2   CoreFoundation                      0x00007fff204a1523 _CFThrowFormattedException + 194
    3   CoreFoundation                      0x00007fff204449db +[__NSArrayI allocWithZone:] + 0
    4   ZYProjectFourteenth000              0x00000001091cd53f -[ViewController exceptionAction:] + 95
    5   UIKitCore                           0x00007fff246c7937 -[UIApplication sendAction:to:from:forEvent:] + 83
    6   UIKitCore                           0x00007fff23fe845d -[UIControl sendAction:to:forEvent:] + 223
    7   UIKitCore                           0x00007fff23fe8780 -[UIControl _sendActionsForEvents:withEvent:] + 332
    8   UIKitCore                           0x00007fff23fe707f -[UIControl touchesEnded:withEvent:] + 500
    9   UIKitCore                           0x00007fff24703d01 -[UIWindow _sendTouchesForEvent:] + 1287
    10  UIKitCore                           0x00007fff24705b8c -[UIWindow sendEvent:] + 4792
    11  UIKitCore                           0x00007fff246dfc89 -[UIApplication sendEvent:] + 596
    12  UIKitCore                           0x00007fff247727ba __processEventQueue + 17124
    13  UIKitCore                           0x00007fff24768560 __eventFetcherSourceCallback + 104
    14  CoreFoundation                      0x00007fff20390ede __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    15  CoreFoundation                      0x00007fff20390dd6 __CFRunLoopDoSource0 + 180
    16  CoreFoundation                      0x00007fff2039029e __CFRunLoopDoSources0 + 242
    17  CoreFoundation                      0x00007fff2038a9f7 __CFRunLoopRun + 875
    18  CoreFoundation                      0x00007fff2038a1a7 CFRunLoopRunSpecific + 567
    19  GraphicsServices                    0x00007fff2b874d85 GSEventRunModal + 139
    20  UIKitCore                           0x00007fff246c14df -[UIApplication _run] + 912
    21  UIKitCore                           0x00007fff246c639c UIApplicationMain + 101
    22  ZYProjectFourteenth000              0x00000001091cd7d2 main + 114
    23  libdyld.dylib                       0x00007fff2025abbd start + 1
)
libc++abi: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndexedSubscript:]: index 5 beyond bounds [0 .. 4]'
terminating with uncaught exception of type NSException
CoreSimulator 757.5 - Device: iPhone 12 (3F4D0D71-98AE-48A7-96B5-720E1FD726F0) - Runtime: iOS 14.5 (18E182) - DeviceType: iPhone 12
(lldb) 

我们bt一下看下堆栈:

补充1.png

从上面看到当异常出现的时候就会objc_exception_rethrow最后进入到objc_objc_terminate()。这里再次验证了上面我们对exception_init()的分析。

下面我们来拦截下这个异常因为在上面我们分析_objc_terminate()的时候就知道了他的回调函数是uncaught_handler。所以我们利用这个函数就能捕获到异常。如下

@interface LGUncaughtExceptionHandle : NSObject

@property (nonatomic) BOOL dismissed;

+ (void)installUncaughtSignalExceptionHandler;

@end
@implementation LGUncaughtExceptionHandle

/// Exception
void LGExceptionHandlers(NSException *exception) {
    NSLog(@"%s",__func__);
    
    int32_t exceptionCount = atomic_fetch_add_explicit(&LGUncaughtExceptionCount,1,memory_order_relaxed);
    if (exceptionCount > LGUncaughtExceptionMaximum) {
        return;
    }
    // 获取堆栈信息 - model 编程思想
    NSArray *callStack = [LGUncaughtExceptionHandle lg_backtrace];
    NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
    [userInfo setObject:exception.name forKey:LGUncaughtExceptionHandlerSignalExceptionName];
    [userInfo setObject:exception.reason forKey:LGUncaughtExceptionHandlerSignalExceptionReason];
    [userInfo setObject:callStack forKey:LGUncaughtExceptionHandlerAddressesKey];
    [userInfo setObject:exception.callStackSymbols forKey:LGUncaughtExceptionHandlerCallStackSymbolsKey];
    [userInfo setObject:@"LGException" forKey:LGUncaughtExceptionHandlerFileKey];
    
    [[[LGUncaughtExceptionHandle alloc] init]
     performSelectorOnMainThread:@selector(lg_handleException:)
     withObject:
     [NSException
      exceptionWithName:[exception name]
      reason:[exception reason]
      userInfo:userInfo]
     waitUntilDone:YES];
    
}

+ (void)installUncaughtSignalExceptionHandler{
    // uncaught_handler() = fn = LGExceptionHandlers
//  objc_setUncaughtExceptionHandler()
    NSSetUncaughtExceptionHandler(&LGExceptionHandlers);
}

#import "AppDelegate.h"
#import "LGUncaughtExceptionHandle.h"

@interface AppDelegate ()

@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [LGUncaughtExceptionHandle installUncaughtSignalExceptionHandler];
    return YES;
}

简单的这样处理下 然后在AppDelegate里调用下。
效果如下:

补充6.png

完整的demo我之后再补上来。

这样我们就能捕获到系统抛出的异常然后在我们自己的方法里做一些记录或者上报处理

文章到此结束。下一篇我们继续上面没有分析完的类的加载处理代码段的realizeClassWithoutSwift(cls, nil);方法进行分析。

遇事不决,可问春风。站在巨人的肩膀上学习,如有疏忽或者错误的地方还请多多指教。谢谢!

你可能感兴趣的:(类加载原理(上))