OC底层原理(十二):应用程序的dyld4流程下

7,548 阅读44分钟

本篇文章将探究上一篇应用程序的dyld4流程遗留的三个点:在objc4源码里的_objc_initmap_images的流程与具体分析、load_image的具体分析

一、_objc_init解析

dyldmain函数之前(pre-main)会间接调用到objc4-886.9_objc_init,在 objc/Source/objc-os.mm中可找到 void _objc_init(void) 的定义其中使用_dyld_objc_register_callbacks注册了3个方法,但在这之前还做了一些初始化的操作。

/***********************************************************************
* _objc_init
* Bootstrap initialization.Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/
void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    // 1.环境变量
    environ_init();
    // 2.静态构造函数
    static_init();
    // 3.runtime准备,创建2张表
    runtime_init();
    // 4.异常初始化
    exception_init();
    // 5.缓存
    cache_t::init();

    // 6.对 imp 的 Block 标记进行初始化
    _imp_implementationWithBlock_init();

    _dyld_objc_callbacks_v1 callbacks = {
        1, // version
        &map_images,
        load_images,
        unmap_image,
        _objc_patch_root_of_class
    };
    // 7.注册回调通知,& 是引用类型的参数
    _dyld_objc_register_callbacks((_dyld_objc_callbacks*)&callbacks);

    // 8.dyld 通知注册标记
    didCallDyldNotifyRegister = true;
}

environ_init

environ_init是读取environment variables的一些配置信息,environment variablesEdit Scheme -> Run -> Argments -> Environment Variables 中配置。

  • 图:

截屏2023-02-10 下午5.47.55.png

  1. 设置相关信息可以打印一些信息。可以设置OBJC_HELP,启动程序后会打印所有可以设置的信息以及含义解释。
objc[72560]: Objective-C runtime debugging. Set variable=YES to enable.
objc[72560]: OBJC_HELP: describe available environment variables
...
...
objc[72560]: OBJC_DEBUG_CLASS_RX_SIGNING: warn about class_rx_t pointer signing mismatches
objc[72560]: OBJC_DISABLE_CLASS_RO_FAULTS: disable os faults for class_ro_t pointer signing mismatches
  • 注解说明如下:
变量名介绍备注
OBJC_PRINT_OPTIONSlist whitch options are set输出OBJC已设置的选项
OBJC_PRINT_IMAGESlog image and library names as they are loaded打印已经加载loaded的镜像imag信息
OBJC_PRINT_LOAD_METHODSlog calls to class and category +load methods打印Class和Category的 +load 方法的调用信息
OBJC_PRINT_INITIALIZE_METHODSlog calls to class +initialize methods打印Class的+initialize方法的调用信息
OBJC_PRINT_RESOLVED_METHODSlog methods created by +resolveClassMethod: and +resolveInstanceMethod:打印通过消息慢速查找后的动态解析复写的+resolveClassMethod: 和 +resolveInstanceMethod:
OBJC_PRINT_CLASS_SETUPlog progress of class and category setup打印Class和Category的设置过程
OBJC_PRINT_PROTOCOL_SETUPlog progress of protocol setup打印Protocol的设置过程
OBJC_PRINT_IVAR_SETUPlog progress of non-fragile ivars打印非易脆的Ivar的设置过程
OBJC_PRINT_VTABLE_SETUPlog processing of class vtables打印类vtable的设置过程
OBJC_PRINT_VTABLE_IMAGESprint vatble images showing overridden methods打印vtable被覆盖的方法
OBJC_PRINT_CACHE_SETUPlog processing of methods caches打印方法缓存的设置过程
OBJC_PRINT_FUTURE_CLASSESlog use of future classes for toll-free bridging打印从CFType无缝转换到NSObject将要使用的类(如CFArrayRef到NSArray *)
OBJC_PRINT_PREOPTIMIZATIONlog preoptimization courtesy of dyld shared cache打印dyld共享缓存优化前的问候语
OBJC_PRINT_CXX_CTORSlog calls to C++ ctors and dtors for instance variables打印类实例中的C++对象的构造与析构调用
OBJC_PRINT_EXCEPTIONSlog exception handing打印异常处理
OBJC_PRINT_EXCEPTION_THROWlog backtrace of every objc_exception_thorow()打印所有异常抛出时的回溯objc_exception_thorow()
OBJC_PRINT_ALT_HANDLERSlog processing of exception alt handlers打印alt操作异常处理
OBJC_PRINT_REPLACED_METHODSlog methods replace by category implementations打印被Category替换的方法
OBJC_PRINT_DEPRECATION_WARNINGSwarn about calls to deprecated runtime functions打印警告所有过时的方法调用
OBJC_PRINT_POOL_HIGHWATERlog hight-water marks for autorelease pools打印autoreleasepool高水位警告
OBJC_PRINT_CUSTOM_CORElog classes with custom core methods打印含有自定义core内核方法的类
OBJC_PRINT_CUSTOM_RRlog classes with custom retain/release methods打印含有未优化的自定义retain/release方法的类
OBJC_PRINT_CUSTOM_AWZlog classes with custom allocWithZone methods打印含有未优化的自定义allocWithZone方法的类
OBJC_PRINT_RAW_ISAlog classes that require raw pointer isa fields打印需要访问原始isa指针的类
OBJC_DEBUG_UNLOADwarn about poorly-behaving bundles when unloaded卸载有不良行为的Bundle时打印警告
OBJC_DEBUG_FRAGILE_SUPERCLASSESwarn about subclasses that may have been broken by subsequent changes to superclasses当子类可能被对父类的修改破坏时打印警告
OBJC_DEBUG_NIL_SYNCwarn about @synchronized(nil), which does no synchronization警告@synchronized(nil)调用,这种情况不会加锁
OBJC_DEBUG_NONFRAGILE_IVARScapriciously rearrange non- fragile ivars打印突发地重新布置non-fragile ivars的行为
OBJC_DEBUG_ALT_HANDLERSrecord more info about bad alt handler use记录更多的alt操作错误信息
OBJC_DEBUG_MISSING_POOLSwarn about autorelease with no pool in place, which may be a leak警告没有pool的情况下使用autorelease,肯内存泄漏
OBJC_DEBUG_POOL_ALLOCATIONhalt when autorelease pools are popped out of order, and allow heap debugges to track当自动释放池按顺序弹出时停止,并允许堆调试跟踪
OBJC_DEBUG_DUPLICATE_CLASSEShalt when multiple classes with the same name are present当出现类重名时停机
OBJC_DEBUG_DONT_CRASHhalt the process by exiting instead of crashing.通过退出而不是崩溃来停止进程。
OBJC_DEBUG_POOL_DEPTHlog fault when at least a set number of autorelease pages has been allocated至少分配了一定数量的自动释放页面时发生日志错误
OBJC_DEBUG_SCRIBBLE_CACHESscribble the IMPs in freed method caches在释放的方法缓存中乱写IMPs
OBJC_DEBUG_SCAN_WEAK_TABLEScan the weak references table continuously in the background - set OBJC_DEBUG_SCAN_WEAK_TABLES_INTERVAL_NANOSECONDS to set scanning interval (default 1000000)弱引用表是否可以在后台连续设置?请将OBJC_DEBUG_SCAN_WEAK_TABLES_INTERVAL_NANOSECONDS设置为扫描间隔(默认值为1000000)
OBJC_DISABLE_VATABLESdisable vatble dispatch关闭vatable分发
OBJC_DISABLE_PREOPTIMIZATIONdisable preoptimization courtesy of dyld shared cache关闭dyld共享缓存优化前的问候语
OBJC_DISABLE_TAGGED_POINTERSdisable tagged pointer optimization of NSNumber et al.关闭NSNumber等的tagged pointer优化
OBJC_DISABLE_TAG_OBFUSCATIONdisable obfuscation of tagged pointers禁用tagged pointer标记指针的模糊处理
OBJC_DISABLE_NONPOINTER_ISAdisable non-pointer isa fields关闭 non-pointer isa 字段的访问
OBJC_DISABLE_INITIALIZE_FORK_SAFETYdisable safety checks for +initialize after fork在fork之后禁用+initialize的安全检查
OBJC_DISABLE_FAULTSdisable os faults禁用操作系统故障
OBJC_DISABLE_PREOPTIMIZED_CACHESdisable preoptimized caches禁用预优化的缓存
OBJC_DISABLE_AUTORELEASE_COALESCINGdisable coalescing of autorelease pool pointers禁用自动释放池指针的合并
OBJC_DISABLE_AUTORELEASE_COALESCING_LRUdisable coalescing of autorelease pool pointers using look back N strategy使用回溯N策略禁用自动释放池指针的合并
OBJC_DISABLE_CLASSRX_SIGNING_ENFORCEMENTdisable class_rx_t pointer signing enforcement禁用class_rx_t指针签名强制
OBJC_DEBUG_CLASS_RX_SIGNINGwarn about class_rx_t pointer signing mismatches警告class_rx_t指针签名不匹配
OBJC_DISABLE_CLASS_RO_FAULTSdisable os faults for class_ro_t pointer signing mismatches为class_ro_t指针签名不匹配禁用os错误
  • 源码:
/***********************************************************************
* environ_init
* 读取影响运行时的环境变量。
* 如果需要,还可以打印环境变量帮助。
**********************************************************************/
void environ_init(void) 
{
    if (issetugid()) {
        // 当setuid或setgid时,所有环境变量都会被忽略
        // 这里包含 OBJC_HELP 和 OBJC_PRINT_OPTIONS 时.
        return;
    } 

    // 默认情况下,对于链接到旧SDKs的应用程序,关闭自动释放autorelease LRU合并。
    // LRU合并可以重新排序发布版本,某些较旧的应用程序意外地依赖于排序。
    // rdar://problem/63886091
···
// 注释掉就代码
···

    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 (opt->internal
//                && !os_variant_allows_internal_security_policies("com.apple.obj-c"))
//                continue;
            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;
        }
    }

//    if (!os_feature_enabled_simple(objc4, preoptimizedCaches, true)) {
//        DisablePreoptCaches = 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 (opt->internal
//                && !os_variant_allows_internal_security_policies("com.apple.obj-c"))
//                continue;
            if (PrintHelp) _objc_inform("%s: %s", opt->env, opt->help);
            if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env);
        }
    }
}

  • 结论:

    除此之外,environ_init里面还对其他工程配置进行读取,比如NSZombiesEnabled僵尸对象检测)。

static_init

static_init主要是执行C++的静态函数,libc会在dyld调用静态函数之前调用_objc_init,也就是系统的C++函数优点其他自定义的函数调用。

/***********************************************************************
* static_init
* 运行C++静态构造函数。
* libc在dyld调用静态构造函数之前调用objc_init(),
* 因此我们必须自己执行。
**********************************************************************/
static void static_init()
{
    size_t count1;
    //获取objc库里面所有的静态构造函数
    auto inits = getLibobjcInitializers(&_mh_dylib_header, &count1);
    for (size_t i = 0; i < count1; i++) {
        inits[i]();
    }
    size_t count2;
    auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count2);
    //遍历调用他们
    for (size_t i = 0; i < count2; i++) {
        UnsignedInitializer init(offsets[i]);
        init();
    }#if DEBUG
    if (count1 == 0 && count2 == 0)
        _objc_inform("No static initializers found in libobjc. This is unexpected for a debug build. Make sure the 'markgc' build phase ran on this dylib. This process is probably going to crash momentarily due to using uninitialized global data.");
#endif
}

runtime_init

runtime_init主要是进行两个步骤:分类的初始化和初始化一张类的表

void runtime_init(void)
{
    objc::disableEnforceClassRXPtrAuth = DisableClassRXSigningEnforcement;
    //分类加载表
    objc::unattachedCategories.init(32);
    //类的加载表
    objc::allocatedClasses.init();
}
  • 结论:

    这里面其实是初始化两张表,以备后边加载类使用,这里可以留意一下这两张表unattachedCategoriesallocatedClasses,后面会再次提及到。

exception_init

主要是注册异常的回调,当下层程序发现错误时,会触发这个回调,从而抛出异常

/***********************************************************************
* exception_init
* 初始化libobjc的异常处理系统。
* Called by map_images().
**********************************************************************/
void exception_init(void)
{
    old_terminate = std::set_terminate(&_objc_terminate);
}

_objc_terminate

/***********************************************************************
* _objc_terminate
* Custom std::terminate handler.
*
* 未捕获的异常回调被实现为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) {
            // 这是一个objc对象。调用Foundation的处理程序(如果有)。
            (*uncaught_handler)((id)e); // 异常时会通过uncaught_handler回调异常内容e
            (*old_terminate)();
        } @catch (...) {
            // 它不是一个对象。继续到C++终止。
            (*old_terminate)();
        }
    }
}
  • 结论:

    整体是一个try-catch运行,下层程序发现异常时,uncaught_handler会回调异常内容e

uncaught_handler

/***********************************************************************
* _objc_default_uncaught_exception_handler
* Default uncaught exception handler. Expected to be overridden by Foundation.
**********************************************************************/
static void _objc_default_uncaught_exception_handler(id exception)
{
}
static objc_uncaught_exception_handler uncaught_handler = _objc_default_uncaught_exception_handler;、

/***********************************************************************
* objc_setUncaughtExceptionHandler
* 为未捕获的Objective-C异常设置处理程序。 
* 返回上一个处理程序。
**********************************************************************/
objc_uncaught_exception_handler 
objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
{
    objc_uncaught_exception_handler result = uncaught_handler;
    uncaught_handler = fn;
    return result;
}

  • 结论:

    objc_setUncaughtExceptionHandler传入一个参数fn,将fn赋值给uncaught_handler,当异常时,uncaught_handler将异常内容再传给外界使用。

捕获异常

根据原理模拟下这个场景,去捕获异常:

  1. 先定义个UncaughtExceptionHanlder捕获处理类,再定义好捕获的代码
  2. 然后再main中调用installHandler方法[UncaughtExceptionHanlder installHandler];
  • main.m源码:
#import <UIKit/UIKit.h>
#import "AppDelegate.h"

@interface UncaughtExceptionHanlder : NSObject

+ (void)installHandler;

@end


@implementation UncaughtExceptionHanlder

void cj_exceptionHander(NSException *exception) {
    NSLog(@"\n捕获到异常是:%@\n", exception);
    NSLog(@"----end-----");
}

+ (void)installHandler {
    NSSetUncaughtExceptionHandler(&cj_exceptionHander);
}

@end

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    NSLog(@"main ...");
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    [UncaughtExceptionHanlder installHandler];
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
@end
    1. ViewController中写一个触发会异常的代码
#import "ViewController.h"
@interface ViewController ()

@property (nonatomic, strong) NSArray *dataSource;

@end

@implementation ViewController

+ (void)load {
    NSLog(@"%s",__func__);
}

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"viewDidLoad ---");
    // Do any additional setup after loading the view.
    self.dataSource = @[@"1", @"2", @"3", @"4", @"5"];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSString *name = self.dataSource[5];
}

@end

    1. 点击页面报错后,查看调试打印异常数据
2023-02-11 02:29:12.224743+0800 DyldTest[1913:556626] +[ViewController load]
2023-02-11 02:29:12.227132+0800 DyldTest[1913:556626] main ...
2023-02-11 02:29:12.450413+0800 DyldTest[1913:556626] viewDidLoad ---
2023-02-11 02:29:13.604925+0800 DyldTest[1913:556626] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[NSConstantArray objectAtIndexedSubscript:]: index 5 beyond bounds [0 .. 4]'
*** First throw call stack:
(0x1ad9c9e88 0x1a6cf78d8 0x1adb6f0a8 0x1ada74204 0x10251db7c 0x1afd7f514 0x1afc796c4 0x1afc78c84 0x1afc77f44 0x1afc77600 0x1afcbf3e4 0x1b09110a4 0x1b01cf740 0x1b0816fd0 0x1b081619c 0x1ada95f54 0x1adaa232c 0x1ada26210 0x1ada3bba8 0x1ada40ed4 0x1e6d3e368 0x1aff1f3d0 0x1aff1f034 0x10251df8c 0x1cc0a8960)
2023-02-11 02:29:13.605178+0800 DyldTest[1913:556626] 
捕获到异常是:*** -[NSConstantArray objectAtIndexedSubscript:]: index 5 beyond bounds [0 .. 4]

2023-02-11 02:29:13.605242+0800 DyldTest[1913:556626] ----end-----
libc++abi: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[NSConstantArray objectAtIndexedSubscript:]: index 5 beyond bounds [0 .. 4]'
terminating with uncaught exception of type NSException
  • 结论:

    成功打印捕获到的异常,证明之前的分析成立。

cache_t::init

void cache_t::init()
{
    // 在调试构建中,断言_flags字段与_originalPreoptCache字段不重叠。
    // 这确实应该是一个静态断言,但很难以编译器可以接受的编译时表达式的方式编写这个测试。
#if !defined(NDEBUG) && CACHE_T_HAS_FLAGS && CONFIG_USE_PREOPT_CACHES
    // 计算包含最大地址的0x00fffffff形式的最小值。
    uintptr_t maxPtr = OBJC_VM_MAX_ADDRESS;
    while (maxPtr & (maxPtr + 1))
        maxPtr |= maxPtr >> 1;

    cache_t testCache;
    testCache._originalPreoptCache.store((preopt_cache_t *)maxPtr, std::memory_order_relaxed);
    ASSERT(testCache._flags == 0);
#endif

#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
}
  • 结论:

    cache_t::init初始化缓存相关。

_imp_implementationWithBlock_init源码

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

/// 初始化trampoline machinery。
/// 通常情况下,这没有任何作用,因为所有的初始化都很慢,
/// 但对于某些进程,我们急切地加载trampolines dylib。
void
_imp_implementationWithBlock_init(void)
{
#if TARGET_OS_OSX
    // 在某些过程中急切地加载libobjc-tropolines.dylib。
    // 一些程序(最著名的是早期版本的嵌入式Chromium使用的QtWebEngineProcess)
    // 启用了一个限制性很强的沙盒配置文件,该文件阻止对该dylib的访问。
    // 如果有任何东西调用imp_implementationWithBlock(正如AppKit已经开始做的那样),
    // 那么我们将在尝试加载它时崩溃。在这里加载它会在启用沙盒配置文件并阻止它之前设置它。
    // This fixes EA Origin (rdar://problem/50813789)
    // and Steam (rdar://problem/55286131)
    if (__progname &&
        (strcmp(__progname, "QtWebEngineProcess") == 0 ||
         strcmp(__progname, "Steam Helper") == 0)) {
        Trampolines.Initialize();
    }
#endif
}
  • 结论:

    MacOS中,让dyld去加载libobjc-trampolines.dylib这个库

二、map_images的调用

从上面的流程中,我们已经知道map_imagesload_images是在objc4-886.9源码里通过_objc_init注册了。load_images已经在探索dyld源码里进行了分析,那map_imagesload_images先调用的函数,又什么时候被调用呢?

  • 在可以运行的objc4-886.9的源码上给map_images方法中添加断点,通过在bt查看运行堆栈。 截屏2023-02-08 下午1.08.34.png

  • llvm:

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 15.1
  * frame #0: 0x00000001006a42d3 libobjc.A.dylib`map_images(count=56, paths=0x00007ff7bfefc690, mhdrs=0x00007ff7bfefcb60) at objc-runtime-new.mm:3154:24
    frame #1: 0x00007ff80de064c3 dyld`invocation function for block in dyld4::RuntimeState::setObjCNotifiers(void (*)(unsigned int, char const* const*, mach_header const* const*), void (*)(char const*, mach_header const*), void (*)(char const*, mach_header const*), void (*)(mach_header const*, void*, mach_header const*, void const*), void (*)(unsigned int, _dyld_objc_notify_mapped_info const*)) + 637
    frame #2: 0x00007ff80de00fff dyld`dyld4::RuntimeState::withLoadersReadLock(void () block_pointer) + 47
    frame #3: 0x00007ff80de06240 dyld`dyld4::RuntimeState::setObjCNotifiers(void (*)(unsigned int, char const* const*, mach_header const* const*), void (*)(char const*, mach_header const*), void (*)(char const*, mach_header const*), void (*)(mach_header const*, void*, mach_header const*, void const*), void (*)(unsigned int, _dyld_objc_notify_mapped_info const*)) + 96
    frame #4: 0x00007ff80de2a5e4 dyld`dyld4::APIs::_dyld_objc_register_callbacks(_dyld_objc_callbacks const*) + 138
    frame #5: 0x00000001006f634d libobjc.A.dylib`_objc_init at objc-os.mm:815:5
    frame #6: 0x00000001000f575d libdispatch.dylib`_os_object_init + 13
    frame #7: 0x0000000100107396 libdispatch.dylib`libdispatch_init + 363
    frame #8: 0x00007ff819d53895 libSystem.B.dylib`libSystem_initializer + 238
    frame #9: 0x00007ff80de10618 dyld`invocation function for block in dyld4::Loader::findAndRunAllInitializers(dyld4::RuntimeState&) const + 172
    frame #10: 0x00007ff80de4fde9 dyld`invocation function for block in dyld3::MachOAnalyzer::forEachInitializer(Diagnostics&, dyld3::MachOAnalyzer::VMAddrConverter const&, void (unsigned int) block_pointer, void const*) const + 242
    frame #11: 0x00007ff80de43ef7 dyld`invocation function for block in dyld3::MachOFile::forEachSection(void (dyld3::MachOFile::SectionInfo const&, bool, bool&) block_pointer) const + 557
    frame #12: 0x00007ff80ddf60b7 dyld`dyld3::MachOFile::forEachLoadCommand(Diagnostics&, void (load_command const*, bool&) block_pointer) const + 245
    frame #13: 0x00007ff80de430a7 dyld`dyld3::MachOFile::forEachSection(void (dyld3::MachOFile::SectionInfo const&, bool, bool&) block_pointer) const + 175
    frame #14: 0x00007ff80de4f8d2 dyld`dyld3::MachOAnalyzer::forEachInitializer(Diagnostics&, dyld3::MachOAnalyzer::VMAddrConverter const&, void (unsigned int) block_pointer, void const*) const + 470
    frame #15: 0x00007ff80de104f6 dyld`dyld4::Loader::findAndRunAllInitializers(dyld4::RuntimeState&) const + 150
    frame #16: 0x00007ff80de3000d dyld`dyld4::APIs::runAllInitializersForMain() + 71
    frame #17: 0x00007ff80ddfb369 dyld`dyld4::prepare(dyld4::APIs&, dyld3::MachOAnalyzer const*) + 3743
    frame #18: 0x00007ff80ddfa281 dyld`start + 2289
  • 结论:

    ①. map_imagesdyld入口部分跟load_images一致,是从startdyld4::APIs::runAllInitializersForMain,中间就是Mach-O解析器解析Mach-O文件内容的部分,这个部分直到objc源码的_objc_init;这里重点说明的dyld调起map_images部分就是setObjCNotifiers后面部分。

    ②. map_images的流程是
    start -->
    dyld4::prepare -->
    dyld4::APIs::runAllInitializersForMain() -->
    dyld4::Loader::findAndRunAllInitializers -->
    dyld3::MachOAnalyzer::forEachInitializer -->
    dyld3::MachOFile::forEachSection -->
    dyld3::MachOFile::forEachLoadCommand -->
    dyld3::MachOFile::forEachSectionblock -->
    dyld3::MachOAnalyzer::forEachInitializerblock -->
    dyld4::Loader::findAndRunAllInitializersblock -->
    libSystem库的libSystem_initializer -->
    libdispatch库的libdispatch_init -->
    libdispatch库的_os_object_init -->
    objc源码的_objc_init -->
    objc源码的_os_object_init -->
    dyld4::APIs::_dyld_objc_register_callbacks -->
    dyld4::RuntimeState::setObjCNotifiers -->
    dyld4::RuntimeState::withLoadersReadLock -->
    dyld4::RuntimeState::setObjCNotifiersblock -->
    map_images

    ③. 之前探究_notifyObjCInit来源的时候就分析了setObjCNotifiers函数,这里就不展开了;直接说明了map_images就是在setObjCNotifiers里注册赋值_notifyObjCMapped = map_images,最后在withLoadersReadLock闭包通过指针_notifyObjCMapped指向运行map_images调用

  • 图:

截屏2023-02-08 下午2.19.04 2.png

三、map_images解析

map_imagesobjc/Source/objc-runtime-new.mm中声明的极其重要的函数。处理由dyld映射的给定的images

/***********************************************************************
* map_images
* 通过动态链接器dyld处理正在映射的给定图像
* 获取ABI-specific锁后调用ABI-agnostic代码。
*
* Locking: 写锁write-locks runtimeLock
************************************************************************ ** **/
void
map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    bool takeEnforcementDisableFault;
    {
        mutex_locker_t lock(runtimeLock);
        map_images_nolock(count, paths, mhdrs, &takeEnforcementDisableFault);
    }

    if (takeEnforcementDisableFault) {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
        bool objcModeNoFaults = DisableFaults
            || DisableClassROFaults
            || getpid() == 1
            || is_root_ramdisk()
            || !os_variant_has_internal_diagnostics("com.apple.obj-c");
        if (!objcModeNoFaults) {
            os_fault_with_payload(OS_REASON_LIBSYSTEM,
                                  OS_REASON_LIBSYSTEM_CODE_FAULT,
                                  NULL, 0,
                                  "class_ro_t enforcement disabled",
                                  0);
        }
#endif
    }
}
  • 结论:

    ①. map_images传入的参数说明:

    • mhCountmach-o header count,即mach-o header个数
    • mhPathsmach-o header Paths,即header的路径数组
    • mhdrs:单个mhdr指的是mach-o headerheader指针

    ②. map_images是用来处理dyld映射的images,可看到加锁runtimeLock)后,直接调用map_images_nolock函数。可以看到是在线程安全的情况下,调用map_images_nolock函数。

map_images_nolock

处理由dyld映射的给定镜像images。执行所有类注册修复(延迟等待发现丢失的父类等),并调用 +load 方法。 开启OBJC_PRINT_IMAGES环境变量时,启动时则打印images数量以及具体的image

void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[],
                  bool *disabledClassROEnforcement)
{
    // 局部静态变量,表示第一次调用
    static bool firstTime = YES;
    static bool executableHasClassROSigning = false;
    static bool executableIsARM64e = false*;

    // hList 是统计mhdrs中的每个 mach_header 对应的 header_info
    header_info *hList[mhCount];
    uint32_t hCount;
    size_t selrefCount = 0;

    *disabledClassROEnforcement = false;

    // 如有必要,请执行首次初始化。
    // 此函数在ordinary library普通库初始化器之前调用。
    // 修复了延迟初始化,直到找到objc-using镜像image?
    // 如果是第一次加载,则准备初始化环境
    if (firstTime) {
        preopt_init();
    }

    // 开启 OBJC_PRINT_IMAGES 环境变量时,启动时则打印 images 数量。
    if (PrintImages) {
        _objc_inform("IMAGES: processing %u newly-mapped images...\n", mhCount);
    }

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

    // 计算class类数量。根据总数调整各种表格table的大小。
    int totalClasses = 0;
    int unoptimizedTotalClasses = 0;
    {
        uint32_t i = mhCount;
        while (i--) {
            // typedef struct mach_header_64 headerType;
            // 取得指定 image 的 header 指针
            const headerType *mhdr = (const headerType *)mhdrs[i];
            
            // 以 mdr 构建其 header_info,并添加到全局的 header 列表中(是一个链表,大概看源码到现在还是第一次看到链表的使用)。
            // 且通过 GETSECT(_getObjc2ClassList, classref_t const, "__objc_classlist"); 读取 __objc_classlist 区中的 class 数量添加到 totalClasses 中, 
            // 以及未从 dyld shared cache 中找到 mhdr 的 header_info 时,添加 class 的数量到 unoptimizedTotalClasses 中。
            auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);
            
            // 这里有两种情况下 hi 为空:
            // 1. mhdr 的 magic 不是既定的 MH_MAGIC、MH_MAGIC_64、MH_CIGAM、MH_CIGAM_64 中的任何一个
            // 2. 从 dyld shared cache 中找到了 mhdr 的 header_info,并且 isLoaded 为 true()
            if (!hi) {
                // 此条目中没有对象objc数据
                continue;
            }

            if (mhdr->filetype == MH_EXECUTE) {
                // 根据主可执行文件的大小调整某些数据结构的大小
                // 如果dyld3优化了主可执行文件,
                // 那么动态映射中不需要任何selref,
                // 因此我们可以初始化为0大小的映射
                if ( !hi->hasPreoptimizedSelectors() ) {
                  // 根据主要可执行文件的大小调整一些数据结构的大小
                  size_t count;
                 
                 // ⬇️ GETSECT(_getObjc2SelectorRefs, SEL, "__objc_selrefs"); 
                 // 获取 __objc_selrefs 区中的 SEL 的数量
                  _getObjc2SelectorRefs(hi, &count);
                  selrefCount += count;
                  
                  // GETSECT(_getObjc2MessageRefs, message_ref_t, "__objc_msgrefs"); 
                // struct message_ref_t {
                //     IMP imp;
                //     SEL sel;
                // };
                // ⬇️ 获取 __objc_msgrefs 区中的 message 数量
                  _getObjc2MessageRefs(hi, &count);
                  selrefCount += count;
                }

#if SUPPORT_GC_COMPAT
                // 如果这是GC应用程序,请停止。
                if (shouldRejectGCApp(hi)) {
                    _objc_fatal_with_reason
                        (OBJC_EXIT_REASON_GC_NOT_SUPPORTED, 
                         OS_REASON_FLAG_CONSISTENT_FAILURE, 
                         "Objective-C garbage collection " 
                         "is no longer supported.");
                }
#endif
                if (hasSignedClassROPointers(hi)) {
                    executableHasClassROSigning = true;
                }
            }

            hList[hCount++] = hi;
            if (PrintImages) {
                _objc_inform("IMAGES: loading image for %s%s%s%s%s\n", 
                             hi->fname(),
                             mhdr->filetype == MH_BUNDLE ? " (bundle)" : "",
                             hi->info()->isReplacement() ? " (replacement)" : "",
                             hi->info()->hasCategoryClassProperties() ? " (has class properties)" : "",
                             hi->info()->optimizedByDyld()?" (preoptimized)":"");
            }
        }
    }
    
    // ⬇️⬇️⬇️
    // 执行一次性one-time运行时初始化,
    // 必须延迟到找到可执行文件本身。
    // 这需要在进一步初始化之前完成。
    // (如果可执行文件不包含Objective-C代码,但稍后会动态加载Objective-C,则该可执行文件可能不出现在infoList中。
    if (firstTime) {
        // 初始化selector表并注册内部使用的selectors。
        sel_init(selrefCount);
        // ⬇️⬇️⬇️ 这里的 arr_init 函数超重要,可看到它内部做了三件事:
        // 1. 自动释放池的初始化(实际是在 TLS 中以 AUTORELEASE_POOL_KEY 为 KEY 写入 tls_dealloc 函数(自动释放池的销毁函数:内部所有 pages pop 并 free))
        // 2. SideTablesMap 初始化,也可理解为 SideTables 的初始化(为 SideTables 这个静态全局变量开辟空间)
        // 3. AssociationsManager 的初始化,即为全局使用的关联对象表开辟空间
        arr_init();
        
#if SUPPORT_GC_COMPAT
        // 拒绝链接到主可执行文件的任何GC映像。
        // 我们已经拒绝了上面的应用程序本身。
        // 启动后加载的镜像Images将被dyld拒绝。
        for (uint32_t i = 0; i < hCount; i++) {
            auto hi = hList[i];
            auto mh = hi->mhdr();
            if (mh->filetype != MH_EXECUTE  &&  shouldRejectGCImage(mh)) {
                _objc_fatal_with_reason
                    (OBJC_EXIT_REASON_GC_NOT_SUPPORTED, 
                     OS_REASON_FLAG_CONSISTENT_FAILURE, 
                     "%s requires Objective-C garbage collection "
                     "which is no longer supported.", hi->fname());
            }
        }
#endif

// 这一段是在较低版本下 DYLD_MACOSX_VERSION_10_13 之前的版本中禁用 +initialize fork safety,大致看看即可
#if TARGET_OS_OSX
        // 如果应用程序太旧(<10.13),禁用+initialize fork安全。
        // 如果应用程序具有__DATA,__objc_fork_ok节,则禁用+initialize fork安全。
//        if (!dyld_program_sdk_at_least(dyld_platform_version_macOS_10_13)) {
//            DisableInitializeForkSafety = true;
//            if (PrintInitializing) {
//                _objc_inform("INITIALIZE: disabling +initialize fork "
//                             "safety enforcement because the app is "
//                             "too old.)");
//            }
//        }

        for (uint32_t i = 0; i < hCount; i++) {
            auto hi = hList[i];
            auto mh = hi->mhdr();
            if (mh->filetype != MH_EXECUTE) continue;
            unsigned long size;
            if (getsectiondata(hi->mhdr(), "__DATA", "__objc_fork_ok", &size)) {
                DisableInitializeForkSafety = true;
                if (PrintInitializing) {
                    _objc_inform("INITIALIZE: disabling +initialize fork "
                                 "safety enforcement because the app has "
                                 "a __DATA,__objc_fork_ok section");
                }
            }
            break// assume only one MH_EXECUTE image
        }
#endif // TARGET_OS_OSX
        // 检查ARM64e版本ARM64e-ness的主可执行文件。
        // 注意,我们无法检查为MH_EXECUTABLE传入的头文件,
        // 因为dyld有助于省略不包含ObjC的图像,并且主可执行文件可能不包含ObjC。
//        const headerType *mainExecutableHeader = (headerType *)_dyld_get_prog_image_header();
//        if (mainExecutableHeader
//            && mainExecutableHeader->cputype == CPU_TYPE_ARM64
//            && ((mainExecutableHeader->cpusubtype & ~CPU_SUBTYPE_MASK)
//                == CPU_SUBTYPE_ARM64E)) {
//            executableIsARM64e = true;
//        }
    }
    
    // 如果主可执行文件是ARM64e,请确保加载的每个镜像image都打开了指针签名pointer signing。
    if (executableIsARM64e) {
        bool shouldWarn = (executableHasClassROSigning
                           && DebugClassRXSigning);
        for (uint32_t i = 0; i < hCount; ++i) {
            auto hi = hList[i];
            if (!hasSignedClassROPointers(hi)) {
                if (!objc::disableEnforceClassRXPtrAuth) {
                    *disabledClassROEnforcement = true;
                    objc::disableEnforceClassRXPtrAuth = 1;

                    // 我们不想在这里打印log,
                    // 因为这会让攻击者知道他们已经成功禁用了强制执行。
  
                    // 后来,当我们真正有信心时,我们也许可以这样做来替代:
                    //
                    // _objc_fatal_with_reason
                    //     (OBJC_EXIT_REASON_CLASS_RO_SIGNING_REQUIRED,
                    //      OS_REASON_FLAG_CONSISTENT_FAILURE,
                    //      "%s was built without class_ro_t pointer signing",
                    //      hi->fname());
                }

                if (shouldWarn) {
                    _objc_inform("%s has un-signed class_ro_t pointers, but the "
                                 "main executable was compiled with class_ro_t "
                                 "pointer signing enabled", hi->fname());
                }
            }
        }
    }

    // ⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️ 下面就来到了最核心的地方
    // 以 header_info *hList[mhCount] 数组中收集到的 images 的 header_info 为参,直接进行 image 的读取
    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

    // 把开始时初始化的静态局部变量 firstTime 置为 NO
    firstTime = NO
    
    // 在设置好所有内容后调用镜像image加载+load函数。
    for (auto func : loadImageFuncs) {
        for (uint32_t i = 0; i < mhCount; i++) {
            func(mhdrs[i]);
        }
    }
}
  • 结论:

    ①. 可以看到,该方法进行了一些初始化操作,并且遍历了所有的动态库的mach-header,生成相应的header_info并且放进hList数组中,于此同时统计totalClasses 和 unoptimizedTotalClasses的数量。

    ②. 最重要的还是调用了_read_images读取所有的动态库信息。

    ③. 这里可以稍微注意一下,从while里面的i--我们可以得知hList的顺序是mhdrs里面的倒序。


preopt_init

void preopt_init(void)
{
    // 获取共享缓存占用的内存区域。
    size_t length;
    const uintptr_t start = (uintptr_t)_dyld_get_shared_cache_range(&length);

    if (start) {
        objc::dataSegmentsRanges.setSharedCacheRange(start, start + length);
    }
    
    // `opt` 在编译时未设置,以检测过早使用
    const char *failure = nil;
    opt = &_objc_opt_data;

    if (DisablePreopt) {
        // OBJC_DISABLE_PREOPTIMIZATION is set
        // If opt->version != VERSION then you continue at your own risk.
        failure = "(by OBJC_DISABLE_PREOPTIMIZATION)";
    } 
    else if (opt->version != 16) {
        // 这不应该发生。您可能忘记编辑objc-sel-table表。
        // 如果dyld真的写了错误的优化版本,那么我们就必须停止,
        // 因为我们不知道dyld在旋转什么。
        _objc_fatal("bad objc preopt version (want %d, got %d)", 
                    objc_opt::VERSION, opt->version);
    }
    else if (!opt->headeropt_ro()) {
        // 缺少一个表。
        failure = "(dyld shared cache is absent or out of date)";
    }
    
    if (failure) {
        // 所有预优化的选择器引用都无效。
        preoptimized = NO;
        opt = nil;
        disableSharedCacheOptimizations();

        if (PrintPreopt) {
            _objc_inform("PREOPTIMIZATION: is DISABLED %s", failure);
        }
    }
    else {
        // 有效优化数据由dyld共享缓存写入
        preoptimized = YES;

        if (PrintPreopt) {
            _objc_inform("PREOPTIMIZATION: is ENABLED "
                         "(version %d)", opt->version);
        }
    }
}

  • 结论:

    preopt_init初始化共享缓存的数据,包括类、协议、头部信息、选择器等。

addHeader

static header_info * addHeader(const headerType *mhdr, const char *path, int &totalClasses, int &unoptimizedTotalClasses)
{
    header_info *hi;

    if (bad_magic(mhdr)) return NULL;

    bool inSharedCache = false;

    // 从dyld共享缓存中查找hinfo。
    hi = preoptimizedHinfoForHeader(mhdr);
    if (hi) {
        // 在dyld共享缓存中找到hinfo。

        // 剔除重复项。
        if (hi->isLoaded()) {
            return NULL;
        }

        inSharedCache = true;

        // 初始化共享缓存未设置的字段
        // hi->next被appendHeader设置
        hi->setLoaded(true);

        if (PrintPreopt) {
            _objc_inform("PREOPTIMIZATION: honoring preoptimized header info at %p for %s", hi, hi->fname());
        }

#if DEBUG
        // 核实 image_info
        size_t info_size = 0;
        const objc_image_info *image_info = _getObjcImageInfo(mhdr,&info_size);
        ASSERT(image_info == hi->info());
#endif
    }
    else 
    {
        // 在dyld共享缓存中找不到hinfo。

        // 找到__OBJC segment段
        size_t info_size = 0;
        unsigned long seg_size;
        const objc_image_info *image_info = _getObjcImageInfo(mhdr,&info_size);
        const uint8_t *objc_segment = getsegmentdata(mhdr,SEG_OBJC,&seg_size);
        if (!objc_segment  &&  !image_info) return NULL;

        // 分配header_info条目。
        // 注意,我们还在header_info内部的rw_data[]中
        // 为单个header_info_rw分配空间。
        hi = (header_info *)calloc(sizeof(header_info) + sizeof(header_info_rw), 1);

        // 设置新的header_info条目。
        hi->setmhdr(mhdr);
        // Install a placeholder image_info if absent to simplify code elsewhere
        static const objc_image_info emptyInfo = {0, 0};
        hi->setinfo(image_info ?: &emptyInfo);

        hi->setLoaded(true);
        hi->setAllClassesRealized(NO);
    }

    {
        size_t count = 0;
        if (_getObjc2ClassList(hi, &count)) {
            totalClasses += (int)count;
            if (!inSharedCache) unoptimizedTotalClasses += count;
        }
    }

    appendHeader(hi);

    return hi;
}

  • 结论:

    addHeader函数大体上分为以下几步:

    • ①. 判断一下当前的headerdyld的共享缓存中有没有
    • ②. 如果有的话直接设置已加载
    • ③. 如果共享缓存中没有,那么就实行“封装操作”
    • ④. 封装成功以后,加入到链表中。 其中preoptimizedHinfoForHeader(mhdr)为判断当前的headerdyld的共享缓存中有没有

    最后我们看到该函数返回的是一个header_info的数据,header_info既是个结构体也是个链表(代码过长不粘了),通过getNext来获取链表的下一节点数据,setNext(header_info *v)设置节点数据,头节点为FirstHeader

preoptimizedHinfoForHeader

header_info *preoptimizedHinfoForHeader(const headerType *mhdr)
{
    objc_headeropt_ro_t *hinfos = opt ? opt->headeropt_ro() : nil;
    if (hinfos) return hinfos->get(mhdr);
    else return nil;
}
opt

可以看到是通过opt来获取数据,也就是说共享缓存的数据都存放在opt内,通过源码可进一步查看opt类型,其为静态变量~0就是0Xffffffff,这是块安全区域

static const objc_opt_t *opt = (objc_opt_t *)~0;

// 顶级优化结构。
// 如果更改此结构,请编辑对象选择表bjc-sel-table.s。
struct alignas(alignof(void*)) objc_opt_t {
    uint32_t version;
    uint32_t flags;
    int32_t selopt_offset;
    int32_t headeropt_ro_offset;
    int32_t clsopt_offset;
    int32_t unused_protocolopt_offset; // This is now 0 as we've moved to the new protocolopt_offset
    int32_t headeropt_rw_offset;
    int32_t protocolopt_offset;

    const objc_selopt_t* selopt() const {
        if (selopt_offset == 0) return NULL;
        return (objc_selopt_t *)((uint8_t *)this + selopt_offset);
    }
    objc_selopt_t* selopt() { 
        if (selopt_offset == 0) return NULL;
        return (objc_selopt_t *)((uint8_t *)this + selopt_offset);
    }

    struct objc_headeropt_ro_t* headeropt_ro() const {
        if (headeropt_ro_offset == 0) return NULL;
        return (struct objc_headeropt_ro_t *)((uint8_t *)this + headeropt_ro_offset);
    }

    struct objc_clsopt_t* clsopt() const { 
        if (clsopt_offset == 0) return NULL;
        return (objc_clsopt_t *)((uint8_t *)this + clsopt_offset);
    }

    struct objc_protocolopt_t* protocolopt() const {
        if (unused_protocolopt_offset == 0) return NULL;
        return (objc_protocolopt_t *)((uint8_t *)this + unused_protocolopt_offset);
    }

    struct objc_protocolopt2_t* protocolopt2() const {
        if (protocolopt_offset == 0) return NULL;
        return (objc_protocolopt2_t *)((uint8_t *)this + protocolopt_offset);
    }

    struct objc_headeropt_rw_t* headeropt_rw() const {
        if (headeropt_rw_offset == 0) return NULL;
        return (struct objc_headeropt_rw_t *)((uint8_t *)this + headeropt_rw_offset);
    }
};
  • 结论:

    objc_opt_t结构体内部存储的数据大体上:

    • 类的缓存clsopt()
    • 协议的缓存protocolopt()
    • 头的缓存headeropt_rw()
    • 选择器的缓存selopt()

appendHeader

addHeader函数中,如果hi有值,那么设置已加载,如果没有值,则进行封装,然后就是往链表中添加数据:

  • FirstHeader为链表的头节点
  • LastHeader为链表的尾节点
/***********************************************************************
* appendHeader.  将新构造的header_info添加到列表中。
**********************************************************************/
void appendHeader(header_info *hi)
{
    // 将标题header添加到标题header列表。
    // 标题header附加到列表中,以保持自下而上的顺序。
    hi->setNext(NULL);
    if (!FirstHeader) {
        // 表是空的
        FirstHeader = LastHeader = hi;
    } else {
        if (!LastHeader) {
            // 列表不为空,但LastHeader无效-请重新计算
            LastHeader = FirstHeader;
            while (LastHeader->getNext()) LastHeader = LastHeader->getNext();
        }
        // LastHeader现在有效
        LastHeader->setNext(hi);
        LastHeader = hi;
    }

    if ((hi->mhdr()->flags & MH_DYLIB_IN_CACHE) == 0) {
        foreach_data_segment(hi->mhdr(), [](const segmentType *seg, intptr_t slide) {
            uintptr_t start = (uintptr_t)seg->vmaddr + slide;
            objc::dataSegmentsRanges.add(start, start + seg->vmsize);
        });
    }
}

sel_init

sel_init主要是初始化selector表,该表定义如下:

void sel_init(size_t selrefCount)
{
#if SUPPORT_PREOPT
    if (PrintPreopt) {
        _objc_inform("PREOPTIMIZATION: using dyld selector opt");
    }
#endif

  namedSelectors.init((unsigned)selrefCount);

    // 注册 selectors 方法 方便 libobjc 使用

    mutex_locker_t lock(selLock);

    SEL_cxx_construct = sel_registerNameNoLock(".cxx_construct", NO);
    SEL_cxx_destruct = sel_registerNameNoLock(".cxx_destruct", NO);
}

static objc::ExplicitInitDenseSet<const char *> namedSelectors;
  • 结论:

    namedSelectors是个全局变量,存储所有的方法名SEL,内部结构是哈希表DenseMap

arr_init

void arr_init(void) 
{
    SideTablesMap.init();
    _objc_associations_init();

    if (DebugScanWeakTables)
        startWeakTableScan();
}

  • 结论:

    arr_init主要做以下工作:

    • ①. 自动释放池的初始化。
    • ②. SideTablesMap初始化,也可理解为SideTables的初始化(为 SideTables这个静态全局变量开辟空间)
    • ③. AssociationsManager的初始化,即为全局使用的关联对象表开辟空间

_read_images

_read_images源码比较多,将每个重点部分进行说明。

  • hList:统计mhdrs中的每个 mach_header 对应的 header_info
  • hCount:统计到的header_info数量
  • totalClasses:计算到的所有class的数量,该数量包含unoptimizedTotalClasses数量,可在addHeader函数中看到对这两个数的赋值(地址引用)。
  • unoptimizedTotalClasses:不在共享缓存区找到的类的数量

/***********************************************************************
* _*read_images*
* 对从headerList开始的链接列表中的头执行初始处理。
*
* Called by: 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;
    
    // 静态局部变量,如果是第一次调用_read_images,则doneOnce 值为 NO
    static bool doneOnce;
    bool launchTime = NO;
    // 环境变量设置 OBJC_PRINT_IMAGE_TIMES = YES 会打印ts.log的内容
    // 可以对照上面的打印表
    TimeLogger ts(PrintImageTimes);
    
    // 加锁
    lockdebug::assert_locked(&runtimeLock);
  1. 这里是初始化一些局部变量,注意OBJC_PRINT_IMAGE_TIMES的设置可以使之后的ts.log()生效。我们这分段也是通过ts.log()来进行分段的。
// EACH_HEADER 是给下面的 for 循环使用的宏,遍历 hList 数组中的 header_info
// 把循环条件定义成通用的
#define EACH_HEADER \
    hIndex = 0;         \
    hIndex < hCount && (hi = hList[hIndex]); \
    hIndex++

    // 第一次调用 _read_images 时,doneOnce 值为 NO,会进入 if 执行里面的代码
    if (!doneOnce) {
        // 把静态局部变量 doneOnce 置为 YES,之后调用 _read_images 都不会再进来
        // 第一次调用 _read_images 的时候,class、protocol、selector、category 都没有,
        // 需要创建容器来保存这些东西,此 if 内部,最后是创建一张存 class 的表。
        doneOnce = YES;
        launchTime = YES;

#if SUPPORT_NONPOINTER_ISA
        // 在某些情况下禁用非指针isa。

# if SUPPORT_INDEXED_ISA
        // 如果任何镜像image包含旧的Swift代码,则禁用nonpointer-isa
        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_OS
        // Disable non-pointer isa if the app is too old
        // (linked before OS X 10.11)
        // Note: we must check for macOS, because Catalyst and Almond apps
        // return false for a Mac SDK check! rdar://78225780
//        if (dyld_get_active_platform() == PLATFORM_MACOS && !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
        // OPTION( DisableTaggedPointers, OBJC_DISABLE_TAGGED_POINTERS, "disable tagged pointer optimization of NSNumber et al.") 
        // 禁用 NSNumber 等的 Tagged Pointers 优化时
        // Taggedpoint的一些初始化
        if (DisableTaggedPointers) {
            // 内部直接把 Tagged Pointers 用到的 mask 全部置为 0
            disableTaggedPointers();
        }

        // OPTION( DisableTaggedPointerObfuscation, OBJC_DISABLE_TAG_OBFUSCATION, "disable obfuscation of tagged pointers")
        // 可开启 OBJC_DISABLE_TAG_OBFUSCATION,禁用 Tagged Pointer 的混淆。 
        // 随机初始化 objc_debug_taggedpointer_obfuscator。 
        // tagged pointer obfuscator 旨在使攻击者在存在缓冲区溢出或其他对某些内存的写控制的情况下更难将特定对象构造为标记指针。 
        // 在设置或检索有效载荷值(payload values)时, obfuscator 与 tagged pointers 进行异或。 
        // 它们在第一次使用时充满了随机性。。
        initializeTaggedPointerObfuscator();
        
        // OBJC_PRINT_CLASS_SETUP设置,可以打印class数量(系统+自定义)
        if (PrintConnecting) {
            _objc_inform("CLASS: found %d classes during launch", totalClasses);
        }

        // namedClasses
        // Preoptimized预先优化的类不在这个表中。
        // NXMapTable的加载因子是4/3
        // isPreoptimized 如果我们有一个有效的优化共享缓存(valid optimized shared cache),则返回 YES。
        // 然后是不管三目运算符返回的是 unoptimizedTotalClasses 还是 totalClasses,它都会和后面的 4 / 3 相乘,
        // 注意是 4 / 3
        int namedClassesSize =
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        // gdb_objc_realized_classes 是一张全局的哈希表,虽然名字中有 realized,但是它的名字其实是一个误称 
        // 实际上它存放的是不在 dyld shared cache 中的 class,无论该 class 是否 realized。
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

        ts.log("IMAGE TIMES: first time tasks");
    }
  1. 注意这部分代码,通过静态变量doneOnce控制,只会执行一次。其中创建的gdb_objc_realized_classes这个表,后期会在addNamedClass静态方法内部对表进行赋值。
    // 注册并修正 selector references
    //(其实就是把 image 的 __objc_selrefs 区中的 selector 放进全局的 selector 集合中,
    // 把其中)
    // 请注意,这必须在任何人使用方法列表之前进行,
    // 因为相对方法列表指向selRef,并假设它们已经修复(未修复)。
    static size_t UnfixedSelectors;
    {
        // 加锁 selLock
        mutex_locker_t lock(selLock);
        
        // 遍历 header_info **hList 中的 header_info
        for (EACH_HEADER) {
            // 如果指定的 hi 不需要预优化则跳过
            if (hi->hasPreoptimizedSelectors()) continue;
            
            // 根据 mhdr()->filetype 判断 image 是否是 MH_BUNDLE 类型
            bool isBundle = hi->isBundle();
            
            // GETSECT(_getObjc2SelectorRefs, SEL, "__objc_selrefs");
            // 取出__objc_selrefs区中的SEL
            SEL *sels = _getObjc2SelectorRefs(hi, &count);
            // 记录数量
            UnfixedSelectors += count;
            
            // static objc::ExplicitInitDenseSet<const char *> namedSelectors;
            // 是一个静态全局 set,用来存放 Selector(名字,Selector 本身就是字符串)
            // 遍历把 sels 中的所有 selector 放进全局的 selector 集合中  
            for (i = 0; i < count; i++) {
                // sel_cname 函数内部实现是返回:(const char *)(void *)sel; 即把 SEL 强转为 char 类型
                const char *name = sel_cname(sels[i]);
                // 注册 SEL,并返回其地址
                // 注册到函数表
                SEL sel = sel_registerNameNoLock(name, isBundle);
                
                // 如果 SEL 地址发生变化,则把它设置为相同
                // 进行修正(fix up)如果地址发送变化,使用函数表中的地址
                if (sels[i] != sel) {
                    sels[i] = sel;
                }
            }
        }
    }

    // 这里打印注册并修正 selector references 用的时间
    ts.log("IMAGE TIMES: fix up selector references");
  1. SEL注册到nameSelectors表,并修复函数指针。把方法selector__objc_selrefs section加载到map_images_nolock中初始化的函数表中,并且进行修正。这里的修正我认为是一种rebase操作(虚拟内存映射物理内存,Aslr相关)。
    // 发现class类。修复unresolved future classes未解决的未来类。标记bundle classes捆绑类。
    bool hasDyldRoots = dyld_shared_cache_some_image_overridden();

    for (EACH_HEADER) {
        if (! mustReadClasses(hi, hasDyldRoots)) {
            // 镜像Image已充分优化,我们不需要调用readClass()
            continue;
        }
       
       // 获取所有的类
       // GETSECT(_getObjc2ClassList, classref_t const, "__objc_classlist"); 
       // 获取 __objc_classlist 区中的 classref_t 
       // 从编译后的类列表中取出所有类,获取到的是一个 classref_t 类型的指针
       // classref_t is unremapped class_t* ➡️ classref_t 是未重映射的 class_t 指针 
       // typedef struct classref * classref_t;
       // classref_t 是 classref 结构体指针
        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];
            // 重点 ⚠️⚠️⚠️⚠️ 在这里:readClass。
            Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);

            if (newCls != cls  &&  newCls) {
           
               // realloc 原型是 extern void *realloc(void *mem_address, unsigned int newsize);
               // 先判断当前的指针是否有足够的连续空间,如果有,扩大 mem_address 指向的地址,并且将 mem_address 返回,
               // 如果空间不够,先按照 newsize 指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,
               // 而后释放原来 mem_address 所指内存区域(注意:原来指针是自动释放,不需要使用 free),
               // 同时返回新分配的内存区域的首地址,即重新分配存储器块的地址。

                // 类已移动但未删除。
               // 目前,只有当新类解析了未来类时,才会出现这种情况。
                // Non-lazily非懒实现下面的类。
                resolvedFutureClasses = (Class *)
                    realloc(resolvedFutureClasses,
                            (resolvedFutureClassCount+1) * sizeof(Class));
                resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
            }
        }
    }
   // 这里打印发现 classes 用的时间
    ts.log("IMAGE TIMES: discover classes");
  1. 这里是注册类。这里的log输出是discover classes,不难看出最重要的是其中的readClass,我们继续看readClass的实现。

readClass

/***********************************************************************
* readClass
* 读取编译器编写的类和元类。
* 返回新的类指针。 This could be:
* - cls
* - nil  (cls类缺少弱链接superclass父类)
* - something else (class类的空间是为将来的类预留的)
*
* **注意** 该函数执行的所有工作都由mustReadClasses()预先触发
* 在不更新该函数的情况下,请勿更改该函数。
*
* Locking: runtimeLock运行锁由map_images或objc_readClassPair获取
************************************************************************ ** **/
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    // 类的名字
    const char *mangledName = cls->nonlazyMangledName();

    // 1. 只有 superclass 不存在时,才会进入判断内
    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 是 swift 类,进行一些修正
    cls->fixupBackwardDeployingStableSwift();

    // 2. 判断 class 是否是 unrealized future class(判断它是否存在与 future_named_class_map 中) 
    Class replacing = nil;
    if (mangledName != nullptr) {
        if (Class newCls = popFutureNamedClass(mangledName)) {
            // 此名称以前被分配为future class将来的类。
            // 复制 objc_class 到 future class将来类的结构。
            // 保留未来的rw数据块。
            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();

            newCls->setSuperclass(cls->getSuperclass());
            newCls->initIsa(cls->getIsa());
            memcpy(&newCls->cache, &cls->cache, sizeof(newCls->cache));
            if (cls->hasCustomRR())
                newCls->setHasCustomRR();
            else
                newCls->setHasDefaultRR();
            rw->set_ro(cls->safe_ro());

            freeIfMutable((char *)old_ro->getName());
            free((void *)old_ro);

            addRemappedClass(cls, newCls);

            replacing = cls;
            cls = newCls;
        }
    }

     // headerIsPreoptimized 是外部参数,只有该类禁用了预优化才会返回 true,所以到这里会走下面的 else
    if (headerIsPreoptimized  &&  !replacing) {
        // 共享缓存中内置的类列表
        // 修复严格断言由于重复而不起作用
        // ASSERT(cls == getClass(name));
        ASSERT(mangledName == nullptr || getClassExceptSomeSwift(mangledName));
    } else {
        // 会执行这里的内容
        if (mangledName) { // 一些Swift泛型类可以懒生成它们的名称
            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的操作是,把类添加到allocatedClasses 这个表中,该表在runtime_init的时候初始化。
        addClassTableEntry(cls);
    }

    // 如果 headerIsBundle 为真,则设置下面的标识位 RO_FROM_BUNDLE
    // 供将来参考:共享缓存从不包含MH_BUNDLEs
    if (headerIsBundle) {
        const_cast<class_ro_t *>(cls->safe_ro())->flags |= RO_FROM_BUNDLE;
        const_cast<class_ro_t *>(cls->ISA()->safe_ro())->flags |= RO_FROM_BUNDLE;
    }

    return cls;
}

  • 结论:

    从上到下可看到有一些情况的处理:例如superclass不存在时,判断class是否是 unrealized future class、判断该类是否禁用了预优化,而最终的绝大部分情况则会是调用:addNamedClass(cls, mangledName, replacing)addClassTableEntry(cls)

addNamedClass
/***********************************************************************
* addNamedClass
* 将 name => cls 添加到命名为 non-meta class map。
* 警告:重复的类名并保留旧的 mapping。
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void addNamedClass(Class cls, const char *name, Class replacing = nil)
{
    lockdebug::assert_locked(&runtimeLock);
    Class old;
    
    // 根据 name 查找对应的类(swift 类除外),
    // 其中会在 NXMapTable *gdb_objc_realized_classes 中查找 和 dyld shared cache 的表中查找,
    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 {
        // 把 cls 和 name 插入到 gdb_objc_realized_classes 中去
        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());
}
  • 结论:

    ①. addNamedClass 函数是把 name => cls 添加到命名为非元类的map中去。

    ②. addNamedClass函数内部则是首先if ((old = getClassExceptSomeSwift(name)) && old != replacing)是根据nameNXMapTable *gdb_objc_realized_classesdyld shared cache 的表中去查找对应对类,如果未找到的话则把 cls 插入到 gdb_objc_realized_classes 中去。

NXMapInsert

NXMapInsert函数是把clsname插入到NXMapTable *gdb_objc_realized_classes中去。

/* This module allows hashing of arbitrary associations [key -> value]. 
 * 该模块允许对任意关联 [key -> value] 进行哈希化。 
 * Keys and values must be pointers or integers, and client is responsible for allocating/deallocating this data. 
 * 键和值必须是指针或整数,client 负责 allocating/deallocating 这些数据。 
 * A deallocation call-back is provided. 
 * 提供 deallocation call-back。 
 * NX_MAPNOTAKEY (-1) is used internally as a marker, and therefore keys must always be different from -1. 
 * NX_MAPNOTAKEY (-1) 在内部用作标记,因此 keys 必须始终不同于 -1。
 * As well-behaved scalable data structures, hash tables double in size when they start becoming full, 
 * 作为行为良好的可扩展数据结构,哈希表在开始变满时大小会增加一倍,从而保证平均恒定时间访问和线性大小。 
 * thus guaranteeing both average constant time access and linear size. 
 */ 

// 这里是 NXMapTable 的结构
typedef struct _NXMapTable {
    /* private data structure; may change */
    const struct _NXMapTablePrototype * _Nonnull prototype;
    unsigned count;
    unsigned nbBucketsMinusOne;
    void * _Nullable buckets;
} NXMapTable OBJC_MAP_AVAILABILITY;

typedef struct OBJC_MAP_AVAILABILITY _NXMapTablePrototype {
    unsigned (* _Nonnull hash)(NXMapTable * _Nonnull,
                                  const void * _Nullable key);
    int (* _Nonnull isEqual)(NXMapTable * _Nonnull,
                                     const void * _Nullable key1,
                                     const void * _Nullable key2);
    void (* _Nonnull free)(NXMapTable * _Nonnull,
                                  void * _Nullable key,
                                  void * _Nullable value);
    int style; /* reserved for future expansion; currently 0 */
} NXMapTablePrototype OBJC_MAP_AVAILABILITY;

void *NXMapInsert(NXMapTable *table, const void *key, const void *value) {
    // 取得 table 的 buckets 成员变量
    MapPair	*pairs = (MapPair *)table->buckets;
    
    // 调用 table->prototype->hash 函数计算 key 在 table 中的哈希值
    unsigned	index = bucketOf(table, key);
    
    // 取得 index 位置的 MapPair
    MapPair	*pair = pairs + index;
    
    // key 不能等于 -1,-1 是保留值
    if (key == NX_MAPNOTAKEY) {
	_objc_inform("*** NXMapInsert: invalid key: -1\n");
	return NULL;
    }
    
    // buckets 长度 
    unsigned numBuckets = table->nbBucketsMinusOne + 1;

    // 上面如果根据 key 的哈希值取得的 pair,该 pair 的 key 是 -1,则表示该位置还没有存入东西,
    // 则把 key 和 value 存在这里,如果当前 table 存储的数据已经超过了其容量的 3 / 4,则进行扩容并重新哈希化里面的数据
    if (pair->key == NX_MAPNOTAKEY) {
	pair->key = key; pair->value = value;
	table->count++;
	if (table->count * 4 > numBuckets * 3) _NXMapRehash(table);
	return NULL;
    }
    
    // 如果 pair 的 key 和入参 key 相同,则表示 key 已经存在于 table 中(则更新 value)
    if (isEqual(table, pair->key, key)) {
        // 取得 pair 的 value,即旧值
	const void	*old = pair->value;
        
        // 如果旧值和入参新值 value 不同,则把 value 赋值给 pair 的 value
	if (old != value) pair->value = value;/* avoid writing unless needed! */
        // 把旧值 old 返回
	return (void *)old;
    } else if (table->count == numBuckets) {
	/* no room: rehash and retry */
        // 扩容并重新哈希旧数据
	_NXMapRehash(table);
        // 再尝试插入 [key value]
	return NXMapInsert(table, key, value);
    } else {
        // 如果进了这里,则表示是产生了哈希碰撞
        // 用 index2 记录下入参 key 在 table 中的哈希值
	unsigned	index2 = index;
        
         // 开放寻址法,nextInde 函数则是:(index + 1) & table->nbBucketsMinusOne,
        // 直到 index2 等于 index,当 index2 等于 index 时表示寻址一遍了,都没有找到位置。
	while ((index2 = nextIndex(table, index2)) != index) {
            // 取得 index2 的 pair
	    pair = pairs + index2;
            // 如果 pair 的 key 值是 -1,即表示为 [key value] 找到了一个空位置
	    if (pair->key == NX_MAPNOTAKEY) {
                // 找到了空位,则把 [key value] 放入
		pair->key = key; pair->value = value;
                // count 自增
		table->count++;
                // 然后判断是否需要扩容,并把旧数据进行重新哈希化
		if (table->count * 4 > numBuckets * 3) _NXMapRehash(table);
		return NULL;
	    }
            
            // 找到一个相同的 key,更新 value,并把旧值返回
	    if (isEqual(table, pair->key, key)) {
		const void	*old = pair->value;
		if (old != value) pair->value = value;/* avoid writing unless needed! */
		return (void *)old;
	    }
	}
        
        // 不可能发生这里,如果执行到了这里表示哈希表出错了,即 NXMapInsert 出现了 bug
	/* no room: can't happen! */
	_objc_inform("**** NXMapInsert: bug\n");
	return NULL;
    }
}
  • 结论:

    上面就是一个普通的哈希表插入的操作,最终将类的名字跟地址进行关联存储到NXMapTable中了。

addClassTableEntry

addClassTableEntry函数,将cls添加到全局的类表中。如果addMetatrue,则也会把cls的元类添加到全局的类表中。

/***********************************************************************
* addClassTableEntry
* 将 cls 添加到全局的类表中。
* 如果 addMeta 参数为 true,则也会把 cls 的元类添加到全局的类表中。
* Locking: runtimeLock must be held by the caller.
**********************************************************************/
static void
addClassTableEntry(Class cls, bool addMeta = true)
{
    lockdebug::assert_locked(&runtimeLock);

    // 允许此类通过 shared cache 或 data segments 成为已知类,但不允许已经在动态表(dynamic table)中的类。
    // 首先我们再看一眼 allocatedClasses,它是 objc 命名空间中的一个静态的 ExplicitInitDenseSet<Class> 类型,
    // 里面装的是 Class 的一个 set。(其中包括类和元类)
    // A table of all classes (and metaclasses) which have been allocated with objc_allocateClassPair.
    
    // namespace objc {
    //  static ExplicitInitDenseSet<Class> allocatedClasses;
    // }
    
    // 然后在上面 runtime_init 函数中,调用了 objc::allocatedClasses.init();
    // 对其进行了初始化。
    auto &set = objc::allocatedClasses.get();

    // 在 set 中找到 cls 的话触发断言
    ASSERT(set.find(cls) == set.end());

    // isKnownClass 函数,如果 runtime 知道该类,则返回 true,当以下情况时返回 true:
    // 1. cls 位于 shared cache
    // 2. cls 在加载 image 的 data segment 内
    // 3. cls 已用 obj_allocateClassPair 分配 
    
    // 此操作的结果会缓存在类的 cls->data()->witness 中,
    // 即我们的 class_rw_t 结构体的 witness 成员变量。
    // struct class_rw_t {
    //   ...
    //   uint16_t witness;
    //   ...
    // }

    if (!isKnownClass(cls))
        // 把 cls 添加到 set 中
        set.insert(cls);
        
    // addMeta 为 true 时,则把 cls->ISA() 即 cls 的元类也添加到 set 中
    if (addMeta)
        // 看到这里递归调用 addClassTableEntry 且把 addMeta 参数置为 false
        addClassTableEntry(cls->ISA(), false);
}
  • 结论:

    addClassTableEntry 函数已经注释的超级清晰了,这里就不展开描述了。

    // 修复重新映射的类
    // Class list类列表和nonlazy class list非懒类列表未被添加。
    // Class refs类引用和super refs父引用被重新映射以用于消息调度。
    // 主要是修复重映射 classes,!noClassesRemapped() 在这里为 false,所以一般走不进来,
    // 将未映射 class 和 super class 重映射,被 remap 的类都是非懒加载的类
    if (!noClassesRemapped()) {
        for (EACH_HEADER) {
            // GETSECT(_getObjc2ClassRefs, Class, "__objc_classrefs");
            // 获取 __objc_classrefs 区中的类引用
            Class *classrefs = _getObjc2ClassRefs(hi, &count);
            
            // 遍历 classrefs 中的类引用,如果类引用已被重新分配或者是被忽略的弱链接类,
            // 就将该类引用重新赋值为从重映射类表中取出新类
            for (i = 0; i < count; i++) {
            // 重映射类,修复class ref,以防所引用的类已重载或是一个忽略 weak-linked弱链接类。
                remapClassRef(&classrefs[i]);
            }

            // 修复为什么测试future1没有发现这一点?
            // GETSECT(_getObjc2SuperRefs, Class, "__objc_superrefs");
            // 获取 __objc_superrefs 区中的父类引用
            classrefs = _getObjc2SuperRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[i]);
            }
        }
    }

    // 这里打印修复重映射 classes 用的时间
    ts.log("IMAGE TIMES: remap classes");
  1. 这里是重映射类。重点通过remapClassRef方法。
#if SUPPORT_FIXUP
    // 修复旧的objc_msgSend_fixup调用站点
    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");
#endif
  1. 部分消息重映射,这部分为适配低版本,可以直接忽略。
    // 发现协议。修复protocol refs协议参考。
    for (EACH_HEADER) {
        extern objc_class OBJC_CLASS_$_Protocol;
        Class cls = (Class)&OBJC_CLASS_$_Protocol;
        ASSERT(cls);
        
        // 创建一个长度是 16 的 NXMapTable
        NXMapTable *protocol_map = protocols();
        bool isPreoptimized = hi->hasPreoptimizedProtocols();

        // 跳过读取协议protocols,如果这是共享缓存中的镜像image来的
        // 并且我们支持起源。
        // 注意,在启动之后,我们确实需要遍历protocol协议,
        // 因为共享缓存中的协议用isCanonical()标记,
        // 如果选择了一些非共享缓存二进制文件作为规范定义,则可能不正确
        if (launchTime && isPreoptimized) {
            if (PrintProtocols) {
                _objc_inform("PROTOCOLS: Skipping reading protocols in image: %s",
                             hi->fname());
            }
            continue;
        }

        bool isBundle = hi->isBundle();

        // GETSECT(_getObjc2ProtocolList, protocol_t * const, "__objc_protolist"); 
        // 获取 hi 的 __objc_protolist 区下的 protocol_t
        protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
        for (i = 0; i < count; i++) {
            readProtocol(protolist[i], cls, protocol_map,
                         isPreoptimized, isBundle);

        }
    }

    // 这里打印发现并修正 protocols 用的时间
    ts.log("IMAGE TIMES: discover protocols");

    // 修复@protocol参考
    // 重新优化的镜像images可能已经有了正确的应答了,但我们不确定。
    for (EACH_HEADER) {
        // 在启动时,我们知道preoptimized image refs预优化的镜像引用 指向协议的 共享缓存定义。
        // 我们可以在启动时跳过检查,但必须访问@protocol refs引用以获取稍后加载的shared cache images共享缓存镜像。
        if (launchTime && hi->isPreoptimized())
            continue;
        protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
        for (i = 0; i < count; i++) {
            // 修复 protocol ref,以防 protocol referenced 已重新分配。
            remapProtocolRef(&protolist[i]);
        }
    }

    // 这里打印 @protocol references 用的时间
    ts.log("IMAGE TIMES: fix up @protocol references");
  1. protocols添加到protocols_map中,并重映射加载协议,并修成协议,rebase
    // 下面把 category 的数据追加到原类中去!超重要....(这个在 category 里面有详细的梳理,这里就不展开了) 
    
    // 发现Categories分类。只有在完成initial category attachment初始分类附件后才能执行此操作。
    // 对于启动时存在的分类,发现将延迟到对_dyld_objc_notify_register的调用
    // 完成后的第一个load_images调用。
    // rdar://problem/53119145
    if (didInitialAttachCategories) {
        for (EACH_HEADER) {
            load_categories_nolock(hi);
        }
    }

     // 这里打印 Discover categories. 用的时间
    // 对于启动时出现的 categories,discovery 被推迟到 _dyld_objc_notify_register 调用完成后的第一个 load_images 调用。
    // 所以这里 if 里面的 category 数据加载是不会执行的。
    ts.log("IMAGE TIMES: discover categories");
  1. 这里需要注意:didInitialAttachCategories只会在load_images中设置为true,也就是说只有执行过load_images,这里才会遍历load_categories_nolock
   
    
    // 当其他线程在该线程完成修复之前调用新的类别代码时,
    // 分类Category发现必须延迟,以避免潜在的竞争。
    // +load 被 prepare_load_methods()处理
    // 实现non-lazy非懒加载类(用于+load方法和静态实例)
    for (EACH_HEADER) {
        // GETSECT(_getObjc2NonlazyClassList, classref_t const, "__objc_nlclslist");
        // 获取 hi 的 __objc_nlclslist 区中的非懒加载类(即实现了 +load 函数的类)
        classref_t const *classlist = hi->nlclslist(&count);
        for (i = 0; i < count; i++) {
            // 重映射类, 获取正确的类指针
            Class cls = remapClass(classlist[i]);
            
            if (!cls) continue;
            
             // static void addClassTableEntry(Class cls, bool addMeta = true) { ... }
            // 将一个类添加到用来存储所有类的全局的 set 中(auto &set = objc::allocatedClasses.get();)。
            // 如果 addMeta 为 true(默认为 true),也自动添加类的元类到这个 set 中。
            // 这个类可以通过 shared cache 或 data segments 成为已知类,但不允许已经在 dynamic table 中。
            
            // allocatedClasses 是 objc 命名空间中的一个静态变量。
            // A table of all classes (and metaclasses) which have been allocated with objc_allocateClassPair.
            // 已使用 objc_allocateClassPair 分配空间的存储所有 classes(和 metaclasses)的 Set。
            // namespace objc {
            //     static ExplicitInitDenseSet<Class> allocatedClasses;
            // }
            
            // 先把 cls 放入 allocatedClasses 中,然后递归把 metaclass 放入 allocatedClasses 中。
            // 把类cls放进allocatedClasses 这个表中,该表在runtime_init的时候初始化
            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());
                }
                
                // 修复:还不允许可重定位类
                // 也禁止 relocatable classes 我们不能因为像 Swift.__EmptyArrayStorage 这样的类而禁止所有 Swift 类
            }
            
            // 实现 Swift 之外的 classes
            // 对类 cls 执行首次初始化,包括分配其读写数据。不执行任何 Swift 端初始化。返回类的真实类结构。
            
            // 大概是设置 ro rw 和一些标识位的过程,也包括递归实现父类(supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);)
            // 和元类(metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);),
            // 然后更新 cls 的父类和元类(cls->superclass = supercls; cls->initClassIsa(metacls);),
            // 将 cls 连接到其父类的子类列表(addSubclass(supercls, cls);)(操作 class_rw_t 的 Class firstSubclass; 和 Class nextSiblingClass; 两个成员变量),
            // 修正 cls 的方法列表、协议列表和属性列表,
            // 以及最后的附加任何未完成的 categories(主要包含 method list、protocol list、property list)
            //(objc::unattachedCategories.attachToClass)。
            realizeClassWithoutSwift(cls, nil);
        }
    }

    // 这里打印 Realize non-lazy classes 用的时间
    ts.log("IMAGE TIMES: realize non-lazy classes");
    
    // 实现newly-resolved新解析的future classes未来类,以防CF操纵它们
    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");
            }
            // 加载类(非swift类)
            realizeClassWithoutSwift(cls, nil);
            
            // 将此类及其所有子类标记为需要原始 isa 指针
            cls->setInstancesRequireRawIsaRecursively(**false**/*inherited*/);
        }
        free(resolvedFutureClasses);
    }

    ts.log("IMAGE TIMES: realize future classes");
  1. 其中realizeClassWithoutSwift的实现中,对rw进行了操作。

realizeClassWithoutSwift

/**********************************************************************
* realizeClassWithoutSwift
* 对类cls执行首次初始化,包括分配其read-write读写数据。
* 不执行任何Swift端初始化。
* **返回** 该类的真实类结构。
* Locking: runtimeLock必须被改方法的调用者写入锁定write-locked
************************************************************************ ** **/
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    lockdebug::assert_locked(&runtimeLock);

    class_rw_t *rw;
    Class supercls;
    Class metacls;

    if (!cls) return nil;
    // -----------------重点------------------
    // 如果该类已经实现了就直接返回
    if (cls->isRealized()) {
        validateAlreadyRealizedClass(cls);
        return cls;
    }
    ASSERT(cls == remapClass(cls));

    // fixme 修复验证类不在共享缓存的un-dlopened部分中的问题?
    // -----------------重点------------------
    // 读取class的data()
    auto ro = cls->safe_ro();
    auto isMeta = ro->flags & RO_META;
    // 判断是否是元类
    if (ro->flags & RO_FUTURE) {
    // -----------------重点------------------
        // 这是未来类。rw数据已分配。
        // 脏内存赋值
        rw = cls->data();
        ro = cls->data()->ro();
        ASSERT(!isMeta);
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
    // -----------------重点------------------
        // 这里将数据读取进来,也赋值完毕了
        // 不是元类的话,这里把ro数据复制到rw
        // 普通类。分配可写类数据
        rw = objc::zalloc<class_rw_t>(); // 申请开辟空间 --rw
        rw->set_ro(ro);
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        cls->setData(rw);
    }

    cls->cache.initializeToEmptyOrPreoptimizedInDisguise();

#if FAST_CACHE_META
    if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif

    // 为此类选择索引。
    // 如果索引不再可用,则设置cls->instancesRequireRawIsa
    cls->chooseClassArrayIndex();

    if (PrintConnecting) {
        _objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
                     cls->nameForLogging(), isMeta ? " (meta)" : "",
                     (void*)cls, ro, cls->classArrayIndex(),
                     cls->isSwiftStable() ? "(swift)" : "",
                     cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
    }

    // 实现父类superclass和元类metaclass,如果它们还没有实现的话。
    // 这需要在上面为根类设置RW_REALIZED之后完成。
    // 这需要在为根元类选择类索引之后完成。
    // 这假设这些类都没有Swift内容,
    // 或者Swift的初始化器已经被调用。
    // 修正了如果我们添加对Swift类的ObjC子类的支持,这个假设将是错误的。
// -----------------重点------------------    
    // 递归调用realizeClassWithoutSwift完善继承链,并处理当前类的父类、元类
    // 递归实现 设置当前类、父类、元类的rw、主要目的是确定继承链(类继承链、元类继承链)
    // 实现元类、父类
    // 当isa找到根元类后,根元类的isa指向自己,因此不会返回nil
    // 如果该类已经被加载过了,则会直接返回该类,因此不会有死循环。也保证了类只加载一次
    supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

#if SUPPORT_NONPOINTER_ISA
    // 如果是元类的话,设置为non pointer ISA
    if (isMeta) {
        // Metaclasses元类不需要非指针ISA的任何功能
        // 这允许为objc_retain/objc_release中的类提供faspath。
        cls->setInstancesRequireRawIsa();
    } else {
        // 对某些类和/或平台禁用非指针non-pointer isa。
        // 设置 instancesRequireRawIsa.
        bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
        bool rawIsaIsInherited = false;
        static bool hackedDispatch = false;

        if (DisableNonpointerIsa) {
            // 非指针Non-pointer isa被环境或应用程序SDK版本禁用
            // 如果做了环境变量相关的配置,这里不管是不是元类,instancesRequireRawIsa 都会赋值为 true
            instancesRequireRawIsa = true;
        }
        else if (!hackedDispatch  &&  0 == strcmp(ro->getName(), "OS_object"))
        {
            // hack for libdispatch et al - isa also acts as vtable pointer
            hackedDispatch = true;
            instancesRequireRawIsa = true;
        }
        else if (supercls  &&  supercls->getSuperclass()  &&
                 supercls->instancesRequireRawIsa())
        {
            // 这也通过addSubclass()传播,
            // 但nonpointer isa安装程序需要它。
            // 特殊情况:instancesRequireRawIsa不会从根类传播到根元类
            instancesRequireRawIsa = true;
            rawIsaIsInherited = true;
        }

        if (instancesRequireRawIsa) {
            cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
        }
    }

// SUPPORT_NONPOINTER_ISA
#endif
// -----------------重点------------------
    // 在重新映射时,设置父类superclass和元类metaclass
    // 将父类和元类赋值给当前类,分别是isa和父类的对应值
    cls->setSuperclass(supercls);
    cls->initClassIsa(metacls);

    // 协调实例变量offsets偏移/layout布局。
    // 这可能会重新分配class_ro_t,更新ro变量。
    if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

    // 如果尚未设置fastInstanceSize,请设置它。
    cls->setInstanceSize(ro->instanceSize);
、
    // 将一些标志从ro复制到rw
    if (ro->flags & RO_HAS_CXX_STRUCTORS) {
        cls->setHasCxxDtor();
        if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
            cls->setHasCxxCtor();
        }
    }

    // 传播associated objects关联对象禁用标志从ro或从superclass父类。
    if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
        (supercls && supercls->forbidsAssociatedObjects()))
    {
        rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
    }

// -----------------重点------------------
    // 双向链表指向关系,父类中可以找到子类,子类中也可以找父类
    // 通过addSubclass把当前类放到父类的子类列表中去
    // 将该类连接到其superclass父类的subclass子类列表
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    // 附加分类category
    methodizeClass(cls, previously);
    return cls;
}
  • 结论:

    初看该函数,内容很多,但大体上可以分成以下几部分操作

    • 读取data数据,并设置rorw
    • 递归调用realizeClassWithoutSwift完善继承链
    • 处理方法、属性、协议列表等;
methodizeClass
  • 源码:
/***********************************************************************
* methodizeClass
* Fixes up cls's method list, protocol list, and property list.
* Attaches any outstanding categories.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void methodizeClass(Class cls, Class previously)
{
    lockdebug::assert_locked(&runtimeLock);

    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro();
    auto rwe = rw->ext();

    // Methodizing for the first time
    if (PrintConnecting) {
        _objc_inform("CLASS: methodizing class '%s' %s",
                     cls->nameForLogging(), isMeta ? "(meta)" : "");
    }

    // Install methods and properties that the class implements itself.
    method_list_t *list = ro->baseMethods;
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
        if (rwe) rwe->methods.attachLists(&list, 1);
    }

    property_list_t *proplist = ro->baseProperties;
    if (rwe && proplist) {
        rwe->properties.attachLists(&proplist, 1);
    }

    protocol_list_t *protolist = ro->baseProtocols;
    if (rwe && protolist) {
        rwe->protocols.attachLists(&protolist, 1);
    }

    // Root classes get bonus method implementations if they don't have
    // them already. These apply before category replacements.
    if (cls->isRootMetaclass()) {
        // root metaclass
        addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
    }

    // Attach categories.
    if (previously) {
        if (isMeta) {
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_METACLASS);
        } else {
            // When a class relocates, categories with class methods
            // may be registered on the class itself rather than on
            // the metaclass. Tell attachToClass to look for those.
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_CLASS_AND_METACLASS);
        }
    }
    objc::unattachedCategories.attachToClass(cls, cls,
                                             isMeta ? ATTACH_METACLASS : ATTACH_CLASS);

#if DEBUG
    // Debug: sanity-check all SELs; log method list contents
    for (const auto& meth : rw->methods()) {
        if (PrintConnecting) {
            _objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-',
                         cls->nameForLogging(), sel_getName(meth.name()));
        }
        ASSERT(sel_registerName(sel_getName(meth.name())) == meth.name());
    }
#endif
}

    // OPTION( DebugNonFragileIvars, OBJC_DEBUG_NONFRAGILE_IVARS, "capriciously rearrange non-fragile ivars")
    //(反复无常地重新排列非脆弱的 ivars)
    // 如果开启了 OBJC_DEBUG_NONFRAGILE_IVARS 这个环境变量,则会执行 realizeAllClasses() 函数,
    // Non-lazily realizes 所有已知 image 中所有未实现的类。(即对已知的 image 中的所有类:懒加载和非懒加载类全部进行实现)

    // OBJC_DEBUG_NONFRAGILE_IVARS 设置
    if (DebugNonFragileIvars) {
        realizeAllClasses();
    }

    // 打印优化前统计信息
    // OBJC_PRINT_PREOPTIMIZATION 设置YES打印
    if (PrintPreopt) {
        static unsigned int PreoptTotalMethodLists;
        static unsigned int PreoptOptimizedMethodLists;
        static unsigned int PreoptTotalClasses;
        static unsigned int PreoptOptimizedClasses;
        for (EACH_HEADER) {
            if (hi->hasPreoptimizedSelectors()) {
                _objc_inform("PREOPTIMIZATION: honoring preoptimized selectors "
                             "in %s", hi->fname());
            }
            else if (hi->info()->optimizedByDyld()) {
                _objc_inform("PREOPTIMIZATION: IGNORING preoptimized selectors "
                             "in %s", hi->fname());
            }

            classref_t const *classlist = _getObjc2ClassList(hi, &count);
            for (i = 0; i < count; i++) {
                Class cls = remapClass(classlist[i]);
                if (!cls) continue;

                PreoptTotalClasses++;
                if (hi->hasPreoptimizedClasses()) {
                    PreoptOptimizedClasses++;
                }

                const method_list_t *mlist;
                if ((mlist = cls->bits.safe_ro()->baseMethods)) {
                    PreoptTotalMethodLists++;
                    if (mlist->isFixedUp()) {
                        PreoptOptimizedMethodLists++;
                    }
                }
                if ((mlist = cls->ISA()->bits.safe_ro()->baseMethods)) {
                    PreoptTotalMethodLists++;
                    if (mlist->isFixedUp()) {
                        PreoptOptimizedMethodLists++;
                    }
                }
            }
        }

        _objc_inform("PREOPTIMIZATION: %zu selector references not "
                     "pre-optimized", UnfixedSelectors);
        _objc_inform("PREOPTIMIZATION: %u/%u (%.3g%%) method lists pre-sorted",
                     PreoptOptimizedMethodLists, PreoptTotalMethodLists,
                     PreoptTotalMethodLists
                     ? 100.0*PreoptOptimizedMethodLists/PreoptTotalMethodLists
                     : 0.0);
        _objc_inform("PREOPTIMIZATION: %u/%u (%.3g%%) classes pre-registered",
                     PreoptOptimizedClasses, PreoptTotalClasses,
                     PreoptTotalClasses
                     ? 100.0*PreoptOptimizedClasses/PreoptTotalClasses
                     : 0.0);
        _objc_inform("PREOPTIMIZATION: %zu protocol references not "
                     "pre-optimized", UnfixedProtocolReferences);
    }

#undef EACH_HEADER
}

  1. 这里是一些其他的打印信息。可以设置环境变量,打印辅助debug

四、load_images的解析

之前的文章中其实已经分析过调起+load函数的流程,这里我们就再梳理一遍load_images的流程。 概括的说load_images函数就是用来调用类以及分类中的+load函数的(仅限于实现了 +load 函数的类或者分类)。

/***********************************************************************
* load_images
* 在 dyld 映射的给定 images 中处理 +load。
*
* Locking: write-locks runtimeLock and loadMethodLock
**********************************************************************/
// 下面是两个外联函数,一个用来判断 image 中是否有 load 函数,
// 另一个用来收集 image 中的 load 函数,然后后面会统一调用
extern bool hasLoadMethods(const headerType *mhdr);
extern void prepare_load_methods(const headerType *mhdr);
void 
load_images(const char *path __unused, const struct mach_header *mh)
{
    // didInitialAttachCategories 标记加载分类的,默认值为 false,
    // didCallDyldNotifyRegister 标记 _dyld_objc_notify_register 是否调用完成,
    // 此时为 false,所以暂时此 if 内部不会执行。
    if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
        didInitialAttachCategories = true;
        // 加载所有的分类
        loadAllCategories();
    }

    // 如果这里没有+load方法,则返回而不使用锁。
    // hasLoadMethods 函数是根据 `headerType *mhdr` 的 `__objc_nlclslist` 区和 `__objc_nlcatlist` 区中是否有数据,
    // 来判断是否有 +load 函数要执行。(即是否包含非懒加载类和非懒加载分类) 
    if (!hasLoadMethods((const headerType *)mh)) return;
    
    // loadMethodLock 是一把递归互斥锁(加锁)
    recursive_mutex_locker_t lock(loadMethodLock);

    // 找到load方法
    {
        // runtimeLock 加锁
        mutex_locker_t lock2(runtimeLock);
        // 获取所有要调用的 +load 方法
        prepare_load_methods((const headerType *)mh);
    }

    // 调用获取到的所有 +load 方法 (without runtimeLock - re-entrant)
    call_load_methods();
}
  • 结论:

    ①. 通过变量didInitialAttachCategoriesdidCallDyldNotifyRegister,来判断是否loadAllCategories加载所有分类

    ②. prepare_load_methods找到load方法

    ③. call_load_methods调用load方法

didInitialAttachCategories

didInitialAttachCategories 是一个定义在objc/Source/objc-runtime-new.mm文件中的静态全局变量,默认值为 false

  1. 全局仅在 load_images 函数的起始处 if 内部被赋值为 true,然后就一直为 true 了,就不会再进入该 if 了。

  2. didInitialAttachCategories 直白一点理解的话它即是用来标记 loadAllCategories(); 函数有没有被调用过的。 (即用来标记分类是否加载过用的)

  • 源码:
/***********************************************************************
* didInitialAttachCategories
* Whether the initial attachment of categories present at startup has been done.
* 是否已完成启动时出现的 categories 的初始附加。
**********************************************************************/
static bool didInitialAttachCategories = false;
  • 结论:

    didInitialAttachCategories就是个是否加载过分类的标识变量。

didCallDyldNotifyRegister

didCallDyldNotifyRegister 是一个定义在objc/Source/objc-runtime-new.mm文件中的全局变量,默认值为 false,用来标记 _dyld_objc_notify_register 是否已经完成。

  • 源码:
/***********************************************************************
* didCallDyldNotifyRegister
* Whether the call to _dyld_objc_notify_register has completed.
* 此全局变量用来标记 _dyld_objc_notify_register 是否已经完成。
**********************************************************************/
bool didCallDyldNotifyRegister = false;

  1. objc/Project Headers/objc-private.h中通过externdidCallDyldNotifyRegister 声明为一个外联变量,来给外部使用。
  • 源码:
#if __OBJC2__
extern bool didCallDyldNotifyRegister;
#endif
  1. 然后最后是在objc/Source/objc-os.mm中的 _objc_init 函数内,在执行完 _dyld_objc_register_callbacks((_dyld_objc_callbacks*)&callbacks); 函数后,把didCallDyldNotifyRegister置为了 true。
  • 图: 截屏2023-02-11 下午2.19.22.png

hasLoadMethods

根据 headerType *mhdr__objc_nlclslist 区和 __objc_nlcatlist 区中是否有数据,来判断是否有 +load 函数要执行。

  • 源码:
// Quick scan for +load methods that doesn't take a lock.
// 快速扫描不带锁的+load方法。
bool hasLoadMethods(const headerType *mhdr)
{
    size_t count;
    
    // GETSECT(_getObjc2NonlazyClassList, classref_t const, "__objc_nlclslist");
    // 读取 __objc_nlclslist 区中的非懒加载类的列表
    if (_getObjc2NonlazyClassList(mhdr, &count)  &&  count > 0) return true;
    
    // GETSECT(_getObjc2NonlazyCategoryList, category_t * const, "__objc_nlcatlist");
    // 读取 __objc_nlcatlist 区中非懒加载分类的列表
    if (_getObjc2NonlazyCategoryList(mhdr, &count)  &&  count > 0) return true;
    
    return false;
}

Lock management

锁管理,在objc/Source/objc-runtime-new.mm文件的开头处,我们能看到如下几把锁,而其中的递归互斥锁loadMethodLock就是在load_images 中使用的。

  • 源码:
/***********************************************************************
* Lock management锁管理
**********************************************************************/
mutex_t runtimeLock;
mutex_t selLock;
#if CONFIG_USE_CACHE_LOCK
mutex_t cacheUpdateLock;
#endif
recursive_mutex_t loadMethodLock;

prepare_load_methods

prepare_load_methods用来获取所有要调用的+load方法(父类、子类、分类)。

  • 源码:
void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;

    lockdebug::assert_locked(&runtimeLock);
   
    // GETSECT(_getObjc2NonlazyClassList, classref_t const, "__objc_nlclslist");
    // 获取所有 __objc_nlclslist 区的数据(所有非懒加载类) 
    classref_t const *classlist =
        _getObjc2NonlazyClassList(mhdr, &count);
    
    // class +load has been called
    // #define RW_LOADED (1<<23)
    
    // List of classes that need +load called (pending superclass +load)
    // This list always has superclasses first because of the way it is constructed
    // 由于其构造方式,此列表始终首先处理 superclasses 的 +load 函数
    // 需要调用 +load 的 classes 列表
    // static struct loadable_class *loadable_classes = nil;

    // 遍历这些非懒加载类,并将其 +load 函数添加到 loadable_classes 数组中,优先添加其父类的 +load 方法,
    // 用于下面 call_load_methods 函数调用 
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));
    }

    // GETSECT(_getObjc2NonlazyCategoryList, category_t * const, "__objc_nlcatlist");
    // 获取所有 __objc_nlcatlist 区的数据(所有非懒加载分类)
     category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    
    // 遍历这些分类
    for (i = 0; i < count; i++) {
        category_t *cat = categorylist[i];
        Class cls = remapClass(cat->cls);
        
        // 如果没有找到分类所属的类就跳出当前循环,处理数组中的下一个分类
        if (!cls) continue;  // category for ignored weak-linked class
        
        if (cls->isSwiftStable()) {
            _objc_fatal("Swift class extensions and categories on Swift "
                        "classes are not allowed to have +load methods");
        }
        
        // 如果分类所属的类没有实现就先去实现
        realizeClassWithoutSwift(cls, nil);
        
        // 断言
        ASSERT(cls->ISA()->isRealized());
        
        // List of categories that need +load called (pending parent class +load)
        // 需要调用 +load 的 categories 列表
        // static struct loadable_category *loadable_categories = nil;
        
        // 遍历这些分类,并将其 +load 方法添加到 loadable_categories 数组中保存
        add_category_to_loadable_list(cat);
    }
}
  • 结论:

    ①. 主类会循环调用schedule_class_load函数,来添加load方法。

    ②. 分类会循环调用add_category_to_loadable_list函数,来添加load方法。

schedule_class_load

schedule_class_load将其+load函数添加到loadable_classes数组中,优先添加其父类的+load方法。(用于后续的 call_load_methods 函数调用。)

  • 源码:
/***********************************************************************
* prepare_load_methods
* Schedule +load for classes in this image, any un-+load-ed 
* superclasses in other images, and any categories in this image.
**********************************************************************/
// Recursively schedule +load for cls and any un-+load-ed superclasses.
// cls must already be connected.
static void schedule_class_load(Class cls)
{
    // 如果 cls 不存在则 return(下面有一个针对 superclass 的递归调用)
    if (!cls) return;
    // DEBUG 模式下的断言,cls 必须是实现过的(这个在 _read_images 中已经实现了)
    ASSERT(cls->isRealized());  // _read_images should realize
    
    // class +load has been called
    // #define RW_LOADED (1<<23)
    
    // 如果flags为RW_LOADED 说明已经load过,函数返回
    if (cls->data()->flags & RW_LOADED) return;

    // 优先处理 superclass 的 +load 函数
    // 父类递归调用本函数
    schedule_class_load(cls->superclass);

    // static struct loadable_class *loadable_classes = nil;
    // struct loadable_class {
    //    Class cls;  // may be nil
    //    IMP method;
    // };
    
    // 将 cls 的 +load 函数添加到全局的 loadable_class 数组 loadable_classes 中,
    // loadable_class 结构体是用来保存类的 +load 函数的一个数据结构,其中 cls 是该类,method 则是 +load 函数的 IMP,
    // 这里也能看出 +load 函数是不走 OC 的消息转发机制的,它是直接通过 +load 函数的地址调用的!
    add_class_to_loadable_list(cls); // 添加到表里
    
    // 设置flags为RW_LOADED 标识已经load过
    cls->setInfo(RW_LOADED); 
}

add_class_to_loadable_list

/***********************************************************************
* add_class_to_loadable_list
* Class cls has just become connected. Schedule it for +load if
* it implements a +load method.
**********************************************************************/
void add_class_to_loadable_list(Class cls)
{
    IMP method;

    lockdebug::assert_locked(&loadMethodLock);
    // 获取load 方法的imp
    method = cls->getLoadMethod();
    if (!method) return;  // Don't bother if cls has no +load method
    
    if (PrintLoading) {
        _objc_inform("LOAD: class '%s' scheduled for +load", 
                     cls->nameForLogging());
    }
    
    if (loadable_classes_used == loadable_classes_allocated) {
        loadable_classes_allocated = loadable_classes_allocated*2 + 16;
        loadable_classes = (struct loadable_class *)
            realloc(loadable_classes,
                              loadable_classes_allocated *
                              sizeof(struct loadable_class));
    }
    // 将 cls 和 method 赋值到 loadable_classes 表中
    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;
}
  • 结论:

    ①. loadable_classes是数组,数组中的元素是loadable_classloadable_class的数据结构保存类与方法:

    image.png

    ②. clsmethod通过loadable_class结构进行赋值,每增加一个load就会loadable_classes_used++,用来统计load的数量和方便下次的赋值。

add_category_to_loadable_list

add_category_to_loadable_list函数和schedule_class_load大同小异。这里我们只要谨记 +load 函数的加载顺序就好了:父类 -> 子类 -> 分类。

  • 源码:
/***********************************************************************
* add_category_to_loadable_list
* 分类cat的父类存在,并且该分类已附加到其类。
* 在父类连接并调用了自己的+load方法后,将该类别安排为+load。
* Category cat's parent class exists and the category has been attached
* to its class. Schedule this category for +load after its parent class
* becomes connected and has its own +load method called.
**********************************************************************/
void add_category_to_loadable_list(Category cat)
{
    IMP method;

    lockdebug::assert_locked(&loadMethodLock);

    method = _category_getLoadMethod(cat);

    // 如果分类cat没有+load方法,就不用麻烦了
    if (!method) return;

    if (PrintLoading) {
        _objc_inform("LOAD: category '%s(%s)' scheduled for +load", 
                     _category_getClassName(cat), _category_getName(cat));
    }
    
    if (loadable_categories_used == loadable_categories_allocated) {
        loadable_categories_allocated = loadable_categories_allocated*2 + 16;
        loadable_categories = (struct loadable_category *)
            realloc(loadable_categories,
                              loadable_categories_allocated *
                              sizeof(struct loadable_category));
    }
    // 将cls 和method 赋值到loadable_categories 表中
    loadable_categories[loadable_categories_used].cat = cat;
    loadable_categories[loadable_categories_used].method = method;
    loadable_categories_used++;
}
  • 结论:

    ①. 分类load方法添加,和主类的添加方式是一样的,只不过是不同的而已,分类的数组元素是loadable_category,数据结构表示为: image.png

call_load_methods

  • 源码:
void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;
    
    // 加锁
    lockdebug::assert_locked(&loadMethodLock);

    // Re-entrant重入呼叫无效;最外面的调用将完成任务。
    // 如果正在 loading 则 return,
    // 保证当前 +load 方法同时只有一次被调用
    if (loading) return;
    loading = YES;

    // 创建自动释放池
    void *pool = objc_autoreleasePoolPush();

    do {
        // 1. 重复调用类class的+loads,直到没有更多
        while (loadable_classes_used > 0) {
            // 调用 loadable_classes 中的的类的 +load 函数,并且把 loadable_classes_used 置为 0
            call_class_loads();
        }

        // 2. 呼叫Category分类+loads一次
        // 调用 分类中的 +load 函数, 只调用一次 call_category_loads,因为上面的 call_class_loads 函数内部,
        // 已经把 loadable_classes_used 置为 0,所以除非有新的分类需要 +load,即 call_category_loads 返回 true,
        // 否则循环就结束了
        more_categories = call_category_loads();

        // 3. 如果有类或更多未尝试的分类category,则运行更多+loads
    } while (loadable_classes_used > 0  ||  more_categories);
    // 如果 loadable_classes_used 大于 0,或者有更多分类需要调用 +load,则循环继续。(一般 loadable_classes_used 到这里基本就是 0 了)

    // 自动释放池进行 pop
    objc_autoreleasePoolPop(pool);
    
    // 标记处理完成了,可以进行下一个了
    loading = NO;
}
  • 结论:

    ①. do while循环调用call_class_loads方法,先循环遍历类、父类的+load函数。

    ②. do while循环调用call_category_loads方法,来调用分类的load方法。

call_class_loads

  • 源码:
/***********************************************************************
* call_class_loads
* 调用所有挂起的类+load方法。
* 果新类变为可加载,则不会为它们调用+load。
*
* 只被call_load_methods()调用.
************************************************************************ ** **/
static void call_class_loads(void)
{
    int i;

    // 分离当前可加载列表。
    struct loadable_class *classes = loadable_classes;
    int used = loadable_classes_used;
    loadable_classes = nil;
    loadable_classes_allocated = 0;
    loadable_classes_used = 0;

    // 调用分离列表的所有+loads。
    for (i = 0; i < used; i++) {
        Class cls = classes[i].cls;
        load_method_t load_method = (load_method_t)classes[i].method;
        if (!cls) continue; 

        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
        }
        (*load_method)(cls, @selector(load));
    }

    // 销毁分离的列表。
    if (classes) free(classes);
}

五、总结

  1. 其实objc4-886.9在程序启动的时候做了很多重要的操作,上下两篇的篇幅和时间有限,不能将所有内容都详细解释。例如_objc_init的调起流程,是dyld4初始过程中如何链接初始化libSytemlibdispatch,这些都可以一层一层深入阅读,里面操作对程序整理理解非常有用。本篇源码较多,所有objc4都是基于目前最新的866.9版本,而dyld4是基于1042.1版本。

  2. dyld4的简化流程 截屏2023-02-06 下午10.48.12.png