其实PE文件和ELF文件结构比较类似,学过PE文件后,ELF文件就很好懂。
通俗点说由汇编器和链接器生成的文件都属于ELF文件。通常我们接触的ELF文件主要有以下三类:
- 可重定位文件(
relocatable file
) ,一般为.o
文件。可以被链接成可执行文件或共享目标文件。静态链接库属于可重定位文件。 - 可执行文件(
excutable file
)它保存了适合直接加载到内存中执行的二进制程序。 - 共享库文件(
shared object file
)一般为.so
文件。一种特殊的可重定位目标文件,可以在加载或者运行时被动态的加载进内存并链接。
Note:段(Segment
)与节(Section
)的区别。很多地方对两者有所混淆。段是程序执行的必要组成,当多个目标文件链接成一个可执行文件时,会将相同权限的节合并到一个段中。相比而言,节的粒度更小。
ELF文件主要由四部分组成:
- ELF Header
- ELF Program Header Table(程序头)
- ELF Section Header Table(节头表)
- ELF sections
ELF Header
ELF Header其实对应的是一个结构体,该结构体定义如下:
1 |
|
1 | ElfN_Addr Unsigned program address, uintN_t |
e_ident
:包含一个magic number、ABI信息,该文件使用的平台、大小端规则e_type
:文件类型, 表示该文件属于可执行文件、可重定位文件、core dump文件或者共享库e_entry
: 表示程序执行的入口地址,规定ELF程序的入口虚拟地址,操作系统在加载完该程序后从这个地址开始执行进程的指令。可重定位指令一般没有入口地址,则该值为0e_phoff
: 表示Program Header的入口偏移量(以字节为单位)e_shoff
: 表示Section Header的入口偏移量(以字节为单位)e_flags
: 保存了这个ELF文件相关的特定处理器的flage_ehsize
: 表示ELF Header大小(以字节为单位)e_phentsize
: 表示Program Header大小(以字节为单位)e_phnum
: 表示Program Header的数量 (十进制数字)e_shentsize
: 表示单个Section Header大小(以字节为单位)e_shnum
: 表示Section Header的数量 (十进制数字)e_shstrndx
: 表示字符串表的索引,字符串表用来保存ELF文件中的字符串,比如段名、变量名。 然后通过字符串在表中的偏移访问字符串。
magic:通过判断该字段可以确定文件格式和类型。如:ELF的可执行文件格式的头4个字节为0x7F
、e
、l
、f
;Java的可执行文件格式的头4个字节为c
、a
、f
、e
;如果被执行的是Shell脚本或perl、python等解释型语言的脚本,那么它的第一行往往是#!/bin/sh
或#!/usr/bin/perl
或#!/usr/bin/python
,此时前两个字节#
和!
就构成了魔数,系统一旦判断到这两个字节,就对后面的字符串进行解析,以确定具体的解释程序路径。
ELF Section Header Table
ELF 节头表是一个节头数组。每一个节头都描述了其所对应的节的信息,如节名、节大小、在文件中的偏移、读写权限等。编译器、链接器、装载器都是通过节头表来定位和访问各个节的属性的。
1 | typedef struct { |
sh_name
:表示该section的名字,节名是一个字符串,保存在一个名为.shstrtab
的字符串表(可通过Section Header索引到)。sh_name的值实际上是其节名字符串在.shstrtab
中的偏移值。sh_type
:表示该section中存放的内容类型,比如符号表,可重定位段等。sh_flags
: 表示该section的一些属性,比如是否可写,可执行等。sh_addr
: 表示该section在程序运行时的内存地址sh_offset
: 如果该节存在于文件中,则表示该节在文件中的偏移;否则无意义,如sh_offset对于BSS 节来说是没有意义的sh_size
: 表示该section的大小sh_link
: 配合sh_info保存section的额外信息sh_info
:保存该section相关的一些额外信息sh_addralign
:表示该section需要的地址对齐信息sh_entsize
:有些section里保存的是一些固定长度的条目,比如符号表。对于这些section来讲,sh_entsize里保存的就是条目的长度。sh_type(节类型)
sh_link、sh_info(节链接信息)
如果节的类型是与链接相关的(无论是动态链接还是静态链接),如重定位表、符号表、等,则sh_link
、sh_info
两个成员所包含的意义如下所示。其他类型的节,这两个成员没有意义。
ELF Sections
在ELF文件中,数据和代码分开存放的,这样可以按照其功能属性分成一些区域,比如程序、数据、符号表等。这些分离存放的区域在ELF文件中反映成section
。ELF文件中典型的section
如下:
.text
: 已编译程序的二进制代码.rodata
: 只读数据段,比如常量.data
: 已初始化的全局变量和静态变量.bss
: 未初始化的全局变量和静态变量,所有被初始化成0的全局变量和静态变量(记得与PE文件区分开).sysmtab
: 符号表,它存放了程序中定义和引用的函数和全局变量的信息.debug
: 调试符号表,它需要以'-g'
选项编译才能得到,里面保存了程序中定义的局部变量和类型定义,程序中定义和引用的全局变量,以及原始的C文件.line
: 原始的C文件行号和.text节
中机器指令之间的映射.strtab
: 字符串表,内容包括.symtab
和.debug
节中的符号表
特殊的,
1)对于可重定位的文件,由于在编译时,并不能确定它引用的外部函数和变量的地址信息,因此,编译器在生成目标文件时,增加了两个section:
.rel.text
保存了程序中引用的外部函数的重定位信息,这些信息用于在链接时重定位其对应的符号。.rel.data
保存了被模块引用或定义的所有全局变量的重定位信息,这些信息用于在链接时重定位其对应的全局变量。
2)对于可执行文件,由于它已经全部完成了重定位工作,可以直接加载到内存中执行,所以它不存在.rel.text
和.rel.data
这两个section。但是,它增加了一个section:
.init
: 这个section里面保存了程序运行前的初始化代码
重定位表是一个Elf_Rel
类型的数组结构,每一项对应一个需要进行重定位的项。
其成员含义如下表所示:
ELF Program Header Table
在可执行文件中,ELF header下面紧接着就是Program Header Table。它描述了各个segment在ELF文件中的位置以及在程序执行过程中系统需要准备的其他信息。它也是用一个结构体数组来表示的。具体代码如下:
1 | typedef uint64_t Elf64_Addr; |
p_type
:描述了当前segment是何种类型的或者如何解释当前segment,比如是动态链接相关的或者可加载类型的等p_flags
:保存了该segment的flagp_offset
:表示从ELF文件到该segment第一个字节的偏移量p_vaddr
:表示该segment的第一个字节在内存中的虚拟地址p_paddr
:对于使用物理地址的系统来讲,这个成员表示该segment的物理地址p_filesz
:表示该segment的大小,以字节表示p_memsz
:表示该segment在内存中的大小,以字节表示p_align
:表示该segment在文件中或者内存中需要以多少字节对齐