程序的本质之⼆ELF⽂件的⽂件头、ctionheader和
programheader
操作系统:CentOS Linux relea 7.7.1908
内核版本:3.10.0-1062.1.1.el7.x86_64
运⾏平台:x86_64
本⽂根据/usr/include/elf.h⽂件和⽂中所述的tanglinux来分析可执⾏⽂件的⽂件头、ction header和program header的构成。
本⽂只介绍32位的可执⾏⽂件,64位的可执⾏⽂件与32位的构成基本⼀致,除了地址和偏移量的长度不同以外。Elf32_Half、
Elf32_Section和Elf32_Versym等类型都是16位的⽆符号整数;Elf32_Addr和Elf32_Off都是32位的⽆符号整数;Elf32_Word和
Elf32_Sword分别为32位的⽆符号和有符号整数(带S);Elf32_Xword和Elf32_Sxword分别为64位的⽆符号和有符号整数(带S)。
⼀、ELF⽂件头
Linux可执⾏⽂件采⽤ELF(Executable and Linkable Format)格式来存储数据。每个ELF⽂件都以以下的数据来开头:
#define EI_NIDENT (16)
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offt */
Elf32_Off e_shoff; /* Section header table file offt */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
} Elf32_Ehdr;
从中可知,ELF⽂件中的主要内容为program header和ction header,两者的⼤⼩、在ELF⽂件中的位置和数量都能通过⽂件头来获取。⽽后⼜可以通过program header来获得每个gment的属性,通过ction header来获得每个ction的属性。
ELF⽂件头的说明如下表所⽰:
其中,由于每个ction header的⼤⼩是固定的,⽽它们的名称属性不可能⼀样长,所以需要⼀个专门的string ction来保存它们的名称属性,⽽⽤来描述这个string ction的ction header在ction表中的位置就由e_shstrndx来确认(e_shoff+ e_shentsize*
e_shstrndx),以达到快速查询的⽬的。
如果e_phnum的值为PN_XNUM(即0xffff),则表⽰program header的数量超过了它所能存储的最⼤值,因此它的值另外保存在索引号为SHN_UNDEF(即0)的ction的Elf32_Shdr. sh_info的位置。
法师ELF⽂件头的标志位总共有16个字节的⼤⼩,⽬前只⽤到9个字节,剩余字节的值都为0。它们的说明如下表所⽰:
风范其中,2's complement的意思就是补码,⽽1's complement的意思为反码。
通过执⾏readelf命令可获得可执⾏⽂件tanglinux的⽂件头信息,如下所⽰:
$ readelf -h tanglinux
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x80482e0
Start of program headers: 52 (bytes into file)
Start of ction headers: 5944 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 9
Size of ction headers: 40 (bytes)
Number of ction headers: 31
Section header string table index: 30
从以上所输出的⽂件头信息可知可执⾏⽂件tanglinux中数据的⼤概分布,如下图所⽰:
⽂件的总⼤⼩为7184个字节。
⼆、ELF⽂件中的ction header
ction header⽤来描述每个ction的特性,如⼤⼩、类型、名称等等,它们都使⽤以下的数据结构来表⽰:
typedef struct
青蛙妈妈{
Elf32_Word sh_name; /* Section name (string tbl index) */
Elf32_Word sh_type; /* Section type */
Elf32_Word sh_flags; /* Section flags */
Elf32_Addr sh_addr; /* Section virtual addr at execution */
Elf32_Off sh_offt; /* Section file offt */
Elf32_Word sh_size; /* Section size in bytes */
Elf32_Word sh_link; /* Link to another ction */
Elf32_Word sh_info; /* Additional ction information */
Elf32_Word sh_addralign; /* Section alignment */
Elf32_Word sh_entsize; /* Entry size if ction holds table */
} Elf32_Shdr;
在ELF⽂件中,每个ction header都有⼀个索引号,从1开始,依次递增。不过ction header表的开头必须定义⼀个类型为
SHT_NULL的表项,它的索引号为SHN_UNDEF(也就是0)。由于ction header使⽤相同的数据结构来定义,并且它们的⼤⼩相同,所以所有的ction header依次存储在⼀起看起来就是⼀个表,因此可以叫它ction header表。类似的,所有program header看起来也是⼀个表。
1、sh_name表⽰ction的名称。由于每个ction名称的长度不相同,并且为了节约空间,于是就将所有ction的名称都存放在⼀个特定的名叫.shstrtab的ction(⽂件头信息中的Elf32_Ehdr. e_shstrndx就是⽤来快速查找它)中,所以这⾥的sh_name的值指的就是在这个特定ction中的偏移量,通过它可以获得⼀个字符串,也就是所需要的ction名。0值表⽰⽆名称,⼀般⽤于类型为SHT_NULL 的ction中。
2、sh_type表⽰ction的类型。它可能的取值有以下这些:
#define SHT_NULL 0 /* Section header table entry unud */
#define SHT_PROGBITS 1 /* Program data */
#define SHT_SYMTAB 2 /* Symbol table */
#define SHT_STRTAB 3 /* String table */
#define SHT_RELA 4 /* Relocation entries with addends */
#define SHT_HASH 5 /* Symbol hash table */
#define SHT_DYNAMIC 6 /* Dynamic linking information */
#define SHT_NOTE 7 /* Notes */
#define SHT_NOBITS 8 /* Program space with no data (bss) */
立秋的谚语#define SHT_REL 9 /* Relocation entries, no addends */
#define SHT_SHLIB 10 /* Rerved */
#define SHT_DYNSYM 11 /* Dynamic linker symbol table */
#define SHT_INIT_ARRAY 14 /* Array of constructors */
#define SHT_FINI_ARRAY 15 /* Array of destructors */
#define SHT_PREINIT_ARRAY 16 /* Array of pre-constructors */
#define SHT_GROUP 17 /* Section group */
#define SHT_SYMTAB_SHNDX 18 /* Extended ction indeces */
#define SHT_NUM 19 /* Number of defined types. */
#define SHT_LOOS 0x60000000 /* Start OS-specific. */
#define SHT_GNU_ATTRIBUTES 0x6ffffff5 /* Object attributes. */
#define SHT_GNU_HASH 0x6ffffff6 /* GNU-style hash table. */
#define SHT_GNU_LIBLIST 0x6ffffff7 /* Prelink library list */
#define SHT_CHECKSUM 0x6ffffff8 /* Checksum for DSO content. */
阴跷脉草原旅游#define SHT_LOSUNW 0x6ffffffa /* Sun-specific low bound. */
农村住宅围墙
#define SHT_SUNW_move 0x6ffffffa
#define SHT_SUNW_COMDAT 0x6ffffffb
#define SHT_SUNW_syminfo 0x6ffffffc
#define SHT_GNU_verdef 0x6ffffffd /* Version definition ction. */
#define SHT_GNU_verneed 0x6ffffffe /* Version needs ction. */
#define SHT_GNU_versym 0x6fffffff /* Version symbol table. */
天歌唱起来原唱#define SHT_HISUNW 0x6fffffff /* Sun-specific high bound. */
#define SHT_HIOS 0x6fffffff /* End OS-specific type */
#define SHT_LOPROC 0x70000000 /* Start of processor-specific */
#define SHT_HIPROC 0x7fffffff /* End of processor-specific */
#define SHT_LOUSER 0x80000000 /* Start of application-specific */
#define SHT_HIUSER 0x8fffffff /* End of application-specific */
其中,每个ELF⽂件中都有⼀个SHT_NULL类型的ction,它只有ction header,没有相应的ction数据,ction header中的数据都为0,没有意义。
SHT_PROGBITS类型的ction中的内容为程序数据,如代码、全局变量等。例如,.interp ction⽤于保存动态链接器的绝对地址,如/lib/ld-linux.so.2;.comment ction⽤于保存版本控制信息,如GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-
39);.eh_frame_hdr和.eh_frame ction⽤于保存异常处理信息,.eh_frame_hdr是对.eh_frame的补充,通过执⾏$ readelf -wf tanglinux命令可以查看.eh_frame ction中的内容。
NOBITS类型⼀般⽤于名叫.bss的ction,该ction也只有ction header,没有实际的内容,不占⽤可执⾏⽂件的空间。
SHT_STRTAB类型的ction⽤于保存字符串,它的格式如下例所⽰:
通过图中的索引Index就可以获得从Index开始到空字符为⽌的⼀段字符串。例如若Index取值为1则可获得字符串“name.”。
.shstrtab就是⼀个典型的SHT_STRTAB类型的ction,执⾏以下命令就可以输出该段中的内容,如下所⽰: