本文接着《Linux系统ELF文件二进制格式分析(一)》继续分析ELF文件格式

二、程序头表

程序头表由几个项组成,结构类似于数组,项数记录在ELF文件头的e_phnum字段,各项有统一的结构,每个项都描述了一个段的信息。

1.      数据结构

typedef struct elf32_phdr{
	Elf32_Word	p_type;
	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;

(1)    p_type表示当前描述的段的种类。常见有以下常数。

#define PT_NULL    0  //空段
#define PT_LOAD    1  //可装载段
#define PT_DYNAMIC 2  //表示该段包含了用于动态连接器的信息
#define PT_INTERP  3  //表示当前段指定了用于动态连接的程序解释器,通常是ld-linux.so
#define PT_NOTE    4  //该段包含有专有的编译器信息
#define PT_SHLIB   5  //该段包含有共享库

(2)    p_offset给出了该段在二进制文件中的偏移量,单位为字节。

(3)    p_vaddr给出了该段需要映射到进程虚拟地址空间中的位置。

(4)    p_paddr在只支持物理寻址,不支持虚拟寻址的系统当中才使用。

(5)    p_filesz给出了该段在二进制文件当中的长度,单位为字节。

(6)    p_memsz给出了段在虚拟地址空间当中的长度,单位为字节。与p_filesz不等时会通过截断数据或者以0填充的方式处理。

(7)    p_flags保存了标志信息,定义了该段的访问权限。有如下值

#define PF_R		0x4     //该段可读
#define PF_W		0x2     //该段可写
#define PF_X		0x1     //该段可执行
(8)    p_align指定了段在内存和二进制文件当中的对齐方式,即p_offset和p_vaddr必须是p_align的整数倍。

2.      可执行文件中程序头信息(目标文件中没有)

由图可知,该程序共有9个程序头表,因此就有9个段。各段的信息用表格的形式表现了出来。另外需要注意的是图片下半部分所示内容:节到段的映射。该表中展示了每个段所包含的节的名称,可以看到,一个段包含了一个到多个的节,而节头表的作用则是记录每个节的信息。

三、节头表

1.      数据结构

typedef struct {
	Elf32_Word	sh_name;
	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;
	Elf32_Word	sh_entsize;
} Elf32_Shdr;

(1)    sh_name指定了节的名称,该值是在字符串表.shstrtab中的一个索引。将在后面介绍字符串表时介绍。

(2)    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  //同样表示保存了符号表(与SHT_SYMTAB的不同在后面介绍)
#define SHT_NUM		12

(3)    sh_flags有一下标志:

#define SHF_WRITE	0x1  //表示可写
#define SHF_ALLOC	0x2  //表示可分配虚拟内存
#define SHF_EXECINSTR	0x4  //表示代码可执行

(4)    sh_addr指定节映射到虚拟地址空间中的位置

(5)    sh_offset指定节在文件中位置的偏移量

(6)    因节的类型不同,sh_link和sh_info会有不同的解释

当节为SHT_DYNAMIC类型时,sh_link指向节数据所用的字符串表,此时sh_info无效。

当节为SHT_HASH类型时,sh_link指向所散列的符号表,此时sh_info无效。

当节为SHT_RELA或SHT_REL类型时,sh_link指向相关的符号表,此时sh_info保存节头表中的索引,表示对哪个节进行重定位。

当节为SHT_DYNSYM或SHT_SYMTAB类型时,sh_link指定了用作符号表的字符串表,sh_info表示符号表中紧随最后一个局部符号之后的索引位置。

(7)    sh_addralign指定了节在内存中对齐系数

(8)    sh_entsize指定了节中各数据项的长度,要求这些数据项长度都相同

2.      目标文件中节信息

从图中可以看出,目标文件各节的sh_addr值是没有意义的,因为目标文件不能加载到内存当中创建进程。

每个节都有自己的类型,定义了节中数据的含义。其中最重要的包括PROGBITS(程序必须解释的信息,如二进制代码)、SYMTAB(符号表)、REL(重定位信息)和STRTAB(与ELF相关的字符串)。

不同的ELF文件可以有不同的节,但Linux标准要求某些节必须存在,如总有一个.text节来保存二进制代码,.rel.text保存text节中重定位信息。

各个节的标志含义如图中最下方说明所示,表明了该节拥有的权限。

3.      可执行文件中节信息


与目标文件相比,可执行文件节的数量明显增加,这里只介绍最主要的几个。

.interp保存了解释器的文件名,是一个ASCII字符串。

.data保存了初始化过的数据,程序可修改这些数据。

.rodata保存了只读数据,可读但不可修改。

.init和.finit分别保存进程初始化代码和结束所用代码。这两个节是编译器自己添加的,程序员一般不关心。

.hash是一个散列表,用于对符号快速访问的实现。

可执行文件当中sh_addr有相应的数值,表示文件加载到虚拟内存时,各个节必须加载到其sh_addr指定的虚拟内存的位置。对Linux程序来说,应用程序各节通常使用0x08000000以上区域。


接下来《Linux系统ELF文件二进制格式分析(三)》将会介绍符号表、字符串表和重定位项。
Logo

更多推荐