当前位置 : 主页 > 编程语言 > c++ >

从 dyld 到 runtime

来源:互联网 收集:自由互联 发布时间:2021-06-23
最近在看《程序员的自我修养》,学到了一些关于编译、linker、dyld等知识,当浏览一些大神的关于dyld的blog时,加上一些open source上源码,有了自己的理解,记录一下。 dyld 加载的宏观

最近在看《程序员的自我修养》,学到了一些关于编译、linker、dyld等知识,当浏览一些大神的关于dyld的blog时,加上一些open source上源码,有了自己的理解,记录一下。

dyld 加载的宏观过程

系统加载dyld执行顺序:

1
2
3
4
5
6
7
8
9
dyld中c++部分:

// In dyld we have to do this manually.
start(){
// others work...
return _main();//而_main()返回的是主程序main()的地址
}
dyld汇编中部分:
__dyld_start: 会jumps 到start()返回的地址

dyld中的_main()的内容

先看看 _main()注释??:

1
2
3
4
5
6
// 
// Entry point for dyld. The kernel loads dyld and jumps to __dyld_start which
// sets up some registers and call this function.
//
// Returns address of main() in target program which __dyld_start jumps to
//

??_main()的主要内容:??

1
2
3
4
5
6
7
8
9
1,// instantiate ImageLoader for main executable
// load shared cache
// Now that shared cache is loaded, setup an versioned dylib overrides
2,// load any inserted libraries
3,// link main executable
4,// link any inserted libraries -- do this after linking main executable so that any dylibs pulled in by inserted // dylibs (e.g. libSystem) will not be in front of dylibs the program uses
5,// run all initializers
6,// find entry point for main executable
7,// Returns address of main() in target program

ImageLoader简介

1
2
3
4
5
6
7
8
9
10
11
12
//
// ImageLoader is an abstract base class. To support loading a particular executable
// file format, you make a concrete subclass of ImageLoader.
//
// 每个.o文件都会生成一个ImageLoader子类实例
// For each executable file (dynamic shared object) in use, an ImageLoader is instantiated.
//
// The ImageLoader base class does the work of linking together images, but it knows nothing
// about any particular file format.
//
//
class ImageLoader {...}

ImageLoader是 抽象类,其子类负责把 mach-o文件 实例化为 image(image :ImageLoader子类的实例), image大概表示一个二进制文件(可执行文件或 so 文件),里面是被编译过的符号、代码等。
ImageLoader 抽象类的作用是将这些images 加载进内存。

dyld加载 主程序的过程(第一步)

instantiate ImageLoader for main executable

在dyld的_main() 中,把主程序的mach-o文件加载成image,并持有image,看code:

1
2
// instantiate ImageLoader for main executable
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
1
2
3
4
5
6
其中instantiateFromLoadedImage()的主要实现是:
检查.o是否合法
加载.o实例化为 image:
根据mach-o文件segments的规则将数据加载到内存中
根据mach-o文件的头部信息,将segments的具体信息构建到image的实例中
addImage():把image添加到images容器中,并更新内存分布

instantiateFromLoadedImage()

把.o 实例化为 image

1
2
3
4
5
6
7
8
9
10
// The kernel maps in main executable before dyld gets control. 
// We need to make an ImageLoader* for the already mapped in main executable.
static ImageLoader* instantiateFromLoadedImage(...){
if ( isCompatibleMachO((const uint8_t*)mh, path) ) { //检查.o是否合法
//instantiateMainExecutable():得到主程序的image。省略不贴code了(影响美观,??)
ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext);
addImage(image);
return image;
}
}

addImage()

把image添加到images容器中,并更新内存分布

1
2
3
4
5
6
7
8
static void addImage(ImageLoader* image){
//add to master list
// update mapped ranges
// other work…
if ( sEnv.DYLD_PRINT_LIBRARIES || ...) {
dyld::log("dyld: loaded: %sn", image->getPath());
}
}

//看到了我们熟悉的 DYLD_PRINT_LIBRARIES,就知道为什么设置环境变量DYLD_PRINT_LIBRARIES后,会打印dylib的path了

dyld 加载其他的dylibs(第二步)

load any inserted libraries
dyld在link主程序之前还会动态的加载一些其他的库文件,

小记:DYLD_INSERT_LIBRARIES就是需要插入的dylib的路径(多个dylib以“:”为分隔符)。
DYLD_INSERT_LIBRARIES应用场景:如果我们想 hook 一个app,swizzle 某个类的方法,那么我们定义一个dylib,在其类的+load()中进行swizzle。那么如何让自定义dylib加入内存呢?就是需要设置需要加载的dylib的path到 DYLD_INSERT_LIBRARIES中就可以了,app在执行 主程序的main()之前就把它加载好了。
除此之外,环境变量DYLD_INSERT_LIBRARIES也是可以判断机器是否越狱,值为NULL说明未越狱,其他址一般会含有Path,就代表已经越狱了(希望app在启动时加载某些dylibs)

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);
}

其中 loadInsertedDylib()中会调用当前文件的load()【不是NSObject中的load()】,
这里的load()的目的是: 加载mach-o并实例化为image,得到image
Load函数的实现为一系列的loadPhase*(loadPhase0、loadPhase1…)函数,主要可以分为这几个部分:

  1. 处理环境变量,生成各种搜索路径,去加载image。
  2. 如果该lib已经加载过,则利用share_cache中已经存在的imageloader实例。
  3. 如果该lib没有加载过,通过读取文件,将mach-o文件映射到内存中,生成imageloader的实例。

初始化dylibs 和 主程序(第五步)

// run all initializers
即调用 initializeMainExecutable();

注意下面的 两个run initialzers…

1
2
3
4
5
6
7
8
void initializeMainExecutable(){
// run initialzers for any inserted dylibs
// run initializers for main executable and everything it brings up
// register cxa_atexit() handler to run static terminators in all loaded images when this process exits
// dump info if requested
if( sEnv.DYLD_PRINT_STATISTICS ){
ImageLoaderMachO::printStatistics((unsigned int)sAllImages.size(), initializerTimes[0]);
}

Q: 在自己 Class 的 +load 方法时能不能替换系统 framework(比如 UIKit)中的某个类的方法实现
A: 可以,因为动态链接过程中,所有依赖库的类是先于自己的类加载的。

其中,看到环境变量 DYLD_PRINT_STATISTICS:统计main()之前的时间消耗

初始化dylibs

run initialzers for any inserted dylibs

当初始化libSystem时,会间接的调用 _objc_init(),其中:_objc_init()就是 objc 和 runtime 的初始化入口,除了 runtime 环境的初始化外,_objc_init中绑定了新 image 被加载后的 callback(map_2_images & load_images)。

调用顺序:libSystem_initializer() -> libdispatch_init() -> _os_object_init() -> _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
31
static void libSystem_initializer(...){
// others init...
libdispatch_init();
}
void libdispatch_init(void){
// others init...
_os_object_init();
}
void _os_object_init(void){
_objc_init();
// others init work...
}
/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Old ABI: called by dyld as a library initializer
* New ABI: called by libSystem BEFORE library initialization time
**********************************************************************/
void _objc_init(void){
// other init work…

// Old objc-runtime.mm(??的主要用这种来介绍)
dyld_register_image_state_change_handler(dyld_image_state_bound,1/*batch*/, &map_2_images);
dyld_register_image_state_change_handler(dyld_image_state_dependents_initialized,0/*not batch*/, &load_images);

// 在新的 objc-runtime.mm中是这样的,反正都差不多就不计较用法了。
// _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
// 关于 dyld_register_image_state_change_handler的介绍,其中第三个参数是callback!!
// Register a handler to be called when any image changes to the requested state.
extern void dyld_register_image_state_change_handler(enum dyld_image_states state, bool batch, dyld_image_state_change_handler handler);

在_objc_init()中注册的callback

map_2_images() & load_images()

map_2_images()

对这个二进制中的ObjC相关的信息进行初始化,把class、category、method…一些信息注册起来。

1
2
3
4
5
6
7
8
9
10
11
调用过程:map_2_images() -> map_images_nolock() -> _read_images()
map_images_nolock注释:
* Process the given images which are being mapped in by dyld.
* All class registration and fixups are performed (or deferred pending discovery of missing superclasses etc), and +load methods are called.
void _read_images(header_info **hList, uint32_t hCount){
// 比较重要的部分:
// Read classes from all images.
// Read categories from all images
// Connect classes from all images.【 Connect the classes in the given image to their superclasses,or register them for later connection if any superclasses are missing.】
// Fix up class refs, selector refs, and protocol objects from all images.
}
load_images()

顺序:parent load-> load -> category load

执行所有 class 的 +load()【看看??的call_load_methods的注释】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const char*load_images(enum dyld_image_states state, uint32_t infoCount,const structdyld_image_info infoList[]){
// other work ...
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
/***********************************************************************
* call_load_methods
* Call all pending class and category +load methods.
* Class +load methods are called superclass-first.
* Category +load methods are not called until after the parent class's +load.
…………
**********************************************************************/
void call_load_methods(void){
// other work...
do{
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
}while (loadable_classes_used > 0 || more_categories);
}

可以看出:

由于 runtime 在 _objc_init()中 向 dyld 绑定了回调,当 image 加载到内存后,dyld 会通知 runtime 进行处理。
runtime 执行callback是 map_2_images 与 load_images,解释如下:

(1) map_2_images:All class registration and fixups are performed
(2) 至此,可执行文件中和动态库所有的符号(Class,Protocol,Selector,IMP,…)都已经按格式成功加载到内存中,在这之后,runtime 的那些方法(动态添加 Class、swizzle 等等)才能生效
(3) load_images:Call all pending class and category +load methods.
可以在 +load()中 进行swizzle操作…

走一遍流程

load main exe -> shared cache -> load inserted dylibs -> link main exe -> link dylibs -> init ( 先dylibs 后 main exe ) -> 返回main exe的入口地址。
其中的init dylibs -> objc register callback when image load-> execute callback-> call_load_methods()

参考:
iOS 程序 main 函数之前发生了什么 · sunnyxx的技术博客
dyld与ObjC - 刘坤的技术博客
dyld中mach-o文件加载的简单分析 | mrh的学习分享
dyld源码分析-动态加载load | mrh的学习分享

原文:大专栏  从 dyld 到 runtime

网友评论