厦门自己建网站,网站主题 模板,公司平台建设怎么写,域名跳转wordpresssection header table 是一个section header的集合#xff0c;每个section header是一个描述section的结构体。在同一个ELF文件中#xff0c;每个section header大小是相同的。
每个section都有一个section header描述它#xff0c;但是一个section header可能在文件中没有…section header table 是一个section header的集合每个section header是一个描述section的结构体。在同一个ELF文件中每个section header大小是相同的。
每个section都有一个section header描述它但是一个section header可能在文件中没有对应的section因为有的section是不占用文件空间的。每个section在文件中是连续的字节序列。section之间不会有重叠。
一个目标文件中可能有未覆盖到的空间比如各种header和section都没有覆盖到。这部分字节的内容是未指定的也是没有意义的。
1. section header定义
section header结构体的定义可以在 /usr/include/elf.h 中找到。
/* Section header. */typedef struct
{Elf32_Word sh_name; /* Section name (string tbl index) */Elf32_Word sh_type; /* Section type */Elf32_Word sh_flags; /* Section flags */Elf32_Addr sh_addr; /* Section virtual addr at execution */Elf32_Off sh_offset; /* Section file offset */Elf32_Word sh_size; /* Section size in bytes */Elf32_Word sh_link; /* Link to another section */Elf32_Word sh_info; /* Additional section information */Elf32_Word sh_addralign; /* Section alignment */Elf32_Word sh_entsize; /* Entry size if section holds table */
} Elf32_Shdr;typedef struct
{Elf64_Word sh_name; /* Section name (string tbl index) */Elf64_Word sh_type; /* Section type */Elf64_Xword sh_flags; /* Section flags */Elf64_Addr sh_addr; /* Section virtual addr at execution */Elf64_Off sh_offset; /* Section file offset */Elf64_Xword sh_size; /* Section size in bytes */Elf64_Word sh_link; /* Link to another section */Elf64_Word sh_info; /* Additional section information */Elf64_Xword sh_addralign; /* Section alignment */Elf64_Xword sh_entsize; /* Entry size if section holds table */
} Elf64_Shdr;下面我们依次讲解结构体各个字段 1sh_name4字节是一个索引值在shstrtablesection header string table包含section name的字符串表也是一个section中的索引。第二讲介绍ELF文件头时里面专门有一个字段e_shstrndx其含义就是shstrtable对应的section header在section header table中的索引。
2sh_type4字节描述了section的类型常见的取值如下
SHT_NULL 0表明section header无效没有关联的section。SHT_PROGBITS 1section包含了程序需要的数据格式和含义由程序解释。SHT_SYMTAB 2 包含了一个符号表。当前一个ELF文件中只有一个符号表。SHT_SYMTAB提供了用于(link editor)链接编辑的符号当然这些符号也可能用于动态链接。这是一个完全的符号表它包含许多符号。SHT_STRTAB 3包含一个字符串表。一个对象文件包含多个字符串表比如.strtab包含符号的名字和.shstrtab包含section的名称。SHT_RELA 4重定位节包含relocation入口参见Elf32_Rela。一个文件可能有多个Relocation Section。比如.rela.text.rela.dyn。SHT_HASH 5这样的section包含一个符号hash表参与动态连接的目标代码文件必须有一个hash表。目前一个ELF文件中只包含一个hash表。讲链接的时候再细讲。SHT_DYNAMIC 6包含动态链接的信息。目前一个ELF文件只有一个DYNAMIC section。SHT_NOTE 7note section, 以某种方式标记文件的信息以后细讲。SHT_NOBITS 8这种section不含字节也不占用文件空间section header中的sh_offset字段只是概念上的偏移。SHT_REL 9 重定位节包含重定位条目。和SHT_RELA基本相同两者的区别在后面讲重定位的时候再细讲。SHT_SHLIB 10保留语义未指定包含这种类型的section的elf文件不符合ABI。SHT_DYNSYM 11 用于动态连接的符号表推测是symbol table的子集。SHT_LOPROC 0x70000000 到 SHT_HIPROC 0x7fffffff为特定于处理器的语义保留。SHT_LOUSER 0x80000000 and SHT_HIUSER 0xffffffff指定了为应用程序保留的索引的下界和上界这个范围内的索引可以被应用程序使用。
3sh_flags, 32位占4字节 64位占8字节。包含位标志用 readelf -S 可以看到很多标志。常用的有
SHF_WRITE 0x1进程执行的时候section内的数据可写。SHF_ALLOC 0x2进程执行的时候section需要占据内存。SHF_EXECINSTR 0x4节内包含可以执行的机器指令。SHF_STRINGS 0x20包含0结尾的字符串。SHF_MASKOS 0x0ff00000这个mask为OS特定的语义保留8位。SHF_MASKPROC 0xf0000000这个mask包含的所有位保留也就是最高字节的高4位为处理器相关的语义使用。
4sh_addr, 对32位来说是4字节64位是8字节。如果section会出现在进程的内存映像中给出了section第一字节的虚拟地址。
5sh_offset对于32位来说是4字节64位是8字节。section相对于文件头的字节偏移。对于不占文件空间的section比如SHT_NOBITS它的sh_offset只是给出了section逻辑上的位置。
6sh_sizesection占多少字节对于SHT_NOBITS类型的sectionsh_size没用其值可能不为0但它也不占文件空间。
7sh_link含有一个section header的index该值的解释依赖于section type。
如果是SHT_DYNAMICsh_link是string table的section header index也就是说指向字符串表。如果是SHT_HASHsh_link指向symbol table的section header indexhash table应用于symbol table。如果是重定位节SHT_REL或SHT_RELAsh_link指向相应符号表的section header index。如果是SHT_SYMTAB或SHT_DYNSYMsh_link指向相关联的符号表暂时不解。对于其它的section typesh_link的值是SHN_UNDEF
8sh_info存放额外的信息值的解释依赖于section type。
如果是SHT_REL和SHT_RELA类型的重定位节sh_info是应用relocation的节的节头索引。如果是SHT_SYMTAB和SHT_DYNSYMsh_info是第一个non-local符号在符号表中的索引。推测local symbol在前面non-local symbols紧跟在后面所以文档中也说sh_info是最后一个本地符号的在符号表中的索引加1。对于其它类型的sectionsh_info是0。
9sh_addralign地址对齐如果一个section有一个doubleword字段系统在载入section时的内存地址必须是doubleword对齐。也就是说sh_addr必须是sh_addralign的整数倍。只有2的正整数幂是有效的。0和1说明没有对齐约束。
10sh_entsize有些section包含固定大小的记录比如符号表。这个值给出了每个记录大小。对于不包含固定大小记录的section这个值是0。
2. 系统预定义的section name
系统预定义了一些节名以.开头这些节有其特定的类型和含义。
.bss包含程序运行时未初始化的数据全局变量和静态变量。当程序运行时这些数据初始化为0。 其类型为SHT_NOBITS表示不占文件空间。SHF_ALLOC SHF_WRITE运行时要占用内存的。.comment 包含版本控制信息是否包含程序的注释信息不包含注释在预处理时已经被删除了。类型为SHT_PROGBITS。.data和.data1包含初始化的全局变量和静态变量。 类型为SHT_PROGBITS标志为SHF_ALLOC SHF_WRITE占用内存可写。.debug包含了符号调试用的信息我们要想用gdb等工具调试程序需要该类型信息类型为SHT_PROGBITS。.dynamic类型SHT_DYNAMIC包含了动态链接的信息。标志SHF_ALLOC是否包含SHF_WRITE和处理器有关。.dynstrSHT_STRTAB包含了动态链接用的字符串通常是和符号表中的符号关联的字符串。标志 SHF_ALLOC.dynsym类型SHT_DYNSYM包含动态链接符号表 标志SHF_ALLOC。.fini类型SHT_PROGBITS程序正常结束时要执行该section中的指令。标志SHF_ALLOC SHF_EXECINSTR占用内存可执行。现在ELF还包含.fini_array section。.got类型SHT_PROGBITS全局偏移表(global offset table)以后会重点讲。.hash类型SHT_HASH包含符号hash表以后细讲。标志SHF_ALLOC。.initSHT_PROGBITS程序运行时先执行该节中的代码。SHF_ALLOC SHF_EXECINSTR和.fini对应。现在ELF还包含.init_array section。.interpSHT_PROGBITS该节内容是一个字符串指定了程序解释器的路径名。如果文件中有一个可加载的segment包含该节属性就包含SHF_ALLOC否则不包含。.lineSHT_PROGBITS包含符号调试的行号信息描述了源程序和机器代码的对应关系。gdb等调试器需要此信息。.note Note Section, 类型SHT_NOTE以后单独讲。.plt 过程链接表Procedure Linkage Table类型SHT_PROGBITS,以后重点讲。.relNAME类型SHT_REL, 包含重定位信息。如果文件有一个可加载的segment包含该sectionsection属性将包含SHF_ALLOC否则不包含。NAME是应用重定位的节的名字比如.text的重定位信息存储在.rel.text中。.relaname 类型SHT_RELA和.rel相同。SHT_RELA和SHT_REL的区别会在讲重定位的时候说明。.rodata和.rodata1。类型SHT_PROGBITS, 包含只读数据组成不可写的段。标志SHF_ALLOC。.shstrtab类型SHT_STRTAB包含section的名字。有读者可能会问section header中不是已经包含名字了吗为什么把名字集中存放在这里 sh_name 包含的是.shstrtab 中的索引真正的字符串存储在.shstrtab中。那么section names为什么要集中存储我想是这样如果有相同的字符串就可以共用一块存储空间。如果字符串存在包含关系也可以共用一块存储空间。.strtab SHT_STRTAB包含字符串通常是符号表中符号对应的变量名字。如果文件有一个可加载的segment包含该section属性将包含SHF_ALLOC。字符串以\0结束 section以\0开始也以\0结束。一个.strtab可以是空的它的sh_size将是0。针对空字符串表的非0索引是允许的。symtab类型SHT_SYMTABSymbol Table符号表。包含了定位、重定位符号定义和引用时需要的信息。符号表是一个数组Index 0 第一个入口它的含义是undefined symbol index STN_UNDEF。如果文件有一个可加载的segment包含该section属性将包含SHF_ALLOC。
3. 练习读取section names
示例从一个ELF文件中读取存储section name的字符串表。前面讲过该字符串表也是一个sectionsection header table中有其对应的section header并且ELF文件头中给出了节名字符串表对应的section header的索引e_shstrndx。
我们的思路是这样
从ELF header中读取section header table的起始位置每个section header的大小以及节名字符串表对应section header的索引。 计算section_header_table_offset section_header_size * e_shstrndx 就是节名字符串表对应section header的偏移。 读取section header可以从中得到节名字符串表在文件中的偏移和大小。 把节名字符串表读取到内存中打印其内容。 代码如下
/* 64位ELF文件读取section name string table */
#include stdio.h
#include stdlib.h
#include stdint.hint main(int argc, char *argv[])
{/* 打开本地的ELF可执行文件hello */FILE *fp fopen(./hello, rb);if(!fp) {perror(open ELF file);exit(1);}/* 1. 通过读取ELF header得到section header table的偏移 *//* for 64 bit ELF,e_ident(16) e_type(2) e_machine(2) e_version(4) e_entry(8) e_phoff(8) 40 */fseek(fp, 40, SEEK_SET);uint64_t sh_off;int r fread(sh_off, 1, 8, fp);if (r ! 8) {perror(read section header offset);exit(2);}/* 得到的这个偏移值可以用reaelf -h hello来验证是否正确 */printf(section header offset in file: %ld (0x%lx)\n, sh_off, sh_off);/* 2. 读取每个section header的大小e_shentsize,section header的数量e_shnum,以及对应section name字符串表的section header的索引e_shstrndx得到这些值后都可以用readelf -h hello来验证是否正确 *//* e_flags(4) e_ehsize(2) e_phentsize(2) e_phnum(2) 10 */fseek(fp, 10, SEEK_CUR);uint16_t sh_ent_size; /* 每个section header的大小 */r fread(sh_ent_size, 1, 2, fp);if (r ! 2) {perror(read section header entry size);exit(2);}printf(section header entry size: %d\n, sh_ent_size);uint16_t sh_num; /* section header的数量 */r fread(sh_num, 1, 2, fp);if (r ! 2) {perror(read section header number);exit(2);}printf(section header number: %d\n, sh_num);uint16_t sh_strtab_index; /* 节名字符串表对应的节头的索引 */r fread(sh_strtab_index, 1, 2, fp);if (r ! 2) {perror(read section header string table index);exit(2);}printf(section header string table index: %d\n, sh_strtab_index);/* 3. read section name string table offset, size *//* 先找到节头字符串表对应的section header的偏移位置 */fseek(fp, sh_off sh_strtab_index * sh_ent_size, SEEK_SET);/* 再从section header中找到节头字符串表的偏移 *//* sh_name(4) sh_type(4) sh_flags(8) sh_addr(8) 24 */fseek(fp, 24, SEEK_CUR);uint64_t str_table_off;r fread(str_table_off, 1, 8, fp);if (r ! 8) {perror(read section name string table offset);exit(2);}printf(section name string table offset: %ld\n, str_table_off);/* 从section header中找到节头字符串表的大小 */uint64_t str_table_size;r fread(str_table_size, 1, 8, fp);if (r ! 8) {perror(read section name string table size);exit(2);}printf(section name string table size: %ld\n, str_table_size);/* 动态分配内存把节头字符串表读到内存中 */char *buf (char *)malloc(str_table_size);if(!buf) {perror(allocate memory for section name string table);exit(3);}fseek(fp, str_table_off, SEEK_SET);r fread(buf, 1, str_table_size, fp);if(r ! str_table_size) {perror(read section name string table);free(buf);exit(2);}uint16_t i;for(i 0; i str_table_size; i) {/* 如果节头字符串表中的字节是0就打印\0 */if (buf[i] 0)printf(\\0);elseprintf(%c, buf[i]);}printf(\n);free(buf);fclose(fp);return 0;
}把以上代码存为chap3_read_section_names.c执行gcc -Wall -o secnames chap3_read_section_names.c进行编译输出的执行文件名叫secnames。执行secnames输出如下
./secnames
section header offset in file: 14768 (0x39b0)
section header entry size: 64
section header number: 29
section header string table index: 28
section name string table offset: 14502
section name string table size: 259
\0.symtab\0.strtab\0.shstrtab\0.interp\0.note.ABI-tag\0.note.gnu.build-id\0.gnu.hash\0.dynsym\0.dynstr\0.gnu.version\0.gnu.version_r\0.rela.dyn\0.rela.plt\0.init\0.text\0.fini\0.rodata\0.eh_frame_hdr\0.eh_frame\0.init_array\0.fini_array\0.dynamic\0.got\0.got.plt\0.data\0.bss\0.comment\0可以发现节头字符串表以\0开始以\0结束。如果一个section的name字段指向0则他指向的字节值是0则它没有名称或名称是空。