ELF是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件的文件格式。是Linux上二进制文件的标准格式。
ELF有四种类型:
除此之外还会有类型不确定的ELF文件( ET_NONE ),即未知文件。
ELF由四部分组成:ELF头( ELF header )、程序头表( Program header table )、节( section )、节头表( section header table ),大致结构如下图所示:
需要注意的是,ELF文件结构的排布并不一定如上图所示,而且也不一定包含上图的所有内容。但是,ELF头一定会有,且在整个文件的第一个部分。
这篇笔记下面就是解释这张图中的信息。
ELF头就是一个结构体,被称为 struct ElfN_Ehdr (程序头、节头都是结构体,在图中已经标出了对应结构体的名称)。该结构主要描述了整个文件的组织,前面提到ELF头以外的内容没有固定的位置是因为它们的位置是由ELF头指出的。
ELF头对应的结构体如下(32位):
#define EI_NIDENT (16)
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
} Elf32_Ehdr;
命令readelf -h可以查看ELF文件的ELF头信息:
#注释:hello是一个打印helloworld的可执行程序,该程序是64位的
$ readelf -h hello
ELF 头:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
类别: ELF64
数据: 2 补码,小端序 (little endian)
版本: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
类型: EXEC (可执行文件)
系统架构: Advanced Micro Devices X86-64
版本: 0x1
入口点地址: 0x400430
程序头起点: 64 (bytes into file)
Start of section headers: 6616 (bytes into file)
标志: 0x0
本头的大小: 64 (字节)
程序头大小: 56 (字节)
Number of program headers: 9
节头大小: 64 (字节)
节头数量: 31
字符串表索引节头: 28
$
从上面我们可以知晓该ELF文件的类型(ELF64指的就是64位的可执行程序)、程序头的位置、节头的位置等等。至于第一行的Magic就是结构体中的那一个字符数组,正好16个字节,其中45 4c 46代表的就是"ELF"这一字符串。
程序头( program header )对应的结构体称为 struct ElfN_Phdr 。程序头是对段( segment )的描述,主要描述了如何装载可执行文件(即可执行文件的内存布局以及如何映射到内存中)。
一个程序的段不止一个,所以程序头当然也不止一个,多个程序头组合在一起就成为了程序头表( program header table )。
一个程序头的结构体如下:
typedef struct
{
Elf32_Word p_type; /* Segment type */
Elf32_Off p_offset; /* Segment file offset */
Elf32_Addr p_vaddr; /* Segment virtual address */
Elf32_Addr p_paddr; /* Segment physical address */
Elf32_Word p_filesz; /* Segment size in file */
Elf32_Word p_memsz; /* Segment size in memory */
Elf32_Word p_flags; /* Segment flags */
Elf32_Word p_align; /* Segment alignment */
} Elf32_Phdr;
程序头还有有不同的类型,主要类型如下:
命令readelf -l可查看ELF文件的程序头表:
$ readelf -l hello
Elf 文件类型为 EXEC (可执行文件)
入口点 0x400430
共有 9 个程序头,开始于偏移量 64
程序头:
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
0x00000000000006fc 0x00000000000006fc R E 200000
LOAD 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
0x0000000000000228 0x0000000000000230 RW 200000
DYNAMIC 0x0000000000000e28 0x0000000000600e28 0x0000000000600e28
0x00000000000001d0 0x00000000000001d0 RW 8
NOTE 0x0000000000000254 0x0000000000400254 0x0000000000400254
0x0000000000000044 0x0000000000000044 R 4
GNU_EH_FRAME 0x00000000000005d0 0x00000000004005d0 0x00000000004005d0
0x0000000000000034 0x0000000000000034 R 4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 10
GNU_RELRO 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
0x00000000000001f0 0x00000000000001f0 R 1
#...省略了部分内容...
section指的是节,而segment指的是段。一个程序会被划分为多个段,而每个段中有会被划分为节。
段是由程序头所描述的,多个程序头又组成了程序头表;而节是由节头描述的。多个节头同样组成了一个节头表( Section Header Table )。
节头对应的结构体称为 struct ElfN_Shdr 。
结构体如下所示:
typedef struct
{
elf32_word sh_name; /* Section name (string tbl index) */
elf32_word sh_type; /* Section type */
elf32_word sh_flags; /* Section flags */
elf32_addr sh_addr; /* Section virtual addr at execution */
elf32_off sh_offset; /* Section file offset */
elf32_word sh_size; /* Section size in bytes */
elf32_word sh_link; /* Link to another section */
elf32_word sh_info; /* Additional section information */
elf32_word sh_addralign; /* Section alignment */
elf32_word sh_entsize; /* Entry size if section holds table */
} elf32_shdr;
命令readelf -S可查看ELF文件的节头:
$ readelf -S hello
共有 31 个节头,从偏移量 0x19d8 开始:
节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000400238 00000238
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.ABI-tag NOTE 0000000000400254 00000254
0000000000000020 0000000000000000 A 0 0 4
[ 3] .note.gnu.build-i NOTE 0000000000400274 00000274
0000000000000024 0000000000000000 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000400298 00000298
000000000000001c 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 00000000004002b8 000002b8
0000000000000060 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 0000000000400318 00000318
000000000000003d 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 0000000000400356 00000356
0000000000000008 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000400360 00000360
0000000000000020 0000000000000000 A 6 1 8
[ 9] .rela.dyn RELA 0000000000400380 00000380
0000000000000018 0000000000000018 A 5 0 8
[10] .rela.plt RELA 0000000000400398 00000398
0000000000000030 0000000000000018 AI 5 24 8
[11] .init PROGBITS 00000000004003c8 000003c8
000000000000001a 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 00000000004003f0 000003f0
0000000000000030 0000000000000010 AX 0 0 16
[13] .plt.got PROGBITS 0000000000400420 00000420
0000000000000008 0000000000000000 AX 0 0 8
[14] .text PROGBITS 0000000000400430 00000430
0000000000000182 0000000000000000 AX 0 0 16
[15] .fini PROGBITS 00000000004005b4 000005b4
0000000000000009 0000000000000000 AX 0 0 4
[16] .rodata PROGBITS 00000000004005c0 000005c0
000000000000000f 0000000000000000 A 0 0 4
[17] .eh_frame_hdr PROGBITS 00000000004005d0 000005d0
0000000000000034 0000000000000000 A 0 0 4
[18] .eh_frame PROGBITS 0000000000400608 00000608
00000000000000f4 0000000000000000 A 0 0 8
[19] .init_array INIT_ARRAY 0000000000600e10 00000e10
0000000000000008 0000000000000000 WA 0 0 8
[20] .fini_array FINI_ARRAY 0000000000600e18 00000e18
0000000000000008 0000000000000000 WA 0 0 8
[21] .jcr PROGBITS 0000000000600e20 00000e20
0000000000000008 0000000000000000 WA 0 0 8
[22] .dynamic DYNAMIC 0000000000600e28 00000e28
00000000000001d0 0000000000000010 WA 6 0 8
[23] .got PROGBITS 0000000000600ff8 00000ff8
0000000000000008 0000000000000008 WA 0 0 8
[24] .got.plt PROGBITS 0000000000601000 00001000
0000000000000028 0000000000000008 WA 0 0 8
[25] .data PROGBITS 0000000000601028 00001028
0000000000000010 0000000000000000 WA 0 0 8
[26] .bss NOBITS 0000000000601038 00001038
0000000000000008 0000000000000000 WA 0 0 1
[27] .comment PROGBITS 0000000000000000 00001038
0000000000000035 0000000000000001 MS 0 0 1
[28] .shstrtab STRTAB 0000000000000000 000018cc
000000000000010c 0000000000000000 0 0 1
[29] .symtab SYMTAB 0000000000000000 00001070
0000000000000648 0000000000000018 30 47 8
[30] .strtab STRTAB 0000000000000000 000016b8
0000000000000214 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)