这是我参与8月更文挑战的第12天,活动详情查看:8月更文挑战
1 dyld加载流程分析(下)
上文说到_objc_init中调用了_dyld_objc_notify_register(&map_images, load_images, unmap_image)对registerObjCNotifiers中的sNotifyObjCMapped
,sNotifyObjCInit
,sNotifyObjCUnmapped
,进行了赋值
。那么sNotifyObjCMapped,sNotifyObjCInit是在哪里调用
的呢
1.1 sNotifyObjCMapped 的调用
在dyld 源码中搜索sNotifyObjCMapped
,看到其在notifyBatchPartial
中被调用。
在搜索notifyBatchPartial在哪里被调用,发现其在被赋值后就直接被调用了
。
1.2 sNotifyObjCInit 的调用
在dyld 源码中搜索sNotifyObjCInit
,发现也是赋值后,sNotifyObjCMapped调用之后调用
。
1.3 load 函数的调用
load函数是哪里调用的呢
?在load函数打个断点,然后输入bt查看,发现是在load_images
里面。
那么load_images 又是如何调用的load函数呢。 首先找到load_images,看到这里有一个prepare_load_methods
。
点击进去看到prepare_load_methods有主类和分类的load方法
先看主类的load 方法,看到这里会进行递归,将类的所有父类也调用schedule_class_load方法
。然后会add_class_to_loadable_list
。
点进去看到有getLoadMethod
。
在点进去看到这里将类中的load方法imp取了出来
。
在看回getLoadMethod,发现其将class 添加到loadable_classes
里面,这里的method就是之前返回的load 方法的imp
。
再来看分类的load方法发现实现差不多,只是添加到了另一个字典loadable_categories
里面。
再回到load_images
方法,看到有call_load_methods
方法。
看到call_load_methods
里面有call_class_loads
和call_category_loads
方法。这里做的就是递归将所有的loadable_classes里面的类和loadable_categories里面的分类的load方法进行调用
。
看一下call_class_loads,发现确实是将loadable_classes里面的class和method取出来,然后进行load方法的调用。
看一下分类的实现方法,也是一摸摸一样样的。
1.4 CXX 方法的调用
在main函数中添加一个CXX 方法。
运行后发现,是在load方法之后。
在CXX 方法里面打个断点,然后输入bt查看方法栈,发现是在doModInitFunctions
里面调用的。
doModInitFunctions是在doInitialization
里面调用的,而doInitialization是在notify之前,也就是load方法之前的,为什么这里反而cxx方法在load之后调用呢?这里因为这里的doInitialization是objc的镜像文件初始化
,而不是工程的初始化
,所以工程里的cxx方法在load后面调用
。证明一下,在_objc_init
中添加cxx方法。
然后运行,可以看到在_objc_init中的c++函数确实是在load方法之前的。
1.5 main 函数的调用
dyld是如何找到main函数并对其进行调用的呢? 又回到最初的起点:dyld_start
。看到这里的最后一行有一个跳转到main函数的地方。
在mac上运行一下证明,在cXX函数打个断点,然后进入汇编模式
。
往下走,看到jmpq *%rax
,也就是之前说会跳转到main函数的地方。
读取一下寄存器
,发现rax就是main函数。所以jmpq *%rax 也就是调用main函数。
如果把main函数名字改成别的,那么还能进行调用吗?
运行一下,发现了报错。可以知道main是写定的函数,写入内存,读取到dyld,如果修改了main函数的名称,那么程序就会报错。