Linux内核中,ELF格式定义在include/uapi/linux/elf.h

对于ELF文件的组成,我们从不同的角度来看,可以分为两种 - “Linking View” 和 “Execution View”。

linking vs execution view

ELF文件格式主要包含三种内容:

ELF Header :

#define EI_NIDENT       16
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;

e_ident : 这个起始的字节标识该文件是否为对象文件,同时提供许多框架无关的数据。关于ELF文件魔数也就是ELFMAG的具体解释,可以看 Linux内核中ELF加载解析(一)

以下部分是e_ident这个数组的索引:

#define EI_MAG0         0               /* e_ident[] indexes */
#define EI_MAG1         1
#define EI_MAG2         2
#define EI_MAG3         3
#define EI_CLASS        4
#define EI_DATA         5
#define EI_VERSION      6
#define EI_OSABI        7
#define EI_PAD          8

这是ELF文件的标志,任何一个ELF文件这四个字节都完全相同。

#define ELFMAG0         0x7f            /* EI_MAG */
#define ELFMAG1         'E'
#define ELFMAG2         'L'
#define ELFMAG3         'F'
#define ELFMAG          "\177ELF"
#define SELFMAG         4

第四个字节表示ELF格式, 1:32bit; 2:64bit

#define ELFCLASSNONE    0               /* EI_CLASS */
#define ELFCLASS32      1
#define ELFCLASS64      2
#define ELFCLASSNUM     3

第五个字节表示数据编码格式,1:小端模式 2:大端模式

#define ELFDATANONE     0               /* e_ident[EI_DATA] */
#define ELFDATA2LSB     1
#define ELFDATA2MSB     2

第六个字节表示文件版本,该值目前必须为1

#define EV_NONE         0               /* e_version, EI_VERSION */
#define EV_CURRENT      1
#define EV_NUM          2

e_type : 确认对象文件格式。

/* These constants define the different elf file types */
#define ET_NONE   0
#define ET_REL    1
#define ET_EXEC   2
#define ET_DYN    3
#define ET_CORE   4
#define ET_LOPROC 0xff00
#define ET_HIPROC 0xffff

e_machine : 确认框架信息

(以下宏定义来自于 include/uapi/linux/elf-em.h)

/* These constants define the various ELF target machines */
#define EM_NONE 0
#define EM_M32 1
#define EM_SPARC 2
#define EM_386 3
#define EM_68K 4
#define EM_88K 5
#define EM_486 6 /* Perhaps disused */
#define EM_860 7
#define EM_MIPS 8
 ...... 

e_version : 确定对象文件的版本

#define EV_NONE         0               /* e_version, EI_VERSION */
#define EV_CURRENT      1
#define EV_NUM          2

e_entry : 系统转移控制权到的虚拟地址,从而开始进程。

e_phoff : 程序头表在ELF文件中的偏移。

e_shoff : 节头表在ELF文件中的偏移。

e_flags : 和此文件相关的处理器标志。

e_ehsize : ELF头的长度(以字节为单位)。

e_phentsize : 程序头表的每一项的字节数,每一项大小都相同。

e_phnum:程序头表的表项的数目,通过e_phentsize和e_phnum,我们可以计算出程序头表的字节数。如果没有程序头表的话,该项为0。

e_shentsize : 节头表的每一项的字节数,每一项大小都相同。

e_shnum : 程序头表的表项的数目,通过 e_shentsize 和 e_shnum ,我们可以计算出程序头表的字节数。如果没有节头表的话,该项为0。

e_shstrndx : 这个字段保留节头表的索引,联系到节名字符串表(section name string table)。关于这个节头表表项,我们可以参看ELF Section Header的结构。从网上拷过来的解释:这是一个整数索引值。节头可以看作是一个结构数组,用这个索引值做为此数组的下标,它在节头中指定的一个结构进一步给出了一个“字符串表”的信息,而这 个字符串表保存着节头中描述的每一个节的名称,包括字符串表自己也是其中的一 个节。

Program Header Table

一个可执行文件或者共享链接库的程序头表是一个数据结构的数组,每一个都描述一个段(segment)或者一个系统需要为程序运行提供信息。一个ELF文件包含一个或多个节(section)。程序头只对可执行文件和共享链接库有意义。

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;

p_type : 段类型,如何解释该段的信息;我们可以看ELF文件可执行栈的深入分析,这个文章就是分析GNU_STACK段的功能;

/* These constants are for the segment types stored in the image headers */
#define PT_NULL    0
#define PT_LOAD    1
#define PT_DYNAMIC 2
#define PT_INTERP  3
#define PT_NOTE    4
#define PT_SHLIB   5
#define PT_PHDR    6
#define PT_TLS     7               /* Thread local storage segment */
#define PT_LOOS    0x60000000      /* OS-specific */
#define PT_HIOS    0x6fffffff      /* OS-specific */
#define PT_LOPROC  0x70000000
#define PT_HIPROC  0x7fffffff
#define PT_GNU_EH_FRAME         0x6474e550

#define PT_GNU_STACK    (PT_LOOS + 0x474e551)

p_offset : 段偏移,这个段的第一个自己字节到文件开始的距离,也就是该段在文件中的偏移;

p_vaddr : 加载到内存中的虚拟地址;

p_paddr : 加载到内存中的物理地址;

p_filesz : 文件映像中段的大小/字节数,可能是零,比如说GNU_STACK段;

p_memsz : 内存映像中段的大小/字节数,可能是零,比如说GNU_STACK段;

p_flags : flags relevant to the segment. 和这个段有关的标志;

/* 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

p_align : 段对齐;

Section Header Table

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;

sh_name : 这个字段确定节的名字。它的值不是一个字符串,而是节名字符串表的索引。 可以参见 ELF Section Header的结构。

sh_type : 这个字段分类节的内容和语义。

/* sh_type */
#define SHT_NULL        0
#define SHT_PROGBITS    1
#define SHT_SYMTAB      2
#define SHT_STRTAB      3
#define SHT_RELA        4
#define SHT_HASH        5
#define SHT_DYNAMIC     6
#define SHT_NOTE        7
#define SHT_NOBITS      8
#define SHT_REL         9
#define SHT_SHLIB       10
#define SHT_DYNSYM      11
#define SHT_NUM         12
#define SHT_LOPROC      0x70000000
#define SHT_HIPROC      0x7fffffff
#define SHT_LOUSER      0x80000000
#define SHT_HIUSER      0xffffffff

sh_flags : 支持一位的标志,描述各种各样的属性

sh_addr : 如果这个节出现在进程的内存映像,这个成员给出这个节第一个字节在内存中的地址。

sh_offset : 这个成员的值给出这个节在文件中的偏移。

sh_size : 这个成员给出节的字节数。

sh_link : 这个成员持有一个节头表索引链接,它的解释器根据节的类型决定。

sh_info : 这个成员持有额外的信息,它的解释器取决于节的类型。

sh_addralign : 对齐限制。

sh_entsize : 有些节持有/保存一个固定尺寸项的表,比如说符号表(symbol table)。对这样一个节,这个成员给出每一项的字节大小。