说明

ELF文件格式可以通过键入命令man 5 elf来查看,本文介绍不如man手册详尽严谨,具体内容还需查看手册!

对于使用C语言读取ELF文件,可以参考我写的代码:https://github.com/EPTansuo/elfread

与ELF的文件信息相关的结构体在<elf.h>中定义。

首先介绍下该头文件中定义的一些数据类型,其中的N=32或64

ElfN_Addr       Unsigned program address, uintN_t
ElfN_Off        Unsigned file offset, uintN_t
ElfN_Section    Unsigned section index, uint16_t
ElfN_Versym     Unsigned version symbol information, uint16_t
Elf_Byte        unsigned char
ElfN_Half       uint16_t
ElfN_Sword      int32_t
ElfN_Word       uint32_t
ElfN_Sxword     int64_t
ElfN_Xword      uint64_t

ELF文件头

ELF的文件头由结构体ElfN_EhdrELF32_EhdrELF64_Ehdr)来描述。

#define EI_NIDENT 16

typedef struct
{
        unsigned char e_ident[EI_NIDENT];
        uint16_t e_type;
        uint16_t e_machine;
        uint32_t e_version;
        ElfN_Addr e_entry;
        ElfN_Off e_phoff;
        ElfN_Off e_shoff;
        uint32_t e_flags;
        uint16_t e_ehsize;
        uint16_t e_phentsize;
        uint16_t e_phnum;
        uint16_t e_shentsize;
        uint16_t e_shnum;
        uint16_t e_shstrndx;
} ElfN_Ehdr;

关于其成员的解释:

  • e_ident: 16个字节的数组,包含文件的"magic numer"以及表征32位或64位的信息,还有大端小端的信息,以及OS/ABI的信息等。具体可查询man手册。
  • e_type: 代表 了文件的类型,如可执行文件,动态链接库文件、核心转储文件等。
  • e_machine:代表了架构的类型,如Intel 80386, ARM, PowerPC等。
  • e_version: 文件的版本,但是只有EV_NONE(无效版本)和EV_CURRENT(当前版本)两个值。
  • e_entry: 程序的入口地址
  • e_phoff: 代表程序头表(program header table)偏移量。
  • e_shoff: 代表节头表(section header table)偏移量。
  • e_flags: 和处理器的编译选项或特性有关的标志。
  • e_ehsize: 代表文件头的大小。
  • e_phentsize: 代表程序头表的每个条目的大小,所有的条目都是相同的大小。
  • e_phnum: 程序头表条目的数量。如果该值超过了0xffff,则该值设为0xffff,实际的条目数量存储在sh_info(见后文)中。
  • e_shentsize: 代表节头表的每个条目的大小,所有的条目都是相同的大小。
  • e_phnum: 节头表条目的数量。如果该值超过了0xff00,则该值设为0,实际的条目数量存储在sh_size(见后文)中。
  • e_shstrndx: 节名称字符串表的索引。

程序头(Program Header)

程序头表由结构体Elf32_Phdr或者Elf64_Phdr组成的数组来存放。数组元素的大小由e_phentsize定义,元素的个数由e_phnum定义。

typedef struct{
        uint32_t p_type;
        Elf32_Off p_offset;
        Elf32_Addr p_vaddr;
        Elf32_Addr p_paddr;
        uint32_t p_filesz;
        uint32_t p_memsz;
        uint32_t p_flags;
        uint32_t p_align;
} Elf32_Phdr;

typedef struct{
        uint32_t p_type;
        uint32_t p_flags;
        Elf64_Off p_offset;
        Elf64_Addr p_vaddr;
        Elf64_Addr p_paddr;
        uint64_t p_filesz;
        uint64_t p_memsz;
        uint64_t p_align;
} Elf64_Phdr;

注意:在32位和64位的系统中,p_flags在结构体中的位置是不同的,这是为了内存对齐,更高效处理数据。

对其成员变量的解释

  • p_type: 代表了段(Segment)的类型和如何去解析数组的元素信息。
  • p_offset: 该值代表了段的偏移量。
  • p_vaddr: 代表段第一个字节在内存中的虚拟地址。
  • p_paddr: 在与物理寻址相关的系统上,为物理地址保留。
  • p_filesz: 段在文件中的大小。
  • p_memsz: 段在内存中的大小。
  • p_flags: 代表了段的权限,如可读、可写、可执行。
  • p_align: 代表了段在内存和文件中的对齐要求。

节头(Section Header)

节头表是Elf32_ShdrElf64_Shdr结构体的数组。其偏移量由ELF文件头的e_shoff成员变量给出。e_shnum代表其数组的元素个数,e_shentsize代表每个元素的大小。

typedef struct{
        uint32_t sh_name;
        uint32_t sh_type;
        uint32_t sh_flags;
        Elf32_Addr sh_addr;
        Elf32_Off sh_offset;
        uint32_t sh_size;
        uint32_t sh_link;
        uint32_t sh_info;
        uint32_t sh_addralign;
        uint32_t sh_entsize;
} Elf32_Shdr;

typedef struct{
        uint32_t sh_name;
        uint32_t sh_type;
        uint64_t sh_flags;
        Elf64_Addr sh_addr;
        Elf64_Off sh_offset;
        uint64_t sh_size;
        uint32_t sh_link;
        uint32_t sh_info;
        uint64_t sh_addralign;
        uint64_t sh_entsize;
} Elf64_Shdr;

对于其成员的解释

  • sh_name: 节字符头表的索引,代表了该节的名字。
  • sh_type: 节头的类型。
  • sh_flags: 节头的属性标志,如可写,可执行等。
  • sh_addr: 节头在内存中的起始地址。
  • sh_offset: 节头在文件中的偏移地址。
  • sh_size: 节头的大小。
  • sh_link: 通常用作索引,其解析功能由sh_type决定。
  • sh_info: 一些额外的信息,其解析功能由sh_type决定。
  • sh_addralign: 和对齐有关的要求。
  • sh_entsize: 对于固定长度的节,这个代表了大小。当节的大小不固定时,则为0。

符号表(Symbol Table)

符号表存储了程序中定义和引用的所有符号信息(如函数等)。符号表的索引可以看成ElfN_Sym结构体数组的索引。

typedef struct
{
        uint32_t st_name;
        Elf32_Addr st_value;
        uint32_t st_size;
        unsigned char st_info;
        unsigned char st_other;
        uint16_t st_shndx;
} Elf32_Sym;

typedef struct
{
        uint32_t st_name;
        unsigned char st_info;
        unsigned char st_other;
        uint16_t st_shndx;
        Elf64_Addr st_value;
        uint64_t st_size;
} Elf64_Sym;

成员变量解析

  • st_name: 符号表的名字,实际上是对字符串表的一个索引。
  • st_value: 符号的值。
  • st_size: 符号的大小。如果符号大小未知或没有大小,则为0。
  • st_info:低4位代表符号的类型,其余位代表绑定的信息。
  • st_other: 定义了符号的可见性。
  • st_shndx: 定义了与之相关联的节头表的相对索引。