1. _objc_init
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
//环境变量的初始化,查看帮助:export OBJC_HELP = 1
environ_init();
//线程key的绑定,例如:线程数据的析构函数
tls_init();
//运行C++静态构造函数
//libc在dyld调用静态构造函数之前调用_objc_init()
static_init();
//运行时环境初始化,创建两张表:unattachedCategories、allocatedClasses
runtime_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 = 1
tls_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 loaded
objc[99433]: OBJC_PRINT_IMAGES is set
objc[99433]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps
objc[99433]: OBJC_PRINT_IMAGE_TIMES is set
objc[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 variables
objc[97117]: OBJC_PRINT_OPTIONS: list which options are set
objc[97117]: OBJC_PRINT_IMAGES: log image and library names as they are loaded
objc[97117]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps
objc[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_KEYS
pthread_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)
来捕获single
NSException
应用级异常:它是未被捕获的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_handler
objc_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_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
}
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
函数
void
map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
mutex_locker_t lock(runtimeLock);
return map_images_nolock(count, paths, mhdrs);
}
进入map_images_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 factor
int 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
后的SEL
sels[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 sites
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
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
// definition
if (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 names
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(cls);
}
// for future reference: shared cache never contains MH_BUNDLEs
if (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 void
addClassTableEntry(Class cls, bool addMeta = true)
{
runtimeLock.assertLocked();
// This class is allowed to be a known class via the shared cache or via
// data segments, but it is not allowed to be in the dynamic table already.
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_ISA
if (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 version
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())
{
// This is also propagated by addSubclass()
// but nonpointer isa setup needs it earlier.
// Special case: instancesRequireRawIsa does not propagate
// from root class to root metaclass
instancesRequireRawIsa = 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 void
prepareMethodLists(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 necessary
if (!mlist->isFixedUp()) {
fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
}
}
...
}
- 循环方法列表,调用
fixupMethodList
函数,修复方法
5.4.3 fixupMethodList
static void
fixupMethodList(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 list
if (!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 = 1
tls_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
中,对方法列表按地址进行升序排序。所以在消息慢速查找流程中,可以使用二分查找法