1. _objc_init

  1. void _objc_init(void)
  2. {
  3. static bool initialized = false;
  4. if (initialized) return;
  5. initialized = true;
  6. //环境变量的初始化,查看帮助:export OBJC_HELP = 1
  7. environ_init();
  8. //线程key的绑定,例如:线程数据的析构函数
  9. tls_init();
  10. //运行C++静态构造函数
  11. //libc在dyld调用静态构造函数之前调用_objc_init()
  12. static_init();
  13. //运行时环境初始化,创建两张表:unattachedCategories、allocatedClasses
  14. runtime_init();
  15. //异常信号处理的初始化
  16. exception_init();
  17. #if __OBJC2__
  18. //缓存的初始化
  19. cache_t::init();
  20. #endif
  21. //启动回调机制。通常情况下,这没有任何作用
  22. //所有的初始化都是惰性的,但是对于某些进程,我们是主动加载的
  23. _imp_implementationWithBlock_init();
  24. //在dyld中注册objc的回调方法
  25. //map_images:image镜像文件加载进内存时,会触发该函数
  26. //load_images:初始化image会触发该函数,调用load方法
  27. //unmap_image:image镜像移除时会触发该函数
  28. _dyld_objc_notify_register(&map_images, load_images, unmap_image);
  29. #if __OBJC2__
  30. didCallDyldNotifyRegister = true;
  31. #endif
  32. }
  • environ_init:环境变量的初始化,查看帮助:export OBJC_HELP = 1
  • tls_init:线程key的绑定,例如:线程数据的析构函数
  • static_init:运行C++静态构造函数

libcdyld调用静态构造函数之前调用_objc_init()

  • runtime_init:运行时环境初始化

◦ 创建两张表:unattachedCategoriesallocatedClasses

  • exception_init:异常信号处理的初始化
  • cache_t::init:缓存的初始化
  • _imp_implementationWithBlock_init:启动回调机制。通常情况下,这没有任何作用

◦ 所有的初始化都是惰性的,但是对于某些进程,我们是主动加载的

  • _dyld_objc_notify_register:在dyld中注册objc的回调方法

map_imagesimage镜像文件加载进内存时,会触发该函数
load_images:初始化image会触发该函数,调用load方法
unmap_imageimage镜像移除时会触发该函数

1.1 environ_init

环境变量的初始化,查看帮助:export OBJC_HELP = 1

查看环境变量的两种方式:

  • 在项目中,使用代码打印出所有环境变量
  • 在终端,通过export OBJC_HELP = 1命令查看

1.1.1 代码打印

environ_init函数中,写入以下代码:

  1. void environ_init(void)
  2. {
  3. ...
  4. for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
  5. const option_t *opt = &Settings[i];
  6. _objc_inform("%s: %s", opt->env, opt->help);
  7. _objc_inform("%s is set", opt->env);
  8. }
  9. ...
  10. }
  11. -------------------------
  12. //输出结果:
  13. objc[99433]: OBJC_PRINT_IMAGES: log image and library names as they are loaded
  14. objc[99433]: OBJC_PRINT_IMAGES is set
  15. objc[99433]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps
  16. objc[99433]: OBJC_PRINT_IMAGE_TIMES is set
  17. objc[99433]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods
  18. ...

1.1.2 终端打印

打开终端,输入命令:

  1. export OBJC_HELP = 1
  2. -------------------------
  3. objc[97117]: Objective-C runtime debugging. Set variable=YES to enable.
  4. objc[97117]: OBJC_HELP: describe available environment variables
  5. objc[97117]: OBJC_PRINT_OPTIONS: list which options are set
  6. objc[97117]: OBJC_PRINT_IMAGES: log image and library names as they are loaded
  7. objc[97117]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps
  8. objc[97117]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods
  9. ...

1.1.3 环境变量说明

动态链接器环境变量

DYLD_PRINT_STATISTICS 打印启动时间等参数
DYLD_PRINT_SEGMENTS 打印segment映射日志
DYLD_PRINT_INITIALIZERS 打印镜像初始化调用日志
DYLD_PRINT_BINDINGS 打印符号绑定日志
DYLD_PRINT_APIS 打印dyld API调用日志
DYLD_PRINT_ENV 打印启动时环境变量
DYLD_PRINT_OPTS 打印启动时的命令行参数
DYLD_PRINT_LIBRARIES_POST_LAUNCH 打印加载库的日志,在main运行之后
DYLD_PRINT_LIBRARIES 打印加载库的日志
DYLD_IMAGE_SUFFIX 搜索具有此后缀的库

Objective-C运行时调试,设置variable=YES启用

OBJC_HELP 描述可用的环境变量
OBJC_PRINT_OPTIONS 输出已设置的选项
OBJC_PRINT_IMAGES 输出已加载的image信息
OBJC_PRINT_IMAGE_TIMES 输出image加载时间
OBJC_PRINT_LOAD_METHODS 输出类和分类的+load方法
OBJC_PRINT_INITIALIZE_METHODS 输出类的+initialize方法
OBJC_PRINT_RESOLVED_METHODS 输出+resolveClassMethod:或+resolveInstanceMethod:生成的类方法
OBJC_PRINT_CLASS_SETUP 输出类和分类设置的进度
OBJC_PRINT_PROTOCOL_SETUP 输出协议的设置进度
OBJC_PRINT_IVAR_SETUP 输出ivars的日志
OBJC_PRINT_VTABLE_SETUP 输出vtable的日志
OBJC_PRINT_VTABLE_IMAGES 输出vtable被覆盖的方法
OBJC_PRINT_CACHE_SETUP 输出方法缓存的日志
OBJC_PRINT_FUTURE_CLASSES 打印桥接类的使用
OBJC_PRINT_PREOPTIMIZATION 日志预优化由dyld共享缓存提供
OBJC_PRINT_CXX_CTORS 日志调用c++的ctors和dtors实例变量
OBJC_PRINT_EXCEPTIONS 日志异常处理
OBJC_PRINT_EXCEPTION_THROW 每个objc_exception_throw()的日志回溯
OBJC_PRINT_ALT_HANDLERS 异常alt处理的日志处理
OBJC_PRINT_REPLACED_METHODS 日志方法被类别实现取代
OBJC_PRINT_DEPRECATION_WARNINGS 对调用已弃用运行时函数发出警告
OBJC_PRINT_POOL_HIGHWATER 日志自动释放池的高水位标记
OBJC_PRINT_CUSTOM_CORE 使用自定义核心方法的日志类
OBJC_PRINT_CUSTOM_RR 带有自定义保留/释放方法的日志类
OBJC_PRINT_CUSTOM_AWZ 带有自定义allocWithZone方法的日志类
OBJC_PRINT_RAW_ISA 需要原始指针字段的日志类
OBJC_DEBUG_UNLOAD 关于行为不佳的包的警告
OBJC_DEBUG_FRAGILE_SUPERCLASSES 警告子类可能已经被后续的超类更改破坏
OBJC_DEBUG_NIL_SYNC 警告@synchronized(nil),它不同步
OBJC_DEBUG_NONFRAGILE_IVARS 随意重新排列非脆弱ivars
OBJC_DEBUG_ALT_HANDLERS 记录关于错误的alt处理程序使用的更多信息
OBJC_DEBUG_MISSING_POOLS 警告在没有池的情况下自动释放,这可能是一个泄漏
OBJC_DEBUG_POOL_ALLOCATION 当自动释放池按顺序弹出时暂停,并允许堆调试器跟踪自动释放池
OBJC_DEBUG_DUPLICATE_CLASSES 当存在多个具有相同名称的类时停止
OBJC_DEBUG_DONT_CRASH 通过退出而不是崩溃来停止进程
OBJC_DEBUG_POOL_DEPTH 当至少分配了一组自动释放页面时的日志错误
OBJC_DEBUG_SCRIBBLE_CACHES 在释放的方法缓存中涂写imp
OBJC_DISABLE_VTABLES 禁用vtable调度
OBJC_DISABLE_PREOPTIMIZATION 禁用预优化的dyld共享缓存
OBJC_DISABLE_TAGGED_POINTERS 禁用NSNumber等标记指针优化
OBJC_DISABLE_TAG_OBFUSCATION 禁用标记指针的混淆
OBJC_DISABLE_NONPOINTER_ISA 禁用非指针isa字段
OBJC_DISABLE_INITIALIZE_FORK_SAFETY 禁用fork后+初始化的安全检查
OBJC_DISABLE_FAULTS 禁用os故障
OBJC_DISABLE_PREOPTIMIZED_CACHES 禁用预优化缓存
OBJC_DISABLE_AUTORELEASE_COALESCING 禁用自动释放池指针的合并
OBJC_DISABLE_AUTORELEASE_COALESCING_LRU 禁用使用回头N策略的自动释放池指针的合并

1.1.4 环境变量使用

通过TargetEdit SchemeRunArgumentsEnvironment Variables,配置环境变量
image.png

案例1:设置DYLD_PRINT_STATISTICSYES,运行时打印启动时间

  1. Total pre-main time: 411015771.6 seconds (0.0%)
  2. dylib loading time: 25.93 milliseconds (0.0%)
  3. rebase/binding time: 411015771.5 seconds (0.0%)
  4. ObjC setup time: 14.15 milliseconds (0.0%)
  5. initializer time: 57.99 milliseconds (0.0%)
  6. slowest intializers :

案例2:设置OBJC_DISABLE_TAG_OBFUSCATIONYES,禁用非nonpointer类型isa
image.png

  • 低位为0,表示纯isa指针,已禁用非nonpointer类型isa

案例3:设置OBJC_DISABLE_TAG_OBFUSCATIONNO,启用非nonpointer类型isa
image.png

  • 低位为1,表示非nonpointer类型isa

案例4:设置OBJC_PRINT_LOAD_METHODSYES,输出类和分类的+load方法
image.png

1.2 tls_init

线程key的绑定,例如:线程数据的析构函数

  1. void tls_init(void)
  2. {
  3. #if SUPPORT_DIRECT_THREAD_KEYS
  4. pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
  5. #else
  6. _objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
  7. #endif
  8. }

1.3 static_init

运行C++静态构造函数

  1. static void static_init()
  2. {
  3. size_t count;
  4. auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
  5. for (size_t i = 0; i < count; i++) {
  6. inits[i]();
  7. }
  8. auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count);
  9. for (size_t i = 0; i < count; i++) {
  10. UnsignedInitializer init(offsets[i]);
  11. init();
  12. }
  13. }
  • libcdyld调用静态构造函数之前调用_objc_init()

1.4 runtime_init

运行时环境初始化

  1. void runtime_init(void)
  2. {
  3. objc::unattachedCategories.init(32);
  4. objc::allocatedClasses.init();
  5. }
  • 创建两张表:unattachedCategoriesallocatedClasses

1.5 exception_init

异常信号处理的初始化

  1. void exception_init(void)
  2. {
  3. old_terminate = std::set_terminate(&_objc_terminate);
  4. }

crash:当系统发现程序中违背了底层的规矩,会由底层发出信号,然后会进入_objc_terminate函数,触发uncaught_handler抛出异常,程序终止

  1. static void (*old_terminate)(void) = nil;
  2. static void _objc_terminate(void)
  3. {
  4. if (PrintExceptions) {
  5. _objc_inform("EXCEPTIONS: terminating");
  6. }
  7. if (! __cxa_current_exception_type()) {
  8. // No current exception.
  9. (*old_terminate)();
  10. }
  11. else {
  12. // There is a current exception. Check if it's an objc exception.
  13. @try {
  14. __cxa_rethrow();
  15. } @catch (id e) {
  16. // It's an objc object. Call Foundation's handler, if any.
  17. (*uncaught_handler)((id)e);
  18. (*old_terminate)();
  19. } @catch (...) {
  20. // It's not an objc object. Continue to C++ terminate.
  21. (*old_terminate)();
  22. }
  23. }
  24. }

1.5.1 crash分类

crash未处理信号的来源:

  • kernel内核
  • 其他进行
  • App本身

所以crash可以划分为三种类型:

  • Mach异常:是指最底层的内核级异常。用户态的开发者可以直接通过Mach API设置threadtaskhost的异常端口,来捕获Mach异常
  • Unix信号:又称BSD 信号,如果开发者没有捕获Mach异常,则会被host层的方法ux_exception()将异常转换为对应的UNIX信号,并通过方法threadsignal()将信号投递到出错线程。可以通过方法signal(x, SignalHandler)来捕获single
  • NSException应用级异常:它是未被捕获的Objective-C异常,导致程序向自身发送了SIGABRT信号而崩溃,对于未捕获的Objective-C异常,是可以通过try catch来捕获的,或者通过NSSetUncaughtExceptionHandler()机制来捕获

针对应用级异常,可以通过注册异常捕获的函数,即NSSetUncaughtExceptionHandler机制,实现线程保活, 收集上传崩溃日志

1.5.2 crash拦截

如果我们在系统中,对底层的uncaught_handler函数,进行下句柄赋值,即可拦截底层抛出的crash

定义LGExceptionHandlers函数

  1. void LGExceptionHandlers(NSException *exception) {
  2. NSLog(@"拦截异常:%s,%@", __func__, exception);
  3. }

在应用启动时,对uncaught_handler函数下句柄赋值

  1. - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  2. NSSetUncaughtExceptionHandler(&LGExceptionHandlers);
  3. return YES;
  4. }

一旦出现异常,会被LGExceptionHandlers函数拦截

  1. 拦截异常:LGExceptionHandlers,*** -[__NSArrayI objectAtIndexedSubscript:]: index 5 beyond bounds [0 .. 4]

uncaught_handler函数在底层的赋值,调用者是objc_setUncaughtExceptionHandler函数,而 NSSetUncaughtExceptionHandler函数是上层的封装

  1. objc_uncaught_exception_handler
  2. objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
  3. {
  4. objc_uncaught_exception_handler result = uncaught_handler;
  5. uncaught_handler = fn;
  6. return result;
  7. }

这种拦截方式,只适用于NSException应用级异常,在崩溃前调用自定义函数,但最终程序还是会崩溃

1.6 cache_t::init

缓存的初始化

  1. void cache_t::init()
  2. {
  3. #if HAVE_TASK_RESTARTABLE_RANGES
  4. mach_msg_type_number_t count = 0;
  5. kern_return_t kr;
  6. while (objc_restartableRanges[count].location) {
  7. count++;
  8. }
  9. kr = task_restartable_ranges_register(mach_task_self(),
  10. objc_restartableRanges, count);
  11. if (kr == KERN_SUCCESS) return;
  12. _objc_fatal("task_restartable_ranges_register failed (result 0x%x: %s)",
  13. kr, mach_error_string(kr));
  14. #endif // HAVE_TASK_RESTARTABLE_RANGES
  15. }

1.7 _imp_implementationWithBlock_init

启动回调机制。通常情况下,这没有任何作用

  1. void
  2. _imp_implementationWithBlock_init(void)
  3. {
  4. #if TARGET_OS_OSX
  5. // Eagerly load libobjc-trampolines.dylib in certain processes. Some
  6. // programs (most notably QtWebEngineProcess used by older versions of
  7. // embedded Chromium) enable a highly restrictive sandbox profile which
  8. // blocks access to that dylib. If anything calls
  9. // imp_implementationWithBlock (as AppKit has started doing) then we'll
  10. // crash trying to load it. Loading it here sets it up before the sandbox
  11. // profile is enabled and blocks it.
  12. //
  13. // This fixes EA Origin (rdar://problem/50813789)
  14. // and Steam (rdar://problem/55286131)
  15. if (__progname &&
  16. (strcmp(__progname, "QtWebEngineProcess") == 0 ||
  17. strcmp(__progname, "Steam Helper") == 0)) {
  18. Trampolines.Initialize();
  19. }
  20. #endif
  21. }
  • 所有的初始化都是惰性的,但是对于某些进程,我们是主动加载的

1.8 _dyld_objc_notify_register

dyld中注册objc的回调方法

  1. void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
  2. _dyld_objc_notify_init init,
  3. _dyld_objc_notify_unmapped unmapped);
  • map_imagesimage镜像文件加载进内存时,会触发该函数
  • load_images:初始化image会触发该函数,调用load方法
  • unmap_imageimage镜像移除时会触发该函数

2. map_images

objc中,调用_dyld_objc_notify_register函数

  1. _dyld_objc_notify_register(&map_images, load_images, unmap_image);
  • map_images:管理⽂件中和动态库中所有的符号(classprotocolselectorcategory
  • load_images:加载执⾏load⽅法

map_images使用指针地址传递,当_dyld_objc_notify_register函数触发后,一旦map_images参数发生改变,无论在dyldobjc中的map_images都会进行同步修改

map_images函数,将镜像文件映射到内存中。代码逻辑具有一定的复杂度,在遍历读取MachO时可能会发生改变,所以使用指针地址传递,保证其同步修改

load_imagesunmap_image函数,处理的业务相对简单,例如调用load方法,并不会导致自身改变,使用值传递即可

进入map_images函数

  1. void
  2. map_images(unsigned count, const char * const paths[],
  3. const struct mach_header * const mhdrs[])
  4. {
  5. mutex_locker_t lock(runtimeLock);
  6. return map_images_nolock(count, paths, mhdrs);
  7. }

进入map_images_nolock函数,我们要明确目标,寻找镜像加载的相关代码
image.png

3. _read_images

进入_read_images函数,业务代码过于复杂,先将代码块进行折叠
image.png
image.png
image.png

_read_images函数中,完成以下十个逻辑:

  • 条件控制进⾏⼀次的加载
  • 修复预编译阶段的@selector的混乱问题
  • 错误混乱的类处理
  • 修复重映射⼀些没有被镜像⽂件加载进来的类
  • 修复旧版objc_msgSend_fixup调用方式
  • 修复协议
  • 修复没有被加载的协议
  • 分类处理
  • 类的加载处理
  • 没有被处理的类,优化那些被侵犯的类

3.1 条件控制进⾏⼀次的加载

doneOnce流程中,通过NXCreateMapTable创建哈希表,存放类信息

  1. if (!doneOnce) {
  2. ...
  3. initializeTaggedPointerObfuscator();
  4. if (PrintConnecting) {
  5. _objc_inform("CLASS: found %d classes during launch", totalClasses);
  6. }
  7. // namedClasses
  8. // Preoptimized classes don't go in this table.
  9. // 4/3 is NXMapTable's load factor
  10. int namedClassesSize =
  11. (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
  12. gdb_objc_realized_classes =
  13. NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
  14. ts.log("IMAGE TIMES: first time tasks");
  15. }
  • initializeTaggedPointerObfuscator:处理TaggedPointer小对象内存地址,小对象的地址中会存储相关值,函数内部会通过位运算进行获取
  • 创建gdb_objc_realized_classes表,4/3NXMapTable的负载因子,使用3/4扩容的逆运算创建总容积

gdb_objc_realized_classes表和runtime_init中创建的两张表的区别:

  • gdb_objc_realized_classes:该类不在dyld共享缓存中,无论该类是否实现,都会存储在表中
  • unattachedCategories:分类使用的表
  • allocatedClasses:已开辟空间的类,存储在表中

3.2 修复预编译阶段的@selector的混乱问题

通过_getObjc2SelectorRefs,读取MachO__DATA__objc_selrefs节。遍历SEL,将dyldRebase后的SEL替换到内存的MachO镜像中

  1. static size_t UnfixedSelectors;
  2. {
  3. mutex_locker_t lock(selLock);
  4. for (EACH_HEADER) {
  5. if (hi->hasPreoptimizedSelectors()) continue;
  6. bool isBundle = hi->isBundle();
  7. SEL *sels = _getObjc2SelectorRefs(hi, &count);
  8. UnfixedSelectors += count;
  9. for (i = 0; i < count; i++) {
  10. const char *name = sel_cname(sels[i]);
  11. SEL sel = sel_registerNameNoLock(name, isBundle);
  12. if (sels[i] != sel) {
  13. sels[i] = sel;
  14. }
  15. }
  16. }
  17. }
  18. ts.log("IMAGE TIMES: fix up selector references");
  • sel:从dyld中,读取Rebase后的SEL
  • sels[i]:从MachO中读取数据,将Rebase后的SEL覆盖

SEL为方法编号,除了名称为包括地址。名称相同的SEL,不一定地址相同。SEL的相等,需要对名称和地址都进行判断。SEL地址,可使用p/x打印
image.png

3.3 错误混乱的类处理

通过_getObjc2SelectorRefs,读取MachO__DATA__objc_classlist节。遍历类的列表,将类添加到表中

  1. bool hasDyldRoots = dyld_shared_cache_some_image_overridden();
  2. for (EACH_HEADER) {
  3. if (! mustReadClasses(hi, hasDyldRoots)) {
  4. // Image is sufficiently optimized that we need not call readClass()
  5. continue;
  6. }
  7. classref_t const *classlist = _getObjc2ClassList(hi, &count);
  8. bool headerIsBundle = hi->isBundle();
  9. bool headerIsPreoptimized = hi->hasPreoptimizedClasses();
  10. for (i = 0; i < count; i++) {
  11. Class cls = (Class)classlist[i];
  12. Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
  13. if (newCls != cls && newCls) {
  14. // Class was moved but not deleted. Currently this occurs
  15. // only when the new class resolved a future class.
  16. // Non-lazily realize the class below.
  17. resolvedFutureClasses = (Class *)
  18. realloc(resolvedFutureClasses,
  19. (resolvedFutureClassCount+1) * sizeof(Class));
  20. resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
  21. }
  22. }
  23. }
  24. ts.log("IMAGE TIMES: discover classes");
  • 当类被移动但没有被删除,出现混乱的情况,进行修复处理

循环中,原本只能打印类的地址,调用readClass函数后,关联上了类的名称
image.png

3.4 修复重映射⼀些没有被镜像⽂件加载进来的类

将未映射的ClassSuper Class进行重映射

  • _getObjc2ClassRefs读取MachO中的__DATA__objc_classrefs节,存储了类的引用
  • _getObjc2SuperRefs读取MachO中的__DATA__objc_superrefs节,存储了父类的引用
  1. if (!noClassesRemapped()) {
  2. for (EACH_HEADER) {
  3. Class *classrefs = _getObjc2ClassRefs(hi, &count);
  4. for (i = 0; i < count; i++) {
  5. remapClassRef(&classrefs[i]);
  6. }
  7. // fixme why doesn't test future1 catch the absence of this?
  8. classrefs = _getObjc2SuperRefs(hi, &count);
  9. for (i = 0; i < count; i++) {
  10. remapClassRef(&classrefs[i]);
  11. }
  12. }
  13. }
  14. ts.log("IMAGE TIMES: remap classes");
  • remapClassRef的类都是懒加载的类,这里需要进行重映射

3.5 修复旧版objc_msgSend_fixup调用方式

通过_getObjc2MessageRefs,读取MachO__DATA__objc_msgrefs节。调用fixupMessageRef函数,将函数指针进行注册,修复为新的函数指针

  1. #if SUPPORT_FIXUP
  2. // Fix up old objc_msgSend_fixup call sites
  3. for (EACH_HEADER) {
  4. message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
  5. if (count == 0) continue;
  6. if (PrintVtables) {
  7. _objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
  8. "call sites in %s", count, hi->fname());
  9. }
  10. for (i = 0; i < count; i++) {
  11. fixupMessageRef(refs+i);
  12. }
  13. }
  14. ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
  15. #endif

3.6 修复协议

调用protocols函数,创建协议的哈希表。通过_getObjc2ProtocolList,读取MachO__DATA__objc_protolist节。遍历协议列表,调用readProtocol函数,将协议添加到protocol_map哈希表中

  1. for (EACH_HEADER) {
  2. extern objc_class OBJC_CLASS_$_Protocol;
  3. Class cls = (Class)&OBJC_CLASS_$_Protocol;
  4. ASSERT(cls);
  5. NXMapTable *protocol_map = protocols();
  6. bool isPreoptimized = hi->hasPreoptimizedProtocols();
  7. // Skip reading protocols if this is an image from the shared cache
  8. // and we support roots
  9. // Note, after launch we do need to walk the protocol as the protocol
  10. // in the shared cache is marked with isCanonical() and that may not
  11. // be true if some non-shared cache binary was chosen as the canonical
  12. // definition
  13. if (launchTime && isPreoptimized) {
  14. if (PrintProtocols) {
  15. _objc_inform("PROTOCOLS: Skipping reading protocols in image: %s",
  16. hi->fname());
  17. }
  18. continue;
  19. }
  20. bool isBundle = hi->isBundle();
  21. protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
  22. for (i = 0; i < count; i++) {
  23. readProtocol(protolist[i], cls, protocol_map,
  24. isPreoptimized, isBundle);
  25. }
  26. }
  27. ts.log("IMAGE TIMES: discover protocols");

3.7 修复没有被加载的协议

通过_getObjc2ProtocolRefs,读取MachO__DATA__objc_protorefs节。遍历需要修复的协议,调用remapProtocolRef函数,比较当前协议和协议列表中的同一个内存地址的协议是否相同,如果不同则替换

  1. for (EACH_HEADER) {
  2. // At launch time, we know preoptimized image refs are pointing at the
  3. // shared cache definition of a protocol. We can skip the check on
  4. // launch, but have to visit @protocol refs for shared cache images
  5. // loaded later.
  6. if (launchTime && hi->isPreoptimized())
  7. continue;
  8. protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
  9. for (i = 0; i < count; i++) {
  10. remapProtocolRef(&protolist[i]);
  11. }
  12. }
  13. ts.log("IMAGE TIMES: fix up @protocol references");

进入remapProtocolRef函数

  1. static size_t UnfixedProtocolReferences;
  2. static void remapProtocolRef(protocol_t **protoref)
  3. {
  4. runtimeLock.assertLocked();
  5. protocol_t *newproto = remapProtocol((protocol_ref_t)*protoref);
  6. if (*protoref != newproto) {
  7. *protoref = newproto;
  8. UnfixedProtocolReferences++;
  9. }
  10. }

3.8 分类处理

在分类初始化之后执行,对于在启动时出现的分类,被延迟到_dyld_objc_notify_register调用完成,第一次load_images调用之后

  1. if (didInitialAttachCategories) {
  2. for (EACH_HEADER) {
  3. load_categories_nolock(hi);
  4. }
  5. }
  6. ts.log("IMAGE TIMES: discover categories");

3.9 类的加载处理

实现非懒加载类load方法和静态实例变量处理

  1. for (EACH_HEADER) {
  2. classref_t const *classlist = hi->nlclslist(&count);
  3. for (i = 0; i < count; i++) {
  4. Class cls = remapClass(classlist[i]);
  5. if (!cls) continue;
  6. addClassTableEntry(cls);
  7. if (cls->isSwiftStable()) {
  8. if (cls->swiftMetadataInitializer()) {
  9. _objc_fatal("Swift class %s with a metadata initializer "
  10. "is not allowed to be non-lazy",
  11. cls->nameForLogging());
  12. }
  13. // fixme also disallow relocatable classes
  14. // We can't disallow all Swift classes because of
  15. // classes like Swift.__EmptyArrayStorage
  16. }
  17. realizeClassWithoutSwift(cls, nil);
  18. }
  19. }
  20. ts.log("IMAGE TIMES: realize non-lazy classes");
  • 通过_getObjc2NonlazyClassList,读取MachO__DATA__objc_nlclslist节,得到非懒加载类的列表
  • 调用addClassTableEntry函数,将非懒加载类插入类表,存储到内存。如果已经添加就不会载添加,需要确保整个结构都被添加
  • 调用realizeClassWithoutSwift函数,完成当前的类的初始化。因为在readClass函数中,读取到内存的仅有类名和地址,其他数据还需要完善

3.10 没有被处理的类,优化那些被侵犯的类

  1. if (resolvedFutureClasses) {
  2. for (i = 0; i < resolvedFutureClassCount; i++) {
  3. Class cls = resolvedFutureClasses[i];
  4. if (cls->isSwiftStable()) {
  5. _objc_fatal("Swift class is not allowed to be future");
  6. }
  7. realizeClassWithoutSwift(cls, nil);
  8. cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
  9. }
  10. free(resolvedFutureClasses);
  11. }
  12. ts.log("IMAGE TIMES: realize future classes");

4.【第三步】错误混乱的类处理流程分析

4.1 readClass

  1. Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
  2. {
  3. const char *mangledName = cls->nonlazyMangledName();
  4. if(strcmp(mangledName, "LGPerson")==0){
  5. printf("%s \n",mangledName);
  6. }
  7. if (missingWeakSuperclass(cls)) {
  8. // No superclass (probably weak-linked).
  9. // Disavow any knowledge of this subclass.
  10. if (PrintConnecting) {
  11. _objc_inform("CLASS: IGNORING class '%s' with "
  12. "missing weak-linked superclass",
  13. cls->nameForLogging());
  14. }
  15. addRemappedClass(cls, nil);
  16. cls->setSuperclass(nil);
  17. return nil;
  18. }
  19. cls->fixupBackwardDeployingStableSwift();
  20. Class replacing = nil;
  21. if (mangledName != nullptr) {
  22. if (Class newCls = popFutureNamedClass(mangledName)) {
  23. // This name was previously allocated as a future class.
  24. // Copy objc_class to future class's struct.
  25. // Preserve future's rw data block.
  26. if (newCls->isAnySwift()) {
  27. _objc_fatal("Can't complete future class request for '%s' "
  28. "because the real class is too big.",
  29. cls->nameForLogging());
  30. }
  31. class_rw_t *rw = newCls->data();
  32. const class_ro_t *old_ro = rw->ro();
  33. memcpy(newCls, cls, sizeof(objc_class));
  34. // Manually set address-discriminated ptrauthed fields
  35. // so that newCls gets the correct signatures.
  36. newCls->setSuperclass(cls->getSuperclass());
  37. newCls->initIsa(cls->getIsa());
  38. rw->set_ro((class_ro_t *)newCls->data());
  39. newCls->setData(rw);
  40. freeIfMutable((char *)old_ro->getName());
  41. free((void *)old_ro);
  42. addRemappedClass(cls, newCls);
  43. replacing = cls;
  44. cls = newCls;
  45. }
  46. }
  47. if (headerIsPreoptimized && !replacing) {
  48. // class list built in shared cache
  49. // fixme strict assert doesn't work because of duplicates
  50. // ASSERT(cls == getClass(name));
  51. ASSERT(mangledName == nullptr || getClassExceptSomeSwift(mangledName));
  52. } else {
  53. if (mangledName) { //some Swift generic classes can lazily generate their names
  54. addNamedClass(cls, mangledName, replacing);
  55. } else {
  56. Class meta = cls->ISA();
  57. const class_ro_t *metaRO = meta->bits.safe_ro();
  58. ASSERT(metaRO->getNonMetaclass() && "Metaclass with lazy name must have a pointer to the corresponding nonmetaclass.");
  59. ASSERT(metaRO->getNonMetaclass() == cls && "Metaclass nonmetaclass pointer must equal the original class.");
  60. }
  61. addClassTableEntry(cls);
  62. }
  63. // for future reference: shared cache never contains MH_BUNDLEs
  64. if (headerIsBundle) {
  65. cls->data()->flags |= RO_FROM_BUNDLE;
  66. cls->ISA()->data()->flags |= RO_FROM_BUNDLE;
  67. }
  68. return cls;
  69. }
  • 正常情况下,不会进入popFutureNamedClass的判断逻辑
  • popFutureNamedClass:当传入的类名称之前被分配为future类,将其objc_class复制到future类的结构体,保留future类的rw数据块
  • 正常会进入addNamedClass函数,然后执行addClassTableEntry函数

4.2 addNamedClass

  1. static void addNamedClass(Class cls, const char *name, Class replacing = nil)
  2. {
  3. runtimeLock.assertLocked();
  4. Class old;
  5. if ((old = getClassExceptSomeSwift(name)) && old != replacing) {
  6. inform_duplicate(name, old, cls);
  7. // getMaybeUnrealizedNonMetaClass uses name lookups.
  8. // Classes not found by name lookup must be in the
  9. // secondary meta->nonmeta table.
  10. addNonMetaClass(cls);
  11. } else {
  12. NXMapInsert(gdb_objc_realized_classes, name, cls);
  13. }
  14. ASSERT(!(cls->data()->flags & RO_META));
  15. // wrong: constructed classes are already realized when they get here
  16. // ASSERT(!cls->isRealized());
  17. }
  • 将类添加到gdb_objc_realized_classes表中,类名和地址进行关联

4.3 addClassTableEntry

  1. static void
  2. addClassTableEntry(Class cls, bool addMeta = true)
  3. {
  4. runtimeLock.assertLocked();
  5. // This class is allowed to be a known class via the shared cache or via
  6. // data segments, but it is not allowed to be in the dynamic table already.
  7. auto &set = objc::allocatedClasses.get();
  8. ASSERT(set.find(cls) == set.end());
  9. if (!isKnownClass(cls))
  10. set.insert(cls);
  11. if (addMeta)
  12. addClassTableEntry(cls->ISA(), false);
  13. }
  • 将类添加到allocatedClasses表中。如果addMeta为真,自动添加类的元类

5. 【第九步】类的加载处理流程分析

核心代码:realizeClassWithoutSwift函数。除了在类的加载处理时可能被调用,在消息慢速查找流程中,也有可能被调用

realizeClassWithoutSwift的任务,完成类在加载过程中的三个步骤:

  • 读取data数据,设置rorw
  • 设置类的继承链和isa指向
  • 修复类的方法列表、协议列表和属性列表

5.1 懒加载类 & 非懒加载类

系统中并不是所有类,都必须在程序启动时就完成初始化,这样对内存的消耗过高,而且不利于启动速度。大部分的类在运行时,消息发送时完成初始化,我们称它们为懒加载类,它们会进行按需加载

一个类实现了load方法,即为非懒加载类,它会在main函数之前完成初始化
image.png

  • 非懒加载类的初始化流程:map_imagesmap_images_nolock_read_imagesrealizeClassWithoutSwift

反之,如果一个类未实现load方法,即为懒加载类,它会在运行时,消息发送时完成初始化

例如:在main函数中,调用LGPersonalloc方法
image.png

  • 懒加载类的初始化流程:lookUpImpOrForwardinitializeAndLeaveLockedinitializeAndMaybeRelockrealizeClassMaybeSwiftAndUnlockrealizeClassMaybeSwiftMaybeRelockrealizeClassWithoutSwift

5.2 读取data数据,设置rorw

  1. auto ro = (const class_ro_t *)cls->data();
  2. auto isMeta = ro->flags & RO_META;
  3. if (ro->flags & RO_FUTURE) {
  4. // This was a future class. rw data is already allocated.
  5. rw = cls->data();
  6. ro = cls->data()->ro();
  7. ASSERT(!isMeta);
  8. cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
  9. } else {
  10. // Normal class. Allocate writeable class data.
  11. rw = objc::zalloc<class_rw_t>();
  12. rw->set_ro(ro);
  13. rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
  14. cls->setData(rw);
  15. }
  • 正常的类会进入else流程
  • 开辟rw空间,把干净的ro内存,复制一份到rw下的ro中,设置flags
  • rw设置到类的data

5.3 设置类的继承链和isa指向

  1. supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
  2. metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
  • 递归调用realizeClassWithoutSwift函数,获得父类和元类
  • 根元类的isa是指向自己,所以在remapClass中,会对类在表中进行查找。如果表中已有该类,返回一个空值,否则返回当前类。通过这种方式,避免了死递归
  1. #if SUPPORT_NONPOINTER_ISA
  2. if (isMeta) {
  3. // Metaclasses do not need any features from non pointer ISA
  4. // This allows for a faspath for classes in objc_retain/objc_release.
  5. cls->setInstancesRequireRawIsa();
  6. } else {
  7. // Disable non-pointer isa for some classes and/or platforms.
  8. // Set instancesRequireRawIsa.
  9. bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
  10. bool rawIsaIsInherited = false;
  11. static bool hackedDispatch = false;
  12. if (DisableNonpointerIsa) {
  13. // Non-pointer isa disabled by environment or app SDK version
  14. instancesRequireRawIsa = true;
  15. }
  16. else if (!hackedDispatch && 0 == strcmp(ro->getName(), "OS_object"))
  17. {
  18. // hack for libdispatch et al - isa also acts as vtable pointer
  19. hackedDispatch = true;
  20. instancesRequireRawIsa = true;
  21. }
  22. else if (supercls && supercls->getSuperclass() &&
  23. supercls->instancesRequireRawIsa())
  24. {
  25. // This is also propagated by addSubclass()
  26. // but nonpointer isa setup needs it earlier.
  27. // Special case: instancesRequireRawIsa does not propagate
  28. // from root class to root metaclass
  29. instancesRequireRawIsa = true;
  30. rawIsaIsInherited = true;
  31. }
  32. if (instancesRequireRawIsa) {
  33. cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
  34. }
  35. }
  36. // SUPPORT_NONPOINTER_ISA
  37. #endif
  • 如果isMeta为真,设置为原始isa
  • 否则,进入else流程,因环境变量或应用SDK版本,设置为原始isa
  1. cls->setSuperclass(supercls);
  2. cls->initClassIsa(metacls);
  • 设置父类
  • isa指向设置为元类
  1. if (supercls) {
  2. addSubclass(supercls, cls);
  3. } else {
  4. addRootClass(cls);
  5. }
  • 继承链结构为双向链,父类中可以找到子类,子类中也可以找到父类
  • 如果父类存在,调用addSubclass函数,将当前类添加为supercls的子类
  • 反正,调用addRootClass函数,将当前类添加为根类

5.4 修复类的方法列表、协议列表和属性列表

  1. methodizeClass(cls, previously);
  • 调用methodizeClass函数,修复类的方法列表、协议列表和属性列表

5.4.1 methodizeClass

进入methodizeClass函数,读取cls中的rwrorwe

  1. bool isMeta = cls->isMetaClass();
  2. auto rw = cls->data();
  3. auto ro = rw->ro();
  4. auto rwe = rw->ext();

ro中读取方法列表,调用prepareMethodLists函数,准备进行方法的修复

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

5.4.2 prepareMethodLists

  1. static void
  2. prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
  3. bool baseMethods, bool methodsFromBundle, const char *why)
  4. {
  5. ...
  6. // Add method lists to array.
  7. // Reallocate un-fixed method lists.
  8. // The new methods are PREPENDED to the method list array.
  9. for (int i = 0; i < addedCount; i++) {
  10. method_list_t *mlist = addedLists[i];
  11. ASSERT(mlist);
  12. // Fixup selectors if necessary
  13. if (!mlist->isFixedUp()) {
  14. fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
  15. }
  16. }
  17. ...
  18. }
  • 循环方法列表,调用fixupMethodList函数,修复方法

5.4.3 fixupMethodList

  1. static void
  2. fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
  3. {
  4. runtimeLock.assertLocked();
  5. ASSERT(!mlist->isFixedUp());
  6. // fixme lock less in attachMethodLists ?
  7. // dyld3 may have already uniqued, but not sorted, the list
  8. if (!mlist->isUniqued()) {
  9. mutex_locker_t lock(selLock);
  10. // Unique selectors in list.
  11. for (auto& meth : *mlist) {
  12. const char *name = sel_cname(meth.name());
  13. meth.setName(sel_registerNameNoLock(name, bundleCopy));
  14. }
  15. }
  16. // Sort by selector address.
  17. // Don't try to sort small lists, as they're immutable.
  18. // Don't try to sort big lists of nonstandard size, as stable_sort
  19. // won't copy the entries properly.
  20. if (sort && !mlist->isSmallList() && mlist->entsize() == method_t::bigSize) {
  21. method_t::SortBySELAddress sorter;
  22. std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter);
  23. }
  24. // Mark method list as uniqued and sorted.
  25. // Can't mark small lists, since they're immutable.
  26. if (!mlist->isSmallList()) {
  27. mlist->setFixedUp();
  28. }
  29. }
  • 将名称和SEL写入到meth中,对方法列表按地址进行升序排序
  • 所以在消息慢速查找流程中,可以使用二分查找法

总结

_objc_init

  • environ_init:环境变量的初始化,查看帮助:export OBJC_HELP = 1
  • tls_init:线程key的绑定,例如:线程数据的析构函数
  • static_init:运行C++静态构造函数

libcdyld调用静态构造函数之前调用_objc_init()

  • runtime_init:运行时环境初始化

◦ 创建两张表:unattachedCategoriesallocatedClasses

  • exception_init:异常信号处理的初始化
  • cache_t::init:缓存的初始化
  • _imp_implementationWithBlock_init:启动回调机制。通常情况下,这没有任何作用

◦ 所有的初始化都是惰性的,但是对于某些进程,我们是主动加载的

  • _dyld_objc_notify_register:在dyld中注册objc的回调方法

map_imagesimage镜像文件加载进内存时,会触发该函数
load_images:初始化image会触发该函数,调用load方法
unmap_imageimage镜像移除时会触发该函数

map_images

  • 函数的作用:管理⽂件中和动态库中所有的符号(classprotocolselectorcategory
  • map_images使用指针地址传递,在遍历读取MachO时可能会发生改变,需要保证其同步修改
  • 核心代码,调用_read_images函数

_read_images

  • 条件控制进⾏⼀次的加载
  • 修复预编译阶段的@selector的混乱问题
  • 错误混乱的类处理
  • 修复重映射⼀些没有被镜像⽂件加载进来的类
  • 修复旧版objc_msgSend_fixup调用方式
  • 修复协议
  • 修复没有被加载的协议
  • 分类处理
  • 类的加载处理
  • 没有被处理的类,优化那些被侵犯的类

【第三步】错误混乱的类处理流程分析:

  • readClass

◦ 正常情况下,不会进入popFutureNamedClass的判断逻辑(处理future类的rorw
◦ 正常情况下,进入addNamedClass函数,然后执行addClassTableEntry函数

  • addNamedClass

◦ 将类添加到gdb_objc_realized_classes表中,类名和地址进行关联

  • addClassTableEntry

◦ 将类添加到allocatedClasses表中。如果addMeta为真,自动添加类的元类

【第九步】类的加载处理流程分析:

  • realizeClassWithoutSwift函数:

◦ 除了在类的加载处理时可能被调用,在消息慢速查找流程中,也有可能被调用


  • realizeClassWithoutSwift函数的作用,完成类在加载过程中的三个步骤:

◦ 读取data数据,设置rorw
◦ 设置类的继承链和isa指向
◦ 修复类的方法列表、协议列表和属性列表

  • 懒加载类 & 非懒加载类

◦ 一个类实现了load方法,即为非懒加载类,它会在main函数之前完成初始化
◦ 反之,如果一个类未实现load方法,即为懒加载类,它会在运行时,消息发送时完成初始化

  • 读取data数据,设置rorw

◦ 正常的类,进入分配可写的类数据流程
◦ 开辟rw空间,把干净的ro内存,复制一份到rw下的ro中,设置flags
◦ 将rw设置到类的data

  • 设置类的继承链和isa指向

◦ 递归调用realizeClassWithoutSwift函数,获得父类和元类
◦ 设置父类,将isa指向设置为元类
◦ 继承链结构为双向链,父类中可以找到子类,子类中也可以找到父类
◦ 如果父类存在,调用addSubclass函数,将当前类添加为supercls的子类。反正,调用addRootClass函数,将当前类添加为根类

  • 修复类的方法列表、协议列表和属性列表

◦ 调用methodizeClass函数,修复类的方法列表、协议列表和属性列
methodizeClassprepareMethodListsfixupMethodList,将名称和SEL写入到meth中,对方法列表按地址进行升序排序。所以在消息慢速查找流程中,可以使用二分查找法