资讯专栏INFORMATION COLUMN

ELF格式探析之三:sections

FleyX / 907人阅读

摘要:,重定位节,包含入口,参见。如果是重定位节或,指向相应符号表的。如果是和类型的重定位节,是应用的节的节头索引。包含程序运行时未初始化的数据全局变量和静态变量。

前文链接:

ELF格式探析之一:Segment和Section

ELF格式探析之二:文件头ELF Header详解

今天我们讲对目标文件(可重定位文件)和可执行文件都很重要的section。

我们在讲ELF Header的时候,讲到了section header table。它是一个section header的集合,每个section header是一个描述section的结构体。在同一个ELF文件中,每个section header大小是相同的。(其实看了源码就知道,32位ELF文件中的section header都是一样的大小,64位ELF文件中的section header也是一样的大小)

每个section都有一个section header描述它,但是一个section header可能在文件中没有对应的section,因为有的section是不占用文件空间的。每个section在文件中是连续的字节序列。section之间不会有重叠。

一个目标文件中可能有未覆盖到的空间,比如各种header和section都没有覆盖到。这部分字节的内容是未指定的,也是没有意义的。

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;

下面我们依次讲解结构体各个字段:

sh_name,4字节,是一个索引值,在shstrtable(section header string table,包含section name的字符串表,也是一个section)中的索引。第二讲介绍ELF文件头时,里面专门有一个字段e_shstrndx,其含义就是shstrtable对应的section header在section header table中的索引。

sh_type,4字节,描述了section的类型,常见的取值如下:

SHT_NULL 0,表明section header无效,没有关联的section。

SHT_PROGBITS 1,section包含了程序需要的数据,格式和含义由程序解释。

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 7,note 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,指定了为应用程序保留的索引的下界和上界,这个范围内的索引可以被应用程序使用。

sh_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位),为处理器相关的语义使用。

sh_addr, 对32位来说是4字节,64位是8字节。如果section会出现在进程的内存映像中,给出了section第一字节的虚拟地址。

sh_offset,对于32位来说是4字节,64位是8字节。section相对于文件头的字节偏移。对于不占文件空间的section(比如SHT_NOBITS),它的sh_offset只是给出了section逻辑上的位置。

sh_size,section占多少字节,对于SHT_NOBITS类型的section,sh_size没用,其值可能不为0,但它也不占文件空间。

sh_link,含有一个section header的index,该值的解释依赖于section type。

如果是SHT_DYNAMICsh_link是string table的section header index,也就是说指向字符串表。

如果是SHT_HASHsh_link指向symbol table的section header index,hash table应用于symbol table。

如果是重定位节SHT_RELSHT_RELAsh_link指向相应符号表的section header index。

如果是SHT_SYMTABSHT_DYNSYMsh_link指向相关联的符号表,暂时不解。

对于其它的section type,sh_link的值是SHN_UNDEF

sh_info,存放额外的信息,值的解释依赖于section type。

如果是SHT_RELSHT_RELA类型的重定位节,sh_info是应用relocation的节的节头索引。

如果是SHT_SYMTABSHT_DYNSYMsh_info是第一个non-local符号在符号表中的索引。推测local symbol在前面,non-local symbols紧跟在后面,所以文档中也说,sh_info是最后一个本地符号的在符号表中的索引加1。

对于其它类型的section,sh_info是0。

sh_addralign,地址对齐,如果一个section有一个doubleword字段,系统在载入section时的内存地址必须是doubleword对齐。也就是说sh_addr必须是sh_addralign的整数倍。只有2的正整数幂是有效的。0和1说明没有对齐约束。

sh_entsize,有些section包含固定大小的记录,比如符号表。这个值给出了每个记录大小。对于不包含固定大小记录的section,这个值是0。

系统预定义的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和处理器有关。

.dynstr,SHT_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

.init,SHT_PROGBITS,程序运行时,先执行该节中的代码。SHF_ALLOC + SHF_EXECINSTR,和.fini对应。现在ELF还包含.init_array section。

.interp,SHT_PROGBITS,该节内容是一个字符串,指定了程序解释器的路径名。如果文件中有一个可加载的segment包含该节,属性就包含SHF_ALLOC,否则不包含。

.line,SHT_PROGBITS,包含符号调试的行号信息,描述了源程序和机器代码的对应关系。gdb等调试器需要此信息。

.note Note Section, 类型SHT_NOTE,以后多带带讲。

.plt 过程链接表(Procedure Linkage Table),类型SHT_PROGBITS,以后重点讲。

.relNAME,类型SHT_REL, 包含重定位信息。如果文件有一个可加载的segment包含该section,section属性将包含SHF_ALLOC,否则不包含。NAME,是应用重定位的节的名字,比如.text的重定位信息存储在.rel.text中。

.relaname 类型SHT_RELA,和.rel相同。SHT_RELASHT_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。字符串以结束, section以开始,也以结束。一个.strtab可以是空的,它的sh_size将是0。针对空字符串表的非0索引是允许的。

symtab,类型SHT_SYMTAB,Symbol Table,符号表。包含了定位、重定位符号定义和引用时需要的信息。符号表是一个数组,Index 0 第一个入口,它的含义是undefined symbol index, STN_UNDEF。如果文件有一个可加载的segment包含该section,属性将包含SHF_ALLOC

练习:读取section names

从这一讲开始,都会有练习,方便我们把前面的理论知识综合运用。

下面这个练习的目标是:从一个ELF文件中读取存储section name的字符串表。前面讲过,该字符串表也是一个section,section 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 
#include 
#include 

int 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)
", 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
", 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
", 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
", 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
", 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
", 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,就打印`` */
        if (buf[i] == 0)
            printf("");
        else
            printf("%c", buf[i]);
    }
    printf("
");
    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
.symtab.strtab.shstrtab.interp.note.ABI-tag.note.gnu.build-id.gnu.hash.dynsym.dynstr.gnu.version.gnu.version_r.rela.dyn.rela.plt.init.text.fini.rodata.eh_frame_hdr.eh_frame.init_array.fini_array.dynamic.got.got.plt.data.bss.comment

可以发现,节头字符串表以开始,以结束。如果一个section的name字段指向0,则他指向的字节值是0,则它没有名称,或名称是空。

总结

本章主要讲解了section header的定义,各字段含义和可能的取值。然后介绍了系统预定义的一些section名称。最后我们综合运用第二章和第三章的知识,做了一个读取section names的练习。

下一章我们将讲述符号表和重定位的原理。此系列文章也会在微信公众号“欢欣之翼”上同步更新,欢迎关注。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/33728.html

相关文章

  • ELF文件格式分析

    摘要:注意尽管图中显示的各个组成部分是有顺序的,实际上除了头部表以外,其他节区和段都没有规定的顺序目标文件中数据表示目标文件格式支持位字节位体系结构。目标文件格式允许人们定义不在上述列表中的节区。 NDK开发最后生成的so文件是基于ELF文件格式。而so是共享目标文件,所以要想对so文件进行加密就必须了解ELF文件格式。 原文链接:ELF文件格式分析 0x00 简介 可执行链接格式(Exec...

    molyzzx 评论0 收藏0
  • ELF文件格式分析

    摘要:注意尽管图中显示的各个组成部分是有顺序的,实际上除了头部表以外,其他节区和段都没有规定的顺序目标文件中数据表示目标文件格式支持位字节位体系结构。目标文件格式允许人们定义不在上述列表中的节区。 NDK开发最后生成的so文件是基于ELF文件格式。而so是共享目标文件,所以要想对so文件进行加密就必须了解ELF文件格式。 原文链接:ELF文件格式分析 0x00 简介 可执行链接格式(Exec...

    hss01248 评论0 收藏0
  • 震惊!这个操作系统的应用加载只需要“毫秒级”耗时

    摘要:本文主要介绍应用程序加载如何做到毫秒级耗时。图不同视角看左边是从汇编器和链接器的视角来看这个文件,开头的描述了体系结构和操作系统等基本信息,并指出和在文件中的什么位置,在汇编和链接过程中没有用到,所以是可有可的,中保存了所有的描述信息。 1、背景 AliOS Thing 是AliOS家族旗...

    incredible 评论0 收藏0
  • 重学计算机组成原理(七)- 程序无法同时在Linux和Windows下运行?

    摘要:链接器会扫描所有输入的目标文件,然后把所有符号表里的信息收集起来,构成一个全局的符号表。这是一本难得的讲解程序的链接装载和运行的好书。 showImg(https://image-static.segmentfault.com/396/693/396693929-5d558865c3a7e_articlex); 既然程序最终都被变成了一条条机器码去执行,那为什么同一个程序,在同一台计算...

    ShowerSun 评论0 收藏0
  • ELF文件解析(一):Segment和Section

    摘要:对我本机上的一个目标代码文件执行,输出如下是显示文件中的信息,中共有个我们省略了其中一些的信息。和的的都是,表示可写,需分配内存,这都是数据段的特征。最后一个段,索引,标志可读写,它包含的节是,可以看到和都包含其中,这段是数据段无疑。 ELF 是Executable and Linking Format的缩写,即可执行和可链接的格式,是Unix/Linux系统ABI (Applicat...

    dreamtecher 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<