天天看点

ELF文件系列第三篇ELF文件静态结构中的节

节概述

在ELF文件中可以包含很多“节”(section),所有这些“节”都记录在一张称为“节头表”(section header table)的数组里。节头表的每一个表项是一个 Elf32_Shdr 结构,通过每一个表项可以定位到对应的节。

在 ELF 文件头中, e_shoff 成员给出节头表在 ELF 文件中相对于文件开始处的偏移量;e_shnum 成员指明节头表中包含多少个表项;e_shentsize 成员指明了每一个表项的大小。

节头表数组中,某些表项的索引值被保留,有特殊的含义。ELF 文件的节头表中不会出现索引值为以下各值的表项:

名字 SHN_UNDEF SHN_LORESERVE SHN_LOPROC SHN_HIPROC SHN_ABS SHN_COMMON SHN_HIRESERVE
0xff00 0xff00 0xff1f 0xfff1 0xfff2 0xffff

SHN_UNDEF 

该值被定义为 0,它表示一个未定义的、不存在的节的索引。尽管索引值 0 是一个未定义的保留值,但在节头表中的索引还是会从 0 开始, 0 号表项的意义将在后面说明。

SHN_LORESERVE:被保留索引号区间的下限。

SHN_LOPROC:为特定处理器定制节所保留的索引号区间的下限。

SHN_HIPROC:为特定处理器定制节所保留的索引号区间的上限。

SHN_ABS:此节中所定义的符号有绝对的值,这个值不会因重定位而改变。

SHN_COMMON:此节中所定义的符号是公共的,比如 FORTRAN COMMON 符号或者未分配的 C 外部变量。

SHN_HIRESERVE:被保留索引号区间的上限。

除 0 以外,节头表中所有保留的索引值都位于 SHN_LORESERVE ~SHN_HIRESERVE 范围之间。与索引值”0”不同,这些大数值的索引并不出现在节头表中。

通常,目标文件中含有众多的“节”,“节”区是文件中最大的部分,它们需要满足下列这些条件:

  • ELf文件中的每一个节一定对应有一个节头,节头中有对节的描述信息;但有的节头可以没有对应的节,而只是一个空的头。
  • 每一个节所占用的空间是连续的。
  • 各个节之间不能互相重叠。
  • 在ELF文件中,节与节之间可能会存在一些多余的字节,这些字节不属于任何节。

节头的结构

可以用以下这个数据结构体来描述节头。

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;
           

各个成员的含义如下:

h_name:本节的名字。名字并不存储在这里,它仅是一个索引号,指向“字符串表”节中的某个位置,那里存储了一个节名字符串。

sh_type: 本节的类型。下表给出了所有的节类型。

名字 解释说明
SHT_NULL 此值表明本节头是个无效的节头,它也没有对应的节。
SHT_PROGBITS 1 本节所含有的信息的格式和含义都由程序来决定。

SHT_SYMTAB

/SHT_DYNSYM

2/11 这两类节都含有符号表,只能各包含一个这两种节。SHT_SYMTAB 提供的符号用于在创建ELF文件的时候编辑连接,在运行期间也有可能会用于动态连接
SHT_STRTAB 3 本节是字符串表,ELF文件中可包含多个字符串表节
SHT_RELA 4 重定位节,一个ELF文件可能含有多个重定位节
SHT_HASH 5 本节包含一张哈希表,所有参与动态连接的ELF文件都必须要包含一个符号哈希表。目前,一个ELF文件中最多只能有一个哈希表
SHT_DYNAMIC 6 动态连接信息。一个ELF文件中最多只能有一个 。
SHT_NOTE 7 本节包含的信息用于以某种方式来标记本文件
SHT_NOBITS 8 这一节的内容是空的,节并不占用实际的空间。
SHT_REL 9 本节是一个重定位节
SHT_SHLIB 10 此值是一个保留值,暂未指定语义。
SHT_LOPROC~SHT_HIPROC 0x70000000~0x7fffffff SHT_LOPROC~SHT_HIPROC区间是为特殊处理器节类型的保留值。
SHT_LOUSER~SHT_HIUSER 0x80000000~0xffffffff SHT_LOUSER~SHT_HIUSER,由应用程序自定义区间类型,是一段保留值。

sh_flags :本节的一些属性,由一系列标志比特位组成,以下是这些标志位的列表及含义。

名字
SHF_WRITE 0x1 如果此标志被设置,表示本节所包含的内容在进程运行过程中是可写的。
 SHF_ALLOC 0x2 如果此标志被设置,表示本节内容在进程运行过程中要占用内存单元。
SHF_EXECINSTR 0x4 如果此标志被设置,表示此节内容是指令代码。                                             
SHF_MASHPROC 0xf0000000 所有被此值所覆盖的位都是保留做特殊处理器扩展用的。

sh_addr:如果本节的内容需要映射到进程空间中去,此成员指定映射的起始地址;如果不需要映射,此值为 0。

sh_offset:指明了本节第一个字节相对于文件开头的偏移量。单位是字节。如果该节的类型为 SHT_NOBITS 的话,表明这一节的内容是空的,节并不占用实际的空间,这时 sh_offset 只代表一个逻辑上的位置概念,并不代表实际的内容。

sh_size:指明节的大小,单位是字节。如果该节的类型为 SHT_NOBITS,此值仍然可能为非零,但没有实际的意义。

sh_link: 此成员是一个索引值,指向节头表中本节所对应的位置。根据节的类型不同,本成员的意义也有所不同,具体见下表。

sh_info: 此成员含有此节的附加信息,根据节的类型不同,本成员的意义也有所不同。

sh_type sh_link sh_info
SHT_DYNAMIC 本节中项目的字符串表在节头表中相应的索引值
SHT_HASH 本节中哈希表的符号表在节头表中相应的索引值
SHT_REL /SHT_RELA 相应符号表在节头表中的索引值 本重定位节所应用到目标节在节头表中的索引值
SHT_SYMTAB/SHT_DYNSYM 相关字符串表的节头索引 符号表中最后一个本地符号的索引值加 1
其它 SHN_UNDEF

sh_addralign 

此成员指明本节内容如何对齐字节,即该节的地址应该向多少个字节对齐。也就是说,本节内容在进程空间中的映射地址 sh_addr 必须是一个向sh_addralign 对齐,即能被 sh_addralign 整除的值。目前,sh_addralign 只能取 0、1或者 2 的正整数倍。如果该值为 0 或 1,表明本节没有字节对齐约束。

sh_entsize

有一些节的内容是一张表,其中每一个表项的大小是固定的,比如符号表。对于这种表来说,本成员指定其每一个表项的大小。如果此值为 0 则表明本节内容不是这种表格。

索引值0节头项内容

下面说一下索引值为 0 的节头表项,它并不表达实际的内容,只是一个空的表项,内容见下表。

名字 意义 名字 意义
sh_name 无名字 sh_size 无大小
sh_type SHT_NULL 非活动类型 sh_link SHN_UNDEF 无节头表对应项
sh_flags 无标志 sh_info 无附加信息
sh_addr 无地址 sh_addralign 无对齐格式
sh_offset 无文件内偏移量 sh_entsize 无表项大小

 特殊节

在 ELF 文件中有一些特定的节是预定义好的,其内容是指令代码或者控制信息,这些节专门为操作系统使用。在构建可执行程序时,连接器(linker)可能需要把一些独立的目标文件和库文件连接在一起,在这个过程中,连接器要解析各个文件中的相互引用,调整某些目标文件中的绝对引用,并重定位指令码。每种操作系统都有自己的一套连接模型,但总的来说,不外乎静态和动态两类:

静态连接 :所有的目标文件和动态连接库被静态地绑定在一起,所有的符号都被解析出来。所创建的目标文件是完整的,运行时不依赖于任何外部的库。

动态连接 :所有的目标文件、系统共享资源以及共享库以动态的形式连接在一起,外部库的内容没有完整地拷贝进来。如果创建的是可执行文件的话,程序在运行的时候,在构建时所依赖的那些库必须在系统中能找到,把它们一并装载之后,程序才能运行起来。运行期间如何解析那些动态连接进来的符号引用,不同的系统有各自不同的方式。

动态连接过程所需要的信息由.dynsym、.dynstr、.interp、.hash、.dynamic、.rel、.rela、.got、.plt 等节提供。.init 和.fini 节用于进程的初始化和终止过程。

名字 类型 属性
.bss SHT_NOBITS SHF_ALLOC+SHF_WRITE 包含未初始化的全局变量。可执行程序在开始运行的时候,系统会把这一段内容清零。但是,在运行期间的 bss 段是由系统初始化而成的,在ELF文件中.bss 节并不包含任何内容,其长度为 0,所以它的节类型为 SHT_NOBITS。
.comment SHT_PROGBITS 本节包含版本控制信息。

.data

.data1

SHT_PROGBITS SHF_ALLOC+SHF_WRITE
这两个节用于存放程序中被初始化过的全局变量。在ELF文件中,它们是占用实际的存储空间的,与.bss 节不同。
.debug SHT_PROGBITS 调试信息,内容格式没有统一规定。
.dynamic SHT_DYNAMIC SHF_ALLOC+[SHF_WRITE] 本节包含动态连接信息
.dynstr SHT_STRTAB SHF_ALLOC 含有用于动态连接的字符串,一般是那些与符号表相关的名字
.dynsym SHT_DYNSYM SHF_ALLOC 此节含有动态连接符号表。
.fini SHT_PROGBITS SHF_ALLOC+SHF_EXECINSTR 此节包含进程终止时要执行的程序指令。当程序正常退出时,系统会执行这一节中的代码。
.got SHT_PROGBITS SHF_ALLOC+SHF_WRITE 此节包含全局偏移量表。
.hash SHT_HASH SHF_ALLOC 本节包含一张符号哈希表。
.init SHT_PROGBITS SHF_ALLOC+SHF_EXECINSTR 此节包含进程初始化时要执行的程序指令。当程序开始运行时,系统会在进入主函数之前执行这一节中的代码。
.interp SHT_PROGBITS [SHF_ALLOC] 此节含有 ELF 程序解析器的路径名。
.line SHT_PROGBITS 本节也是一个用于调试的节,它包含那些调试符号的行号,为程序指令码与源文件的行号建立起联系。其内容格式没有统一规定。
.note SHT_NOTE 注释节详细描述
.plt SHT_PROGBITS SHF_ALLOC+SHF_EXECINSTR 此节包含函数连接表。

.relname

.relaname

SHT_REL

/SHT_RELA

[SHF_ALLOC] 重定位信息。注意,这两个节的名字中”name”是可替换的部分,执照惯例,对哪一节做重定位就把”name”换成哪一节的名字。比如,.text 节的重定位节的名字将是.rel.text 或.rela.text。

.rodata

.rodata1

SHT_PROGBITS SHF_ALLOC
本节包含程序中的只读数据,在程序装载时,它们一般会被装入进程空间中那些只读的段中去。
.shstrtab SHT_STRTAB 本节是“节名字表”,含有所有其它节的名字。
.strtab SHT_STRTAB [SHF_ALLOC] 本节用于存放字符串,主要是那些符号表项的名字。
.symtab SHT_SYMTAB [SHF_ALLOC] 本节用于存放符号表。
.text SHT_PROGBITS SHF_ALLOC+SHF_EXECINSTR 本节包含程序指令代码。

以点号”.”为前缀的节名字是为系统保留的。应用程序也可以构造自己的段,但最好不要取与上述系统已定义的节相同的名字,也不要取以点号开头的名字,以避免潜在的冲突。注意,目标文件中节的名字并不具有唯一性,可以存在多个相同名字的节。