前言
dyld加载流程(精简)
由于dyld
流程加载是一个比较复杂的过程,这里我们只做简单的分析。
下面是main
函数之前调用堆栈的信息:
通过上面的堆栈信息我们可以简单分析出来一个流程
_dyld_start
函数,是整个加载的入口。- 经过
dyldbooststrap::start
->dyld::start
->ImageLoad::
->ImageLoadMackO::
这些流程把MachO
里面的信息读取到。libSystem_initializer
,初始化系统库。libdispatch_init
,初始化libdispatch库
。_os_object_init
->::_objc_init
,初始化objc
库。
dyld
主要的步骤都在读取MachO
段里面的数据,这一步在今后的文章中会做分析,敬请期待。
objc_init流程
这里我们提供objc_init
的源码部分
1. _objc_init
注释
/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/
看只是我们可以得到两个信息
- 注册
image
在dyld
这个阶段;- 调用
_objc_init
在libSystem
系统库之后。
2. environ_init
初始化环境变量,我们可以通过export OBJC_HELP=1
ls
在终端查看相关的环境变量
我们可以调整相关的环境变量的值。如下
2.1 修改OBJC_PRINT_LOAD_METHOD
修改该环境变量之后观察控制台打印输出的结果
- 系统的
load
方法会全部打印出来。- 自己定义的
load
方法也会打印出来,同时我们熊这里入手,在做启动优化的时候可以快速定位调整。
2.1 修改OBJC_PRINT_NONPOINTER_ISA
修改该环境变量之后观察控制台打印输出的结果
- 如果关闭
nonpointer
之后,类的内存地址和isa
的内存地址就相同了,这里也验证了iOS 底层探索篇 —— isa的初始化&指向分析isa
的初始化的时候,直接将类赋值isa
的cls
成员。
3. tls_init()
初始化关于线程key
的绑定,比如线程的析构函数。
4. static_init()
初始化静态构造函数,这里的注释部分也在讲在dyld
之后。
这里我们做一个实验,比对一下我们自己增加几种构造函数前后count
的值。
-
未增加自己的构造函数
没有增加自己的构造函数之前的
count
值为11。 -
增加自己的构造函数
增加自己的构造函数之前的
count
值为11。
通过上面两种方式的比对,我们可以知道static_init
初始化的是系统的构造函数。
5. lock_init()
初始化objc
各种锁
这里主要是4把锁的初始化,代码为空我们分析以下几种可能
- 没有做相关处理。
- 有做处理,但是在其他位置。
- 有做处理,没有做到完全开源。
6. exception_init
报错的初始化
若是出现奔溃信息,则会在_objc_terminate
这个函数里面
7. _dyld_objc_notify_register(&map_images, load_images, unmap_image)
通过三个函数指针直接发起回调。这里分析_dyld_objc_notify_mapped
这个函数,在dyld
里面通过注册函数的内存地址,然后通过函数指针又回调到了map_images
这个函数。
map_images
,下面进入到map_images
分析。
map_Images
1. map_images
函数
/***********************************************************************
* map_images
* Process the given images which are being mapped in by dyld.
* Calls ABI-agnostic code after taking ABI-specific locks.
*
* Locking: write-locks runtimeLock
**********************************************************************/
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
函数是一个提供外部调用入口,函数的真正实现在map_images_nolock
。
2. map_images_nolock
函数
由于代码比较多,我们去掉一些无关紧要的代码,比如打印日志信息、代码的注释、iOS系统用到的,精简之后如下
void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
static bool firstTime = YES;
header_info *hList[mhCount];
uint32_t hCount;
size_t selrefCount = 0;
if (firstTime) {
preopt_init();
}
// Find all images with Objective-C metadata.
hCount = 0;
// Count classes. Size various table based on the total.
int totalClasses = 0;
int unoptimizedTotalClasses = 0;
{
uint32_t i = mhCount;
while (i--) {
const headerType *mhdr = (const headerType *)mhdrs[i];
auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);
if (!hi) {
// no objc data in this entry
continue;
}
if (mhdr->filetype == MH_EXECUTE) {
// Size some data structures based on main executable is size
#if __OBJC2__
size_t count;
_getObjc2SelectorRefs(hi, &count);
selrefCount += count;
_getObjc2MessageRefs(hi, &count);
selrefCount += count;
#else
_getObjcSelectorRefs(hi, &selrefCount);
#endif
}
hList[hCount++] = hi;
}
}
// Perform one-time runtime initialization that must be deferred until
// the executable itself is found. This needs to be done before
// further initialization.
// (The executable may not be present in this infoList if the
// executable does not contain Objective-C code but Objective-C
// is dynamically loaded later.
if (firstTime) {
sel_init(selrefCount);
arr_init();
}
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
firstTime = NO;
}
现在开始分析精简之后的代码
firstTime
一个用static
修饰的局部成员变量,记录第一次需要做的事。_getObjc2SelectorRefs
函数,从mach_o
读取__objc_selrefs
段的信息。_getObjc2MessageRefs
函数,从mach_o
读取__objc_msgrefs
段的信息。- 第一次需要执行的函数
preopt_init()
、sel_init()
、arr_init()
。_read_images()
函数,读取镜像文件。
2.1 preopt_init()
注释描述直到objc-using
镜像文件可以发现延迟初始化,主要是共享缓存有了之后的处理。
2.2 sel_init()
去掉其他信息之后,我们可以看到是注册了一些系统提供的API
。
2.3 arr_init()
这一块也是比较重要的信息,让我们知道了AutorereleasePoolPage
和SideTable
的初始化在这里是有执行的。
2.4 _read_images()
前面的三个函数不是我们目前探索的重点,只是简单的分析了一下。下面我们开始深入_read_images()
,请看接下来的类加载部分。
类加载
1. _read_images()
函数
由于这个函数的代码比较多,我们把其中的代码先用伪代码的信息展示出来,然后再来讲解每一步。
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
1. 定义局部变量,完成doneOnce第一次要处理的事情。
if (!doneOnce){ ... }
2. Discover classes
for (EACH_HEADER) { ... }
3. Fix up remapped classes
if (!noClassesRemapped()) { ... }
4. Fix up @selector references
{ ... }
5. Fix up old objc_msgSend_fixup call sites
for (EACH_HEADER) { ... }
6. Discover protocols
for (EACH_HEADER) { ... }
7. Fix up @protocol references
for (EACH_HEADER) { ... }
8. Realize non-lazy classes (for +load methods and static instances)
for (EACH_HEADER) { ... }
9. Realize newly-resolved future classes
if (resolvedFutureClasses) { ... }
10. Discover categories.
for (EACH_HEADER) { ... }
11. +load handled by prepare_load_methods()
if (DebugNonFragileIvars) { ... }
}
这里我们只对第1步、第2步、第11步做分析,其他的步骤会在对应的文章中讲解。
1.1 doneOnce
去掉打印部分的代码、与iOS操作系统不相干的代码。
if (!doneOnce) {
doneOnce = YES;
#if SUPPORT_NONPOINTER_ISA
# if SUPPORT_INDEXED_ISA
# endif
# if TARGET_OS_OSX
# endif
#endif
if (DisableTaggedPointers) {
disableTaggedPointers();
}
initializeTaggedPointerObfuscator();
if (PrintConnecting) {
_objc_inform("CLASS: found %d classes during launch", totalClasses);
}
// namedClasses
// Preoptimized classes do not go in this table.
// 4/3 is NXMapTable is load factor
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
allocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil);
ts.log("IMAGE TIMES: first time tasks");
}
disableTaggedPointers()
函数,条件允许的时候,禁用taggedPointers
。initializeTaggedPointerObfuscator
函数,初始化taggedPointer
。gdb_objc_realized_classes
通过NX技术创建的mapTable
,这张表里面包含的所有类的信息,我们称之为总表。allocatedClasses
通过NX技术创建的HashTable
,这张表只包含已经开辟的类的信息,我们称之为已开辟类的表。
1.2 Discover classes
for (EACH_HEADER) {
classref_t *classlist = _getObjc2ClassList(hi, &count);
if (! mustReadClasses(hi)) {
// Image is sufficiently optimized that we need not call readClass()
continue;
}
bool headerIsBundle = hi->isBundle();
bool headerIsPreoptimized = hi->isPreoptimized();
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;
}
}
}
-
_getObjc2ClassList
从mach_o
读取__objc_classlist
段的信息。 -
readClass
函数
rw
、ro
在处理,但是并不是,它是对future
的class
的处理。我们自己定义的类并不会进来。
- 第一步,将
cls
无条件的插入到gdb_objc_realized_classes
这个总表里面。 - 第二步,若
cls
可以识别,即是已开辟的类,插入到allocatedClasses
这个表里面,并且会递归元类插入。
2. realizeAllClasses()
函数
循环处理header_info
,函数实现realizedAllClassesInImage()
.
2.1 realizedAllClassesInImage()
函数
remapClass
重新映射类,realizeClassMaybeSwiftAndLeaveLocked
函数调用。
realizeClassMaybeSwiftMaybeRelock
函数真正调用的总入口。
通过判断是否Swift class
来区分Swift
和OC
类的实现入口。
2.2 realizeClassWithoutSwift()
函数
函数的信息比较多,这里全部贴出来
static Class realizeClassWithoutSwift(Class cls)
{
runtimeLock.assertLocked();
const class_ro_t *ro;
class_rw_t *rw;
Class supercls;
Class metacls;
bool isMeta;
if (!cls) return nil;
if (cls->isRealized()) return cls;
assert(cls == remapClass(cls));
// fixme verify class is not in an un-dlopened part of the shared cache?
ro = (const class_ro_t *)cls->data();
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
rw = cls->data();
ro = cls->data()->ro;
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// Normal class. Allocate writeable class data.
rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
rw->ro = ro;
rw->flags = RW_REALIZED|RW_REALIZING;
cls->setData(rw);
}
isMeta = ro->flags & RO_META;
rw->version = isMeta ? 7 : 0; // old runtime went up to 6
// Choose an index for this class.
// Sets cls->instancesRequireRawIsa if indexes no more indexes are available
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)" : "");
}
// Realize superclass and metaclass, if they aren't already.
// This needs to be done after RW_REALIZED is set above, for root classes.
// This needs to be done after class index is chosen, for root metaclasses.
// This assumes that none of those classes have Swift contents,
// or that Swift's initializers have already been called.
// fixme that assumption will be wrong if we add support
// for ObjC subclasses of Swift classes.
supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));
#if SUPPORT_NONPOINTER_ISA
// 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 && !(ro->flags & RO_META) &&
0 == strcmp(ro->name, "OS_object"))
{
// hack for libdispatch et al - isa also acts as vtable pointer
hackedDispatch = true;
instancesRequireRawIsa = true;
}
else if (supercls && supercls->superclass &&
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->setInstancesRequireRawIsa(rawIsaIsInherited);
}
// SUPPORT_NONPOINTER_ISA
#endif
// Update superclass and metaclass in case of remapping
cls->superclass = supercls;
cls->initClassIsa(metacls);
// Reconcile instance variable offsets / layout.
// This may reallocate class_ro_t, updating our ro variable.
if (supercls && !isMeta) reconcileInstanceVariables(cls, supercls, ro);
// Set fastInstanceSize if it was not set already.
cls->setInstanceSize(ro->instanceSize);
// Copy some flags from ro to rw
if (ro->flags & RO_HAS_CXX_STRUCTORS) {
cls->setHasCxxDtor();
if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
cls->setHasCxxCtor();
}
}
// Propagate the associated objects forbidden flag from ro or from
// the superclass.
if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
(supercls && supercls->forbidsAssociatedObjects()))
{
rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
}
// Connect this class to its superclass is subclass lists
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
// Attach categories
methodizeClass(cls);
return cls;
}
这里的信息比较多,基本是都是对类的结构体的成员初始化处理。我们抽其中比较重要的描述。
rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
为rw
结构体开辟空间。同时在为其成员赋值。supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
递归父类的信息初始化,出口是父类为nil
。metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));
递归元类的信息初始化,出口是元类的元类为自己。if (supercls) {addSubclass(supercls, cls);} else { addRootClass(cls);}
,处理class_data_bits_t
成员双向绑定父类,子类。methodizeClass()
处理类的相关信息,看下面的介绍。
methodizeClass()
函数
简化一下打印的代码之后
static void methodizeClass(Class cls)
{
runtimeLock.assertLocked();
bool isMeta = cls->isMetaClass();
auto rw = cls->data();
auto ro = rw->ro;
// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
rw->methods.attachLists(&list, 1);
}
property_list_t *proplist = ro->baseProperties;
if (proplist) {
rw->properties.attachLists(&proplist, 1);
}
protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
rw->protocols.attachLists(&protolist, 1);
}
// Root classes get bonus method implementations if they do not have
// them already. These apply before category replacements.
if (cls->isRootMetaclass()) {
// root metaclass
addMethod(cls, SEL_initialize, (IMP)&objc_noop_imp, "", NO);
}
// Attach categories.
category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
attachCategories(cls, cats, false /*don't flush caches*/);
if (cats) free(cats);
}
我们可以发现是在这里将
ro
的信息给了rw
,可以说明ro
的信息是在编译阶段就已经处理好了,rw
则是在运行阶段处理的。
method_list_t
、property_list_t
、protocol_list_t
,他们的数据类型是相同,都是entsize_list_tt
这么一个二维数组。- 他们都是通过获取
ro
里面的相关信息之后,然后通过attachLists
函数处理来赋值类的rw
的信息。attachCategories
是把拿到的分类列表进行处理,最终的处理也是将信息通过attachLists()
函数处理来赋值类的rw
的信息。
attachLists()
函数分析
主要有三种情况来处理
- 多对多;
- 0对1;
- 1对多。
- 多对多的分析
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
这种情况分下面几步完成
- 扩大已有容器的内存;
- 将原本已有的数据移动增加的数量
addedCount
;- 将需要增加的
addedLists
放在扩容之后的内存的前面。
- 0对1的分许
list = addedLists[0];
这就是一步直接的赋值。
- 1对多的分析
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
- 扩大已有容器的内存;
- 将旧数据放在指定的位置;
- 将需要增加的数据放在前面。
总结
_read_images()
读取镜像文件。realizeAllClasses()
实现所有的类。包括Swift
和OC
,同时会初始化其他objc_class
结构体相关的信息methodizeClass()
把类的ro
信息给到rw
,同时也会加载分类的信息到类里面。
学习之路,砥砺前行
不足之处可以在评论区指出