【Linux】ELF文件格式深度剖析——从目标文件到可执行文件

目录

[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

对于可执行文件,TypeEXECDYN,并且会有程序头表。

内核中的 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

链接器会将 putsrun 的最终地址填入相应位置。

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机制以及进程的虚拟地址空间。