ELF文件格式
ELF可执行可链接格式,最初由UNIX系统实验室作为应用程序二进制接口的一部分制定,是COFF(common file format)的变种,相关定义在/usr/include/elf.h文件中。
ELF 文件类型
说明:linux和windows识别文件格式不同,linux是根据文件头的字段,windows直接根据文件名后缀。
主要分为三类:
- 可执行文件executable file:经过链接、可执行的目标文件,也被称为程序。
- 可重定位文(relocatable file):由源文件编译但是没有链接的目标文件,通常以.o作为拓展名。用于与其他文件链接构成可执行文件或动态链接库,通常是一段位置独立的代码(Position Independent code, PIC)
- 共享目标文件(shared object file):动态链接库文件,用于在链接的过程中与其他动态链接库或可重定位文件一起构建可执行文件。
除此之外,还存在 核心转储文件(core dump file)作为进程以外终止时地址空间的转储,也是ELF文件的一种。可以使用gdb工具读取辅助调试和查找程序崩溃的原因。
在内核中的定义(/include/uapi/linux/elf.h):
#define ET_REL 1
#define ET_EXEC 2
#define ET_DYN 3
#define ET_CORE 4
ELF文件格式
总体布局
ELF文件格式提供了两种视图,分别是链接视图和执行视图。链接视图是以节(section)为单位,执行视图是以段(segment)为单位。接视图就是在链接时用到的视图,而执行视图则是在执行时用到的视图。
可以使用readelf -l hello查看一个链接后的elf可执行文件,Section to Segment 的映射关系
Elf 文件类型为 EXEC (可执行文件)
Entry point 0x401cc0
There are 8 program headers, starting at offset 64
程序头:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x0000000000000488 0x0000000000000488 R 0x1000
LOAD 0x0000000000001000 0x0000000000401000 0x0000000000401000
0x000000000007fab1 0x000000000007fab1 R E 0x1000
LOAD 0x0000000000081000 0x0000000000481000 0x0000000000481000
0x0000000000026e0b 0x0000000000026e0b R 0x1000
LOAD 0x00000000000a8000 0x00000000004a9000 0x00000000004a9000
0x00000000000052f0 0x0000000000006b20 RW 0x1000
NOTE 0x0000000000000200 0x0000000000400200 0x0000000000400200
0x0000000000000044 0x0000000000000044 R 0x4
TLS 0x00000000000a8000 0x00000000004a9000 0x00000000004a9000
0x0000000000000020 0x0000000000000060 R 0x8
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x10
GNU_RELRO 0x00000000000a8000 0x00000000004a9000 0x00000000004a9000
0x0000000000003000 0x0000000000003000 R 0x1
Section to Segment mapping:
段节...
00 .note.gnu.build-id .note.ABI-tag .rela.plt
01 .init .plt .text __libc_freeres_fn .fini
02 .rodata .eh_frame .gcc_except_table
03 .tdata .init_array .fini_array .data.rel.ro .got .got.plt .data __libc_subfreeres __libc_IO_vtables __libc_atexit .bss __libc_freeres_ptrs
04 .note.gnu.build-id .note.ABI-tag
05 .tdata .tbss
06
07 .tdata .init_array .fini_array .data.rel.ro .got
下面的段序号和上面程序头里的段一一对应。
文件结构
主要从链接的角度看elf文件。
elf头
描述文件的一些基本信息,文件头部存在魔术字符7f 45 4c 46即字符串"\177ELF",当文件被映射到内存时可以通过该字符串查找位置,dump内存。
readelf -h relocfile //查看文件头信息
ELF 头:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
类别: ELF64
数据: 2 补码,小端序 (little endian)
Version: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
类型: REL (可重定位文件)
系统架构: Advanced Micro Devices X86-64
版本: 0x1
入口点地址: 0x0
程序头起点: 0 (bytes into file)
Start of section headers: 816 (bytes into file)
标志: 0x0
Size of this header: 64 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 64 (bytes)
Number of section headers: 13
Section header string table index: 12
typedef struct elf32_hdr
{
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;
程序头表(program header table)
列举了所有有效的段(segments)和他们的属性(执行视图),链接视图是可选的,一般没有。
程序头是一个结构的数组,每一个结构都表示一个段(segments)。在可执行文件或者共享链接库中所有的节(sections)都被分为不同的几个段(segments)。
typedef struct elf32_phdr{
Elf32_Word p_type; /* Magic number and other info */
Elf32_Off p_offset;
Elf32_Addr p_vaddr;
Elf32_Addr p_paddr;
Elf32_Word p_filesz;
Elf32_Word p_memsz;
Elf32_Word p_flags;
Elf32_Word p_align;
} Elf32_Phdr;
程序头的索引地址(e_phoff)、段数量(e_phnum)、表项大小(e_phentsize)都是通过 ELF头部信息获取的。
可通过readelf -l 读取ELF程序头信息:
节头表(section header table)
一个ELF文件中到底有哪些具体的 sections,由包含在这个ELF文件中的 section head table(SHT)决定。每个section描述了这个段的信息,比如每个段的段名、段的长度、在文件中的偏移、读写权限及段的其它属性。
typedef struct elf32_shdr{
Elf32_Word sh_name; //节区名,名字是一个 NULL 结尾的字符串。
Elf32_Word sh_type; //为节区类型
Elf32_Word sh_flags; //节区标志
Elf32_Addr sh_addr; //节区的第一个字节应处的位置。否则,此字段为 0。
Elf32_Off sh_offset; //此成员的取值给出节区的第一个字节与文件头之间的偏移。
Elf32_Word sh_size; //此 成 员 给 出 节 区 的 长 度 ( 字 节 数 )。
Elf32_Word sh_link; //此成员给出节区头部表索引链接。其具体的解释依赖于节区类型。
Elf32_Word sh_info; //此成员给出附加信息,其解释依赖于节区类型。
Elf32_Word sh_addralign; //某些节区带有地址对齐约束.
Elf32_Word sh_entsize; //给出每个表项的长度字节数。
}Elf32_Shdr;
节区名存储在.海枫写的《Linux动态链接中的PLT和GOT》。
相关命令
readelf -S relocfile //查看节头表objdump -x -s -d relocfile //查看.text汇编readelf -x .strtab relocfile //查看字符串表readelf -r file //查看重定位表
可执行文件的装载
当运行一个可执行文件时,首先需要将该文件和动态链接库装载到进程空间,形成一个进程镜像。
每个进程都有独立的虚拟地址空间,空间的布局由程序头(program header)决定,通过readelf -l file可以看到节到段的映射,以及段的地址空间布局。每一个段都包含一个或多个节,因为随着节数量的增多,在进行内存映射时就会大量浪费空间和资源。系统并不关心节的实际内容,而是不同节的读写、执行的权限。
参考
ELF文件格式 - 知乎 (zhihu.com)
gcc 编译命令详解及最佳实践 - 知乎 (zhihu.com)