应用程序会依赖很多的库,包括系统的,如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
=mapped
sNotifyObjCInit
=init
sNotifyObjCUnmapped
=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
函数