0%

dyld和启动流程

先看一个断点

App Launch 以及 dyld

自行下载参考dyld源码,下面只是我的简要记录

dyldbootstrap::start
{
rebaseDyld 符号便宜aslr address layout random
__guard_setup 栈溢出保护
dyld::_main
}

rebaseDyld {
遍历所有固定的 chains 然后 rebase dyld
所有基于修正链的映像的基地址为零,因此slide == 加载地址

  // now that rebasing done, initialize mach/syscall layer
mach_init(); // from libc.a

}

重点分析的方法

dyld::_main分析

  1. 初始化程序运行环境

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
     //获取主程序的macho_header结构以及主程序的slide偏移值
    sMainExecutableMachHeader = mainExecutableMH;
    sMainExecutableSlide = mainExecutableSlide;


    //获取主程序路径
    // Pickup the pointer to the exec path.
    sExecPath = _simple_getenv(apple, "executable_path");
    if (!sExecPath) sExecPath = apple[0];
    if ( sExecPath[0] != '/' ) {
    // have relative path, use cwd to make absolute
    ....
    }

    //获取进程名称
    // Remember short name of process for later logging
    sExecShortName = ::strrchr(sExecPath, '/');
    if ( sExecShortName != NULL )
    ++sExecShortName;
    else
    sExecShortName = sExecPath;

    //配置进程受限模式
    configureProcessRestrictions(mainExecutableMH, envp);

    //检测环境变量
    checkEnvironmentVariables(envp);
    defaultUninitializedFallbackPaths(envp);

    `所谓的启动优化就是在这里,添加dyly参数,进行打印的`

    //判断是否设置了sEnv.DYLD_PRINT_OPTS以及sEnv.DYLD_PRINT_ENV,分别打印argv参数和envp环境变量
    if ( sEnv.DYLD_PRINT_OPTS )
    printOptions(argv);
    if ( sEnv.DYLD_PRINT_ENV )
    printEnvironmentVariables(envp);
    //获取当前程序架构
    getHostInfo(mainExecutableMH, mainExecutableSlide);
  2. 加载共享缓存 shared cache
    mapSharedCache();

  3. 实例化主程序,并赋值给ImageLoader::LinkContext

    1
    2
    3
    4
    5
    6
    7
    8
    //
    try {
    // add dyld itself to UUID list
    addDyldImageToUUIDList();
    sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
    gLinkContext.mainExecutable = sMainExecutable;
    gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);
    }
  4. 加载插入的动态库++++++++++++++++++++

    1
    2
    3
    4
    5
    // load any inserted libraries
    if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
    for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib)
    loadInsertedDylib(*lib);
    }
  5. 链接主程序++++++++++++++

    1
    2
    3
    gLinkContext.linkingMainExecutable = true;
    link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
    sMainExecutable->setNeverUnloadRecursive();
  6. 链接插入的动态库++++++++

    1
    2
    3
    4
    5
    6
    7
    8
    9
    ...
    ImageLoader::applyInterposingToDyldCache(gLinkContext);
    // Bind and notify for the inserted images now interposing has been registered
    if ( sInsertedDylibCount > 0 ) {
    for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
    ImageLoader* image = sAllImages[i+1];
    image->recursiveBind(gLinkContext, sEnv.DYLD_BIND_AT_LAUNCH, true);
    }
    }
  7. 在链接所有插入的image后,执行弱绑定++++++++++++++++++++++++++++++

    1
    2
    3
    4
    5
    // <rdar://problem/12186933> do weak binding only after all inserted images linked
    sMainExecutable->weakBind(gLinkContext);
    gLinkContext.linkingMainExecutable = false;

    sMainExecutable->recursiveMakeDataReadOnly(gLinkContext);
  8. 执行所有的初始化方法+++++++++++++++++++++

    1
    2
    // run all initializers
    initializeMainExecutable();
  9. 查找主程序的入口点并返回

    1
    2
    3
    4
    5
        // find entry point for main executable
    result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();

    return result;
    }

总结dyld::_main主要做了以下操作(就不一一分析了):

主程序运行环境初始化及配置,拿到Mach-O头文件 (macho_header里面包含整个Mach-O文件信息其中包括所有链入的动态库信息)
加载共享缓存 shared cache
实例化主程序,并赋值给ImageLoader::LinkContext
加载所有插入的动态库,将可执行文件以及相应的依赖库与插入库加载进内存生成对应的ImageLoader类的image(镜像文件)对象
链接主程序(必须先链接主程序后才能插入)
链接所有的动态库ImageLoader的image(镜像文件)对象,并注册插入的信息,方便后续进行绑定
在链接完所有插入的动态库镜像文件之后执行弱绑定
执行所有动态库image的初始化方法initializeMainExecutable
查找主程序的入口点LC_MAIN并返回result结果,结束整个_dyld_start流程,进入我们App的main()函数!

这里解释一下共享缓存机制:网上自己查询 dyld1.0版本 - dyld3 版本, 当前使用dyld3版本

dyld加载时,为了优化程序启动,在dyld::_main中启用了共享缓存(shared cache)技术。共享缓存会在进程启动时被dyld映射到内存中,之后,当任何Mach-O映像加载时,dyld首先会检查该Mach-O映像与所需的动态库是否在共享缓存中,如果存在,则直接将它在共享内存中的内存地址映射到进程的内存地址空间。在程序依赖的系统动态库很多的情况下,这种做法对程序启动性能会有明显提升。

分析一下_main的第8步,initializeMainExecutable() 倒数第二步

自行根据下面的方法阅读源码
initializeMainExecutable -> runInitializers -> processInitializers -> recursiveInitialization -> notifySingle

最后我想说的就是这个notifySingle,

1
context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);

找到一个关键的函数指针* sNotifyObjCInit, 全局搜索找到赋值

1
2
3
4
5
6
7
8
9
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
// record functions to call
sNotifyObjCMapped = mapped;
sNotifyObjCInit = init;
sNotifyObjCUnmapped = unmapped;

...
}

全局搜索,看看registerObjCNotifiers这个方法会被谁调用,找到调用的地方_dyld_objc_notify_register函数

不用找了,在objc的源码里面, _dyld_objc_notify_register_objc_init进行调用的。而_objc_init函数则是Runtime的入口函数!

打开Objc源码,搜索_objc_init

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/

void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;

// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
lock_init();
exception_init();

//注册回调函数
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

_dyld_objc_notify_register 注释

1
2
3
4
5
6
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* //引导程序初始化。 用dyld注册我们的image通知程序。
* Called by libSystem BEFORE library initialization time
* //在库初始化之前由libSystem调用!!!!!
*

_objc_init的调用时机是在其他动态库加载之前由libSystem系统库先调用的。

那么到现在就很明确了,其实在dyld::_main主程序的第8步,初始化所有动态库及主程序的时候之前,就先注册了load_images的回调,之后在Runtime调用load_images加载完所有load方法之后,就会回调到dyld::_main的initializeMainExecutable()内部执行回调。

map_images

自行下载objc源码, 找打 _objc_init 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/

void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;

// fixme defer initialization until an objc-using image is found?

//读取影响运行时的环境变量。
environ_init();

tls_init();

//运行C ++静态构造函数。libc在dyld调用我们的静态构造函数之前调用_objc_init()
static_init();

lock_init();
//初始化libobjc的异常处理系统。由map_images()调用。
exception_init();

注册回调函数
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

map_images直接返回了map_images_nolock,直接看实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
定义一系列变量
......

必要时执行首次初始化

//如果是第一次,就准备初始化环境
if (firstTime) {
preopt_init();
}

// Find all images with Objective-C metadata.
hCount = 0;

计算class数量,根据总数调整各种表的大小。
// Count classes. Size various table based on the total.
int totalClasses = 0;
int unoptimizedTotalClasses = 0;
{
......
}
......

执行一次运行时初始化,必须将其推迟到找到可执行文件本身为止。 这需要在进一步初始化之前完成。(如果可执行文件不包含Objective-C代码,但稍后会动态加载Objective-C,则该可执行文件可能不会出现在此infoList中。

if (firstTime) {

//初始化sel方法表 并注册系统内部专门的方法。
sel_init(selrefCount);
arr_init();
}

直接开始image读取
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}

firstTime = NO;
}

总结map_images_nolock的流程就是:

  • 判断firstTime,firstTime为YES,则执行环境初始化的准备,为NO就不执行
  • 计算class数量,根据总数调整各种表的大小并做了GC相关逻辑处理(不支持GC则打印提示信息)
  • 判断firstTime,firstTime为YES,执行各种表初始化操作,为NO则不执行
  • 执行_read_images进行读取,然后将firstTime置为NO,就不再进入上面的逻辑了,下次进入map_images_nolock就开始直接_read_images

接下来我们重点分析_read_images

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
/***********************************************************************
* _read_images
* Perform initial processing of the headers in the linked
* list beginning with headerList.
*
* Called by: map_images_nolock
*
* Locking: runtimeLock acquired by map_images
**********************************************************************/
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{

定义一系列局部变量
......

1. 重新初始化TaggedPointer环境****************

if (!doneOnce) {
doneOnce = YES;

......

if (DisableTaggedPointers) {
disableTaggedPointers();
}

initializeTaggedPointerObfuscator();

......

注意!!!!!创建表 gdb_objc_realized_classes 和 allocatedClasses

......

int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

allocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil);

......
}


// Discover classes. Fix up unresolved future classes. Mark bundle classes.

2. 开始遍历头文件,进行类与元类的读取操作并标记(旧类改动后会生成新的类,并重映射到新的类上)************************

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];

//!!!!!!核心操作,readClass读取类的信息及类的更新
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);

......

}
}

......

3. 读取@selector*************************************

// Fix up @selector references
static size_t UnfixedSelectors;
{
mutex_locker_t lock(selLock);
for (EACH_HEADER) {
if (hi->isPreoptimized()) 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]);
sels[i] = sel_registerNameNoLock(name, isBundle);
}
}
}


......


4. 读取协议protocol*************************************
// Discover protocols. Fix up protocol refs.
for (EACH_HEADER) {
extern objc_class OBJC_CLASS_$_Protocol;
Class cls = (Class)&OBJC_CLASS_$_Protocol;
assert(cls);
NXMapTable *protocol_map = protocols();
bool isPreoptimized = hi->isPreoptimized();
bool isBundle = hi->isBundle();

protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
for (i = 0; i < count; i++) {
readProtocol(protolist[i], cls, protocol_map,
isPreoptimized, isBundle);
}
}

......


5. 处理分类category,并rebuild重建这个类的方法列表method list*******************************

// Discover categories.
for (EACH_HEADER) {
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();

for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);

......

bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}

if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
}
}

......

if (DebugNonFragileIvars) {
realizeAllClasses();
}


最后是一堆打印***********
......

}

_read_images的实现主要分为以下步骤:

  1. 重新初始化TaggedPointer环境
  2. 开始遍历头文件,进行类与元类的读取操作并标记(旧类改动后会生成新的类,并重映射到新的类上)
  3. 读取@selector方法
  4. 读取协议protocol
  5. 处理分类category,并rebuild重建这个类的方法列表method list

两个表,一个叫gdb_objc_realized_classes用来存放已命名的类的列表,另一个叫allocatedClasses用来存放已分配的所有类(和元类)

逐步分析 readClass、@selector、 protocol、 category

从源码中可以看出, readClass 方法有返回值,并且包含三种逻辑处理:

  • 找不到该类的父类,可能是弱绑定,直接返回nil;
  • 找到类了,判断这个类是否是一个future的类(可以理解为需要实现的一个类,也可以理解为这个类是否有变化),如果有变化则创建新类,并把旧类的数据拷贝一份然后赋值给新类newCls,然后调用addRemappedClass进行重映射,用新的类替换掉旧的类,并返回新类newCls的地址
  • 找到类了,如果类没有任何变化,则不进行任何操作,直接返回class

从readClass的底层实现部分做个延伸思考:日常开发中,对于已经启动完成的工程项目,如果我们未修改任何类的数据,那么再次点击运行会很快完成,但是一旦我们在对这些类进行修改后,在读取这些类的信息(包括类本身的信息以及下面我们要继续分析的协议protocol、分类category、方法selector),就需要对该类的数据进行更新,这个更新实际上是新建一个类,然后拷贝旧类的数据赋值给新类,然后重映射并用新类替换掉新类,这里面的拷贝以及读写过程其实是相当耗时的!这是类信息改动之后项目再次Run运行起来会比较慢的原因之一。

已经读取完成的类,会被存放到了这个表gdb_objc_realized_classes里面!

分析源码注释及源码得出,addClassTableEntry里面会把这个读取完成的类直接先添加到allocatedClasses表里面,然后再判断addMeta是否为YES,然后会把当前这个类的元类metaClass也添加到allocatedClasses这个表里面。

@selector

点击sel_registerNameNoLock,找到__sel_registerName,在它里面找到关键代码
逻辑其实就是:把方法名插入并存储到namedSelectors这个表里面.

protocol

找到关键函数readProtocol,进入发现其实读取protocol的操作是把protocol存进协议表protocol_map。

category

分类category的读取,里面主要做了下面这些步骤:

  1. 从头文件中获取所有的分类列表catlist,然后循环遍历这个列表
  2. 在循环中,判断当前分类cat所属的类是否存在,如果不存在则把这个分类置为空catlist[i] = nil; 如果这个分类所属的类存在,那么开始下面两个步骤:
  3. 第一个步骤:判断这个分类cat中是否有实例方法instanceMethods,协议protocols以及属性实例instanceProperties,如果有,那么进入remethodizeClass,重新rebuild当前类cls的方法列表
  4. 第二个步骤:继续判断这个分类cat中是否有类方法classMethods,协议protocols以及类属性_classProperties,然后重新rebuild当前类所对应元类cls->ISA()的方法列表。

loadimage

调用load方法,父类-子类-所有分类

总结

_objc_init 是系统库,很早就已经 由libSystem系统库先调用的,初始化了很多环境 其中有_dyld_objc_notify_register(&map_images, load_images, unmap_image);

dyld 初始化进程环境,链接动态库,进行 _dyld_objc_notify_register 注册

参考

https://www.jianshu.com/p/ea680941e084

希望对您有所帮助,您的支持将是我莫大的动力!