ELF 文件(Executable Linkable Format
)是一种文件存储格式。
Linux 下的目标文件和可执行文件都按照该格式进行存储。
- 段(segment)是程序执行的必要组成,当多个目标文件链接成一个可执行文件时,会将相同权限的节(section)合并到一个段中。
- 在全局变量或函数之前加上
__attribute__((section("name")))
属性就可以把相应的变量或函数放到以"name"作为段名的段中。 - 在全局变量或函数定义时加上
__attribute__((weak))
或声明时加上__attribute__((weakref))
属性就可以把相应的变量或函数变成弱符号或弱引用。 - 如果用于编译和链接(可重定位文件),则编译器和链接器将把 ELF 文件看作是节头表描述的节的集合,程序头表可选。
- 如果用于加载执行(可执行文件),则加载器则将把 ELF 文件看作是程序头表描述的段的集合,一个段可能包含多个节,节头表可选。
ELF File Header
#define EI_NIDENT 16
typedef struct elf32_hdr // elf文件头
{
unsigned char e_ident[EI_NIDENT]; // elf文件标识
Elf32_Half e_type; // elf文件类型
Elf32_Half e_machine; // elf文件机器架构
Elf32_Word e_version; // elf文件版本号
Elf32_Addr e_entry; /* Entry point */ // elf执行入口点
Elf32_Off e_phoff; // program header table的偏移
Elf32_Off e_shoff; // section header table的偏移
Elf32_Word e_flags; // 特定于处理器的标志
Elf32_Half e_ehsize; // ELF文件头的大小,32位ELF是52字节,64位是64字节
Elf32_Half e_phentsize; // program header table中每个入口的大小
Elf32_Half e_phnum; // program header table的入口个数
Elf32_Half e_shentsize; // section header table中每个入口的大小
Elf32_Half e_shnum; // section header table的入口个数
Elf32_Half e_shstrndx; // section header table中字符串段(.shstrtab)的索引
} Elf32_Ehdr;
typedef struct elf64_hdr
{
unsigned char e_ident[EI_NIDENT]; /* ELF "magic number" */
Elf64_Half e_type;
Elf64_Half e_machine;
Elf64_Word e_version;
Elf64_Addr e_entry; /* Entry point virtual address */
Elf64_Off e_phoff; /* Program header table file offset */
Elf64_Off e_shoff; /* Section header table file offset */
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;
ELF Program Header
// 程序头权限属性
/* These constants define the permissions on sections in the program
header, p_flags. */
#define PF_R 0x4 // 可读
#define PF_W 0x2 // 可写
#define PF_X 0x1 // 可运行
typedef struct elf32_phdr // elf程序头表 segment
{
Elf32_Word p_type; // 段类型 PT_XXX
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;
typedef struct elf64_phdr
{
Elf64_Word p_type;
Elf64_Word p_flags;
Elf64_Off p_offset; /* Segment file offset */
Elf64_Addr p_vaddr; /* Segment virtual address */
Elf64_Addr p_paddr; /* Segment physical address */
Elf64_Xword p_filesz; /* Segment size in file */
Elf64_Xword p_memsz; /* Segment size in memory */
Elf64_Xword p_align; /* Segment alignment, file & memory */
} Elf64_Phdr;
ELF Section Header
typedef struct elf32_shdr // elf段表描述结构
{
Elf32_Word sh_name; //.shstrtab中的索引
Elf32_Word sh_type; // 段类型
Elf32_Word sh_flags; // 段标志
Elf32_Addr sh_addr; // 段虚拟地址
Elf32_Off sh_offset; // 段在文件中的偏移
Elf32_Word sh_size; // 段大小
Elf32_Word sh_link; // 段使用的字符串表或符号表在段表中的索引
Elf32_Word sh_info; // 重定位表所作用的段在段表中的索引
Elf32_Word sh_addralign; // 段对齐 2的n次幂
Elf32_Word sh_entsize; // 段中每项大小(如果可用)
} Elf32_Shdr;
typedef struct elf64_shdr
{
Elf64_Word sh_name; /* Section name, index in string tbl */
Elf64_Word sh_type; /* Type of section */
Elf64_Xword sh_flags; /* Miscellaneous section attributes */
Elf64_Addr sh_addr; /* Section virtual addr at execution */
Elf64_Off sh_offset; /* Section file offset */
Elf64_Xword sh_size; /* Size of section in bytes */
Elf64_Word sh_link; /* Index of another section */
Elf64_Word sh_info; /* Additional section information */
Elf64_Xword sh_addralign; /* Section alignment */
Elf64_Xword sh_entsize; /* Entry size if section holds table */
} Elf64_Shdr;
- 段名的字符串信息在
.strtab
段,需要从e_shoff
的地址开始偏移e_shstrndx
+ 1 个e_shentsize
。
- 保存可执行文件需要的动态链接器的路径。
- 保存程序代码指令。
- 保存只读数据。
- 保存初始化的全局或局部静态变量等数据。
- 存在于
data
段中,占用空间不超过4
字节,仅表示这个节本生的空间。 - 保存未进行初始化的全局或局部静态数据。程序加载时数据被初始化为
0
,在程序执行期间可以进行赋值。
- 包含编译器版本信息。
- 调试信息。
- GOT(Global Offset Table)全局偏移表。链接器为外部符号填充的实际偏移表。
- PLT(Procedure Linkage Table)程序链接表。可以在
.got.plt
节中拿到地址,并跳转。当.got.plt
没有所需地址的时,触发链接器去找到所需地址。 - .got.plt 中的值是 GOT 的一部分。包含上述 PLT 表所需地址(已经找到的和需要去触发的)。
- 节头字符串表,用于保存节头表中用到的字符串,可通过
sh_name
进行索引。
符号表段
typedef struct elf32_sym // 符号表结构
{
Elf32_Word st_name; // 字符串表中的索引
Elf32_Addr st_value; // 符号值 绝对值或在段中偏移的地址值
Elf32_Word st_size; // 符号大小
unsigned char st_info; // 低4位标识符号类型 高4位标识绑定信息
unsigned char st_other; // 0
Elf32_Half st_shndx; // 符号所在的段
} Elf32_Sym;
typedef struct elf64_sym
{
Elf64_Word st_name; /* Symbol name, index in string tbl */
unsigned char st_info; /* Type and binding attributes */
unsigned char st_other; /* No defined meaning, 0 */
Elf64_Half st_shndx; /* Associated section index */
Elf64_Addr st_value; /* Value of the symbol */
Elf64_Xword st_size; /* Associated symbol size */
} Elf64_Sym;
- 保存了符号信息。
- 保存了可执行文件的本地符号和从共享库导入的动态符号。
- 只是用来进行调试和链接的。
- 保存符号字符串表,表中的内容会被
.symtab
的结构引用。
- 保存了从共享库导入的动态符号表和全局符号,是
.symtab
所保存符号的子集。 - 保存的符号只能在运行时被解析,是运行时动态链接器所需的唯一符号。对于动态链接可执行文件的执行是必需的。
- 保存动态链接字符串表,表中存放字符串代表符号名称,以空字符作为终止符。
动态段
typedef struct dynamic // .dynamic段节点结构
{
Elf32_Sword d_tag; // 类型标识 DT_XXX
union
{
Elf32_Sword d_val; // 值
Elf32_Addr d_ptr; // 地址
} d_un;
} Elf32_Dyn;
typedef struct
{
Elf64_Sxword d_tag; /* entry tag value */
union
{
Elf64_Xword d_val;
Elf64_Addr d_ptr;
} d_un;
} Elf64_Dyn;
- 保存了动态链接所需要的基本信息,依赖于哪些共享对象、动态链接符号表的位置、动态链接重定位表的位置、共享对象初始化代码的地址等。
- 也称为
.gnu.hash
,保存了一个用于查找符号的散列表。
重定位表
/* The following are used with relocations */ // 提取符号重定位信息
#define ELF32_R_SYM(x) ((x) >> 8) // 提取符号重定位绑定信息
#define ELF32_R_TYPE(x) ((x)&0xff) // 提取符号重定位类型
#define ELF64_R_SYM(i) ((i) >> 32)
#define ELF64_R_TYPE(i) ((i)&0xffffffff)
typedef struct elf32_rel // 重定位表入口结构
{
Elf32_Addr r_offset; // 段偏移或虚拟地址
Elf32_Word r_info; // 低8位标识入口类型 高24位标识入口符号在符号表的下标
} Elf32_Rel;
typedef struct elf64_rel
{
Elf64_Addr r_offset; /* Location at which to apply the action */
Elf64_Xword r_info; /* index and type of relocation */
} Elf64_Rel;
typedef struct elf32_rela // 重定位表入口结构
{
Elf32_Addr r_offset; // 段偏移或虚拟地址
Elf32_Word r_info; // 低8位标识入口类型 高24位标识入口符号在符号表的下标
Elf32_Sword r_addend; // 辅助计算修订值 某些指令使用的是下一条指令的地址作为偏移寻址,则可以将这部分的偏移信息放在r_addend里面
} Elf32_Rela;
typedef struct elf64_rela
{
Elf64_Addr r_offset; /* Location at which to apply the action */
Elf64_Xword r_info; /* index and type of relocation */
Elf64_Sxword r_addend; /* Constant addend used to compute value */
} Elf64_Rela;
- 保存了重定位相关的信息,描述了如何在链接或运行时,对 ELF 目标文件的某部分或者进程镜像进行补充或修改。
- 构造器和析构器分别保存了指向构造函数和析构函数的函数指针,构造函数是在 main 函数执行之前需要执行的代码;析构函数是在 main 函数之后需要执行的代码。
__attribute__((constructor))
/__attribute__((destructor))
声明会将函数名地址加入到.ctors/.dtors
指示的可变长数组。
- 支持全局和静态对象的构造和析构。
- 若共享对象有 .init 段或 .finit 段,那么动态链接器将会执行段中的代码,以实现共享对象特有的初始化和反初始化过程。