应用程序会依赖很多的库,包括系统的,如UIKit、CoreFoundation,还有第三方的。
什么是库?
- 库是可执行的二进制文件,能够被操作系统加载到内存。
- 库又分为静态库和动态库。
编译过程
- 源文件经过预编译进行词法语法的分析
- 将预编译结果编译成汇编
- 链接库文件生成可执行文件
动态链接器dyld引出
在我们使用断点断住程序的时候,xCode左侧Thread1最下面会调用start函数这个函数来自libdyld.dylib
动态链接器dyld源码分析
1.下载dyld最新源码,目前最新是852。dyld源码下载
2.全局搜索_dyld_start
3.全局搜索c++函数的命名空间dyldbootstrap,在命名空间所在文件搜索start函数
4.在start函数中最后一步return到dyld::_main,进入main函数。
能看到
main函数占用了八百多行代码。这里不打算一开始就从上往下一行一行分析,而是使用反推法倒着分析,先了解主要流程。
dyld::_main函数源码分析主要流程
1.函数的末尾返回了result,查看result在函数体内有哪些赋值操作。
2.下面两处都是sMainExecutable在调用函数的返回值
3.查看sMainExecutable在 _main函数中的出处
查看初始化函数instantiateFromLoadedImage
返回
machO读取对象。
4.链接sMainExecutable
5.弱引用绑定主程序
6.运行所有已经初始化的东西
7.通知主程序进入的main()函数
主线流程已经分析完,下面跟随主线流程看看细节
dyld::_main函数源码分析流程细节补充
1.首先_main函数前两百多行代码是条件准备,包括环境、平台、版本、路径、主机等信息的处理
加载插入的动态库
2.读取共享缓存
3.在链接主程序前面,读取插入的动态库
4.在链接主程序后面,链接插入的动态库
initializeMainExecutable分析
插入的动态库、主程序都调用了
runInitializers函数
因为gLinkContext.notifySingle = ¬ifySingle;
全局搜索registerObjCNotifiers的调用
发现是_dyld_objc_notify_register调用的,而且这个函数是在objc源码是函数_objc_init调用的
接下来在
objc源码中_objc_init打上断点,打印调用栈
再次使用反推法:
由函数调用栈能看到
-
libobjc.A.dylib调用了_objc_init -
libdispatch.dylib调用了_os_object_init- 分析
_os_object_init实现:_os_object_init函数内部调用了_objc_init
- 分析
-
libdispatch.dylib调用了libdispatch_init- 分析
libdispatch_init实现:libdispatch_init调用了_os_object_init
- 分析
-
libSystem.B.dylib调用了libSystem_initializer- 分析
libSystem_initializer实现:
- 分析
libSystem_initializer调用了libdispatch_init
- 调用了
dyld ImageLoaderMachO::doModInitFunctions函数- 分析
doModInitFunctions实现:也就是说
doModInitFunctions确保libSystem初始化
- 分析
dyld ImageLoaderMachO::doInitialization- 分析
doInitialization实现:doInitialization调用了doModInitFunctions
- 分析
dyld ImageLoader::recursiveInitialization- 分析
recursiveInitialization实现:这一块
initializeMainExecutable分析流程也提到了
- 分析
_dyld_objc_notify_register
_dyld_objc_notify_register(&map_images, load_images, unmap_image); 在
_objc_init中_dyld_objc_notify_register有三个参数&map_images、load_images、unmap_image,_dyld_objc_notify_register把参数原封不动的传给了registerObjCNotifiers
在
registerObjCNotifiers内部:
sNotifyObjCMapped=mappedsNotifyObjCInit=initsNotifyObjCUnmapped=unmapped
- 所以
map_images调用就是sNotifyObjCMapped的调用 全局搜索sNotifyObjCMapped在哪里调用?
全局搜索notifyBatchPartial在哪里调用?
也就是map_images(sNotifyObjCMapped)在registerObjCNotifiers->notifyBatchPartial函数内调用
- 同理
load_images调用就是sNotifyObjCInit的调用 全局搜索sNotifyObjCInit在哪里调用?
全局搜索notifySingle在哪里调用?
load、c++函数、main函数调用顺序分析
测试代码准备:
int main(int argc, char * argv[]) {
printf("main函数调用 %s \n",__func__);
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
__attribute__((constructor)) void cppFunc(){
printf("C++函数调用 : %s \n",__func__);
}
@implementation ViewController
+ (void)load{
NSLog(@"\n %s 函数调用",__func__);
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
@end
执行结果如下:
调用顺序依次为
load、c++函数、main函数
load方法调用时机分析
在_objc_init调用_dyld_objc_notify_register,_dyld_objc_notify_register第二个参数load_images定义如下:
- 查找
load的方法: - 调用所有
load方法
所以
load的方法在_objc_init即将结束时就调用了。
c++函数调用时机分析
在c++函数内打上断点,查看函数调用栈
在调用
doModInitFunctions后调用了cppFunc,doModInitFunctions是读取machO的,所以这个c++函数是写在machO中的
main函数调用时机分析
在dyld源码中搜索_dyld_start的汇编
在调用完
dyldbootstrap::start后会调到main函数
实操验证:
- 断点过掉
dyldbootstrap::start register read读取寄存器rax就是main函数