ELF,链接,加载
ELF:
ELF类型的文件主要有四种:
1.可重定位文件:即XXX.o,包含与其他目标文件链接来创建可执行文件或者目标共享文件的代码和数据
2.可执行文件:即可执行程序
3.共享目标文件:即XXX.so
4.内核转储:存放当前进程的执行上下文,用于dump信号触发
ELF文件包含一下四个部分:
1.ELF头:描述文件的主要特性,位于文件的头部,用来定位文件的其他部分。
2.程序头表:列举了所有有效的段(段是由节合并而来的)和他们的属性。表里记录的每一个段开始的位置,位移和长度,毕竟这些段都是紧密的放在二进制的文件中,需要段表的描述信息来将他们分开
3.节头表:包含对节的描述
4.节:ELF文件的基本组成单位,包含了特定类型的数据。ELF文件的各种信息和数据都存储在不同的节中,如代码节存储了可执行程序,数据节存储了全局变量的静态数据等。
常见的节:代码节,数据节
看到上面的section headers一共是30个节
ELF文件主要包含代码节(.text)已初始化数据节(.data)未初始化数据节(.bss)。全局变量是在程序的数据段的,局部变量是在栈上分配的,全局变量是在程序启动的时候就加载到内存中的,局部变量时调用到相应的函数的时候在会在栈上分配。所以说局部变量是不会在ELF文件中的数据段的,初始化的全局变量放到.data数据节中,未初始化的全局变量放到.bss数据节中。bss(better save space更好的节省空间),因为我们知道未初始化的全局变量都是默认初始化为0的,所以我们不需要将其数据存起来,我们只需要知道有几个未初始化的全局变量,后面加载到内存的时候统一分配空间一律初始化为0即可。所以节省的空间实际上是磁盘空间。
“ELF中存不存在局部变量的数据?”,如果局部变量是非静态的,那ELF里没有它们的实际数据,而是只有关于如何分配栈空间或寄存器的指令。而如果是静态局部变量,那么ELF的(.data)有它们的初始化值。
ELF形成可执行
1.先将多份的C/C++源代码编译为.o文件
2.将多份的.o文件的节进行和合并(实际上的合并就是链接的过程,并不是简单的合并,也会涉及对库的合并)
ELF可执行文件的加载
1.一个ELF会有多个不同的section,在加载到内存的时候这些Section也会进行合并,形成segment
2.合并原则:相同的属性,比如可读可写可执行
3.很显然,这个合并工作的合并方式在进行链接之后就已经确定了,其合并方式被记录在了ELF的程序头表中(还记得吗,程序头表列举了所有有效的段(合并的节)和他们的属性。表里记录的每一个段开始的位置,位移和长度)
可以看到一共有九个段,section to segment mapping中指明了是哪几个节合并到一起形成的段。
链接器在链接阶段(生成可执行文件时)会 显式记录 Section 到 Segment 的映射关系,并将这些信息写入 ELF 文件的 Program Header Table 中。操作系统加载程序时,直接通过 Program Header 中的 Segment 定义(而非原始的 Section)将程序映射到内存。
操作系统加载程序时,完全依赖 Program Header Table:
-
读取每个 Segment 的权限、文件偏移和内存地址。
-
将 Segment 对应的文件内容直接映射到内存(如通过
mmap
系统调用)。 -
设置内存页的权限(如代码段设为可读+可执行,数据段设为可读+可写)。
注意:加载器不关心原始的 Section 划分,仅按 Segment 加载。因此,Section 信息(如 .text
, .data
)在调试或反编译时有用,但对程序运行非必需。
编译链接视图(Linking view) - 对应节头表 Section header table

那么为什么要将section合并成为segment?