iOS14 类加载原理

1,109 阅读7分钟

objc_init

查看源码

EC8D7463-8218-4FD4-B684-07A4F69391EE.png

  • environ_init:读取影响运行时的环境变量,如果需要还可以打印环境变量帮助 export OBJC_HELP = 1

  • tls_init:关于线程key的绑定,比如每个线程数据的析构函数

  • static_init:运行C++静态构造函数。在dyld调用我们的静态构造函数之前,lib会调用_objc_init先调用自己的C++构造函数

  • runtime_initruntime运行时环境初始化,里面主要是unattachedCategoriesallocatedClasses两张表

  • exception_init:初始化libobjc库的异常处理系统

  • cache_t::init:缓存条件初始化

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

  • _dyld_objc_notify_register:注册map_imagesload_imagesunmap_image的回调

environ_init

 void  environ_init(void) 
{ 
     // Print OBJC_HELP and OBJC_PRINT_OPTIONS output.
    if(PrintHelp || PrintOptions) { 
         ...
    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);
        }
    }
}
  • 可以看到是否打印环境变量是通过PrintHelp || PrintOptions控制的。

有三种方式打印出当前的环境变量配置。

1, 修改代码输出日志信息
//在if(PrintHelp  ||  PrintOptions)前面加
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);
}

输出结果

AC7F5C36-D856-40F8-BD9C-376B9B085D4E.png

2, 终端输出

export OBJC_HELP=1

F4CE38EC-34A9-4D5D-8915-DBE6EB73B399.png

  • 先导出OBJC_HELP,然后执行一个终端命令就会打印出环境变量了。
3, 源文件查看
  • objc源码objc-env.h文件中可以查看

DBB94AD0-A682-420B-AA79-6C0E6AFFEFBB.png

验证环境变量

在环境变量中有两个配置:OBJC_DISABLE_NONPOINTER_ISAOBJC_PRINT_LOAD_METHODS。直接在scheme->Arguments->Environment Variables中配置。

OBJC_DISABLE_NONPOINTER_ISA:配置是否开启NONPOINTER指针。

(lldb) x/4gx person
0x100705e70: 0x011d800100008211 0x0000000000000000
0x100705e80: 0x0000000000000000 0xf04d6221bc37e2af
(lldb) p/t 0x011d800100008211
(long) $1 = 0b0000000100011101100000000000000100000000000000001000001000010001
  • 可以看到isa最后一位在开启前是1开启后变为了0。也就是是否是纯指针。 OBJC_PRINT_LOAD_METHODS:打印所有实现了+ load方法的类信息。

39317AB7-3D23-4AE4-B806-BE647AC962BA.png

所以直接配置就可以知道哪些类实现了+ load方法。可以监控所有的+load方法,从而处理启动优化。

tls_init

关于线程key的绑定,比如线程数据的析构函数。

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++静态构造函数。在dyld调用静态构造函数之前,libc 会调用 _objc_init()

static void static_init()
{

//这两个是根据SECTION_TYPE不同读取不同的section,
//当为S_MOD_INIT_FUNC_POINTERS时读取__mod_init_funcs,
//当为S_INIT_FUNC_OFFSETS(0x16)时读取__init_offsets
    size_t count;
    
    //查找__objc_init_func,对应macho文件中的 __DATA __mod_init_func。
    //调用c++构造函数,这里是通过下标平移获取
    auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
    for (size_t i = 0; i < count; i++) {
        inits[i]();
    }
    
    //查找__TEXT __objc_init_offs,
    //对应macho文件中的 __TEXT  __init_offsets
    //调用c++构造函数,通过偏移值获取
    auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count);
    for (size_t i = 0; i < count; i++) {
        UnsignedInitializer init(offsets[i]);
        init();
    }
}
GETSECT(getLibobjcInitializers,       UnsignedInitializer, "__objc_init_func");

uint32_t *getLibobjcInitializerOffsets(const headerType *mhdr, size_t *outCount) {
    unsigned long byteCount = 0;
    uint32_t *offsets = (uint32_t *)getsectiondata(mhdr, "__TEXT", "__objc_init_offs", &byteCount);
    if (outCount) *outCount = byteCount / sizeof(uint32_t);
    return offsets;
}
  • 全局静态c++函数调用,这里只是调用objc自己的。在dyld doModInitFunctions调用之前,相当于objcc++构造函数是自己调用的,不是dyld调用的。因为dyld调用的时机太晚了,objc对这个section进行了替换,所以后续dyld`不会调用到这块。

  • 内部有两个逻辑一个是通过__mod_init_funcs,一个是通过__init_offsetsmacho文件读取c++构造函数

84F59E47-9CBA-4893-92ED-50E07F0E2B4D.png

上面对macho文件的读取发现和macho文件是对不上的,在dosect (markgc.cpp)中发现了对macho文件对应的section数据进行的映射修改:

template <typename P>
void dosect(uint8_t *start, macho_section<P> *sect)
{
    if (debug) printf("section %.16s from segment %.16s\n",
                      sect->sectname(), sect->segname());

    // Strip S_MOD_INIT/TERM_FUNC_POINTERS. We don't want dyld to call 
    // our init funcs because it is too late, and we don't want anyone to 
    // call our term funcs ever.
    if (segnameStartsWith(sect->segname(), "__DATA")  &&  
        sectnameEquals(sect->sectname(), "__mod_init_func"))
    {
        // section type 0 is S_REGULAR
        sect->set_flags(sect->flags() & ~SECTION_TYPE);
        sect->set_sectname("__objc_init_func");
        if (debug) printf("disabled __mod_init_func section\n");
    }
    if (segnameStartsWith(sect->segname(), "__TEXT")  &&
        sectnameEquals(sect->sectname(), "__init_offsets"))
    {
        // section type 0 is S_REGULAR
        sect->set_flags(sect->flags() & ~SECTION_TYPE);
        sect->set_sectname("__objc_init_offs");
        if (debug) printf("disabled __mod_init_func section\n");
    }
    if (segnameStartsWith(sect->segname(), "__DATA")  &&  
        sectnameEquals(sect->sectname(), "__mod_term_func"))
    {
        // section type 0 is S_REGULAR
        sect->set_flags(sect->flags() & ~SECTION_TYPE);
        sect->set_sectname("__objc_term_func");
        if (debug) printf("disabled __mod_term_func section\n");
    }
}
  • __mod_init_func被修改称为__objc_init_funcSECTION_TYPES_MOD_INIT_FUNC_POINTERS(0x9)

  • __init_offsets被修改称为__objc_init_offsSECTION_TYPES_INIT_FUNC_OFFSETS(0x16)
    他们的区别是获取func的方式不同:

//S_MOD_INIT_FUNC_POINTERS
Initializer* inits = (Initializer*)(sect->addr + fSlide);
Initializer func = inits[j];
//S_INIT_FUNC_OFFSETS
Initializer func = (Initializer)((uint8_t*)this->machHeader() + funcOffset);
  • __mod_term_func被修改成为__objc_term_func。这个也就是TerminatorsSECTION_TYPES_MOD_TERM_FUNC_POINTERS(0xa)。这个实现了destructor函数就有了。

  • 这个函数最终是被自己的main所调用的。注释中已经说明不想被dyld调用,所以进行了strip,因为dyld调用的时机太晚了。

  • 通过查看macho文件,应该在编译期就替换了。

runtime_init

  • runtime运行时环境初始化,里面主要是unattachedCategoriesallocatedClasses两张表
void runtime_init(void)
{  
   objc::unattachedCategories.init(32);//分类表的初始化
   objc::allocatedClasses.init();//类表的初始化
}

exception_init

  • 初始化libobjc库的异常处理系统
void exception_init(void)
{
    old_terminate = std::set_terminate(&_objc_terminate);
}

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 exception回调
            (*uncaught_handler)((id)e);
            (*old_terminate)();
        } @catch (...) {
            // It's not an objc object. Continue to C++ terminate.
            (*old_terminate)();
        }
    }
}

  • exception_init初始化了_objc_terminate进行异常处理。
  • 底层在处理的时候发现了处理不了的exp,也就是异常会调用uncaught_handler回调。
objc_uncaught_exception_handler 
objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
{
    objc_uncaught_exception_handler result = uncaught_handler;
    uncaught_handler = fn;
    return result;
}
  • uncaught_handler = fn 告诉大家可以自己传一个函数的句柄,fn可以是自己定义的函数,然后回调时,可以自己处理异常的信息

cache_t::init

缓存条件初始化

void cache_t::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
    if (__progname &&
        (strcmp(__progname, "QtWebEngineProcess") == 0 ||
         strcmp(__progname, "Steam Helper") == 0)) {
        Trampolines.Initialize();
    }
#endif
}

_dyld_objc_notify_register

dyld的注册回调,_dyld_objc_notify_register仅供objc运行时调用且方法的实现在dyld源码中

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

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
	}

	// <rdar://problem/32209809> call 'init' function on all images already init'ed (below libSystem)
	for (std::vector<ImageLoader*>::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());
		}
	}
}

_dyld_objc_notify_register中有3个参数

  • &map_imagesdyldimage加载到内存中会调用该函数
  • load_imagesdyld初始化所有的image文件会调用
  • unmap_image:将image移除时会调用

dyldmap_imagesload_images的调用没有区别。但是在这里传递的参数map_images&操作。map_images是指针拷贝,load_images是值传递。map_images需要同步变化,否则有可能发生错乱。而load_images比较简单只是load的调用,不需要同步变化。

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只是对map_images_nolock的调用。

map_images_nolock

map_images_nolock的核心逻辑是要找镜像文件是怎么被加载的,也就是对class、 Protocol、 selector、 category等相关的操作。在map_images_nolock中最终发现了_read_images的调用:

void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    ……

    if (hCount > 0) {
        //类的加载映射
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }
    ……
}
  • 核心逻辑肯定在_read_images中。

  • 点击进入_read_images方法,由于方法内代码很多有点无从下手要么一点点读,发现苹果的开发者提供了log日志

void _read_images(header_info hList, uint32_t hCount, int 
totalClasses, int 
unoptimizedTotalClasses)
{
   ... //表示省略部分代码
#define EACH_HEADER \
    hIndex = 0;         \
    hIndex < hCount && (hi = hList[hIndex]); \
    hIndex++
    // 条件控制进行一次的加载
    if (!doneOnce) { ... }
    // 修复预编译阶段的`@selector`的混乱的问题
    // 就是不同类中有相同的方法 但是相同的方法地址是不一样的
    // Fix up @selector references
    static size_t UnfixedSelectors;
    { ... }
    ts.log("IMAGE TIMES: fix up selector references");
    
    // 错误混乱的类处理
    // Discover classes. Fix up unresolved future classes. Mark bundle classes.
    bool hasDyldRoots = dyld_shared_cache_some_image_overridden();
    for (EACH_HEADER) { ... }
    ts.log("IMAGE TIMES: discover classes");
    
    // 修复重映射一些没有被镜像文件加载进来的类
    // Fix up remapped classes
    // Class list and nonlazy class list remain unremapped.
    // Class refs and super refs are remapped for message dispatching.
    if (!noClassesRemapped()) { ... }
    ts.log("IMAGE TIMES: remap classes");

#if SUPPORT_FIXUP
    // 修复一些消息
    // Fix up old objc_msgSend_fixup call sites
    for (EACH_HEADER) { ... }
    ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");

#endif
    // 当类中有协议时:`readProtocol`
    // Discover protocols. Fix up protocol refs.
    for (EACH_HEADER) { ... }
    ts.log("IMAGE TIMES: discover protocols");
    
    // 修复没有被加载的协议
    // Fix up @protocol references
    // Preoptimized images may have the right 
    // answer already but we don't know for sure.
    for (EACH_HEADER) { ... }
    ts.log("IMAGE TIMES: fix up @protocol references");
    
    // 分类的处理
    // 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.  
    if (didInitialAttachCategories) { ... }
    ts.log("IMAGE TIMES: discover categories");
    
    // 类的加载处理
    // Category discovery MUST BE Late to avoid potential races
    // when other threads call the new category code befor
    // this thread finishes its fixups.
    // +load handled by prepare_load_methods()
    // Realize non-lazy classes (for +load methods and static instances)
    for (EACH_HEADER) { ... }
    ts.log("IMAGE TIMES: realize non-lazy classes");
    
    // 没有被处理的类,优化那些被侵犯的类
    // Realize newly-resolved future classes, in case CF manipulates them
    if (resolvedFutureClasses) { ... }
    ts.log("IMAGE TIMES: realize future classes");
   ...
#undef EACH_HEADER

}

根据log提示整理出主要流程模块

  • 条件控制进行一次加载
  • 修复预编译阶段的@selector的混乱的问题
  • 错误混乱的类处理
  • 修复重映射一些没有被镜像文件加载进来的类
  • 修复一些消息
  • 当类中有协议时:readProtocol
  • 修复没有被加载的协议
  • 分类的处理
  • 类的加载处理
  • 没有被处理的类,优化那些被侵犯的类

下面就根据日志的提示重点的单独分析

  • 只加载一次

 if(!doneOnce) {
        doneOnce = YES; // 加载一次后,就不会在进判断 doneOnce = YES
        launchTime = YES;
         ...
        // 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");
  }

加载一次后doneOnce=YES,下次就不会在进入判断。第一次进来主要创建表gdb_objc_realized_classes,表里存放所有的类不管是实现的还是没有实现

修复@selector的混乱

static size_t UnfixedSelectors;
{
    mutex_locker_t lock(selLock);
    for (EACH_HEADER) {
        if (hi->hasPreoptimizedSelectors()) continue;
        bool isBundle = hi->isBundle();
        // 从macho文件中获取方法名列表
        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;
            }
        }
    }
}
  • 因为不同类中可能相同的方法,但是虽然是相同的方法但是地址不同,对那些混乱的方法进行修复。因为方法是存放在类中,每个类中的位置是不一样的,所以方法的地址也就不一样

错误混乱的类处理

for (EACH_HEADER) {
    if (! mustReadClasses(hi, hasDyldRoots)) {
        // Image is sufficiently optimized that we need not call readClass()
        continue;
    }
    //从macho中读取类列表信息
    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;
        }
    }
}

  • cls指向的是一块地址,而newCls此时还没有赋值,系统随机给我分配一块脏地址
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    // 获取类名
    const char *mangledName = cls->nonlazyMangledName();
    if (missingWeakSuperclass(cls)) { ... }
    cls->fixupBackwardDeployingStableSwift();
    Class replacing = nil;

    if (mangledName != nullptr) { ... }

    if (headerIsPreoptimized  &&  !replacing) {...
    } else {
        if (mangledName) { 
        //some Swift generic classes can lazily generate their names
            //将类名和地址关联起来
            addNamedClass(cls, mangledName, replacing);
        } else { ...}
        //将关联的类插入到另一张哈希表中
        addClassTableEntry(cls);
    }
    // for future reference: shared cache never contains MH_BUNDLEs
    if (headerIsBundle) { ... }
    return cls;

}

  • nonlazyMangledName获取类名
  • rw的赋值和ro的获取并不在readClass里面,等下运行源码探究下
  • addNamedClass将类名和地址关联绑定起来
  • addClassTableEntry将关联的类插入到哈希表中,这张表中都是初始化过的类

探究下nonlazyMangledName怎么获取类名的

const char *nonlazyMangledName() const {
    return bits.safe_ro()->getName();
}

  • 进入safe_ro,源码如下
const class_ro_t *safe_ro() const {
    class_rw_t *maybe_rw = data();
    if (maybe_rw->flags & RW_REALIZED) {
        // maybe_rw is rw
        // rw有值 直接从rw中的ro获取
        return maybe_rw->ro();
    } else 
        // maybe_rw is actually ro
        // 直接从ro中获取,ro是macho中的数据
        return (class_ro_t *)maybe_rw;、
    }
}

探究下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 {
        //更新gdb_objc_realized_classes表,将key设置为 name value 设置为cls
        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());
}

更新gdb_objc_realized_classes哈希表,keynamevaluecls

探究下addClassTableEntry插入另一张表

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.
    // allocatedClasses
    auto &set = objc::allocatedClasses.get();
    ASSERT(set.find(cls) == set.end());
    if (!isKnownClass(cls))
        set.insert(cls);
    if (addMeta)
        //将元类插入哈希表中
        addClassTableEntry(cls->ISA(), false);
}
  • allocatedClasses_objc_initruntime_init运行时环境初始化,里面主要是unattachedCategoriesallocatedClasses两张表,此时插入allocatedClasses表中
  • addMeta = true 将元类添加allocatedClasses表中

rw的赋值和ro的获取并不在readClass里面