目录
[1. 引言](#1. 引言)
[2. ELF文件类型](#2. ELF文件类型)
[3. ELF 整体布局](#3. ELF 整体布局)
[4. ELF Header 详解](#4. ELF Header 详解)
[5. 节头表(Section Header Table)](#5. 节头表(Section Header Table))
[6. 程序头表(Program Header Table)](#6. 程序头表(Program Header Table))
[7. 目标文件中的重定位表](#7. 目标文件中的重定位表)
[8. 符号表(.symtab)](#8. 符号表(.symtab))
[9. 小结](#9. 小结)
1. 引言
ELF(Executable and Linkable Format)是Linux下可执行文件、目标文件、共享库和核心转储文件的标准格式。理解ELF结构是掌握链接、加载、动态链接等高级话题的基础。本文将详细介绍ELF文件类型、ELF头、节头表、程序头表以及常见的节(Section)。
2. ELF文件类型
使用 file 命令可以查看ELF类型:
| 类型 | 说明 | 示例 |
|---|---|---|
REL(可重定位文件) |
编译产生的 .o 目标文件 |
hello.o |
EXEC(可执行文件) |
可执行的程序 | /bin/ls |
DYN(共享目标文件) |
动态库 .so |
libc.so.6 |
CORE(核心转储) |
进程崩溃时产生的 core dump | core.1234 |
3. ELF 整体布局
一个ELF文件包含以下部分:
text
+-------------------------+
| ELF Header | (文件头,固定位置)
+-------------------------+
| Program Header Table | (程序头表,描述段,用于加载)
| (可选,目标文件无) |
+-------------------------+
| Sections | (各个节,如 .text, .data)
| ... |
+-------------------------+
| Section Header Table | (节头表,描述节)
+-------------------------+
-
ELF Header:位于文件开头,描述文件类型、架构、入口点地址、程序头表和节头表的位置和大小。
-
Program Header Table:告诉系统如何将文件映射到内存,只存在于可执行文件和共享库中。
-
Section Header Table:描述各个节的信息,用于链接和调试。
4. ELF Header 详解
使用 readelf -h hello.o 查看:
text
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 ...
Class: ELF64
Data: 2's complement, little endian
Type: REL (Relocatable file)
Machine: Advanced Micro Devices X86-64
Entry point address: 0x0
Start of program headers: 0 (bytes into file)
Start of section headers: 728 (bytes into file)
Size of this header: 64 (bytes)
Number of program headers: 0
Number of section headers: 13
对于可执行文件,Type 为 EXEC 或 DYN,并且会有程序头表。
内核中的 ELF Header 定义 (<linux/elf.h>):
c
typedef struct elf64_hdr {
unsigned char e_ident[EI_NIDENT];
Elf64_Half e_type;
Elf64_Half e_machine;
Elf64_Word e_version;
Elf64_Addr e_entry; // 入口点虚拟地址
Elf64_Off e_phoff; // 程序头表偏移
Elf64_Off e_shoff; // 节头表偏移
Elf64_Word e_flags;
Elf64_Half e_ehsize;
Elf64_Half e_phentsize;
Elf64_Half e_phnum;
Elf64_Half e_shentsize;
Elf64_Half e_shnum;
Elf64_Half e_shstrndx;
} Elf64_Ehdr;
5. 节头表(Section Header Table)
节头表描述了每个节的名称、类型、大小、偏移、地址和对齐等。使用 readelf -S hello.o 查看。
常见的节:
| 节名 | 类型 | 属性 | 说明 |
|---|---|---|---|
.text |
PROGBITS | AX | 可执行代码 |
.data |
PROGBITS | WA | 已初始化的全局变量和静态变量 |
.bss |
NOBITS | WA | 未初始化的全局变量和静态变量(不占用文件空间) |
.rodata |
PROGBITS | A | 只读数据(如字符串常量) |
.symtab |
SYMTAB | - | 符号表(函数名、变量名等) |
.strtab |
STRTAB | - | 字符串表 |
.rela.text |
RELA | - | 代码段的重定位信息 |
属性标志:
-
A(ALLOC):需要分配内存(加载到内存) -
X(EXEC):可执行 -
W(WRITE):可写
示例 :通过 size 命令查看节大小:
bash
size a.out
text data bss dec hex filename
3331 628 16 3975 f87 a.out
6. 程序头表(Program Header Table)
程序头表只在可执行文件和共享库中存在,用于描述段(Segment)。多个节可以合并成一个段,相同属性的节(如可读可执行)会放在同一个段中,以减少内存页碎片。
使用 readelf -l a.out 查看:
text
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x400000 0x400000 0x000d24 0x000d24 R E 0x200000
LOAD 0x000e10 0x600e10 0x600e10 0x000254 0x000258 RW 0x200000
DYNAMIC 0x000e28 0x600e28 0x600e28 0x0001d0 0x0001d0 RW 0x8
...
-
LOAD类型的段需要被加载到内存。 -
R E:可读、可执行(通常对应.text、.rodata等)。 -
RW:可读、可写(对应.data、.bss、.got等)。
Section to Segment 映射:
text
Segment 02: .interp .note.ABI-tag .gnu.hash .dynsym .dynstr .init .plt .text .fini .rodata ...
Segment 03: .init_array .fini_array .dynamic .got .got.plt .data .bss
7. 目标文件中的重定位表
当编译一个调用外部函数的源文件时,编译器不知道外部函数的地址,会在目标文件中生成重定位表 (.rela.text)。链接器会解析重定位表,修正代码中的地址。
使用 objdump -d hello.o 查看反汇编:
assembly
0000000000000000 <main>:
0: 55 push %rbp
4: bf 00 00 00 00 mov $0x0,%edi # 字符串地址待重定位
9: e8 00 00 00 00 callq e <main+0xe> # puts 函数地址待重定位
...
callq 的地址为 0,这就是需要重定位的地方。
使用 readelf -r hello.o 查看重定位表:
text
Relocation section '.rela.text' at offset 0x3e0 contains 2 entries:
Offset Info Type Sym. Value Sym. Name
000000000005 000300000002 R_X86_64_PC32 0000000000000000 puts - 4
00000000000a 000400000002 R_X86_64_PC32 0000000000000000 run - 4
链接器会将 puts 和 run 的最终地址填入相应位置。
8. 符号表(.symtab)
符号表记录了目标文件中定义和引用的符号(函数、全局变量等)。使用 readelf -s hello.o 查看:
text
Symbol table '.symtab' contains 14 entries:
Num: Value Size Type Bind Vis Ndx Name
10: 0000000000000000 37 FUNC GLOBAL DEFAULT 1 main
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND puts
13: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND run
UND表示未定义(在本目标文件中未实现),需要链接器从其他.o或库中解析。
9. 小结
本文详细介绍了ELF文件格式的三个核心结构:ELF头、节头表和程序头表,并解释了节与段的关系、重定位表以及符号表的作用。理解ELF是掌握链接和加载过程的关键。下一篇我们将深入链接和加载的原理,包括静态链接的地址重定位、动态链接的GOT/PLT机制以及进程的虚拟地址空间。