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. 使用ajax请求上传多个或者多个附件

    jsp页面 <%@ page language="java" pageEncoding="UTF-8"%> <!DOCTYPE HTML> ...

  2. 【译】对Rust中的std::io::Error的研究

    原文标题:Study of std::io::Error 原文链接:https://matklad.github.io/2020/10/15/study-of-std-io-error.html 公众 ...

  3. JAVADOC 文档注释命令

    简介 javadoc命令是用来生成自己API文档的 javadoc参数信息 @author 作者名 @version 版本号 @since 指明需要最早使用的jdk版本 @param 参数名 @ret ...

  4. java数组之基本语义

    A[] a; B[] b=new B[5];print(b)print(b.length)a={new A(),new A()}//聚合初始化:隐式使用new在堆中创建A[] d=new A[];a= ...

  5. —用python写图片格式批量处理工具

    python爬取微博评论(无重复数据) 前言 一.整体思路 二.获取微博地址 1.获取ajax地址 2.解析页面中的微博地址 3.获取指定用户微博地址 三.获取主评论 四.获取子评论 1.解析子评论 ...

  6. spark:join与cogroup

    1.RDD[K,V],键值对类型的rdd的函数在PairRDDFunctions这个类中 rdd类中,通过隐士转换让rdd有了PairRDDFunctions这个类里面方法的功能 2.rdd 的joi ...

  7. Docker部署&MySQL部署

    Docker部署 本文采用的是阿里云的centos7 # 更新yum yum update # 安装docker yum install docker # 启动docker systemctl sta ...

  8. Go语言快速安装手册

    Go 是一个开源的编程语言,它能让构造简单.可靠且高效的软件变得容易. Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发,后来还加入了Ia ...

  9. filleSystemBasises

    基本查询命令 pwd 查看当前目录 dir 显示当前目录下的文件信息 more 查看文本文件的具体内容 cd 修改用户当前目录 mkdir 创建新的目录 rmdir 删除目录 copy filenam ...

  10. 【Java基础】Java9 新特性

    Java9 新特性 模块化系统 Java 和相关生态在不断丰富的同时也越来越暴露出一些问题: Java 运行环境的膨胀和臃肿.每次 JVM 启动的时候,至少会 30-60MB 的内存加载,主要原因是 ...