ELF

ELF (Executable and Linkable Format)是一种为可执行文件,目标文件,共享链接库和内核转储(core dumps)准备的标准文件格式。 Linux和很多类Unix操作系统都使用这个格式。

目前常见的Linux、 Android可执行文件、共享库 (.so)、目标文件 ( .o)以及Core 文件 (吐核)均为此格式。

64位ELF文件格式的结构

一个ELF文件由以下三部分组成:

ELF头(ELF header) - 描述文件的主要特性:类型,CPU架构,入口地址,现有部分的大小和偏移等等;

程序头表(Program header table) - 列举了所有有效的段(segments)和他们的属性。 程序头表需要加载器将文件中的节加载到虚拟内存段中;

节头表(Section header table) - 包含对节(sections)的描述。

ELF头(ELF header)

ELF头(ELF header)位于文件的开始位置。 它的主要目的是定位文件的其他部分。 文件头主要包含以下字段:

ELF文件鉴定 - 一个字节数组用来确认文件是否是一个ELF文件,并且提供普通文件特征的信息; 文件类型 - 确定文件类型。 这个字段描述文件是一个重定位文件,或可执行文件,或…; 目标结构; ELF文件格式的版本; 程序入口地址; 程序头表的文件偏移; 节头表的文件偏移; ELF头(ELF header)的大小; 程序头表的表项大小; 其他字段…

节(sections)

所有的数据都存储在ELF文件的节(sections)中。 我们通过节头表中的索引(index)来确认节(sections)。 节头表表项包含以下字段:

节的名字; 节的类型; 节的属性; 内存地址; 文件中的偏移; 节的大小; 到其他节的链接; 各种各样的信息; 地址对齐; 这个表项的大小,如果有的话;

程序头表(Program header table)

在可执行文件或者共享链接库中所有的节(sections)都被分为多个段(segments)。 程序头是一个结构的数组,每一个结构都表示一个段(segments)。

为什么需要ELF 隔离应用服务和内核服务 有了ELF,我们的应用程序都可以通过编译成ELF的方式从外部加载,系统内核部分只提供关键的内核服务 (内存管理/中断/调度/IPC)和系统服务 (文件/网络),用户服务程序都可以通过ELF的方式进行加载。同时库的同步更新可以在不更新内核的情况进行同步更新。 提供统一编程接口 我们通过提供统一的POSIX标准库,来为用户提供标准的编程接口,方便了应用程序开发人员进行标准开发 应用程序可以动态加载和卸载 通过ELF,用户可以动态的加载和卸载相关服务,增强了应用程序加载的灵活性 ELF加载方式 静态库-独立exec 我们把所有相关源代码进行编译,链接,最后生成可执行文件,这个文件不依赖于其他模块,是一个完整的可执行单元。 操作系统处理这类文件的流程是直接将elf的所有段拷贝到内存中,然后将PC指针指向entry就可以运行了,什么场景下会这样使用呢?

简单的应用程序,不依赖于其他模块 依赖于其他模块,其他模块以静态库的方式链接到应用程序 优点:程序是一个完整的可执行单元,不需要操作系统去进行重定向操作 缺点:每一个依赖于某个静态库的应用程序都会包含完整的静态库,这样每一个应用程序都会占用磁盘空间和内存空间,如果这样的应用程序有成千上万,那资源浪费很大

动态库-非独立exec 非独立exec我们这里只讲依赖于动态库的应用程序,一般情况我们在编写应用程序的时候,会用到很多库,这些库是其他工程师已经写好了,我们直接用就可以了,最典型的就是C和C++库,我们在写应用程序的时候只需要关注我们自己的业务功能就可以了。 外部库的存在方式以静态和动态2种,静态库在上面已经分析了,接下来我们着重分析动态库 如果我们的C库已经编译成了动态库,那么我们把他链接到我们的应用程序的时候,编译器只会对所需C库的符号进行分析,并把他记录到rel.dyn里,并不会真正的把动态库中的具体内容进行拷贝,所以这样编译出的应用程序就不包括动态库的内容,等到将应用程序真正加载到操作系统去运行的时候,我们操作系统中会有一个动态链接器dl去完成外部符号的链接与重定位 优点: 应用程序依赖的动态库在内存中只存在一份,当然数据段是每个应用程序私有的,这样会节约磁盘空间和内存空间 当我们要更新动态库时,只需要更新动态库文件即可,所依赖的应用程序不需要单独更新

缺点: 应用程序的启动会牺牲一部分启动时间 动态库更新需要考虑兼容性问题

https://zhuanlan.zhihu.com/p/401446080