elf 文件与链接

1.为什么需要 elf 文件

1.1 前置知识

操作系统在启动以后,cpu 与内存的管理权限完全由操作系统接管,若此时想要在操作系统之上运行其他的二进制程序,则其必须满足 ABI 的标准。

ABI (application binary intrface):是指两程序模块间的接口,定义了数据的组织方式或者指令的访问过程被机器码。通常其中一个模块会是库或操作系统所提供的服务,而另一边的模块则是用户所运行的程序。

1.2 elf 文件是什么

维基百科原文如下:

In computing, the Executable and Linkable Format (ELF, formerly named Extensible Linking Format), is a common standard file format for executable files, object code, shared libraries, and core dumps. First published in the specification for the application binary interface (ABI) of the Unix operating system version named System V Release 4 (SVR4),[2] and later in the Tool Interface Standard,[1] it was quickly accepted among different vendors of Unix systems. In 1999, it was chosen as the standard binary file format for Unix and Unix-like systems on x86 processors by the 86open project.

上述这段内容的主要含义是

elf 是一种文件格式,是可执行文件、目标代码、共享库和核心转储的通用标准文件格式。它首先在Unix操作系统版本的应用程序二进制接口(ABI)规范中发布

1.3 结论

符合 elf 格式的文件才能够被操作系统识别并运行。

不用过多关注编译器如何将一种高级语言编译成低级语言被机器运行的我们,犹如站在巨人肩膀上。

2. elf 文件里有什么

2.1 前置知识

在软件源代码的组织中,人们将代码按照功能或者性质进行划分,形成不同的功能模块。

注:在 C 语言中,最小的单位是变量和函数,若干变量和函数组成一个模块,存放在一个 .c 的源代码文件中。(ps 思考下其他语言的模块划分单位是什么,比如 java、go

2.2 elf 结构之——问答设计

如同学习 tcp 协议设计思想就是学习 tcp 协议包头部的每个字节的意义一样,分析 elf 文件就是分析 elf 文件的格式设计。

问:如何证明自己是一个 elf 文件呢?

答:简单,设计一个 elf 文件头字段,里面可以针对不同平台设计不同的头部标识,事实上,elf 文件也是这么设计的。

问:如何对源文件的符号进行划分?(ps 就是源代码的内容啦……

答:综合各种因素,比如 cpu 的缓存策略、读写分离等因素,将至分为代码段(.text)和数据段(.data),当然还有其他很多不知名的段比如 .comment 等。

问:假设某个模块中引用了其他模块的变量或者符号,这种时候怎么确定引用符号的地址?

答:额……,增加一个全局的索引表应该可以解决吧,bingo,.symtab 就是这样的一个功能。

问:为什么要分 section 和 segment 呢?

答:因为要省内存,每个 section 等价于一个 segment 的设计思路也是可以的,但是由于字节对齐要求的存在,这将会是一种浪费内存的行为。但是文件在磁盘上的存储内容其实仍是按照 section 的方式,只是在进程加载的时候会将相同属性的 section 进行合并以 segment 的形式分配内存。

elf 文件主要包含两类信息,一类 section headers 的信息,一类是 program header 信息(即 segment 信息)

在这里插入图片描述

2.1 从链接的视角

链接视角以「section」为划分单位。section header table,这个保存了所有 section 的信息。

.text 段存放程序代码片段,比如函数指令等。

.data 段保存初始化的全局静态变量和局部静态变量。

.bss 段存放为初始化的全局变量和局部静态变量。

其他常见段:(ps 此处书是指《程序员的自我修养》

在这里插入图片描述

2.2 从运行的视角

运行视角以「segment」为划分单位。progam header table 给出 segment 与 section 之间的映射关系。segment 的详细介绍跟 elf 文件的加载有很紧密的关系,打算后面写的时候详细介绍,此处就不写啦~~(ps 其实是我困了

3. 链接

3.1 基本概念

概念:链接的主要工作就是把各个模块之间的「互相引用」的部分处理好,使得各个模块之间能够正确的衔接。

原理:将一些指令对其他符号地址的引用加以修正。

3.2 过程

我们知道将模块的源代码文件(如.c)经过编译器编译会产生目标文件(.o 文件)。将产生的目标文件与库文件(library)一起链接形成 elf 可执行文件的过程称之为——链接,具体过程如下图所示。

在这里插入图片描述

3.2.1 空间与地址分配

相似段合并,将相同性质的段合并到一起,比如将所有 .o 目标文件的 .text 合并到 elf 文件的 .text 段,.data 等其他段类似。

即——扫描所有 .o 目标文件,获得它们的各个段的长度、属性和位置,并且将 .o 文件中的符号表中所有符号定义和符号引用收集起来,统一放到一个全局符号表。在这一步骤中,链接器能够获得所有 .o 文件和共享库文件的段长度,并且将他们合并,计算出链接后的 elf 文件中各个段的长度与位置,并建立映射关系。

在这里插入图片描述

注:思考个小问题,如何理解「bss」段在目标文件和可执行文件并不占用文件的空间这句话?

3.2.2 符号解析和重定位

读取 .o 文件和库文件中的段的数据、重定位信息,并且进行符号解析、重定位、调整代码中地址等。

注:在 elf 文件中有一个叫重定位表的结构专门用来保存于重定位相关的信息。该表的存在用于通知链接器哪些字段是需要重定位的。

以下面两个 .c 的代码为示例介绍:

// a.c 文件
extern int shared;

int main() {
    
    
  int a = 100;
  swap(&a, &shared);
}
// b.c 文件
int shard = 1;
void swap(int * a, int * b) {
    
    
  *a ^= *b ^= *a ^ = *b;   
}

未进行地址修正重定位之前的 a.o 文件的反汇编代码如下:

在这里插入图片描述

注:在重定位之前,对于暂时不知道地址的变量和函数使用了占位符的假地址,在地址确定后,对假地址进行修正。

此处:0x00000000 和 0xfffffffc 均为占位符地址。

链接器在完成地址和空间分配之后就可以确定所有符号的虚拟地址(假设为静态链接),那么链接器就可以根据符号的地址对每个需要重定位的指令进行地址修正,修正后的反汇编如下:

在这里插入图片描述

4. 碎碎念

「尽信书,则不如无书」觉得这句话很有道理,很多事情是需要很认真的思考过以后才会有体会的,所以这篇大部分都是摘抄的文章,自己感觉写的不是很满意,所以打算后面再写一篇有自己体会的。嗯,就酱,睡觉去了……

5. 参考资料

猜你喜欢

转载自blog.csdn.net/phantom_111/article/details/106202752