美文网首页计算机基础
程序的链接(二):ELF目标文件

程序的链接(二):ELF目标文件

作者: N8_xEnn7nA1 | 来源:发表于2018-07-06 05:18 被阅读0次

    在学习链接的具体过程前,有必要好好了解一下ELF目标文件。

    ELF的目标文件分为三类:

    • 可重定位目标文件(.o)
    • 其代码和数据可和其他可重定位文件合并为可执行文件
    • 每个 .o 文件由对应的 .c 文件生成
    • 每个 .o 文件的代码和数据地址都是从0开始的偏移
    • 可执行目标文件(默认为a.out)
    • 包含的代码和数据可以被直接复制到内存并执行
    • 代码和数据的地址是虚拟地址空间中的地址
    • 共享的目标文件(.so 共享库)
    • 特殊的可重定位目标文件,能在装载到内存或运行时自动被链接,称为共享库文件

    可通过objdump命令对比可重定位目标文件和可执行目标文件的不同:

    image.png

    可以看到,确实可重定位目标文件中的地址是从0开始的,而可执行目标文件中的地址是虚拟地址空间中的地址。


    接下来介绍ELF文件的两种视图:

    • 链接视图:可重定位文件(Relocatable object files)
    • 执行视图:可执行目标文件(Executable object files)


      image.png

    链接视图 —— 可重定位目标文件

    来看一个简单的C代码及其所生成的可重定位目标文件的关系图:


    image.png

    如上图,编译后的代码部分放到 .text节,已初始化的全局变量和已初始化的静态变量会放到 .data节,未初始化的全局变量和未初始化的静态变量会放到 .bss节。
    实际上,为了进行链接,可重定位目标文件还需要许多其他信息,如符号表、重定位信息等。这些后面会陆续介绍。

    在这里要特别说明一下 .bss节。该节在可重定位目标文件中并不占用空间,只是在节头表相应的表项中说明要为 .bss节预留多大的空间。

    可重定位目标文件中包含有很多的节,格式如下图:


    image.png

    其中:

    • ELF头

    包括16字节的标识信息、文件类型(.o,exec,.so)、机器类型(如Intel 80386)、节头表的偏移、节头表的表项大小及表项个数。

    • .text节

    编译后的代码部分。

    • .rodata节

    只读数据,如 printf用到的格式串、switch跳转表等。

    • .data节

    已初始化的全局变量和静态变量。

    • .bss节

    未初始化全局变量和静态变量,仅是占位符,不占据任何磁盘空间。区分初始化和非初始化是为了空间效率。

    • .symtab节

    存放函数和全局变量(符号表)的信息,它不包括局部变量。

    • .rel.text节

    .text节的重定位信息,用于重新修改代码段的指令中的地址信息。

    • .rel.data节

    .data节的重定位信息,用于对被模块使用或定义的全局变量进行重定位的信息。

    • .debug节

    调试用的符号表(gcc -g)

    • .strtab节

    包含 .symtab节和 .debug节中的符号及节名

    • 节头表(Section header table)

    包含每个节的节名在.strtab节中的偏移、节的偏移和节的大小.

    下边分别举例讲一下ELF头节头表

    -------------------------------------- ELF头 (ELF Header)------------------------------
    ELF头位于ELF文件的开始,其包含了文件结构的说明信息。其结构体定义如下:

    #define EI_NIDENT 16
    
    typedef struct {
        unsigned char e_ident[EI_NIDENT];              
        uint16_t      e_type;
        uint16_t      e_machine;
        uint32_t      e_version;
        ElfN_Addr    e_entry;
        ElfN_Off      e_phoff;
        ElfN_Off      e_shoff;
        uint32_t      e_flags;
        uint16_t      e_ehsize;
        uint16_t      e_phentsize;
        uint16_t      e_phnum;
        uint16_t      e_shentsize;
        uint16_t      e_shnum;
        uint16_t      e_shstrndx;
    } ElfN_Ehdr;
    

    用readelf -h 查看ELF头。下表是ELF头中各个成员的含义与readelf输出结果的对照表:

    成员 readelf输出结果及含义
    e_ident Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
    Class: ELF32
    Data: 2's complement, little endian
    Version: 1 (current)
    OS/ABI: UNIX - System V
    ABI Version: 0
    e_type Type: REL(Relocatable file)
    ELF文件类型
    e_machine Machine: Intel 80386
    ELF文件的CPU平台属性,相关常量以EM_开头
    e_version Version: 0x1
    ELF版本号。一般为常数1
    e_entry Entry point address: 0x0
    入口地址,规定ELF程序的入口虚拟地址,操作系统在加载完该程序后从这个地址开始执行进程的指令。可重定位目标文件一般没有入口地址,则这个值为0
    e_phoff Start of program header: 0 (bytes into file)
    程序头表在文件中的偏移,可重定位目标文件不存在程序头表,故该值为0
    e_shoff Start of section header: 280(bytes into file)
    节头表在文件中的偏移
    e_flags Flags: 0x0
    ELF标志位,用来标识一些ELF文件平台相关的属性,相关常量的格式一般为EF_machine_flag,machine为平台,flag为标志
    e_ehsize Size of this header: 52(bytes)
    即ELF文件头本身的大小
    e_phentsize Size of program headers: 0(bytes)
    程序头表表项的大小
    e_phnum Number of program headers: 0
    程序头表表项的数目
    e_shentsize Size of section headers: 40(bytes)
    节表表项的大小
    e_shnum Number of section headers: 11
    节表表项的数目
    e_shstrndx Section header string table index: 8
    节表字符串表在节头表中的下标

    -------------------------------------- 节头表(Section Header Table) ----------------------
    除了ELF头之外,节头表是ELF可重定位目标文件中最重要的部分内容。
    它描述了每个节的节名、在文件中的偏移、大小、访问属性、对齐方式等。其32位结构定义如下(每项占40字节):

    typedef struct {
        Elf32_Word sh_name;   //节名字符串在.strtab中的偏移
        Elf32_Word sh_type;   //节类型:无效/代码或数据/符号/字符串/…
        Elf32_Word sh_flags;  //节标志:该节在虚拟空间中的访问属性
        Elf32_Addr sh_addr;   //虚拟地址:若可被加载,则对应虚拟地址
        Elf32_Off sh_offset;  //在文件中的偏移地址,对.bss节而言则无意义
        Elf32_Word sh_size;   //节在文件中所占的长度
        Elf32_Word sh_link;   //sh_link和sh_info用于与链接相关的节(如
        Elf32_Word sh_info;   //     .rel.text节、.rel.data节、.symtab节等)   
        Elf32_Word sh_addralign; //节的对齐要求
        Elf32_Word sh_entsize;   //节中每个表项的长度,0表示无固定长度表项
    } Elf32_Shdr;
    

    使用 readelf -S命令查看节头表,示例如下:


    image.png
    image.png

    A(alloc)标志表示该节将进程空间中必须要分配空间。可以看到,.text、.data、.bss、.rodata节都有这个标志。

    执行视图 —— 可执行目标文件

    再来看ELF的执行视图,也就是可执行目标文件的格式:


    image.png

    它与可重定位目标文件稍有不同:

    1. ELF文件头中的字段e_entry给出了执行程序时第一条指令的地址,而在可重定位目标文件中,此字段为0;程序头表的偏移e_poff和大小e_phentsiz和程序头表项的个数e_phnum不为0;
    2. 多了一个程序头表,也称为段头表(segment header table),是一个结构体数组;
    3. 多了一个 .init节,用于定义 _init函数,该函数用于在可执行目标文件开始执行时的初始化工作。
    4. 少了两个 .rel节(.rel.text和.rel.data),因为可执行目标文件已经在链接的过程中完成了重定位,已无须重定位。

    使用readelf -h 来看可执行目标文件的ELF头,示例如下


    image.png

    程序头表描述的可执行文件中的节(section)与虚拟地址空间中的存储段(segment)之间的映射关系。

    程序头表的结构定义如下:

    typedef struct {
        Elf32_Word p_type;      //段类型
        Elf32_Off p_offset;        //该段在文件中的起始偏移
        Elf32_Addr p_vaddr;     //该段的虚拟地址
        Elf32_Addr p_paddr;     //该段的物理地址
        Elf32_Word p_filesz;     //该段在文件中的大小
        Elf32_Word p_memsz;  //该段在内存中的大小
        Elf32_Word p_flags;     //段的标志位,表示访问权限(Read|Write|Exec)
        Elf32_Word p_align;     //段在内存中的对齐要求
    } Elf32_Phdr;
    

    下图是用readelf -l 查看到的某可执行目标文件的程序头表:


    image.png

    Type为Load表示可装入内存的段。
    第一个可装入段:第0x00000~0x004d3字节(包括ELF头、程序头表、.init节、.text节和.rodata节),映射到虚拟地址0x8048000开始长度为0x4d4字节的区域,按0x1000=4KB对齐,具有只读/执行权限(Flg=RE),是只读代码段。
    第二个可装入段:第0x000f0c~开始长度为0x108字节的 .data节,映射到虚拟地址0x8049f0c开始长度为0x110字节的存储区域,在0x110=272B存储区中,前0x108=264B用 .data节内容初始化,后面272-264=8B对应.bss节,初始化为0,按0x1000=64K对齐,具有可读可写权限(Flg=RW),是可读写数据段。

    映射到虚拟地址空间中如图:


    image.png

    相关文章

      网友评论

        本文标题:程序的链接(二):ELF目标文件

        本文链接:https://www.haomeiwen.com/subject/igemuftx.html