1. _objc_init
void _objc_init(void){static bool initialized = false;if (initialized) return;initialized = true;//环境变量的初始化,查看帮助:export OBJC_HELP = 1environ_init();//线程key的绑定,例如:线程数据的析构函数tls_init();//运行C++静态构造函数//libc在dyld调用静态构造函数之前调用_objc_init()static_init();//运行时环境初始化,创建两张表:unattachedCategories、allocatedClassesruntime_init();//异常信号处理的初始化exception_init();#if __OBJC2__//缓存的初始化cache_t::init();#endif//启动回调机制。通常情况下,这没有任何作用//所有的初始化都是惰性的,但是对于某些进程,我们是主动加载的_imp_implementationWithBlock_init();//在dyld中注册objc的回调方法//map_images:image镜像文件加载进内存时,会触发该函数//load_images:初始化image会触发该函数,调用load方法//unmap_image:image镜像移除时会触发该函数_dyld_objc_notify_register(&map_images, load_images, unmap_image);#if __OBJC2__didCallDyldNotifyRegister = true;#endif}
environ_init:环境变量的初始化,查看帮助:export OBJC_HELP = 1tls_init:线程key的绑定,例如:线程数据的析构函数static_init:运行C++静态构造函数
◦ libc在dyld调用静态构造函数之前调用_objc_init()
runtime_init:运行时环境初始化
◦ 创建两张表:unattachedCategories、allocatedClasses
exception_init:异常信号处理的初始化cache_t::init:缓存的初始化_imp_implementationWithBlock_init:启动回调机制。通常情况下,这没有任何作用
◦ 所有的初始化都是惰性的,但是对于某些进程,我们是主动加载的
_dyld_objc_notify_register:在dyld中注册objc的回调方法
◦ map_images:image镜像文件加载进内存时,会触发该函数
◦ load_images:初始化image会触发该函数,调用load方法
◦ unmap_image:image镜像移除时会触发该函数
1.1 environ_init
环境变量的初始化,查看帮助:export OBJC_HELP = 1
查看环境变量的两种方式:
- 在项目中,使用代码打印出所有环境变量
- 在终端,通过
export OBJC_HELP = 1命令查看
1.1.1 代码打印
在environ_init函数中,写入以下代码:
void environ_init(void){...for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {const option_t *opt = &Settings[i];_objc_inform("%s: %s", opt->env, opt->help);_objc_inform("%s is set", opt->env);}...}-------------------------//输出结果:objc[99433]: OBJC_PRINT_IMAGES: log image and library names as they are loadedobjc[99433]: OBJC_PRINT_IMAGES is setobjc[99433]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading stepsobjc[99433]: OBJC_PRINT_IMAGE_TIMES is setobjc[99433]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods...
1.1.2 终端打印
打开终端,输入命令:
export OBJC_HELP = 1-------------------------objc[97117]: Objective-C runtime debugging. Set variable=YES to enable.objc[97117]: OBJC_HELP: describe available environment variablesobjc[97117]: OBJC_PRINT_OPTIONS: list which options are setobjc[97117]: OBJC_PRINT_IMAGES: log image and library names as they are loadedobjc[97117]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading stepsobjc[97117]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods...
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 环境变量使用
通过Target→Edit Scheme→Run→Arguments→Environment Variables,配置环境变量
案例1:设置DYLD_PRINT_STATISTICS为YES,运行时打印启动时间
Total pre-main time: 411015771.6 seconds (0.0%)dylib loading time: 25.93 milliseconds (0.0%)rebase/binding time: 411015771.5 seconds (0.0%)ObjC setup time: 14.15 milliseconds (0.0%)initializer time: 57.99 milliseconds (0.0%)slowest intializers :
案例2:设置OBJC_DISABLE_TAG_OBFUSCATION为YES,禁用非nonpointer类型isa
- 低位为
0,表示纯isa指针,已禁用非nonpointer类型isa
案例3:设置OBJC_DISABLE_TAG_OBFUSCATION为NO,启用非nonpointer类型isa
- 低位为
1,表示非nonpointer类型isa
案例4:设置OBJC_PRINT_LOAD_METHODS为YES,输出类和分类的+load方法
1.2 tls_init
线程key的绑定,例如:线程数据的析构函数
void tls_init(void){#if SUPPORT_DIRECT_THREAD_KEYSpthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);#else_objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);#endif}
1.3 static_init
运行C++静态构造函数
static void static_init(){size_t count;auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);for (size_t i = 0; i < count; i++) {inits[i]();}auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count);for (size_t i = 0; i < count; i++) {UnsignedInitializer init(offsets[i]);init();}}
libc在dyld调用静态构造函数之前调用_objc_init()
1.4 runtime_init
运行时环境初始化
void runtime_init(void){objc::unattachedCategories.init(32);objc::allocatedClasses.init();}
- 创建两张表:
unattachedCategories、allocatedClasses
1.5 exception_init
异常信号处理的初始化
void exception_init(void){old_terminate = std::set_terminate(&_objc_terminate);}
crash:当系统发现程序中违背了底层的规矩,会由底层发出信号,然后会进入_objc_terminate函数,触发uncaught_handler抛出异常,程序终止
static void (*old_terminate)(void) = nil;static void _objc_terminate(void){if (PrintExceptions) {_objc_inform("EXCEPTIONS: terminating");}if (! __cxa_current_exception_type()) {// No current exception.(*old_terminate)();}else {// There is a current exception. Check if it's an objc exception.@try {__cxa_rethrow();} @catch (id e) {// It's an objc object. Call Foundation's handler, if any.(*uncaught_handler)((id)e);(*old_terminate)();} @catch (...) {// It's not an objc object. Continue to C++ terminate.(*old_terminate)();}}}
1.5.1 crash分类
crash未处理信号的来源:
kernel内核- 其他进行
App本身
所以crash可以划分为三种类型:
Mach异常:是指最底层的内核级异常。用户态的开发者可以直接通过Mach API设置thread,task,host的异常端口,来捕获Mach异常Unix信号:又称BSD信号,如果开发者没有捕获Mach异常,则会被host层的方法ux_exception()将异常转换为对应的UNIX信号,并通过方法threadsignal()将信号投递到出错线程。可以通过方法signal(x, SignalHandler)来捕获singleNSException应用级异常:它是未被捕获的Objective-C异常,导致程序向自身发送了SIGABRT信号而崩溃,对于未捕获的Objective-C异常,是可以通过try catch来捕获的,或者通过NSSetUncaughtExceptionHandler()机制来捕获
针对应用级异常,可以通过注册异常捕获的函数,即NSSetUncaughtExceptionHandler机制,实现线程保活, 收集上传崩溃日志
1.5.2 crash拦截
如果我们在系统中,对底层的uncaught_handler函数,进行下句柄赋值,即可拦截底层抛出的crash
定义LGExceptionHandlers函数
void LGExceptionHandlers(NSException *exception) {NSLog(@"拦截异常:%s,%@", __func__, exception);}
在应用启动时,对uncaught_handler函数下句柄赋值
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {NSSetUncaughtExceptionHandler(&LGExceptionHandlers);return YES;}
一旦出现异常,会被LGExceptionHandlers函数拦截
拦截异常:LGExceptionHandlers,*** -[__NSArrayI objectAtIndexedSubscript:]: index 5 beyond bounds [0 .. 4]
uncaught_handler函数在底层的赋值,调用者是objc_setUncaughtExceptionHandler函数,而 NSSetUncaughtExceptionHandler函数是上层的封装
objc_uncaught_exception_handlerobjc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn){objc_uncaught_exception_handler result = uncaught_handler;uncaught_handler = fn;return result;}
这种拦截方式,只适用于NSException应用级异常,在崩溃前调用自定义函数,但最终程序还是会崩溃
1.6 cache_t::init
缓存的初始化
void cache_t::init(){#if HAVE_TASK_RESTARTABLE_RANGESmach_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}
1.7 _imp_implementationWithBlock_init
启动回调机制。通常情况下,这没有任何作用
void_imp_implementationWithBlock_init(void){#if TARGET_OS_OSX// Eagerly load libobjc-trampolines.dylib in certain processes. Some// programs (most notably QtWebEngineProcess used by older versions of// embedded Chromium) enable a highly restrictive sandbox profile which// blocks access to that dylib. If anything calls// imp_implementationWithBlock (as AppKit has started doing) then we'll// crash trying to load it. Loading it here sets it up before the sandbox// profile is enabled and blocks it.//// 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}
- 所有的初始化都是惰性的,但是对于某些进程,我们是主动加载的
1.8 _dyld_objc_notify_register
在dyld中注册objc的回调方法
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,_dyld_objc_notify_init init,_dyld_objc_notify_unmapped unmapped);
map_images:image镜像文件加载进内存时,会触发该函数load_images:初始化image会触发该函数,调用load方法unmap_image:image镜像移除时会触发该函数
2. map_images
在objc中,调用_dyld_objc_notify_register函数
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
map_images:管理⽂件中和动态库中所有的符号(class、protocol、selector、category)load_images:加载执⾏load⽅法
map_images使用指针地址传递,当_dyld_objc_notify_register函数触发后,一旦map_images参数发生改变,无论在dyld或objc中的map_images都会进行同步修改
map_images函数,将镜像文件映射到内存中。代码逻辑具有一定的复杂度,在遍历读取MachO时可能会发生改变,所以使用指针地址传递,保证其同步修改
load_images和unmap_image函数,处理的业务相对简单,例如调用load方法,并不会导致自身改变,使用值传递即可
进入map_images函数
voidmap_images(unsigned count, const char * const paths[],const struct mach_header * const mhdrs[]){mutex_locker_t lock(runtimeLock);return map_images_nolock(count, paths, mhdrs);}
进入map_images_nolock函数,我们要明确目标,寻找镜像加载的相关代码
3. _read_images
进入_read_images函数,业务代码过于复杂,先将代码块进行折叠


在_read_images函数中,完成以下十个逻辑:
- 条件控制进⾏⼀次的加载
- 修复预编译阶段的
@selector的混乱问题 - 错误混乱的类处理
- 修复重映射⼀些没有被镜像⽂件加载进来的类
- 修复旧版
objc_msgSend_fixup调用方式 - 修复协议
- 修复没有被加载的协议
- 分类处理
- 类的加载处理
- 没有被处理的类,优化那些被侵犯的类
3.1 条件控制进⾏⼀次的加载
在doneOnce流程中,通过NXCreateMapTable创建哈希表,存放类信息
if (!doneOnce) {...initializeTaggedPointerObfuscator();if (PrintConnecting) {_objc_inform("CLASS: found %d classes during launch", totalClasses);}// namedClasses// Preoptimized classes don't go in this table.// 4/3 is NXMapTable's load factorint namedClassesSize =(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;gdb_objc_realized_classes =NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);ts.log("IMAGE TIMES: first time tasks");}
initializeTaggedPointerObfuscator:处理TaggedPointer小对象内存地址,小对象的地址中会存储相关值,函数内部会通过位运算进行获取- 创建
gdb_objc_realized_classes表,4/3是NXMapTable的负载因子,使用3/4扩容的逆运算创建总容积
gdb_objc_realized_classes表和runtime_init中创建的两张表的区别:
gdb_objc_realized_classes:该类不在dyld共享缓存中,无论该类是否实现,都会存储在表中unattachedCategories:分类使用的表allocatedClasses:已开辟空间的类,存储在表中
3.2 修复预编译阶段的@selector的混乱问题
通过_getObjc2SelectorRefs,读取MachO中__DATA段__objc_selrefs节。遍历SEL,将dyld中Rebase后的SEL替换到内存的MachO镜像中
static size_t UnfixedSelectors;{mutex_locker_t lock(selLock);for (EACH_HEADER) {if (hi->hasPreoptimizedSelectors()) continue;bool isBundle = hi->isBundle();SEL *sels = _getObjc2SelectorRefs(hi, &count);UnfixedSelectors += count;for (i = 0; i < count; i++) {const char *name = sel_cname(sels[i]);SEL sel = sel_registerNameNoLock(name, isBundle);if (sels[i] != sel) {sels[i] = sel;}}}}ts.log("IMAGE TIMES: fix up selector references");
sel:从dyld中,读取Rebase后的SELsels[i]:从MachO中读取数据,将Rebase后的SEL覆盖
SEL为方法编号,除了名称为包括地址。名称相同的SEL,不一定地址相同。SEL的相等,需要对名称和地址都进行判断。SEL地址,可使用p/x打印
3.3 错误混乱的类处理
通过_getObjc2SelectorRefs,读取MachO中__DATA段__objc_classlist节。遍历类的列表,将类添加到表中
bool hasDyldRoots = dyld_shared_cache_some_image_overridden();for (EACH_HEADER) {if (! mustReadClasses(hi, hasDyldRoots)) {// Image is sufficiently optimized that we need not call readClass()continue;}classref_t const *classlist = _getObjc2ClassList(hi, &count);bool headerIsBundle = hi->isBundle();bool headerIsPreoptimized = hi->hasPreoptimizedClasses();for (i = 0; i < count; i++) {Class cls = (Class)classlist[i];Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);if (newCls != cls && newCls) {// Class was moved but not deleted. Currently this occurs// only when the new class resolved a future class.// Non-lazily realize the class below.resolvedFutureClasses = (Class *)realloc(resolvedFutureClasses,(resolvedFutureClassCount+1) * sizeof(Class));resolvedFutureClasses[resolvedFutureClassCount++] = newCls;}}}ts.log("IMAGE TIMES: discover classes");
- 当类被移动但没有被删除,出现混乱的情况,进行修复处理
循环中,原本只能打印类的地址,调用readClass函数后,关联上了类的名称
3.4 修复重映射⼀些没有被镜像⽂件加载进来的类
将未映射的Class和Super Class进行重映射
_getObjc2ClassRefs读取MachO中的__DATA段__objc_classrefs节,存储了类的引用_getObjc2SuperRefs读取MachO中的__DATA段__objc_superrefs节,存储了父类的引用
if (!noClassesRemapped()) {for (EACH_HEADER) {Class *classrefs = _getObjc2ClassRefs(hi, &count);for (i = 0; i < count; i++) {remapClassRef(&classrefs[i]);}// fixme why doesn't test future1 catch the absence of this?classrefs = _getObjc2SuperRefs(hi, &count);for (i = 0; i < count; i++) {remapClassRef(&classrefs[i]);}}}ts.log("IMAGE TIMES: remap classes");
- 被
remapClassRef的类都是懒加载的类,这里需要进行重映射
3.5 修复旧版objc_msgSend_fixup调用方式
通过_getObjc2MessageRefs,读取MachO中__DATA段__objc_msgrefs节。调用fixupMessageRef函数,将函数指针进行注册,修复为新的函数指针
#if SUPPORT_FIXUP// Fix up old objc_msgSend_fixup call sitesfor (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
3.6 修复协议
调用protocols函数,创建协议的哈希表。通过_getObjc2ProtocolList,读取MachO中__DATA段__objc_protolist节。遍历协议列表,调用readProtocol函数,将协议添加到protocol_map哈希表中
for (EACH_HEADER) {extern objc_class OBJC_CLASS_$_Protocol;Class cls = (Class)&OBJC_CLASS_$_Protocol;ASSERT(cls);NXMapTable *protocol_map = protocols();bool isPreoptimized = hi->hasPreoptimizedProtocols();// Skip reading protocols if this is an image from the shared cache// and we support roots// Note, after launch we do need to walk the protocol as the protocol// in the shared cache is marked with isCanonical() and that may not// be true if some non-shared cache binary was chosen as the canonical// definitionif (launchTime && isPreoptimized) {if (PrintProtocols) {_objc_inform("PROTOCOLS: Skipping reading protocols in image: %s",hi->fname());}continue;}bool isBundle = hi->isBundle();protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);for (i = 0; i < count; i++) {readProtocol(protolist[i], cls, protocol_map,isPreoptimized, isBundle);}}ts.log("IMAGE TIMES: discover protocols");
3.7 修复没有被加载的协议
通过_getObjc2ProtocolRefs,读取MachO中__DATA段__objc_protorefs节。遍历需要修复的协议,调用remapProtocolRef函数,比较当前协议和协议列表中的同一个内存地址的协议是否相同,如果不同则替换
for (EACH_HEADER) {// At launch time, we know preoptimized image refs are pointing at the// shared cache definition of a protocol. We can skip the check on// launch, but have to visit @protocol refs for shared cache images// loaded later.if (launchTime && hi->isPreoptimized())continue;protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);for (i = 0; i < count; i++) {remapProtocolRef(&protolist[i]);}}ts.log("IMAGE TIMES: fix up @protocol references");
进入remapProtocolRef函数
static size_t UnfixedProtocolReferences;static void remapProtocolRef(protocol_t **protoref){runtimeLock.assertLocked();protocol_t *newproto = remapProtocol((protocol_ref_t)*protoref);if (*protoref != newproto) {*protoref = newproto;UnfixedProtocolReferences++;}}
3.8 分类处理
在分类初始化之后执行,对于在启动时出现的分类,被延迟到_dyld_objc_notify_register调用完成,第一次load_images调用之后
if (didInitialAttachCategories) {for (EACH_HEADER) {load_categories_nolock(hi);}}ts.log("IMAGE TIMES: discover categories");
3.9 类的加载处理
实现非懒加载类load方法和静态实例变量处理
for (EACH_HEADER) {classref_t const *classlist = hi->nlclslist(&count);for (i = 0; i < count; i++) {Class cls = remapClass(classlist[i]);if (!cls) continue;addClassTableEntry(cls);if (cls->isSwiftStable()) {if (cls->swiftMetadataInitializer()) {_objc_fatal("Swift class %s with a metadata initializer ""is not allowed to be non-lazy",cls->nameForLogging());}// fixme also disallow relocatable classes// We can't disallow all Swift classes because of// classes like Swift.__EmptyArrayStorage}realizeClassWithoutSwift(cls, nil);}}ts.log("IMAGE TIMES: realize non-lazy classes");
- 通过
_getObjc2NonlazyClassList,读取MachO中__DATA段__objc_nlclslist节,得到非懒加载类的列表 - 调用
addClassTableEntry函数,将非懒加载类插入类表,存储到内存。如果已经添加就不会载添加,需要确保整个结构都被添加 - 调用
realizeClassWithoutSwift函数,完成当前的类的初始化。因为在readClass函数中,读取到内存的仅有类名和地址,其他数据还需要完善
3.10 没有被处理的类,优化那些被侵犯的类
if (resolvedFutureClasses) {for (i = 0; i < resolvedFutureClassCount; i++) {Class cls = resolvedFutureClasses[i];if (cls->isSwiftStable()) {_objc_fatal("Swift class is not allowed to be future");}realizeClassWithoutSwift(cls, nil);cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);}free(resolvedFutureClasses);}ts.log("IMAGE TIMES: realize future classes");
4.【第三步】错误混乱的类处理流程分析
4.1 readClass
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized){const char *mangledName = cls->nonlazyMangledName();if(strcmp(mangledName, "LGPerson")==0){printf("%s \n",mangledName);}if (missingWeakSuperclass(cls)) {// No superclass (probably weak-linked).// Disavow any knowledge of this subclass.if (PrintConnecting) {_objc_inform("CLASS: IGNORING class '%s' with ""missing weak-linked superclass",cls->nameForLogging());}addRemappedClass(cls, nil);cls->setSuperclass(nil);return nil;}cls->fixupBackwardDeployingStableSwift();Class replacing = nil;if (mangledName != nullptr) {if (Class newCls = popFutureNamedClass(mangledName)) {// This name was previously allocated as a future class.// Copy objc_class to future class's struct.// Preserve future's rw data block.if (newCls->isAnySwift()) {_objc_fatal("Can't complete future class request for '%s' ""because the real class is too big.",cls->nameForLogging());}class_rw_t *rw = newCls->data();const class_ro_t *old_ro = rw->ro();memcpy(newCls, cls, sizeof(objc_class));// Manually set address-discriminated ptrauthed fields// so that newCls gets the correct signatures.newCls->setSuperclass(cls->getSuperclass());newCls->initIsa(cls->getIsa());rw->set_ro((class_ro_t *)newCls->data());newCls->setData(rw);freeIfMutable((char *)old_ro->getName());free((void *)old_ro);addRemappedClass(cls, newCls);replacing = cls;cls = newCls;}}if (headerIsPreoptimized && !replacing) {// class list built in shared cache// fixme strict assert doesn't work because of duplicates// ASSERT(cls == getClass(name));ASSERT(mangledName == nullptr || getClassExceptSomeSwift(mangledName));} else {if (mangledName) { //some Swift generic classes can lazily generate their namesaddNamedClass(cls, mangledName, replacing);} else {Class meta = cls->ISA();const class_ro_t *metaRO = meta->bits.safe_ro();ASSERT(metaRO->getNonMetaclass() && "Metaclass with lazy name must have a pointer to the corresponding nonmetaclass.");ASSERT(metaRO->getNonMetaclass() == cls && "Metaclass nonmetaclass pointer must equal the original class.");}addClassTableEntry(cls);}// for future reference: shared cache never contains MH_BUNDLEsif (headerIsBundle) {cls->data()->flags |= RO_FROM_BUNDLE;cls->ISA()->data()->flags |= RO_FROM_BUNDLE;}return cls;}
- 正常情况下,不会进入
popFutureNamedClass的判断逻辑 popFutureNamedClass:当传入的类名称之前被分配为future类,将其objc_class复制到future类的结构体,保留future类的rw数据块- 正常会进入
addNamedClass函数,然后执行addClassTableEntry函数
4.2 addNamedClass
static void addNamedClass(Class cls, const char *name, Class replacing = nil){runtimeLock.assertLocked();Class old;if ((old = getClassExceptSomeSwift(name)) && old != replacing) {inform_duplicate(name, old, cls);// getMaybeUnrealizedNonMetaClass uses name lookups.// Classes not found by name lookup must be in the// secondary meta->nonmeta table.addNonMetaClass(cls);} else {NXMapInsert(gdb_objc_realized_classes, name, cls);}ASSERT(!(cls->data()->flags & RO_META));// wrong: constructed classes are already realized when they get here// ASSERT(!cls->isRealized());}
- 将类添加到
gdb_objc_realized_classes表中,类名和地址进行关联
4.3 addClassTableEntry
static voidaddClassTableEntry(Class cls, bool addMeta = true){runtimeLock.assertLocked();// This class is allowed to be a known class via the shared cache or via// data segments, but it is not allowed to be in the dynamic table already.auto &set = objc::allocatedClasses.get();ASSERT(set.find(cls) == set.end());if (!isKnownClass(cls))set.insert(cls);if (addMeta)addClassTableEntry(cls->ISA(), false);}
- 将类添加到
allocatedClasses表中。如果addMeta为真,自动添加类的元类
5. 【第九步】类的加载处理流程分析
核心代码:realizeClassWithoutSwift函数。除了在类的加载处理时可能被调用,在消息慢速查找流程中,也有可能被调用
realizeClassWithoutSwift的任务,完成类在加载过程中的三个步骤:
- 读取
data数据,设置ro和rw - 设置类的继承链和
isa指向 - 修复类的方法列表、协议列表和属性列表
5.1 懒加载类 & 非懒加载类
系统中并不是所有类,都必须在程序启动时就完成初始化,这样对内存的消耗过高,而且不利于启动速度。大部分的类在运行时,消息发送时完成初始化,我们称它们为懒加载类,它们会进行按需加载
一个类实现了load方法,即为非懒加载类,它会在main函数之前完成初始化
- 非懒加载类的初始化流程:
map_images→map_images_nolock→_read_images→realizeClassWithoutSwift
反之,如果一个类未实现load方法,即为懒加载类,它会在运行时,消息发送时完成初始化
例如:在main函数中,调用LGPerson的alloc方法
- 懒加载类的初始化流程:
lookUpImpOrForward→initializeAndLeaveLocked→initializeAndMaybeRelock→realizeClassMaybeSwiftAndUnlock→realizeClassMaybeSwiftMaybeRelock→realizeClassWithoutSwift
5.2 读取data数据,设置ro和rw
auto ro = (const class_ro_t *)cls->data();auto isMeta = ro->flags & RO_META;if (ro->flags & RO_FUTURE) {// This was a future class. rw data is already allocated.rw = cls->data();ro = cls->data()->ro();ASSERT(!isMeta);cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);} else {// Normal class. Allocate writeable class data.rw = objc::zalloc<class_rw_t>();rw->set_ro(ro);rw->flags = RW_REALIZED|RW_REALIZING|isMeta;cls->setData(rw);}
- 正常的类会进入
else流程 - 开辟
rw空间,把干净的ro内存,复制一份到rw下的ro中,设置flags - 将
rw设置到类的data中
5.3 设置类的继承链和isa指向
supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
- 递归调用
realizeClassWithoutSwift函数,获得父类和元类 - 根元类的
isa是指向自己,所以在remapClass中,会对类在表中进行查找。如果表中已有该类,返回一个空值,否则返回当前类。通过这种方式,避免了死递归
#if SUPPORT_NONPOINTER_ISAif (isMeta) {// Metaclasses do not need any features from non pointer ISA// This allows for a faspath for classes in objc_retain/objc_release.cls->setInstancesRequireRawIsa();} else {// Disable non-pointer isa for some classes and/or platforms.// Set instancesRequireRawIsa.bool instancesRequireRawIsa = cls->instancesRequireRawIsa();bool rawIsaIsInherited = false;static bool hackedDispatch = false;if (DisableNonpointerIsa) {// Non-pointer isa disabled by environment or app SDK versioninstancesRequireRawIsa = true;}else if (!hackedDispatch && 0 == strcmp(ro->getName(), "OS_object")){// hack for libdispatch et al - isa also acts as vtable pointerhackedDispatch = true;instancesRequireRawIsa = true;}else if (supercls && supercls->getSuperclass() &&supercls->instancesRequireRawIsa()){// This is also propagated by addSubclass()// but nonpointer isa setup needs it earlier.// Special case: instancesRequireRawIsa does not propagate// from root class to root metaclassinstancesRequireRawIsa = true;rawIsaIsInherited = true;}if (instancesRequireRawIsa) {cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);}}// SUPPORT_NONPOINTER_ISA#endif
- 如果
isMeta为真,设置为原始isa - 否则,进入
else流程,因环境变量或应用SDK版本,设置为原始isa
cls->setSuperclass(supercls);cls->initClassIsa(metacls);
- 设置父类
- 将
isa指向设置为元类
if (supercls) {addSubclass(supercls, cls);} else {addRootClass(cls);}
- 继承链结构为双向链,父类中可以找到子类,子类中也可以找到父类
- 如果父类存在,调用
addSubclass函数,将当前类添加为supercls的子类 - 反正,调用
addRootClass函数,将当前类添加为根类
5.4 修复类的方法列表、协议列表和属性列表
methodizeClass(cls, previously);
- 调用
methodizeClass函数,修复类的方法列表、协议列表和属性列表
5.4.1 methodizeClass
进入methodizeClass函数,读取cls中的rw、ro、rwe
bool isMeta = cls->isMetaClass();auto rw = cls->data();auto ro = rw->ro();auto rwe = rw->ext();
从ro中读取方法列表,调用prepareMethodLists函数,准备进行方法的修复
// 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);}
5.4.2 prepareMethodLists
static voidprepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,bool baseMethods, bool methodsFromBundle, const char *why){...// Add method lists to array.// Reallocate un-fixed method lists.// The new methods are PREPENDED to the method list array.for (int i = 0; i < addedCount; i++) {method_list_t *mlist = addedLists[i];ASSERT(mlist);// Fixup selectors if necessaryif (!mlist->isFixedUp()) {fixupMethodList(mlist, methodsFromBundle, true/*sort*/);}}...}
- 循环方法列表,调用
fixupMethodList函数,修复方法
5.4.3 fixupMethodList
static voidfixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort){runtimeLock.assertLocked();ASSERT(!mlist->isFixedUp());// fixme lock less in attachMethodLists ?// dyld3 may have already uniqued, but not sorted, the listif (!mlist->isUniqued()) {mutex_locker_t lock(selLock);// Unique selectors in list.for (auto& meth : *mlist) {const char *name = sel_cname(meth.name());meth.setName(sel_registerNameNoLock(name, bundleCopy));}}// Sort by selector address.// Don't try to sort small lists, as they're immutable.// Don't try to sort big lists of nonstandard size, as stable_sort// won't copy the entries properly.if (sort && !mlist->isSmallList() && mlist->entsize() == method_t::bigSize) {method_t::SortBySELAddress sorter;std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter);}// Mark method list as uniqued and sorted.// Can't mark small lists, since they're immutable.if (!mlist->isSmallList()) {mlist->setFixedUp();}}
- 将名称和
SEL写入到meth中,对方法列表按地址进行升序排序 - 所以在消息慢速查找流程中,可以使用二分查找法
总结
_objc_init:
environ_init:环境变量的初始化,查看帮助:export OBJC_HELP = 1tls_init:线程key的绑定,例如:线程数据的析构函数static_init:运行C++静态构造函数
◦ libc在dyld调用静态构造函数之前调用_objc_init()
runtime_init:运行时环境初始化
◦ 创建两张表:unattachedCategories、allocatedClasses
exception_init:异常信号处理的初始化cache_t::init:缓存的初始化_imp_implementationWithBlock_init:启动回调机制。通常情况下,这没有任何作用
◦ 所有的初始化都是惰性的,但是对于某些进程,我们是主动加载的
_dyld_objc_notify_register:在dyld中注册objc的回调方法
◦ map_images:image镜像文件加载进内存时,会触发该函数
◦ load_images:初始化image会触发该函数,调用load方法
◦ unmap_image:image镜像移除时会触发该函数
map_images:
- 函数的作用:管理⽂件中和动态库中所有的符号(
class、protocol、selector、category) map_images使用指针地址传递,在遍历读取MachO时可能会发生改变,需要保证其同步修改- 核心代码,调用
_read_images函数
_read_images:
- 条件控制进⾏⼀次的加载
- 修复预编译阶段的
@selector的混乱问题 - 错误混乱的类处理
- 修复重映射⼀些没有被镜像⽂件加载进来的类
- 修复旧版
objc_msgSend_fixup调用方式 - 修复协议
- 修复没有被加载的协议
- 分类处理
- 类的加载处理
- 没有被处理的类,优化那些被侵犯的类
【第三步】错误混乱的类处理流程分析:
readClass
◦ 正常情况下,不会进入popFutureNamedClass的判断逻辑(处理future类的ro、rw)
◦ 正常情况下,进入addNamedClass函数,然后执行addClassTableEntry函数
addNamedClass
◦ 将类添加到gdb_objc_realized_classes表中,类名和地址进行关联
addClassTableEntry
◦ 将类添加到allocatedClasses表中。如果addMeta为真,自动添加类的元类
【第九步】类的加载处理流程分析:
realizeClassWithoutSwift函数:
◦ 除了在类的加载处理时可能被调用,在消息慢速查找流程中,也有可能被调用
realizeClassWithoutSwift函数的作用,完成类在加载过程中的三个步骤:
◦ 读取data数据,设置ro和rw
◦ 设置类的继承链和isa指向
◦ 修复类的方法列表、协议列表和属性列表
- 懒加载类 & 非懒加载类
◦ 一个类实现了load方法,即为非懒加载类,它会在main函数之前完成初始化
◦ 反之,如果一个类未实现load方法,即为懒加载类,它会在运行时,消息发送时完成初始化
- 读取
data数据,设置ro和rw
◦ 正常的类,进入分配可写的类数据流程
◦ 开辟rw空间,把干净的ro内存,复制一份到rw下的ro中,设置flags
◦ 将rw设置到类的data中
- 设置类的继承链和
isa指向
◦ 递归调用realizeClassWithoutSwift函数,获得父类和元类
◦ 设置父类,将isa指向设置为元类
◦ 继承链结构为双向链,父类中可以找到子类,子类中也可以找到父类
◦ 如果父类存在,调用addSubclass函数,将当前类添加为supercls的子类。反正,调用addRootClass函数,将当前类添加为根类
- 修复类的方法列表、协议列表和属性列表
◦ 调用methodizeClass函数,修复类的方法列表、协议列表和属性列
◦ methodizeClass→prepareMethodLists→fixupMethodList,将名称和SEL写入到meth中,对方法列表按地址进行升序排序。所以在消息慢速查找流程中,可以使用二分查找法
