本文接着《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;
#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. 可执行文件中程序头信息(目标文件中没有)
三、节头表
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指定了节中各数据项的长度,要求这些数据项长度都相同
从图中可以看出,目标文件各节的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以上区域。