本篇文章将探求上一篇应用程序的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 中装备。

  • 图:

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

  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_OPTIONS list whitch options are set 输出OBJC已设置的选项
OBJC_PRINT_IMAGES log image and library names as they are loaded 打印现已加载loaded的镜像imag信息
OBJC_PRINT_LOAD_METHODS log calls to class and category +load methods 打印Class和Category的 +load 办法的调用信息
OBJC_PRINT_INITIALIZE_METHODS log calls to class +initialize methods 打印Class的+initialize办法的调用信息
OBJC_PRINT_RESOLVED_METHODS log methods created by +resolveClassMethod: and +resolveInstanceMethod: 打印经过音讯慢速查找后的动态解析复写的+resolveClassMethod: 和 +resolveInstanceMethod:
OBJC_PRINT_CLASS_SETUP log progress of class and category setup 打印Class和Category的设置进程
OBJC_PRINT_PROTOCOL_SETUP log progress of protocol setup 打印Protocol的设置进程
OBJC_PRINT_IVAR_SETUP log progress of non-fragile ivars 打印非易脆的Ivar的设置进程
OBJC_PRINT_VTABLE_SETUP log processing of class vtables 打印类vtable的设置进程
OBJC_PRINT_VTABLE_IMAGES print vatble images showing overridden methods 打印vtable被掩盖的办法
OBJC_PRINT_CACHE_SETUP log processing of methods caches 打印办法缓存的设置进程
OBJC_PRINT_FUTURE_CLASSES log use of future classes for toll-free bridging 打印从CFType无缝转换到NSObject将要运用的类(如CFArrayRef到NSArray *)
OBJC_PRINT_PREOPTIMIZATION log preoptimization courtesy of dyld shared cache 打印dyld同享缓存优化前的问候语
OBJC_PRINT_CXX_CTORS log calls to C++ ctors and dtors for instance variables 打印类实例中的C++目标的结构与析构调用
OBJC_PRINT_EXCEPTIONS log exception handing 打印反常处理
OBJC_PRINT_EXCEPTION_THROW log backtrace of every objc_exception_thorow() 打印一切反常抛出时的回溯objc_exception_thorow()
OBJC_PRINT_ALT_HANDLERS log processing of exception alt handlers 打印alt操作反常处理
OBJC_PRINT_REPLACED_METHODS log methods replace by category implementations 打印被Category替换的办法
OBJC_PRINT_DEPRECATION_WARNINGS warn about calls to deprecated runtime functions 打印正告一切过时的办法调用
OBJC_PRINT_POOL_HIGHWATER log hight-water marks for autorelease pools 打印autoreleasepool高水位正告
OBJC_PRINT_CUSTOM_CORE log classes with custom core methods 打印含有自界说core内核办法的类
OBJC_PRINT_CUSTOM_RR log classes with custom retain/release methods 打印含有未优化的自界说retain/release办法的类
OBJC_PRINT_CUSTOM_AWZ log classes with custom allocWithZone methods 打印含有未优化的自界说allocWithZone办法的类
OBJC_PRINT_RAW_ISA log classes that require raw pointer isa fields 打印需求拜访原始isa指针的类
OBJC_DEBUG_UNLOAD warn about poorly-behaving bundles when unloaded 卸载有不良行为的Bundle时打印正告
OBJC_DEBUG_FRAGILE_SUPERCLASSES warn about subclasses that may have been broken by subsequent changes to superclasses 当子类或许被对父类的批改破坏时打印正告
OBJC_DEBUG_NIL_SYNC warn about @synchronized(nil), which does no synchronization 正告@synchronized(nil)调用,这种状况不会加锁
OBJC_DEBUG_NONFRAGILE_IVARS capriciously rearrange non- fragile ivars 打印突发地从头布置non-fragile ivars的行为
OBJC_DEBUG_ALT_HANDLERS record more info about bad alt handler use 记录更多的alt操作过错信息
OBJC_DEBUG_MISSING_POOLS warn about autorelease with no pool in place, which may be a leak 正告没有pool的状况下运用autorelease,肯内存泄漏
OBJC_DEBUG_POOL_ALLOCATION halt when autorelease pools are popped out of order, and allow heap debugges to track 当主动开释池按次序弹出时中止,并答应堆调试跟踪
OBJC_DEBUG_DUPLICATE_CLASSES halt when multiple classes with the same name are present 当呈现类重名时停机
OBJC_DEBUG_DONT_CRASH halt the process by exiting instead of crashing. 经过退出而不是溃散来中止进程。
OBJC_DEBUG_POOL_DEPTH log fault when at least a set number of autorelease pages has been allocated 至少分配了必定数量的主动开释页面时产生日志过错
OBJC_DEBUG_SCRIBBLE_CACHES scribble the IMPs in freed method caches 在开释的办法缓存中乱写IMPs
OBJC_DEBUG_SCAN_WEAK_TABLES can 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_VATABLES disable vatble dispatch 封闭vatable分发
OBJC_DISABLE_PREOPTIMIZATION disable preoptimization courtesy of dyld shared cache 封闭dyld同享缓存优化前的问候语
OBJC_DISABLE_TAGGED_POINTERS disable tagged pointer optimization of NSNumber et al. 封闭NSNumber等的tagged pointer优化
OBJC_DISABLE_TAG_OBFUSCATION disable obfuscation of tagged pointers 禁用tagged pointer符号指针的模糊处理
OBJC_DISABLE_NONPOINTER_ISA disable non-pointer isa fields 封闭 non-pointer isa 字段的拜访
OBJC_DISABLE_INITIALIZE_FORK_SAFETY disable safety checks for +initialize after fork 在fork之后禁用+initialize的安全查看
OBJC_DISABLE_FAULTS disable os faults 禁用操作系统故障
OBJC_DISABLE_PREOPTIMIZED_CACHES disable preoptimized caches 禁用预优化的缓存
OBJC_DISABLE_AUTORELEASE_COALESCING disable coalescing of autorelease pool pointers 禁用主动开释池指针的兼并
OBJC_DISABLE_AUTORELEASE_COALESCING_LRU disable coalescing of autorelease pool pointers using look back N strategy 运用回溯N战略禁用主动开释池指针的兼并
OBJC_DISABLE_CLASSRX_SIGNING_ENFORCEMENT disable class_rx_t pointer signing enforcement 禁用class_rx_t指针签名强制
OBJC_DEBUG_CLASS_RX_SIGNING warn about class_rx_t pointer signing mismatches 正告class_rx_t指针签名不匹配
OBJC_DISABLE_CLASS_RO_FAULTS disable 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查看运转堆栈。

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

  • 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调用

  • 图:

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

三、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数组中,于此一起核算totalClassesunoptimizedTotalClasses的数量。

    ②. 最重要的仍是调用了_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_selrefssection加载到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。
  • 图:
    OC底层原理(十二):应用程序的dyld4流程下

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的数据结构保存类与办法:

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

    ②. 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,数据结构表明为:

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

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初始进程中如何链接初始化libSytem与libdispatch,这些都能够一层一层深入阅读,里边操刁难程序收拾理解非常有用。本篇源码较多,一切objc4都是根据目前最新的866.9版别,而dyld4是根据1042.1版别。

  2. dyld4的简化流程

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