节概述
在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 | |
---|---|---|---|---|---|---|---|---|
值 | 0 | 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 | 0 | 此值表明本节头是个无效的节头,它也没有对应的节。 |
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 | 本节中项目的字符串表在节头表中相应的索引值 | 0 |
SHT_HASH | 本节中哈希表的符号表在节头表中相应的索引值 | 0 |
SHT_REL /SHT_RELA | 相应符号表在节头表中的索引值 | 本重定位节所应用到目标节在节头表中的索引值 |
SHT_SYMTAB/SHT_DYNSYM | 相关字符串表的节头索引 | 符号表中最后一个本地符号的索引值加 1 |
其它 | SHN_UNDEF | 0 |
sh_addralign
此成员指明本节内容如何对齐字节,即该节的地址应该向多少个字节对齐。也就是说,本节内容在进程空间中的映射地址 sh_addr 必须是一个向sh_addralign 对齐,即能被 sh_addralign 整除的值。目前,sh_addralign 只能取 0、1或者 2 的正整数倍。如果该值为 0 或 1,表明本节没有字节对齐约束。
sh_entsize
有一些节的内容是一张表,其中每一个表项的大小是固定的,比如符号表。对于这种表来说,本成员指定其每一个表项的大小。如果此值为 0 则表明本节内容不是这种表格。
索引值0节头项内容
下面说一下索引值为 0 的节头表项,它并不表达实际的内容,只是一个空的表项,内容见下表。
名字 | 值 | 意义 | 名字 | 值 | 意义 |
sh_name | 0 | 无名字 | sh_size | 0 | 无大小 |
sh_type | SHT_NULL | 非活动类型 | sh_link | SHN_UNDEF | 无节头表对应项 |
sh_flags | 0 | 无标志 | sh_info | 0 | 无附加信息 |
sh_addr | 0 | 无地址 | sh_addralign | 0 | 无对齐格式 |
sh_offset | 0 | 无文件内偏移量 | sh_entsize | 0 | 无表项大小 |
特殊节
在 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 | 本节包含程序指令代码。 |
以点号”.”为前缀的节名字是为系统保留的。应用程序也可以构造自己的段,但最好不要取与上述系统已定义的节相同的名字,也不要取以点号开头的名字,以避免潜在的冲突。注意,目标文件中节的名字并不具有唯一性,可以存在多个相同名字的节。