ELF文件(Executable Linkable Format)是一种文件存储格式。Linux下的目标文件和可执行文件都按照该格式进行存储,有必要做个总结。

1. 链接举例

  在介绍ELF文件之前,我们先看下,一个.c程序是如何变成可执行目标文件的。下面举个例子。

  该程序由main.c和sum.c两个模块组成。sum.c接收数组和数组长度两个参数,最后将数组求和的结果返回。main.c调用sum函数,并传递一个两元素的int数组array,将计算结果保存在val中。

//main.c
int sum(int *a, int n); int array[2] = {1, 2}; int main(int argc, char** argv)
{
int val = sum(array, 2);
return val;
}
//sum.c
int sum(int *a, int n)
{
int i, s = 0; for (i = 0; i < n; i++) {
s += a[i];
}
return s;
}

  让我们来看看如果我们使用GCC编译两个模块会发生什么?

  main.c和sum.c将分别通过翻译器将源文件处理为可重定位的目标文件main.o和sum.o。翻译器处理的过程包括了预处理(ccp)、编译(ccl)、汇编(as)三个过程。最后,链接器(ld)将可重定位的目标文件main.o和sum.o以及一些必要的系统文件组合起来,创建一个可执行目标文件prog。具体过程如下图所示。

  由上面的过程,我们可以看出在经过汇编器后会输出一个.o文件,这个叫做可重定位的目标文件。将main.o和sum.o输入链接器后,链接器输出的prog文件叫做可执行目标文件。那这两个目标文件有什么样的区别呢?

2. ELF文件类型

2.1 可重定位目标文件(.o文件)

  包含二进制代码和数据,其形式可以和其他目标文件进行合并,创建一个可执行目标文件。例如lib*.o文件。

2.2 可执行目标文件(a.out文件)

  包含二进制代码和数据,可直接被加载器加载执行。例如编译好的可执行文件a.out。

2.3 共享对象文件(.so文件)

  用于和其他共享目标文件或者可重定位文件一起生成ELF目标文件或者和执行文件一起创建进程映像,例如lib*.so文件。

3. ELF文件作用

  ELF文件参与程序的连接(建立一个程序)和程序的执行(运行一个程序),所以可以从不同的角度来看待ELF格式的文件:

  1.如果用于编译和链接(可重定位文件),则编译器和链接器将把ELF文件看作是节头表描述的节的集合,程序头表可选。

  2.如果用于加载执行(可执行文件),则加载器则将把ELF文件看作是程序头表描述的段的集合,一个段可能包含多个节,节头表可选。

4. ELF文件格式

4.1 从编译和链接角度看ELF文件(可重定位目标文件)

ELF头

  每个ELF文件都必须存在一个ELF_Header,这里存放了很多重要的信息用来描述整个文件的组织,如: 版本信息,入口信息,偏移信息等。程序执行也必须依靠其提供的信息。

段头表

  段头表。存放的是所有不同段将在内存中的位置

.text section

  代码段。存放已编译程序的机器代码,一般是只读的。

.rodata section

  只读数据段。此段的数据不可修改,存放常量。比如,printf中的格式化语句。

.data section

  数据段。存放已初始化的全局变量、常量。

.bss section

  bss段。未初始化全局变量,仅是占位符,不占据任何实际磁盘空间。目标文件格式区分初始化和非初始化是为了空间效率.

.symtab section

  符号表,它存放在程序中定义和引用的函数和全局变量的信息。

.rel.txt section

  .text节的重定位信息,用于重新修改代码段的指令中的地址信息。

.rel.data section

  .data节的重定位信息,用于对被模块使用或定义的全局变量进行重定位的信息。

.debug section

  调试用的符号表。

.strtab section

  包含 symtab和 debug节中符号及节名。

节头部表

  每个节的节名、偏移和大小。

  以下是32位系统对应的节头表数据结构,说明了每个节的节名、在文件中的偏移、大小、访问属性、对齐方式等。

typedef struct {
Elf32_Word sh_name; //节名字符串在.strtab节(字符串表)中的偏移
Elf32_Word sh_type; //节类型:无效/代码或数据/符号/字符串/...
Elf32_Word sh_flags; //节标志:该节在虚拟空间中的访问属性
Elf32_Addr sh_addr; //虚拟地址:若可被加载,则对应虚拟地址
Elf32_Off sh_offset; //在文件中的偏移地址,对.bss节而言则无意义
Elf32_Word sh_size; //节在文件中所占的长度
Elf32_Word sh_link; //sh_link和sh_info用于与链接相关的节(如 .rel.text节、.rel.data节、.symtab节等)
Elf32_Word sh_info;
Elf32_Word sh_addralign; //节的对齐要求
Elf32_Word sh_entsize; //节中每个表项的长度,0表示无固定长度表项
} Elf32_Shdr;

  使用readelf命令命令查看节头表内容

[ubuntu@localhost interpositioning]$ readelf -S main.o
There are 13 section headers, starting at offset 0x3f8: Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
0000000000000071 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 000002d0
0000000000000090 0000000000000018 I 11 1 8
[ 3] .data PROGBITS 0000000000000000 000000b1
0000000000000049 0000000000000000 WA 0 0 1
[ 4] .bss NOBITS 0000000000000000 000000b1
000000000000000c 0000000000000000 WA 0 0 1
[ 5] .rodata PROGBITS 0000000000000000 000000b1
0000000000000019 0000000000000000 A 0 0 1
[ 6] .comment PROGBITS 0000000000000000 000000ca
0000000000000035 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 000000ff
0000000000000000 0000000000000000 0 0 1
[ 8] .eh_frame PROGBITS 0000000000000000 00000100
0000000000000058 0000000000000000 A 0 0 8
[ 9] .rela.eh_frame RELA 0000000000000000 00000360
0000000000000030 0000000000000018 I 11 8 8
[10] .shstrtab STRTAB 0000000000000000 00000390
0000000000000061 0000000000000000 0 0 1
[11] .symtab SYMTAB 0000000000000000 00000158
0000000000000150 0000000000000018 12 9 8
[12] .strtab STRTAB 0000000000000000 000002a8
0000000000000023 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)

  可重定位目标文件中,每个可装入节的起始地址总是0。

  .bss节应占000000000000000c大小,但只有装入内存时才会分配。

4.2 从程序执行角度看ELF文件(可执行文件)

  与可重定位文件的不同

  1.ELF头中字段 e_entry给出执行程序时第一条指令的地址,而在可重定位文件中,此字段为0。

  2.多一个init节,用于定义init函数,该函数用来进行可执行目标文件开始执行时的初始化工作。

  3.少两.rel节(无需重定位)。

  4.多一个程序头表,也称段头表,是一个结构数组。

  使用readelf命令查看ELF头的内容:

[ubuntu@localhost interpositioning]$readelf -h main.o
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: REL (Relocatable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x0
Start of program headers: 0 (bytes into file)
Start of section headers: 1064 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 32 (bytes) //程序头表每项32B
Number of program headers: 8 //程序头表共8项
Size of section headers: 64 (bytes)
Number of section headers: 13
Section header string table index: 10 //.strtab在节头表中的索引

  装入内存时,ELF头、程序头表、.init节、.rodata节会被装入只读代码段。.data节和.bss节会被装入读写数据段。

  段头表能够描述可执行文件中的节与虚拟空间中的存储段之间的映射关系。一个表项32B,说明虚拟地址空间中一个连续的片段或一个特殊的节。以下是32位系统对应的段头表数据结构:

typedef struct {
Elf32_Word p_type; //此数组元素描述的段的类型,或者如何解释此数组元素的信息。
Elf32_Off p_offset; //此成员给出从文件头到该段第一个字节的偏移
Elf32_Addr p_vaddr; //此成员给出段的第一个字节将被放到内存中的虚拟地址
Elf32_Addr p_paddr; //此成员仅用于与物理地址相关的系统中。System V忽略所有应用程序的物理地址信息。
Elf32_Word p_filesz; //此成员给出段在文件映像中所占的字节数。可以为0。
Elf32_Word p_memsz; //此成员给出段在内存映像中占用的字节数。可以为0。
Elf32_Word p_flags; //此成员给出与段相关的标志。
Elf32_Word p_align; //此成员给出段在文件中和内存中如何对齐。
} Elf32_phdr;

  使用readelf命令某可执行目标文件的程序头表

[ubuntu@localhost interpositioning]$readelf -l main

Elf file type is EXEC (Executable file)
Entry point 0x400550
There are 9 program headers, starting at offset 64 Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x00000000000001f8 0x00000000000001f8 R E 8
INTERP 0x0000000000000238 0x0000000000400238 0x0000000000400238
0x000000000000001c 0x000000000000001c R 1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x00000000000008ac 0x00000000000008ac R E 200000
LOAD 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
0x0000000000000240 0x0000000000000248 RW 200000
DYNAMIC 0x0000000000000e28 0x0000000000600e28 0x0000000000600e28
0x00000000000001d0 0x00000000000001d0 RW 8
NOTE 0x0000000000000254 0x0000000000400254 0x0000000000400254
0x0000000000000044 0x0000000000000044 R 4
GNU_EH_FRAME 0x0000000000000780 0x0000000000400780 0x0000000000400780
0x0000000000000034 0x0000000000000034 R 4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 10
GNU_RELRO 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
0x00000000000001f0 0x00000000000001f0 R 1

  程序头表信息有9个表项,其中两个为可装入段(即Type=LOAD):

  第一可装入段(第15,16行):第0x00000~0x0x8ab的长度为0x8ac字节的ELF头、程序头表、.init、.text和.rodata节,映射到虚拟地址0x400000开始长度为0x8ac字节的区域 ,按0x200000=2MB对齐,具有只读/执行权限(Flg=RE),是只读代码段。

  第二可装入段(第17,18行):第0xe10~0x104f的长度为0x240字节的.data节和磁盘中不占存储空间的.bss节,映射到虚拟地址0x600e10开始长度为0x248字节的存储区域,在0x248=584B存储区中,前0x240=576B用.data节内容初始化,后面584-576=8B对应.bss节,初始化为0 ,按0x200000=2MB对齐,具有可读可写权限(Flg=RW),是可读写数据段。

  由此看出.bss节在文件中不占用磁盘空间,但在存储器中需要给它分配相应大小的空间

5.总结

  1.链接处理涉及到三种目标文件格式:可重定位目标文件、可执行目标文件和共享目标文件。共享库文件是一种特殊的可重定位目标。

  2.ELF目标文件格式可以从编译链接角度程序执行角度两个角度看,前者是可重定位目标格式,后者是可执行目标格式。从编译链接角度看,可重定位目标文件中包含ELF头、各个节以及节头表。可执行目标文件中包含ELF头、程序头表(段头表)以及各种节组成的段。

  3.bss段在可执行目标文件中不会有它的空间,只有当可执行目标文件装载运行时,才会被分配内存(并且位于data段内存块之后),并且初始化为0

本文参考

《深入理解计算机系统》

扒一扒ELF文件的更多相关文章

  1. ASP.NET Core 2.2 : 十六.扒一扒新的Endpoint路由方案 try.dot.net 的正确使用姿势 .Net NPOI 根据excel模板导出excel、直接生成excel .Net NPOI 上传excel文件、提交后台获取excel里的数据

    ASP.NET Core 2.2 : 十六.扒一扒新的Endpoint路由方案   ASP.NET Core 从2.2版本开始,采用了一个新的名为Endpoint的路由方案,与原来的方案在使用上差别不 ...

  2. View绘制详解(三),扒一扒View的测量过程

    所有东西都是难者不会,会者不难,Android开发中有很多小伙伴觉得自定义View和事件分发或者Binder机制等是难点,其实不然,如果静下心来花点时间把这几个技术点都研究一遍,你会发现其实这些东西都 ...

  3. 扒一扒.NET Core的环境配置提供程序

    很久之前,在玩Docker的时候顺便扒了扒,最近,终于下定决心花了些时间整理并成文,希望能够给大家一些帮助. 目录 .NET Core中的配置 ASP.NET Core中的配置 扒一扒环境变量提供程序 ...

  4. 扒一扒MathType不为人知的技巧

    MathType作为一款编辑数学公式的神器,很多人在使用它时只是很简单地使用了一些最基本的模板,很多功能都没有使用.MathType功能比你想象中的大很多,今天我们就来扒一扒MathType那些不为人 ...

  5. 扒一扒spring,dom4j实现模拟实现读取xml

    今天leadr提出需求,原来公司项目中读取解析xml文件的代码效率太低,考虑切换一种xml为数据封装格式与读取方式以提高效率.我这灵机一动spring对bean的依赖注入就是读取xml文件,可以尝试扒 ...

  6. ELF文件

    ELF文件格式是一个开发标准,各种UNIX系统的可执行文件都采用ELF格式,它有三种不同的类型: 可重定位的目标文件 可执行文件 共享库 现在分析一下上一篇文章中经过汇编之后生成的目标文件max.o和 ...

  7. linux实践之ELF文件分析

    linux实践之ELF文件分析 下面开始elf文件的分析. 我们首先编写一个简单的C代码. 编译链接生成可执行文件. 首先,查看scn15elf.o文件的详细信息. 以16进制形式查看scn15elf ...

  8. elf文件中的.plt .rel.dyn .rel.plt .got .got.plt的关系

    .plt的作用是一个跳板,保存了某个符号在重定位表中的偏移量(用来第一次查找某个符号)和对应的.got.plt的对应的地址 .rel.dyn重定向表,在程序启动时就需要重定位完成. .rel.plt保 ...

  9. linux2.6.24内核源代码分析(2)——扒一扒网络数据包在链路层的流向路径之一

    在2.6.24内核中链路层接收网络数据包出现了两种方法,第一种是传统方法,利用中断来接收网络数据包,适用于低速设备:第二种是New Api(简称NAPI)方法,利用了中断+轮询的方法来接收网络数据包, ...

随机推荐

  1. easyui中在formatter: function (value, row,index) {中添加删除方法

    { field : 'abj', title : '操作', align : 'center', resizable:false, width:'10%', formatter: function ( ...

  2. 手写一个简单的starter组件

    spring-boot中有很多第三方包,都封装成starter组件,在maven中引用后,启动springBoot项目时会自动装配到spring ioc容器中. 思考: 为什么我们springBoot ...

  3. J.U.C关于Execute实现

    JAVASE5的Execute将为你管理Thread对象,是启动任务的优选方案 /***newCachedThreadPool在程序的执行过程中通常会创建于所需任务相同数量的线程即可以达到Intege ...

  4. linkedhashmap中关于LRU算法的实现

    //LinkedHashMap的一个构造函数,当参数accessOrder为true时,即会按照访问顺序排序,最近访问的放在最前,最早访问的放在后面 public LinkedHashMap(int ...

  5. Spring Cloud Sleuth链路监控应用(十四)

    https://docs.spring.io/spring-cloud-sleuth/docs/2.2.5.RELEASE/reference/html/ 一.Sleuth介绍   为什么要使用微服务 ...

  6. 一文彻底吃透MyBatis源码!!

    写在前面 随着互联网的发展,越来越多的公司摒弃了Hibernate,而选择拥抱了MyBatis.而且,很多大厂在面试的时候喜欢问MyBatis底层的原理和源码实现.总之,MyBatis几乎成为了Jav ...

  7. 计算-服务器最大并发量-http协议请求-以webSphere服务器为例-考虑线程池

    请求的处理流程 广域网上有大量的并发用户同时访问Web服务器,Web服务器传递请求给应用服务器(Web容器),Web容器传递请求给EJB容器,然后EJB容器发送数据库连接请求给数据库. 请求的处理流程 ...

  8. mysql词法分析和语法分析

    如果没有命中查询缓存,就要开始真正执行语句了.首先,MySQL 需要知道你要做什么,因此需要对 SQL 语句做解析.分析器先会做"词法分析".你输入的是由多个字符串和空格组成的一条 ...

  9. 使用SharePoint App-Only获得访问权限

    目前在开发SharePoint Online的过程中,主要使用通过Azure AD的方式获得应用的访问权限,但是SharePoint App-Only的方式依旧被保留了.使用这种方式进行CSOM开发比 ...

  10. Python Kafka Client 性能测试

    一.前言 由于工作原因使用到了 Kafka,而现有的代码并不能满足性能需求,所以需要开发高效读写 Kafka 的工具,本文是一个 Python Kafka Client 的性能测试记录,通过本次测试, ...