小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
1.程序加载的原理
系统内核将可执行文件从磁盘中加载到内存中,内存中的二进制文件,我们称之为image镜像文件。之后,系统会加载动态链接器dyld。dyld只会负责动态库的加载,主程序也会作为镜像形式被dyld管理起来。
dyld从可执行文件的依赖开始,递归加载所有以来的动态库。无论是动态链还是APP本身的可执行文件,它们都是image镜像,而每个APP都是以image为单位进行加载的。
2.编译过程
编译过程如下图所示:
- 源文件:
.h、.m、.cpp等文件 - 预编译:替换宏,删除注释,展开头文件,词法分析、语法分析。生成.i文件
- 编译:转换成汇编语言,生成
.s文件 - 汇编:把汇编语言文件转换为机器码文件,产生
.o文件 - 链接:对
.o文件中引用其他库的地方进行引用,生成最后的可执行文件
3.静态库&动态库
库(Library):就是一段编译好的二进制代码,可以被系统加载,加上头文件就可以供别人使用,常用的库文件格式有:.a、.dyld、.framework、.xcframework、.tdb
3.1 什么时候会用到库?
- 某些代码需要给别人使用,但是我们不希望别人看到源码,就需要以库的形式进行封装,只暴露出头文件
- 对于某些不会进行大改动的代码,我们想减少编译的时间,就可以把它打包成库。因为库是已经编译好的二进制,编译的时候只需要
Link一下,不会浪费编译时间
3.2 什么是链接
库(Library)在使用的时候需要链接(Link)
链接的方式有两种:静态和动态。
3.3 什么是静态库?
静态库即静态链接库:可以简单的看成一组目标文件的集合。即很多目标文件经过压缩打包成形成的文件。Windows下的.lib,Linux和Mac下的.a,Mac独有的.framework。
缺点:浪费内存和磁盘空间,模块更新困难
3.4 什么是动态库?
与静态库相反,动态库在编译时并不会被拷贝到目标程序中,目标程序中只会存储指向动态库的引用。等到程序运行时,动态库才会被真正加载进来。格式有:.framework、.dyld、.tdb
缺点:会导致一些性能损失。但是可以优化,比如延迟绑定(Lazy Binding)技术
4. dyld
4.1 dyld是什么?
dyld:动态链接器,加载所有的库和可执行文件
libdyld.dylib:给我们的程序提供在Runtime期间能使用动态连接功能
4.2 加载程序的过程
- 调用
fork函数,创建一个process(进程)调用execve或其衍生函数,在该进程上加载,执行我们的Mach-O文件 - 将文件加载到内存
- 开始分析Mach-O中的
mach_header,以确认它时有效的Mach-O文件 - 验证通过,根据
mach_header解析load commands。根据解析结果,将程序各个部分加载到指定的地址空间,同时设置保护标记 - 从
LC_LOAD_DYLINKEN中加载dyld - dyld开始工作
4.3 dyld的工作是什么?
- 执行自身初始化配置加载环境
LC_DYLD_INFO_ONLY - 加载当前程序链接的所有动态库到指定的内存中
LC_LOAD_DYLIB - 搜索所有的动态库,绑定需要在调用程序之前用的符号(非懒加载符号)
LC_DYSYMTAB - 在间接符号表(
indirect symbol table)中,将需要绑定的导入符号真是地址替换LC_DYSYMTAB - 向程序提供在Runtime时使用dyld的接口函数(存在
libdyld.dylib中,由LC_LOAD_DYLIB提供) - 配置Runtime,执行所有动态库。
image中使用的全局构造函数 dyld调用程序入口函数,开始执行程序LC_MAIN
4.4 程序启动的初始方法
在ViewController中,加入load方法
@implementation ViewController
+ (void)load{
NSLog(@"ViewController load方法");
}
@end
在main.m中加入C++构造函数
__attribute__((constructor)) void func(){
printf("\n C++构造函数:%s \n",__func__);
}
在main.m函数中,增加NSLog打印
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
NSLog(@"main函数");
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
-------------------------
//输出结果:
ViewController load方法
C++构造函数:func
main函数
- load方法 -> C++构造函数 -> main函数
main函数为程序入口,但load方法和C++构造函数的执行时机比main函数更早,它们时被谁调用的? 在load方法中设置断点,查看函数调用栈
可以发现应用启动时的初始方法,由dyld中的
_dyld_start开始的。