Linux内核之ELF格式解析(一)
Linux
内核中,ELF
格式定义在include/uapi/linux/elf.h
。
对于ELF
文件的组成,我们从不同的角度来看,可以分为两种 - “Linking View” 和 “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
)。对这样一个节,这个成员给出每一项的字节大小。