ELF文件-段和程序头

来源:互联网 发布:php array sort 排序 编辑:程序博客网 时间:2024/05/29 10:27
可执行文件和共享目标文件(动态链接库)是程序在磁盘中的静态存储形式;要执行一个程序,系统就要先把相应的可执行文件和共享目标文件装载到进程的地址空间中,这样就形成一个可运行进程的内存空间布局,称为进程镜像;一个已经装载完成的进程空间中会包含多个不同的(Segment),比如,代码段、数据段、堆栈段,等等;
准备一个程序的内存镜像,大体上可以分为两个步骤:装载链接;前者是把目标文件装载到内存中,后者是解析目标文件中的符号引用;
一个可执行文件及其依赖的共享目标文件被完全成功地装载到进程的内存地址空间中之后,这个可执行文件或共享目标文件中的程序头部表(Program Header Table)就是必须存在的、不可缺少的必需品,程序头部表是一个数组,数组中的每一个元素就称为一个程序头(Program Header),每一个程序头描述一个内存段(Segment)或者一块用于准备执行程序的信息;内存中的一个目标文件中的段包含一个或多个;也就是ELF文件在磁盘中的一个或多个节可能会被映射到内存中的同一个段中;程序头只对可执行文件或共享目标文件有意义,对于其它类型的目标文件,该信息可以忽略;
在目标文件的ELF文件头部结构中,成员e_phentsize和e_phnum分别给出程序头部表中元素(程序头)的大小和数量;
程序头使用结构体Elf32_Phdr/Elf64_Phdr来定义:
struct Elf32_Phdr
{
  Elf32_Word  p_type;   /* Segment type */
  Elf32_Off   p_offset; /* Segment file offset */
  Elf32_Addr  p_vaddr;  /* Segment virtual address */
  Elf32_Addr  p_paddr;  /* Segment physical address */
  Elf32_Word  p_filesz; /* Segment size in file */
  Elf32_Word  p_memsz;  /* Segment size in memory */
  Elf32_Word  p_flags;  /* Segment flags */
  Elf32_Word  p_align;  /* Segment alignment */
};
struct Elf64_Phdr
{
  Elf64_Word  p_type;   /* Segment type */
  Elf64_Word  p_flags;  /* Segment 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 */
};
------字段详解------
1、p_type:
该字段指明该程序头所描述的内存段的类型,或者如何解析该程序头的信息;取值如下:
PT_NULL    = 0: 该段没有被使用;
PT_LOAD    = 1: 该段是一个可装载的内存段;
PT_DYNAMIC = 2: 该段描述的是动态链接信息;
PT_INTERP     = 3: 该段描述的是一个以"\0"结尾的字符串,这个字符串是一个ELF解析器的路径;这种段类型只对可执行程序由意义,对其它类型的ELF文件无意义;在一个ELF文件中它最多只能出现一次,而且必须出现在其它可装载段的表项之前;
PT_NOTE    = 4: 该段描述一个以"\n"结尾的字符串,这个字符串包含一些附加的信息;
PT_SHLIB   = 5: 保留值;
PT_PHDR    = 6: 此类段如果存在的话,则它表示的是其自身所在的程序头部表在文件或内存中的位置和大小;这样的段在文件中可以不存在,只有当所在的程序头部表所覆盖的段只是整个程序的一部分时,才会出现一次这种表项,而且一定会出现在其它可装载段的表项之前;
PT_TLS     = 7: Thread-local storage segment;
PT_NUM     = 8: Number of defined types;
PT_LOOS    = 0x60000000 /* Start of OS-specific */
PT_GNU_EH_FRAME = 0x6474e550 /* GCC .eh_frame_hdr segment */
PT_GNU_STACK = 0x6474e551 /* Indicates stack executability */
PT_GNU_RELRO = 0x6474e552 /* Read-only after relocation */
PT_LOSUNW    = 0x6ffffffa
PT_SUNWBSS   = 0x6ffffffa /* Sun Specific segment */
PT_SUNWSTACK = 0x6ffffffb /* Stack segment */
PT_HISUNW    = 0x6fffffff
PT_HIOS      = 0x6fffffff /* End of OS-specific */
PT_LOPROC    = 0x70000000 /* Start of processor-specific */
PT_HIPROC    = 0x7fffffff /* End of processor-specific */
除非有特别要求,否则所有程序头的段类型字段p_type都是可选项,不是必须存在的;在所有程序头都不指定段类型的情况下,程序头部表中所有的表项都不代表任何特别的类型,而只是作为一种索引,表明其相应的段的大小和位置;

2、p_offset:
该字段指明该段中内容在文件中的位置,即,段中内容的起始位置相对于文件开头处的偏移量;

3、p_vaddr:
该字段指明该段中内容的起始位置在进程地址空间中的虚拟地址;

4、p_paddr:
该字段指明该段中内容的起始位置在进程地址空间中的物理地址;对于目前大多数现代操作系统而言,应用程序中段的物理地址是不可预先得知的,所以,目前这个字段大多数情况下保留不用,或者被操作系统改作它用;

5、p_filesz:
该字段指明该段中内容在文件中的大小,也可以是0;单位是字节;

6、p_memsz:
该字段指明该段中内容在内存镜像中的大小,也可以是0;单位是字节;

7、p_flags:
该字段指明该段中内容的属性;取值如下:
PF_X = (1 << 0) /* Segment is executable */
PF_W = (1 << 1) /* Segment is writable */
PF_R = (1 << 2) /* Segment is readable */
PF_MASKOS   = 0x0ff00000 /* OS-specific */
PF_MASKPROC = 0xf0000000 /* Processor-specific */

8、p_align:
该字段指明该段中内容如何在内存和文件中对齐;对于可装载的段来说,其p_vaddr和p_offset的值至少要向内存页面大小对齐;如果值为0或1,则表明没有对齐要求,否则,p_align应该是一个正整数,并且是2的幂次数;p_vaddr和p_offset在对p_align取模后应该相等;

备注:
p_type=PT_LOAD时,段的内容会被从文件中拷贝到内存中,如果p_memsz>p_filesz,则在内存中多出的存储空间中填0补充,即,段在内存中可以比在文件中占用更大空间;相反,p_filesz永远都不应该比p_memsz大,因为这样的话,在内存中就将无法完整地映射段的内容;在程序头部表中,所有PT_LOAD类型的程序头都按照p_vaddr的值做升序排列;
0 0