ELF文件格式详解
什么是ELF文件格式
ELF(Executable and Linkable Format)是一种用于应用程序、库和操作系统的二进制文件格式。它是目前主流的可执行文件格式,在大多数现代操作系统中广泛使用。
ELF文件格式定义了二进制文件的组织结构和加载方式,使得操作系统能够正确加载运行程序。它包含了程序的代码、数据、符号表、重定位表等信息。
ELF文件格式的结构
ELF文件格式可以分为三个部分:ELF头部、程序头部表和节头部表。
ELF头部(ELF Header)
ELF头部位于文件的开头,它包含了一些关键信息,用于描述整个ELF文件的结构。
ELF头部的结构如下:
typedef struct {
unsigned char e_ident[16]; // ELF标识
uint16_t e_type; // 对象文件类型
uint16_t e_machine; // 目标体系结构
uint32_t e_version; // 版本号
uint64_t e_entry; // 程序入口地址
uint64_t e_phoff; // 程序头部表的偏移量
uint64_t e_shoff; // 节头部表的偏移量
uint32_t e_flags; // 处理器特定标志
uint16_t e_ehsize; // ELF头部的大小
uint16_t e_phentsize; // 程序头部表项的大小
uint16_t e_phnum; // 程序头部表的表项数目
uint16_t e_shentsize; // 节头部表项的大小
uint16_t e_shnum; // 节头部表的表项数目
uint16_t e_shstrndx; // 节头部表字符串表索引
} Elf64_Ehdr;
其中,e_ident
数组用于识别ELF文件类型,e_type
表示ELF文件的类型(可执行文件、共享库、目标文件等),e_machine
表示目标体系结构(如x86、ARM、MIPS等),e_entry
是程序入口地址,e_phoff
是程序头部表的偏移量,e_shoff
是节头部表的偏移量,e_phnum
和e_shnum
分别是程序头部表和节头部表的表项数目。
程序头部表(Program Header Table)
程序头部表位于ELF头部之后,用于描述如何将ELF文件中的各个段加载到内存中。
程序头部表的结构如下:
typedef struct {
uint32_t p_type; // 段类型
uint32_t p_flags; // 段标志
uint64_t p_offset; // 段在文件中的偏移量
uint64_t p_vaddr; // 段在内存中的虚拟地址
uint64_t p_paddr; // 段在内存中的物理地址
uint64_t p_filesz; // 段在文件中的长度
uint64_t p_memsz; // 段在内存中的长度(可能会大于文件长度)
uint64_t p_align; // 段在内存中的对齐方式
} Elf64_Phdr;
程序头部表中的每个表项对应一个段(Segment),段是ELF文件的基本组成单位。常见的段类型有可执行代码段(PT_LOAD
)、数据段(PT_DYNAMIC
)、动态链接表段(PT_INTERP
)等。
节头部表(Section Header Table)
节头部表位于程序头部表之后,用于描述ELF文件中各个节的信息。
节头部表的结构如下:
typedef struct {
uint32_t sh_name; // 节名称在节头部字符串表中的索引
uint32_t sh_type; // 节类型
uint64_t sh_flags; // 节标志
uint64_t sh_addr; // 节在内存中的虚拟地址
uint64_t sh_offset; // 节在文件中的偏移量
uint64_t sh_size; // 节在文件中的长度
uint32_t sh_link; // 链接到的其他节的索引
uint32_t sh_info; // 附加信息
uint64_t sh_addralign; // 节在内存中的对齐方式
uint64_t sh_entsize; // 节中表项(如符号表、重定位表)的大小
} Elf64_Shdr;
节头部表中的每个表项对应一个节(Section),节是ELF文件中的数据块,用于存储不同类型的数据,如代码、数据、符号表等。常见的节类型有代码段(.text
)、数据段(.data
)、BSS段(.bss
)、字符串表节(.strtab
)、符号表节(.symtab
)等。
ELF文件的加载和运行
操作系统通过解析ELF文件的结构,将ELF文件加载到合适的内存地址,并启动程序执行。
代码段加载
操作系统根据程序头部表中的段类型和偏移量,将代码段加载到内存中的合适位置。代码段是可执行代码的存储区域,对应ELF文件中的.text
节。
数据段加载
操作系统同样根据程序头部表中的段类型和偏移量,将数据段加载到内存中。数据段用于存储程序需要使用的全局变量和静态变量等,对应ELF文件中的.data
节和.bss
节。
符号解析
程序中的符号(如函数、变量)需要在运行时能够正确地被链接和使用。符号解析的过程主要依赖于符号表和重定位表。
符号表记录了ELF文件中的符号信息,包括符号名、符号类型、所在节等。重定位表则用于指导链接器进行符号的重定位操作,使得程序能够正确地访问符号。
其他节的加载
除了代码段和数据段外,ELF文件中还可以包含其他类型的节,如重定位表节、字符串表节等。操作系统在加载完代码段和数据段后,会根据节头部表的信息,加载其他需要的节。
示例代码
下面是一个使用C语言编写的ELF文件的示例代码,可以生成一个简单的ELF可执行文件:
#include <stdio.h>
int main() {
printf("Hello, ELF!\n");
return 0;
}
编译并运行上述代码,可得到一个名为hello
的ELF可执行文件。我们可以使用readelf
命令查看该文件的结构:
我们可以使用readelf
命令查看该文件的结构:
readelf -a hello
运行命令后,可以看到ELF文件的详细信息,包括ELF头部、程序头部表、节头部表等:
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: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x401040
Start of program headers: 64 (bytes into file)
Start of section headers: 177040 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 9
Size of section headers: 64 (bytes)
Number of section headers: 28
Section header string table index: 27
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 0000000000401000 00001000
000000000000007b 0000000000000000 AX 0 0 16
[ 2] .data PROGBITS 0000000000602000 00002000
000000000000000c 0000000000000000 WA 0 0 4
[ 3] .bss NOBITS 0000000000602010 0000200c
0000000000000004 0000000000000000 WA 0 0 1
[ 4] .rodata PROGBITS 0000000000602014 00002010
000000000000000f 0000000000000000 A 0 0 1
[ 5] .eh_frame PROGBITS 0000000000602024 00002024
0000000000000048 0000000000000000 A 0 0 8
[ 6] .shstrtab STRTAB 0000000000000000 0000206c
000000000000011d 0000000000000000 0 0 1
[ 7] .symtab SYMTAB 0000000000000000 0000208c
00000000000005d0 0000000000000018 26 19 8