精华内容
下载资源
问答
  • elf文件
    千次阅读
    2022-01-17 15:54:30

    ELF文件解析

    ELF(Executable and Linkable Format)是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件的文件格式。

    本文我们来解析一些ELF文件的具体格式信息。

    1. 简介

    1.1 分类

    ELF文件有四种类型:

    1. 重定位文件(ET_REL),也就是常称的目标文件,包含适合于与其他目标文件链接来创建可执行文件或者共享目标文件的代码和数据。

    2. 可执行文件(ET_EXEC),包含适合于执行的一个程序,此文件规定了exec() 如何创建一个程序的进程映像。

    3. 共享目标文件(ET_DYN),即共享对象文件、动态库文件, 包含可在两种上下文中链接的代码和数据。

      • 首先链接编辑器可以将它和其它可重定位文件和共享目标文件一起处理, 生成另外一个目标文件。
      • 其次动态链接器可能将它与某 个可执行文件以及其它共享目标一起组合,创建进程映像。
    4. 核心转储文件(ET_CORE),包括程序运行的内存数据和代码。

    除此之外还会有类型不确定的ELF文件(ET_NONE),即未知文件。

    1.2 作用

    ELF文件参与了二进制文件的两个过程:

    1. 链接。
    2. 执行。

    所以可以从不同的角度来看待ELF格式的文件:

    1. 如果用于编译和链接(可重定位文件),则编译器和链接器将把ELF文件看作是节头表描述的节的集合(Section),程序头表可选

    2. 如果用于加载执行(可执行文件),则加载器则将把ELF文件看作是程序头表描述的段的集合(Segment),一个段可能包含多个节,节头表可选

    3. 如果是共享文件,则两者都含有。

    所以ELF文件的大致结构可以视为如下:

    链接视图执行视图
    ELF文件头部ELF文件头部
    程序头部表(可选)程序头部表
    Section 1Segment 1
    Section ...
    Section NSegment 2
    Section ...
    Section ...Segment ...
    节区头部表节区头部表(可选)

    对于程序头部表和节区头部表来说:

    1. 程序头部表(Program Header Table) : 如果存在的话,告诉系统如何创建进程映像。
    2. 节区头部表(Section Header Table) :如果存在的话,包含了描述文件节区的信息,比如大小、偏移等。

    如下我们可以看到Section和Segment的对应关系(Section to Segment mapping):

    $ readelf -l hello
    
    Elf file type is DYN (Shared object file)
    Entry point 0x1060
    There are 13 program headers, starting at offset 64
    
    Program Headers:
      Type           Offset             VirtAddr           PhysAddr
                     FileSiz            MemSiz              Flags  Align
      PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040
                     0x00000000000002d8 0x00000000000002d8  R      0x8
      INTERP         0x0000000000000318 0x0000000000000318 0x0000000000000318
                     0x000000000000001c 0x000000000000001c  R      0x1
          [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
      LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                     0x00000000000005f8 0x00000000000005f8  R      0x1000
      LOAD           0x0000000000001000 0x0000000000001000 0x0000000000001000
                     0x00000000000001f5 0x00000000000001f5  R E    0x1000
      LOAD           0x0000000000002000 0x0000000000002000 0x0000000000002000
                     0x0000000000000160 0x0000000000000160  R      0x1000
      LOAD           0x0000000000002db8 0x0000000000003db8 0x0000000000003db8
                     0x0000000000000258 0x0000000000000260  RW     0x1000
      DYNAMIC        0x0000000000002dc8 0x0000000000003dc8 0x0000000000003dc8
                     0x00000000000001f0 0x00000000000001f0  RW     0x8
      NOTE           0x0000000000000338 0x0000000000000338 0x0000000000000338
                     0x0000000000000020 0x0000000000000020  R      0x8
      NOTE           0x0000000000000358 0x0000000000000358 0x0000000000000358
                     0x0000000000000044 0x0000000000000044  R      0x4
      GNU_PROPERTY   0x0000000000000338 0x0000000000000338 0x0000000000000338
                     0x0000000000000020 0x0000000000000020  R      0x8
      GNU_EH_FRAME   0x0000000000002014 0x0000000000002014 0x0000000000002014
                     0x0000000000000044 0x0000000000000044  R      0x4
      GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                     0x0000000000000000 0x0000000000000000  RW     0x10
      GNU_RELRO      0x0000000000002db8 0x0000000000003db8 0x0000000000003db8
                     0x0000000000000248 0x0000000000000248  R      0x1
    
     Section to Segment mapping:
      Segment Sections...
       00     
       01     .interp 
       02     .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt 
       03     .init .plt .plt.got .plt.sec .text .fini 
       04     .rodata .eh_frame_hdr .eh_frame 
       05     .init_array .fini_array .dynamic .got .data .bss 
       06     .dynamic 
       07     .note.gnu.property 
       08     .note.gnu.build-id .note.ABI-tag 
       09     .note.gnu.property 
       10     .eh_frame_hdr 
       11     
       12     .init_array .fini_array .dynamic .got 
    

    2. 格式分析

    2.1 源码

    我们来准备一个最简单的源码:

    #include <stdio.h>
    
    int main(int argc, char* argv[])
    {
    	printf("hello world!\n");
    	return 0;
    }
    

    分别将其编译成为三种不同的文件:

    1. gcc -g -c hello.c : 中间文件,生成hello.o。
    2. gcc -g -o hello hello. : 编译成可执行文件。
    3. gcc -g -fPIC -o libhello.so -shared hello.c : 编译成共享库。

    2.2 ELF头部

    ELF文件头部定义如下:

    #define EI_NIDENT 16
    
    #define SHN_UNDEF	0
    
    typedef struct {
    	unsigned char e_ident[EI_NIDENT];
    	uint16_t      e_type;  //文件标识和类型信息(包括魔术)
    	uint16_t      e_machine;  //适用的处理器体系结构
    	uint32_t      e_version;  //目标文件的版本,EV_CURRENT
    	ElfN_Addr     e_entry;  //程序入口地址
    	ElfN_Off      e_phoff;  //程序头表(program header table)开始处在文件中的偏移量
    	ElfN_Off      e_shoff;  //节头表(section header table)开始处在文件中的偏移量
    	uint32_t      e_flags;  //处理器特定的标志位
    	uint16_t      e_ehsize;  // ELF 文件头的大小,以字节为单位
    	uint16_t      e_phentsize; //程序头表中每一个表项的大小,以字节为单位
    	uint16_t      e_phnum;  //程序头表中总共有多少个表项
    	uint16_t      e_shentsize;  //节头表中每一个表项的大小,以字节为单位
    	uint16_t      e_shnum;  //节头表中总共有多少个表项
    	uint16_t      e_shstrndx;  //节头表中与节名字表相对应的表项的索引。如果文件没有节名字表,此值应设置为 SHN_UNDEF。
    } ElfN_Ehdr;
    

    对于这个结构体的每个字段,我们可用在man(5)文档中找到详细信息,这里不再详细介绍,使用readelf -h可用查看ELF的文件头,如下:

    $ readelf -h hello
    ELF Header:
      Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
      Class:                             ELF64
      Data:                              2's complement, little endian
      Version:                           1 (current)
      OS/ABI:                            UNIX - System V
      ABI Version:                       0
      Type:                              DYN (Shared object file)
      Machine:                           Advanced Micro Devices X86-64
      Version:                           0x1
      Entry point address:               0x1060
      Start of program headers:          64 (bytes into file)
      Start of section headers:          16928 (bytes into file)
      Flags:                             0x0
      Size of this header:               64 (bytes)
      Size of program headers:           56 (bytes)
      Number of program headers:         13
      Size of section headers:           64 (bytes)
      Number of section headers:         36
      Section header string table index: 35
    

    我们使用hexdump查看原始文件内容信息如下:

    $ hexdump -C -n 512 hello
    00000000  7f 45 4c 46 02 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
    00000010  03 00 3e 00 01 00 00 00  60 10 00 00 00 00 00 00  |..>.....`.......|
    00000020  40 00 00 00 00 00 00 00  20 42 00 00 00 00 00 00  |@....... B......|
    00000030  00 00 00 00 40 00 38 00  0d 00 40 00 24 00 23 00  |....@.8...@.$.#.|
    00000040  06 00 00 00 04 00 00 00  40 00 00 00 00 00 00 00  |........@.......|
    00000050  40 00 00 00 00 00 00 00  40 00 00 00 00 00 00 00  |@.......@.......|
    00000060  d8 02 00 00 00 00 00 00  d8 02 00 00 00 00 00 00  |................|
    00000070  08 00 00 00 00 00 00 00  03 00 00 00 04 00 00 00  |................|
    00000080  18 03 00 00 00 00 00 00  18 03 00 00 00 00 00 00  |................|
    00000090  18 03 00 00 00 00 00 00  1c 00 00 00 00 00 00 00  |................|
    000000a0  1c 00 00 00 00 00 00 00  01 00 00 00 00 00 00 00  |................|
    

    2.3 Section

    ELF文件中的节是从编译器链接角度来看文件的组成的。从链接器的角度上来看,包括指令、数据、符号以及重定位表等等。

    在ELF 文件头中,有很多字段是描述Section头的:

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

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

    //SHN 大致是section indexes的简写
    
    /* special section indexes */
    #define SHN_UNDEF	0   //一个未定义的、不存在的节的索引
    #define SHN_LORESERVE	0xff00  //被保留索引号区间的下限
    #define SHN_LOPROC	0xff00 //处理器定制节所保留的索引号区间的下限
    #define SHN_HIPROC	0xff1f  //处理器定制节所保留的索引号区间的上限
    #define SHN_LIVEPATCH	0xff20
    #define SHN_ABS		0xfff1  //此节中所定义的符号有绝对的值,这个值不会因重定位而改变
    #define SHN_COMMON	0xfff2 //此节中所定义的符号是公共的
    #define SHN_HIRESERVE	0xffff //被保留索引号区间的上限
    

    在程序的编译链接过程中,编译器将一个一个.o文件链接成一个可以执行的ELF文件的过程中,同时也生成了一个表。这个表记录了各个Section所处的区域。在程序中,程序的section header有多个项,但是大小是一样,结构定义如下:

    typedef struct {
    	uint32_t   sh_name;  //名称,字符串表的索引(偏移值 .shstrtab)
    	uint32_t   sh_type;  //节类型, SHT_XXX
    	uint64_t   sh_flags;  //节的读写执行属性
    	Elf64_Addr sh_addr;  //映射地址,如果本节的内容需要映射到进程空间中去,此成员指定映射的起始地址;如果不需要映射,此值为 0。
    	Elf64_Off  sh_offset;  //偏移
    	uint64_t   sh_size;  //大小
    	uint32_t   sh_link;  //此成员是一个索引值,指向节头表中本节所对应的位置
    	uint32_t   sh_info;  //此成员含有此节的附加信息
    	uint64_t   sh_addralign;  //对齐字节
    	uint64_t   sh_entsize;  //有一些节的内容是一张表,其中每一个表项的大小是固定的,比如符号表。对于这种表来说,本成员指定其每一个表项的大小。
    } Elf64_Shdr;
    

    在这个结构体里面,sh_typesh_flags代表节的类型和属性,有如下定义

    /* sh_type */
    #define SHT_NULL	0  //一个无效的节头,它也没有对应的节
    #define SHT_PROGBITS	1  //此值表明本节所含有的信息是由程序定义的
    #define SHT_SYMTAB	2  //完整符号表
    #define SHT_STRTAB	3   //字符串表
    #define SHT_RELA	4   //重定位节, 含有带明确加数(addend)的重定位项(一般来说jmp指令需要跳过指令长度)
    #define SHT_HASH	5  //哈希表,所有参与动态连接的目标文件都必须要包含一个符号哈希表。
    #define SHT_DYNAMIC	6  //动态连接信息
    #define SHT_NOTE	7  //表明本节包含的信息用于以某种方式来标记本文件
    #define SHT_NOBITS	8  //此值表明这一节的内容是空的,节并不占用实际的空间
    #define SHT_REL		9  //无附加的重定位项
    #define SHT_SHLIB	10  //保留值
    #define SHT_DYNSYM	11  //动态链接符号表
    #define SHT_NUM		12
    #define SHT_LOPROC	0x70000000  //为特殊处理器保留的节类型索引值的下边界
    #define SHT_HIPROC	0x7fffffff  //为特殊处理器保留的节类型索引值的上边界
    #define SHT_LOUSER	0x80000000  //为应用程序保留节类型索引值的下边界
    #define SHT_HIUSER	0xffffffff  //为应用程序保留节类型索引值的下边界
    
    /* sh_flags */
    #define SHF_WRITE		0x1  //本节所包含的内容在进程运行过程中是可写的
    #define SHF_ALLOC		0x2  //表示本节内容在进程运行过程中要占用内存单元(并不是所有节都会占用实际的内存,有一些起控制作用的节,在目标文件映射到进程空间时,并不需要占用内存)
    #define SHF_EXECINSTR		0x4  //表示此节内容是指令代码
    

    此外对于Section header有两个比较特殊的成员:

    1. sh_link : 此成员是一个索引值,指向节头表中本节所对应的位置。根据节的类型不同,本成员的意义也有所不同。
    2. sh_info : 此成员含有此节的附加信息,根据节的类型不同,本成员的意义也有所不同。

    对于某些节类型来说,sh_linksh_info含有特殊的信息,见下表。

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

    Section 通过偏移和大小来确定具体信息,同样使用readelf -t我们可用查看所有section 头部数组的结构,如下:

    $ readelf -t hello
    There are 36 section headers, starting at offset 0x4220:
    
    Section Headers:
      [Nr] Name
           Type              Address          Offset            Link
           Size              EntSize          Info              Align
           Flags
      [ 0] 
           NULL             0000000000000000  0000000000000000  0
           0000000000000000 0000000000000000  0                 0
           [0000000000000000]: 
      [ 1] .interp
           PROGBITS         0000000000000318  0000000000000318  0
           000000000000001c 0000000000000000  0                 1
           [0000000000000002]: ALLOC
      [ 2] .note.gnu.property
           NOTE             0000000000000338  0000000000000338  0
           0000000000000020 0000000000000000  0                 8
           [0000000000000002]: ALLOC
    

    同样我们知道在Section header在文件中的偏移(ELF文件头部有结构),可用查看数据如下:

    $ hexdump -C -n 512 -s 0x4220  hello
    00004220  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    *
    00004260  1b 00 00 00 01 00 00 00  02 00 00 00 00 00 00 00  |................|
    00004270  18 03 00 00 00 00 00 00  18 03 00 00 00 00 00 00  |................|
    00004280  1c 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    00004290  01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    000042a0  23 00 00 00 07 00 00 00  02 00 00 00 00 00 00 00  |#...............|
    000042b0  38 03 00 00 00 00 00 00  38 03 00 00 00 00 00 00  |8.......8.......|
    000042c0  20 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  | ...............|
    000042d0  08 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    000042e0  36 00 00 00 07 00 00 00  02 00 00 00 00 00 00 00  |6...............|
    000042f0  58 03 00 00 00 00 00 00  58 03 00 00 00 00 00 00  |X.......X.......|
    00004300  24 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |$...............|
    00004310  04 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    00004320  49 00 00 00 07 00 00 00  02 00 00 00 00 00 00 00  |I...............|
    00004330  7c 03 00 00 00 00 00 00  7c 03 00 00 00 00 00 00  ||.......|.......|
    00004340  20 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  | ...............|
    00004350  04 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    00004360  57 00 00 00 f6 ff ff 6f  02 00 00 00 00 00 00 00  |W......o........|
    00004370  a0 03 00 00 00 00 00 00  a0 03 00 00 00 00 00 00  |................|
    00004380  24 00 00 00 00 00 00 00  06 00 00 00 00 00 00 00  |$...............|
    00004390  08 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    000043a0  61 00 00 00 0b 00 00 00  02 00 00 00 00 00 00 00  |a...............|
    000043b0  c8 03 00 00 00 00 00 00  c8 03 00 00 00 00 00 00  |................|
    000043c0  a8 00 00 00 00 00 00 00  07 00 00 00 01 00 00 00  |................|
    000043d0  08 00 00 00 00 00 00 00  18 00 00 00 00 00 00 00  |................|
    000043e0  69 00 00 00 03 00 00 00  02 00 00 00 00 00 00 00  |i...............|
    000043f0  70 04 00 00 00 00 00 00  70 04 00 00 00 00 00 00  |p.......p.......|
    00004400  82 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    

    因为Elf64_Shdr的大小为0x40,所以我们可用看一下几个Section:

    1. 00004260 1b 00 00 00.
    2. 000042a0 23 00 00 00.
    3. 000042e0 36 00 00 00.
    4. 00004320 49 00 00 00.

    这些值都是对应的uint32_t sh_name成员在文件中的值。因为对于section的名称,有专门的section 来记录,索引为uint16_t e_shstrndx;,因此我们可用找到这个Section的具体内容如下:

    $ hexdump -C -n 512 -s 0x40c3 hello
    000040c3  00 2e 73 79 6d 74 61 62  00 2e 73 74 72 74 61 62  |..symtab..strtab|
    000040d3  00 2e 73 68 73 74 72 74  61 62 00 2e 69 6e 74 65  |..shstrtab..inte|
    000040e3  72 70 00 2e 6e 6f 74 65  2e 67 6e 75 2e 70 72 6f  |rp..note.gnu.pro|
    000040f3  70 65 72 74 79 00 2e 6e  6f 74 65 2e 67 6e 75 2e  |perty..note.gnu.|
    00004103  62 75 69 6c 64 2d 69 64  00 2e 6e 6f 74 65 2e 41  |build-id..note.A|
    00004113  42 49 2d 74 61 67 00 2e  67 6e 75 2e 68 61 73 68  |BI-tag..gnu.hash|
    00004123  00 2e 64 79 6e 73 79 6d  00 2e 64 79 6e 73 74 72  |..dynsym..dynstr|
    00004133  00 2e 67 6e 75 2e 76 65  72 73 69 6f 6e 00 2e 67  |..gnu.version..g|
    00004143  6e 75 2e 76 65 72 73 69  6f 6e 5f 72 00 2e 72 65  |nu.version_r..re|
    00004153  6c 61 2e 64 79 6e 00 2e  72 65 6c 61 2e 70 6c 74  |la.dyn..rela.plt|
    00004163  00 2e 69 6e 69 74 00 2e  70 6c 74 2e 67 6f 74 00  |..init..plt.got.|
    00004173  2e 70 6c 74 2e 73 65 63  00 2e 74 65 78 74 00 2e  |.plt.sec..text..|
    00004183  66 69 6e 69 00 2e 72 6f  64 61 74 61 00 2e 65 68  |fini..rodata..eh|
    00004193  5f 66 72 61 6d 65 5f 68  64 72 00 2e 65 68 5f 66  |_frame_hdr..eh_f|
    000041a3  72 61 6d 65 00 2e 69 6e  69 74 5f 61 72 72 61 79  |rame..init_array|
    000041b3  00 2e 66 69 6e 69 5f 61  72 72 61 79 00 2e 64 79  |..fini_array..dy|
    000041c3  6e 61 6d 69 63 00 2e 64  61 74 61 00 2e 62 73 73  |namic..data..bss|
    000041d3  00 2e 63 6f 6d 6d 65 6e  74 00 2e 64 65 62 75 67  |..comment..debug|
    000041e3  5f 61 72 61 6e 67 65 73  00 2e 64 65 62 75 67 5f  |_aranges..debug_|
    000041f3  69 6e 66 6f 00 2e 64 65  62 75 67 5f 61 62 62 72  |info..debug_abbr|
    00004203  65 76 00 2e 64 65 62 75  67 5f 6c 69 6e 65 00 2e  |ev..debug_line..|
    00004213  64 65 62 75 67 5f 73 74  72 00 00 00 00 00 00 00  |debug_str.......|
    00004223  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    
    1. 00004260 1b 00 00 00 : 1b偏移处的名字为:.interp
    2. 000042a0 23 00 00 00 : 23偏移处的名字为:.note.gnu.property
    3. 000042e0 36 00 00 00 : 23偏移处的名字为:.note.gnu.build-id
    4. 00004320 49 00 00 00 : 49偏移处的名字为:.note.ABI-tag

    和Section 头部完全匹配,如下:

    $ readelf -t hello
    There are 36 section headers, starting at offset 0x4220:
    
    Section Headers:
      [Nr] Name
           Type              Address          Offset            Link
           Size              EntSize          Info              Align
           Flags
      [ 0] 
           NULL             0000000000000000  0000000000000000  0
           0000000000000000 0000000000000000  0                 0
           [0000000000000000]: 
      [ 1] .interp
           PROGBITS         0000000000000318  0000000000000318  0
           000000000000001c 0000000000000000  0                 1
           [0000000000000002]: ALLOC
      [ 2] .note.gnu.property
           NOTE             0000000000000338  0000000000000338  0
           0000000000000020 0000000000000000  0                 8
           [0000000000000002]: ALLOC
      [ 3] .note.gnu.build-id
           NOTE             0000000000000358  0000000000000358  0
           0000000000000024 0000000000000000  0                 4
           [0000000000000002]: ALLOC
      [ 4] .note.ABI-tag
           NOTE             000000000000037c  000000000000037c  0
           0000000000000020 0000000000000000  0                 4
           [0000000000000002]: ALLOC
    

    因此这里我们也知道uint32_t sh_name是字符串表中的偏移值。

    2.4 字符串表

    字符串表是一个包含了若干以NULL结尾的字符序列,即字符串。在目标文件中这些字符串通常是符号的名字或者节的名字。在目标文件的其它部分中,当需要引用某个字符串时,只需要提供该字符串在字符串表中的序号即可。

    字符串表中的第一个字符串(序号为 NULL)永远是空串,即NULL,它可以用于表示一个空的名字或者没有名字。所以,字符串表的第一个字节是NULL。由于每一个字符串都是以NULL结尾,所以字符串表的最后一个字节也必然为NULL。

    字符串表也可以是空的,不含有任何字符串,这时,节头中的 sh_size 成员必须是 0。

    一个目标文件中可能有多个字符串表,例如:

    1. .shstrtab : Section的名字表。
    2. .strtab : 普通字符串表。
    3. .dynstr : 动态链接的字符串表。

    我们看一个.shstrtab字符串表的实例:

    $ hexdump -C -n 512 -s 0x40c3 hello
    000040c3  00 2e 73 79 6d 74 61 62  00 2e 73 74 72 74 61 62  |..symtab..strtab|
    000040d3  00 2e 73 68 73 74 72 74  61 62 00 2e 69 6e 74 65  |..shstrtab..inte|
    000040e3  72 70 00 2e 6e 6f 74 65  2e 67 6e 75 2e 70 72 6f  |rp..note.gnu.pro|
    000040f3  70 65 72 74 79 00 2e 6e  6f 74 65 2e 67 6e 75 2e  |perty..note.gnu.|
    00004103  62 75 69 6c 64 2d 69 64  00 2e 6e 6f 74 65 2e 41  |build-id..note.A|
    00004113  42 49 2d 74 61 67 00 2e  67 6e 75 2e 68 61 73 68  |BI-tag..gnu.hash|
    00004123  00 2e 64 79 6e 73 79 6d  00 2e 64 79 6e 73 74 72  |..dynsym..dynstr|
    

    这个字符串表大致可用表示如下:

    序号(sh_name)字符串
    0x0NULL
    0x1.symtab
    0x9.strtab
    0x11.shstrtab
    0x1b.interp

    这里也可用看到序号(sh_name)其实就是字符串表中的偏移值。

    我们看一下一个字符串表的DUMP信息,如下:

    $ readelf -t hello
    There are 36 section headers, starting at offset 0x4220:
    
    Section Headers:
      [Nr] Name
           Type              Address          Offset            Link
           Size              EntSize          Info              Align
           Flags
      [ 0] 
           NULL             0000000000000000  0000000000000000  0
           0000000000000000 0000000000000000  0                 0
           [0000000000000000]: 
      [ ...]
      [34] .strtab
           STRTAB           0000000000000000  0000000000003ec0  0
           0000000000000203 0000000000000000  0                 1
           [0000000000000000]: 
      [35] .shstrtab
           STRTAB           0000000000000000  00000000000040c3  0
           000000000000015a 0000000000000000  0                 1
           [0000000000000000]: 
    

    这里有两个字符串表:

    1. .strtab : 偏移为0x3ec0.
    2. .shstrtab : 偏移为0x40c3.

    dump的文件内容如下:

    $ hexdump -C -n 512 -s 0x3ec0  hello
    00003ec0  00 63 72 74 73 74 75 66  66 2e 63 00 64 65 72 65  |.crtstuff.c.dere|
    00003ed0  67 69 73 74 65 72 5f 74  6d 5f 63 6c 6f 6e 65 73  |gister_tm_clones|
    00003ee0  00 5f 5f 64 6f 5f 67 6c  6f 62 61 6c 5f 64 74 6f  |.__do_global_dto|
    00003ef0  72 73 5f 61 75 78 00 63  6f 6d 70 6c 65 74 65 64  |rs_aux.completed|
    00003f00  2e 37 39 37 30 00 5f 5f  64 6f 5f 67 6c 6f 62 61  |.7970.__do_globa|
    00003f10  6c 5f 64 74 6f 72 73 5f  61 75 78 5f 66 69 6e 69  |l_dtors_aux_fini|
    00003f20  5f 61 72 72 61 79 5f 65  6e 74 72 79 00 66 72 61  |_array_entry.fra|
    00003f30  6d 65 5f 64 75 6d 6d 79  00 5f 5f 66 72 61 6d 65  |me_dummy.__frame|
    00003f40  5f 64 75 6d 6d 79 5f 69  6e 69 74 5f 61 72 72 61  |_dummy_init_arra|
    00003f50  79 5f 65 6e 74 72 79 00  68 65 6c 6c 6f 2e 63 00  |y_entry.hello.c.|
    00003f60  5f 5f 46 52 41 4d 45 5f  45 4e 44 5f 5f 00 5f 5f  |__FRAME_END__.__|
    
    $ hexdump -C -n 512 -s 0x40c3 hello
    000040c3  00 2e 73 79 6d 74 61 62  00 2e 73 74 72 74 61 62  |..symtab..strtab|
    000040d3  00 2e 73 68 73 74 72 74  61 62 00 2e 69 6e 74 65  |..shstrtab..inte|
    000040e3  72 70 00 2e 6e 6f 74 65  2e 67 6e 75 2e 70 72 6f  |rp..note.gnu.pro|
    000040f3  70 65 72 74 79 00 2e 6e  6f 74 65 2e 67 6e 75 2e  |perty..note.gnu.|
    00004103  62 75 69 6c 64 2d 69 64  00 2e 6e 6f 74 65 2e 41  |build-id..note.A|
    00004113  42 49 2d 74 61 67 00 2e  67 6e 75 2e 68 61 73 68  |BI-tag..gnu.hash|
    00004123  00 2e 64 79 6e 73 79 6d  00 2e 64 79 6e 73 74 72  |..dynsym..dynstr|
    00004133  00 2e 67 6e 75 2e 76 65  72 73 69 6f 6e 00 2e 67  |..gnu.version..g|
    00004143  6e 75 2e 76 65 72 73 69  6f 6e 5f 72 00 2e 72 65  |nu.version_r..re|
    00004153  6c 61 2e 64 79 6e 00 2e  72 65 6c 61 2e 70 6c 74  |la.dyn..rela.plt|
    00004163  00 2e 69 6e 69 74 00 2e  70 6c 74 2e 67 6f 74 00  |..init..plt.got.|
    

    2.5 符号表

    目标文件中的符号表(symbol table)所包含的信息用于定位和重定位程序中的符号定义和引用。目标文件的其它部分通过一个符号在这个表中的索引值来使用该符号。索引值从 0 开始计数,但值为 0 的表项(即第一项)并没有实际的意义,它表示未定义的符号。这里用常量 STN_UNDEF 来表示未定义的符号。

    一般来说,符号表包括两个部分:

    1. .dynsym : 使用的动态库的符号。
    2. .symtab : 可执行文件的所有符号,当然也包括.dynsym部分的符号。

    符号表项的定义格式如下:

    typedef struct {
    	uint32_t      st_name;  //字符串表的索引(偏移),字符串表有节头部指定下标
    	Elf32_Addr    st_value;  //符号的值或者地址(重定位文件中是偏移)
    	uint32_t      st_size;  //各种符号的大小各不相同,比如一个对象的大小就是它实际占用的字节数。如果一个符号的大小为 0 或者大小未知,则这个值为 0。
    	unsigned char st_info;  //符号的类型和属性
    	unsigned char st_other;   //本数据成员目前暂未使用,在目标文件中一律赋值为 0
    	uint16_t      st_shndx;  //相关联的节(符号在那个节中的索引)
    } Elf32_Sym;
    
    typedef struct {
    	uint32_t      st_name;
    	unsigned char st_info;
    	unsigned char st_other;
    	uint16_t      st_shndx;
    	Elf64_Addr    st_value;
    	uint64_t      st_size;
    } Elf64_Sym;
    

    对于st_value的值,没有固定的类型,它可能代表一个数值,也可以是一个地址,具体是什么要看上下文。对于不同的目标文件类型,符号表项的 st_value 的含义略有不同:

    • 在重定位文件中,如果一个符号对应的节的索引值是SHN_COMMON, st_value值是这个节内容的字节对齐数。
    • 在重定位文件中,如果一个符号是已定义的,那么它的st_value值是该符号的起始地址在其所在节中的偏移量,而其所在的节的索引由st_shndx给出。
    • 在可执行文件和共享库文件中, st_value不再是一个节内的偏移量,而是一个虚拟地址,直接指向符号所在的内存位置。这种情况下, st_shndx就不再需要了。

    对于符号成员st_info比较重要,由一系列的比特位构成,标识了“符号绑定(symbol binding)”、“符号类型(symbol type)”和“符号信息(symbol infomation)”三种属性。下面几个宏分别用于读取这三种属性值。

    #define ELF_ST_BIND(x)		((x) >> 4)
    #define ELF_ST_TYPE(x)		(((unsigned int) x) & 0xf)
    
    #define ELF32_ST_BIND(x)	ELF_ST_BIND(x)
    #define ELF32_ST_TYPE(x)	ELF_ST_TYPE(x)
    
    #define ELF64_ST_BIND(x)	ELF_ST_BIND(x)
    #define ELF64_ST_TYPE(x)	ELF_ST_TYPE(x)
    

    符号绑定(Symbol Binding),符号绑定属性由ELF32_ST_BIND指定,如下:

    名字
    STB_LOCAL0
    STB_GLOBAL1
    STB_WEAK2
    STB_LOPROC13
    STB_HIPROC15
    1. STB_LOCAL : 表明本符号是一个本地符号。它只出现在本文件中,在本文件外该符号无效。所以在不同的文件中可以定义相同的符号名,它们之间不会互相影响(类似Static 函数)。
    2. STB_GLOBAL : 表明本符号是一个全局符号。当有多个文件被连接在一起时,在所有文件中该符号都是可见的。正常情况下,在一个文件中定义的全局符号,一定是在其它文件中需要被引用,否则无须定义为全局。
    3. STB_WEAK : 类似于全局符号,但是相对于 STB_GLOBAL,它们的优先级更低。 全局符号(global symbol)和弱符号(weak symbol)在以下两方面有区别:
      • 当连接编辑器把若干个可重定位目标文件连接起来时,同名的STB_GLOBAL 符号不允许出现多次。而如果在一个目标文件中已经定义了一个全局的符号(global symbol),当一个同名的弱符号(weak symbol)出现时,并不会发生错误。连接编辑器会以全局符号为准,忽略弱符号。与全局符号相似,如果已经存在的是一个公用符号,即 st_shndx 域为SHN_COMMON 值的符号,当一个同名的弱符号(weak symbol)出现时,也不会发生错误。连接编辑器会以公用符号为准,忽略弱符号。
      • 在查找符号定义时,连接编辑器可能会搜索存档的库文件。如果是查找全局符号,连接编辑器会提取包含该未定义的全局符号的存档成员,存档成员可能是一个全局的符号,也可能是弱符号;而如果是查找弱符号,连接编辑器不会去提取存档成员。未解析的弱符号值为 0。

    符号类型(Symbol Types),符号类型属性由ELF32_ST_TYPE指定,如下:

    名字
    STT_NOTYPE0
    STT_OBJECT1
    STT_FUNC2
    STT_SECTION3
    STT_FILE4
    STT_LOPROC13
    STT_HIPROC15
    1. STT_NOTYPE : 本符号类型未指定。
    2. STT_OBJECT : 本符号是一个数据对象,比如变量、数组等。
    3. STT_FUNC : 本符号是一个函数,或者其它的可执行代码。函数符号在共享目标文件中有特殊的意义。当另外一个目标文件引用一个共享目标文件中的函数符号时,连接编辑器为被引用符号自动创建一个连接表项。非 STT_FUNC类型的共享目标符号不会通过这种连接表项被自动引用。
    4. STT_SECTION : 本符号与一个节相关联,用于重定位,通常具有 STB_LOCAL 属性。
    5. STT_FILE : 本符号是一个文件符号,它具有 STB_LOCAL 属性,它的节索引值是SHN_ABS。在符号表中如果存在本类符号的话,它会出现在所有STB_LOCAL 类符号的前部。

    此外还有个st_shndx也是比较重要,任何一个符号表项的定义都与某一个“节”相联系,因为符号是为节而定义,在节中被引用。st_shndx数据成员即指明了相关联的节。本数据成员是一个索引值,它指向相关联的节在节头表中的索引。在重定位过程中,节的位置会改变,本数据成员的值也随之改变,继续指向节的新位置。当本数据成员指向下面三种特殊的节索引值时,本符号具有如下特别的意义:

    1. SHN_ABS : 符号的值是绝对的,具有常量性,在重定位过程中,此值不需要改变。
    2. SHN_COMMON : 本符号所关联的是一个还没有分配的公共节,本符号的值规定了其内容的字节对齐规则,与 sh_addralign 相似。也就是说,连接器会为本符号分配存储空间,而且其起始地址是向 st_value 对齐的。本符号的值指明了要分配的字节数。
    3. SHN_UNDEF : 当一个符号指向第 1 节(SHN_UNDEF)时,表明本符号在当前目标文件中未定义,在连接过程中,连接器会找到此符号被定义的文件,并把这些文件连接在一起。本文件中对该符号的引用会被连接到实际的定义上去。

    我们可用使用readelf 和 objdump来查看符号信息,如下:

    $ readelf -s hello
    
    Symbol table '.dynsym' contains 7 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
         1: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTab
         2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.2.5 (2)
         3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)
         4: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
         5: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable
         6: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@GLIBC_2.2.5 (2)
    
    Symbol table '.symtab' contains 70 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
         1: 0000000000000318     0 SECTION LOCAL  DEFAULT    1 
         2: 0000000000000338     0 SECTION LOCAL  DEFAULT    2 
         3: 0000000000000358     0 SECTION LOCAL  DEFAULT    3 
         4: 000000000000037c     0 SECTION LOCAL  DEFAULT    4 
         5: 00000000000003a0     0 SECTION LOCAL  DEFAULT    5 
         6: 00000000000003c8     0 SECTION LOCAL  DEFAULT    6 
         7: 0000000000000470     0 SECTION LOCAL  DEFAULT    7 
         8: 00000000000004f2     0 SECTION LOCAL  DEFAULT    8 
         9: 0000000000000500     0 SECTION LOCAL  DEFAULT    9 
        10: 0000000000000520     0 SECTION LOCAL  DEFAULT   10 
        11: 00000000000005e0     0 SECTION LOCAL  DEFAULT   11 
        12: 0000000000001000     0 SECTION LOCAL  DEFAULT   12 
        13: 0000000000001020     0 SECTION LOCAL  DEFAULT   13 
        14: 0000000000001040     0 SECTION LOCAL  DEFAULT   14 
        15: 0000000000001050     0 SECTION LOCAL  DEFAULT   15 
        16: 0000000000001060     0 SECTION LOCAL  DEFAULT   16 
        17: 00000000000011e8     0 SECTION LOCAL  DEFAULT   17 
        18: 0000000000002000     0 SECTION LOCAL  DEFAULT   18 
        19: 0000000000002014     0 SECTION LOCAL  DEFAULT   19 
        20: 0000000000002058     0 SECTION LOCAL  DEFAULT   20 
        21: 0000000000003db8     0 SECTION LOCAL  DEFAULT   21 
        22: 0000000000003dc0     0 SECTION LOCAL  DEFAULT   22 
        23: 0000000000003dc8     0 SECTION LOCAL  DEFAULT   23 
        24: 0000000000003fb8     0 SECTION LOCAL  DEFAULT   24 
        25: 0000000000004000     0 SECTION LOCAL  DEFAULT   25 
        26: 0000000000004010     0 SECTION LOCAL  DEFAULT   26 
        27: 0000000000000000     0 SECTION LOCAL  DEFAULT   27 
        28: 0000000000000000     0 SECTION LOCAL  DEFAULT   28 
        29: 0000000000000000     0 SECTION LOCAL  DEFAULT   29 
        30: 0000000000000000     0 SECTION LOCAL  DEFAULT   30 
        31: 0000000000000000     0 SECTION LOCAL  DEFAULT   31 
        32: 0000000000000000     0 SECTION LOCAL  DEFAULT   32 
        33: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c
        34: 0000000000001090     0 FUNC    LOCAL  DEFAULT   16 deregister_tm_clones
        35: 00000000000010c0     0 FUNC    LOCAL  DEFAULT   16 register_tm_clones
        36: 0000000000001100     0 FUNC    LOCAL  DEFAULT   16 __do_global_dtors_aux
    

    我们使用hexdump查看文件中符号的原始数据,如下:

    $ readelf -S hello
    There are 36 section headers, starting at offset 0x4220:
    
    Section Headers:
      [Nr] Name              Type             Address           Offset
           Size              EntSize          Flags  Link  Info  Align
      [ 0]                   NULL             0000000000000000  00000000
           0000000000000000  0000000000000000           0     0     0
      [ 1] .interp           PROGBITS         0000000000000318  00000318
           000000000000001c  0000000000000000   A       0     0     1
      [ 2] .note.gnu.propert NOTE             0000000000000338  00000338
           0000000000000020  0000000000000000   A       0     0     8
      [ 3] .note.gnu.build-i NOTE             0000000000000358  00000358
           0000000000000024  0000000000000000   A       0     0     4
      [ 4] .note.ABI-tag     NOTE             000000000000037c  0000037c
           0000000000000020  0000000000000000   A       0     0     4
      [ 5] .gnu.hash         GNU_HASH         00000000000003a0  000003a0
           0000000000000024  0000000000000000   A       6     0     8
      [ 6] .dynsym           DYNSYM           00000000000003c8  000003c8
           00000000000000a8  0000000000000018   A       7     1     8
      [ 7] .dynstr           STRTAB           0000000000000470  00000470
           0000000000000082  0000000000000000   A       0     0     1
    
    $ hexdump -C -n 512 -s 0x3c8 hello
    000003c8  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    000003d8  00 00 00 00 00 00 00 00  3d 00 00 00 20 00 00 00  |........=... ...|
    000003e8  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    000003f8  0b 00 00 00 12 00 00 00  00 00 00 00 00 00 00 00  |................|
    00000408  00 00 00 00 00 00 00 00  1f 00 00 00 12 00 00 00  |................|
    00000418  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    00000428  59 00 00 00 20 00 00 00  00 00 00 00 00 00 00 00  |Y... ...........|
    00000438  00 00 00 00 00 00 00 00  68 00 00 00 20 00 00 00  |........h... ...|
    00000448  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    00000458  10 00 00 00 22 00 00 00  00 00 00 00 00 00 00 00  |...."...........|
    
    $ objdump -s --section=.dynsym hello
    
    hello:     file format elf64-x86-64
    
    Contents of section .dynsym:
     03c8 00000000 00000000 00000000 00000000  ................
     03d8 00000000 00000000 3d000000 20000000  ........=... ...
     03e8 00000000 00000000 00000000 00000000  ................
     03f8 0b000000 12000000 00000000 00000000  ................
     0408 00000000 00000000 1f000000 12000000  ................
     0418 00000000 00000000 00000000 00000000  ................
     0428 59000000 20000000 00000000 00000000  Y... ...........
     0438 00000000 00000000 68000000 20000000  ........h... ...
     0448 00000000 00000000 00000000 00000000  ................
     0458 10000000 22000000 00000000 00000000  ...."...........
     0468 00000000 00000000                    ........       
    

    2.6 重定位

    重定位(relocation)是把符号引用与符号定义连接在一起的过程。比如,当程序调用一个函数时,将从当前运行的指令跳转到一个新的指令地址去执行。在编写程序的时候,我们只需指明所要调用的函数名(即符号引用),在重定位的过程中,函数名会与实际的函数所在地址(即符号定义)联系起来,使程序知道应该跳转到哪里去。

    重定位文件必须知道如何修改其所包含的“节”的内容,在构建可执行文件或共享目标文件的时候,把节中的符号引用换成这些符号在进程空间中的虚拟地址。包含这些转换信息的数据也就是“重定位项(relocation entries)”。

    重定位结构信息定义如下:

    typedef struct {
    	Elf32_Addr r_offset; //重定位所作用的位置
    	uint32_t   r_info;
    } Elf32_Rel;
    
    typedef struct {
    	Elf64_Addr r_offset;
    	uint64_t   r_info;
    } Elf64_Rel;
    
    Relocation structures that need an addend :
    
    typedef struct {
    	Elf32_Addr r_offset;
    	uint32_t   r_info;
    	int32_t    r_addend;  //额外的加数
    } Elf32_Rela;
    
    typedef struct {
    	Elf64_Addr r_offset;
    	uint64_t   r_info;
    	int64_t    r_addend;
    } Elf64_Rela;
    
    1. r_offset : 给出重定位所作用的位置。对于重定位文件来说,此值是受重定位作用的存储单元在节中的字节偏移量(相对节的偏移);对于可执行文件或共享目标文件来说,此值是受重定位作用的存储单元的虚拟地址

    2. r_info : 既给出了重定位所作用的符号表索引,也给出了重定位的类型。比如,如果是一个函数的重定位,本数据成员将要持有被调用函数所对应的符号表索引。如果索引值为 STN_UNDEF,即未定义索引,那么重定位过程中将使用 0 作为符号值。以下是应用于 r_info 的宏定义:

    #define ELF32_R_SYM(x) ((x) >> 8)
    #define ELF32_R_TYPE(x) ((x) & 0xff)
    
    #define ELF64_R_SYM(i)			((i) >> 32)
    #define ELF64_R_TYPE(i)			((i) & 0xffffffff)
    
    1. r_addend : 指定了一个加数,这个加数用于计算需要重定位的域的值。

    一个“重定位节(relocation section)”需要引用另外两个节:一个是符号表节,一个是被修改节。在重定位节中,节头的 sh_infosh_link 成员分别指明了引用关系。不同的目标文件中,重定位项的r_offset成员的含义略有不同。

    • 在重定位文件中r_offset成员含有一个节偏移量。也就是说,重定位节本身描述的是如何修改文件中的另一个节的内容,重定位偏移量(r_offset)指向了另一个节中的一个存储单元地址。

    • 在可执行文件或共享目标文件中r_offset含有的是符号定义在进程空间中的虚拟地址。可执行文件和共享目标文件是用于运行程序而不是构建程序的,所以对它们来说更有用的信息是运行期的内存虚拟地址,而不是某个符号定义在文件中的位置。

    综上所述,链接器可用通过这个节里面的内容找到:

    1. 需要重定位的文件地址。
    2. 需要重定位的链接符号。

    而对于动态装载器,可以通过这个段里面的内容找到:

    1. 需要重定位的虚拟地址。
    2. 需要重定位的动态符号。

    因此就可用在其他目标文件中找到相应的符号进行重定位,举个链接文件的例子:

    //file1.c
    void fun();
    
    void _start()
    {
            fun();
    }
    
    //file2.c
    void fun()
    {
    }
    

    我们对这两个文件进行编译:

    $ gcc -g -c -O0 file1.c
    $ gcc -g -c -O0 file2.c
    $ gcc -g -nostdlib file1.o file2.o -o file.o
    

    对于file1.o 我们可用看到信息如下:

    $ readelf -r file1.o
    
    Relocation section '.rela.text' at offset 0x3b8 contains 1 entry:
      Offset          Info           Type           Sym. Value    Sym. Name + Addend
    00000000000a  000f00000004 R_X86_64_PLT32    0000000000000000 fun - 4
    
    $ objdump -S file1.o
    
    file1.o:     file format elf64-x86-64
    
    
    Disassembly of section .text:
    
    0000000000000000 <_start>:
    void fun();
    
    void _start()
    {
       0:   55                      push   %rbp
       1:   48 89 e5                mov    %rsp,%rbp
            fun();
       4:   b8 00 00 00 00          mov    $0x0,%eax
       9:   e8 00 00 00 00          callq  e <_start+0xe>
    }
       e:   90                      nop
       f:   5d                      pop    %rbp
      10:   c3                      retq
    

    偏移地址为00000000000a,以及符号名称fun,这样链接器就可用对其进行重定位了,结果如下:

    $ objdump -S file.o
    
    file.o:     file format elf64-x86-64
    
    
    Disassembly of section .text:
    
    00000000000002b1 <_start>:
    void fun();
    
    void _start()
    {
     2b1:   55                      push   %rbp
     2b2:   48 89 e5                mov    %rsp,%rbp
            fun();
     2b5:   b8 00 00 00 00          mov    $0x0,%eax
     2ba:   e8 03 00 00 00          callq  2c2 <fun>
    }
     2bf:   90                      nop
     2c0:   5d                      pop    %rbp
     2c1:   c3                      retq
    
    00000000000002c2 <fun>:
    void fun()
    {
     2c2:   55                      push   %rbp
     2c3:   48 89 e5                mov    %rsp,%rbp
    }
     2c6:   90                      nop
     2c7:   5d                      pop    %rbp
     2c8:   c3                      retq
    

    对于动态加载器来说,重定位节是修改动态导入变量或者函数的地址的,如下:

     readelf -r hello
    
    Relocation section '.rela.dyn' at offset 0x520 contains 8 entries:
      Offset          Info           Type           Sym. Value    Sym. Name + Addend
    000000003db8  000000000008 R_X86_64_RELATIVE                    1140
    000000003dc0  000000000008 R_X86_64_RELATIVE                    1100
    000000004008  000000000008 R_X86_64_RELATIVE                    4008
    000000003fd8  000100000006 R_X86_64_GLOB_DAT 0000000000000000 _ITM_deregisterTMClone + 0
    000000003fe0  000300000006 R_X86_64_GLOB_DAT 0000000000000000 __libc_start_main@GLIBC_2.2.5 + 0
    000000003fe8  000400000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
    000000003ff0  000500000006 R_X86_64_GLOB_DAT 0000000000000000 _ITM_registerTMCloneTa + 0
    000000003ff8  000600000006 R_X86_64_GLOB_DAT 0000000000000000 __cxa_finalize@GLIBC_2.2.5 + 0
    
    Relocation section '.rela.plt' at offset 0x5e0 contains 1 entry:
      Offset          Info           Type           Sym. Value    Sym. Name + Addend
    000000003fd0  000200000007 R_X86_64_JUMP_SLO 0000000000000000 puts@GLIBC_2.2.5 + 0
    
    
    $ objdump -s --section=.got hello
    
    hello:     file format elf64-x86-64
    
    Contents of section .got:
     3fb8 c83d0000 00000000 00000000 00000000  .=..............
     3fc8 00000000 00000000 30100000 00000000  ........0.......
     3fd8 00000000 00000000 00000000 00000000  ................
     3fe8 00000000 00000000 00000000 00000000  ................
     3ff8 00000000 00000000                    ........      
    

    对于puts函数,重定位的地址为000000003fd0,这是一个虚拟地址,同样我们通过名称puts@GLIBC_2.2.5 + 0可以找到符号表对应的虚拟地址,填入地址000000003fd0就完成了整个重定位的过程。

    3. 动态装载与动态链接

    可执行文件和共享目标文件(动态连接库)是程序的静态存储形式。要执行一个程序,系统要先把相应的可执行文件和动态连接库装载到进程空间中,这样形成一个可运行的进程的内存空间布局,也可以称它为“进程镜像”。一个已装载完成的进程空间会包含多个不同的“段(segment)”,比如代码段(text segment),数据段(data segment),堆栈段(stack
    segment)等等。

    准备一个程序的内存镜像,可以大体上分为装载和连接两个步骤。

    1. 把目标文件装载入内存。
    2. 解析目标文件中的符号引用

    3.1 程序头

    一个可执行文件或共享目标文件的程序头表(program header table)是一个数组,数组中的每一个元素称为“程序头(program header)”,每一个程序头描述了一个“段(segment)”或者一块用于准备执行程序的信息。一个目标文件中的“段(segment)”包含一个或者多个“节(section)”。程序头只对可执行文件或共享目标文件有意义,对于其它类型的目标文件,该信息可以忽略。在目标文件的文件头(elf header)中, e_phentsizee_phnum 成员指定了程序头的大小。

    program header table数据结构定义如下:

    /* These constants are for the segment types stored in the image headers */
    #define PT_NULL    0
    #define PT_LOAD    1
    #define PT_DYNAMIC 2
    #define PT_INTERP  3
    #define PT_NOTE    4
    #define PT_SHLIB   5
    #define PT_PHDR    6
    #define PT_TLS     7               /* Thread local storage segment */
    #define PT_LOOS    0x60000000      /* OS-specific */
    #define PT_HIOS    0x6fffffff      /* OS-specific */
    #define PT_LOPROC  0x70000000
    #define PT_HIPROC  0x7fffffff
    #define PT_GNU_EH_FRAME		0x6474e550
    
    #define PT_GNU_STACK	(PT_LOOS + 0x474e551)
    
    typedef struct {
    	uint32_t   p_type;  //描述段的类型,或者如何解析本程序头的信息
    	uint32_t   p_flags;
    	Elf64_Off  p_offset;  //文件中的偏移
    	Elf64_Addr p_vaddr;   //在进程空间中虚拟地址
    	Elf64_Addr p_paddr;   //在进程空间中物理地址,不再可用
    	uint64_t   p_filesz;  //文件大小
    	uint64_t   p_memsz;   //内存大小
    	uint64_t   p_align;   //内存对齐
    } Elf64_Phdr;
    

    对于p_type,取值如下:

    1. PT_NULL : 此类型表明本程序头是未使用的,本程序头内的其它成员值均无意义。具有此种类型的程序头应该被忽略。
    2. PT_LOAD : 此类型表明本程序头指向一个可装载的段。段的内容会被从文件中拷贝到内存中。如前所述,段在文件中的大小是 p_filesz,在内存中的大小是p_memsz。如果 p_memsz 大于 p_filesz,在内存中多出的存储空间应填 0 补充。在程序头表中,所有PT_LOAD类型的程序头按照 p_vaddr 的值做升序排列。
    3. PT_DYNAMIC : 此类型表明本段指明了动态连接的信息。
    4. PT_INTERP : 本段指向了一个以NULL结尾的字符串,这个字符串是一个 ELF 解析器的路径。这种段类型只对可执行程序有意义,当它出现在共享目标文件中时,是一个无意义的多余项。在一个 ELF 文件中它最多只能出现一次,而且必须出现在其它可装载段的表项之前。
    5. PT_NOTE : 本段指向了一个以NULL结尾的字符串,这个字符串包含一些附加的信息。
    6. PT_SHLIB : 该段类型是保留的,而且未定义语法。 UNIX System V 系统上的应用程序不会包含这种表项。
    7. PT_PHDR : 此类型的程序头如果存在的话,它表明的是其自身所在的程序头表在文件或内存中的位置和大小。这样的段在文件中可以不存在,只有当所在程序头表所覆盖的段只是整个程序的一部分时,才会出现一次这种表项,而且这种表项一定出现在其它可装载段的表项之前。
    8. PT_LOPROC ~ PT_HIPROC : 类型值在这个区间的程序头是为特定处理器保留的。

    3.1.1 基地址

    程序头中出现的虚拟地址不能代表其相应的数据在进程内存空间中的虚拟地址。可执行文件中需要含有绝对的地址,比如变量地址,函数地址等,为了让程序正确地执行,“段”中出现的虚拟地址必须在创建可执行程序时被重新计算。另一方面,出于 ELF 通用性的要求,目标文件的段中又不能出现绝对地址,其代码是不应依赖于具体存储位置的,即同一个段在被加载到两个不同的进程中时,它的地址可能不同,但它的行为不能表现出不一样。

    在被加载到进程空间里时,尽管“段”会被分配到一个不确定的地址,但是不同的段之间会有确定的“相对位置(relative position)”。也就是说,在目标文件中存储的两个段,它们的位置之间有多少偏移,当它们被加载到内存中时,这两个段的位置之间仍然保持这么大的偏移(距离)。一个段在内存中的虚拟地址与其在目标文件中的地址一般是不相等的,它们之间会有一个偏移量,这个偏移量被称为“基地址(base address)”,基地址的作用之一就是在动态连接过程中为程序重定位内存镜像

    一个可执行文件或共享目标文件的基地址是在运行期间由以下三个值计算出来的:内存加载地址,最大页面大小,程序可装载段的最低地址。为计算基地址,首先找出类型为 PT_LOAD(即可加载)而且 p_vaddr(段地址)最低的那个段,把这个段在内存中的地址与最大页面大小相除,得到一个段地址的余数;再把p_vaddr 与最大页面大小相除,得到一个 p_vaddr 的余数。基地址就是段地址的余数与p_vaddr的余数之差。

    3.1.2 段权限

    虽然 ELF 文件格式中没有规定,但是一个可执行程序至少会有一个可加载的段。当为可加载段创建内存镜像时,系统会按照 p_flags 的指示给段赋予一定的权限。

    名字含义
    PF_X0x1可执行
    PF_W0x2只写
    PF_R0x4只读
    PF_MASKPROC0xf0000000未指定

    具体定义值如下:

    /* These constants define the permissions on sections in the program
       header, p_flags. */
    #define PF_R		0x4
    #define PF_W		0x2
    #define PF_X		0x1
    

    3.1.3 实例

    我们看一个ELF文件的程序头部信息,如下:

    $ readelf -l hello
    
    Elf file type is DYN (Shared object file)
    Entry point 0x1060
    There are 13 program headers, starting at offset 64
    
    Program Headers:
      Type           Offset             VirtAddr           PhysAddr
                     FileSiz            MemSiz              Flags  Align
      PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040
                     0x00000000000002d8 0x00000000000002d8  R      0x8
      INTERP         0x0000000000000318 0x0000000000000318 0x0000000000000318
                     0x000000000000001c 0x000000000000001c  R      0x1
          [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
      LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                     0x00000000000005f8 0x00000000000005f8  R      0x1000
      LOAD           0x0000000000001000 0x0000000000001000 0x0000000000001000
                     0x00000000000001f5 0x00000000000001f5  R E    0x1000
      LOAD           0x0000000000002000 0x0000000000002000 0x0000000000002000
                     0x0000000000000160 0x0000000000000160  R      0x1000
      LOAD           0x0000000000002db8 0x0000000000003db8 0x0000000000003db8
                     0x0000000000000258 0x0000000000000260  RW     0x1000
      DYNAMIC        0x0000000000002dc8 0x0000000000003dc8 0x0000000000003dc8
                     0x00000000000001f0 0x00000000000001f0  RW     0x8
      NOTE           0x0000000000000338 0x0000000000000338 0x0000000000000338
                     0x0000000000000020 0x0000000000000020  R      0x8
      NOTE           0x0000000000000358 0x0000000000000358 0x0000000000000358
                     0x0000000000000044 0x0000000000000044  R      0x4
      GNU_PROPERTY   0x0000000000000338 0x0000000000000338 0x0000000000000338
                     0x0000000000000020 0x0000000000000020  R      0x8
      GNU_EH_FRAME   0x0000000000002014 0x0000000000002014 0x0000000000002014
                     0x0000000000000044 0x0000000000000044  R      0x4
      GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                     0x0000000000000000 0x0000000000000000  RW     0x10
      GNU_RELRO      0x0000000000002db8 0x0000000000003db8 0x0000000000003db8
                     0x0000000000000248 0x0000000000000248  R      0x1
    
     Section to Segment mapping:
      Segment Sections...
       00     
       01     .interp 
       02     .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt 
       03     .init .plt .plt.got .plt.sec .text .fini 
       04     .rodata .eh_frame_hdr .eh_frame 
       05     .init_array .fini_array .dynamic .got .data .bss 
       06     .dynamic 
       07     .note.gnu.property 
       08     .note.gnu.build-id .note.ABI-tag 
       09     .note.gnu.property 
       10     .eh_frame_hdr 
       11     
       12     .init_array .fini_array .dynamic .got 
    

    在最后面,我们可用看到每一个段对应的节的关系,一个Segment包含多个Section。

    3.2 注释段

    类型为 PT_NOTE 的段往往会包含类型为 SHT_NOTE 的节, SHT_NOTE 节可以为目标文件提供一些特别的信息,用于给其它的程序检查目标文件的一致性和兼容性。这些信息我们称为“注释信息”,这样的节称为“注释节(note section)”,所在的段即为“注释段(note segment)”。注释信息可以包含任意数量的“注释项”,每一个注释项是一个数组,数组的每一个成员大小为 4 字节,格式依目标处理器而定。下图解释了注释信息是如何组织的,但这仅是一种参考,不是规范的一部分。

    内容(4字节)
    namesz
    descsz
    type
    name…
    desc…

    对于其中的每一个字段,含义分别如下:

    1. namesz 和 name : Namesz 和 name 成对使用。 Namesz 是一个 4 字节整数,而 name 是一个以NULL结尾的字符串。 Namesz 是 name 字符串的长度。字符串 name 的内容是本项的所有者的名字。没有正式的机制来避免名字冲突,一般按照惯例,系统提供商应把他们自己的名字写进 name 项里,比如”XYZ Computer Company”。如果没有名字
      的话, namesz 是 0。由于数组项的大小是向 4 字节对齐的,所以如果字符串长度不是整 4 字节的话,需要填 0 补位。如果有补位的话, namesz 只计字符串长度,不计所补的空位。

    2. descsz 和 desc : Descsz 和 desc 也成对使用,它们的格式与 namesz/name 完全相同。不过,desc 的内容没有任何规定、限制,甚至建议,它包含哪些信息完全是自由的。

    3. type : 这个字段给出描述项(desc)的解释,或者说是描述项的类型。每一个提供商都会定义自己的类型,所以同一类型值对于不同的提供商其解释也是不同的。当一个程序读取注释信息的时候,它必须同时辨认出 name 和 type 才能理解 desc 的内容。

    下面我们看一下notes段的内容如下:

    $ readelf -n main
    
    Displaying notes found in: .note.ABI-tag
      Owner                 Data size       Description
      HNU                  0x00000010       NT_VERSION (version)
       description data: 00 00 00 00 03 00 00 00 02 00 00 00 00 00 00 00
    
    Displaying notes found in: .note.gnu.build-id
      Owner                 Data size       Description
      GNU                  0x00000014       NT_GNU_BUILD_ID (unique build ID bitstring)
        Build ID: 6628b6f822b0c5175adbb067b53d66b4b4a806ba
    

    我们可用读取一下原始内容如下:

    $ hexdump -C -s 0x254 -n 32 main
    00000254  04 00 00 00 10 00 00 00  01 00 00 00 48 4e 55 00  |............HNU.|
    00000264  00 00 00 00 03 00 00 00  02 00 00 00 00 00 00 00  |................|
    00000274
    
    1. namesz的值为0x4.
    2. descsz的值为0x10.
    3. typs的值为0x1.
    4. name的值为HNU.
    5. desc的值为00 00 00 00 03 00 00 00 02 00 00 00 00 00 00 00.

    上面的这些数据和使用命令readelf -n读取出来的一致。

    3.3 动态链接

    动态链接也就是解析符号引用的过程,这个过程在进程初始化和进程运行期间都可能发生。

    3.3.1 程序解析器

    一个参与动态链接的可执行文件会包含一个类型为PT_INTERP的程序头项。当执行一个程序的时候,系统函数exec(cmd)会被调用,在这个函数中,内核会去读取这个PT_INTERP段,解析出其包含的一个路径字符串,这个串指明了一个ELF程序解析器,系统会转去初始化该解析器的进程镜像。也就是,在这时系统会暂停原来的工作,不是用待执行文件的段内容去初始化进程空间,而是把进程空间暂时“借”给解析器程序使用。然后,解析器程序将从系统手中接过控制权继续执行。

    例如,我们可用在ELF看到解析器如下:

    f$ file main
    main: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=6628b6f822b0c5175adbb067b53d66b4b4a806ba, with debug_info, not stripped
    
    $ hexdump -C -s 0x238 -n 32 main
    00000238  2f 6c 69 62 36 34 2f 6c  64 2d 6c 69 6e 75 78 2d  |/lib64/ld-linux-|
    00000248  78 38 36 2d 36 34 2e 73  6f 2e 32 00 04 00 00 00  |x86-64.so.2.....|
    00000258
    

    解析器以两种方式来接手系统的控制:

    1. 第一种,解析器取得可执行文件的描述符,内容指针定位于文件开始处,解析器可以读取并映射可执行程序的段到内存中。

    2. 第二种,对于有些可执行文件格式,系统直接将文件内容载入内存,并不把其文件描述符给解析器。

    解析器可以是一个共享目标文件,也可以是一个可执行文件。

    • 一般情况下,解析器会是一个共享目标文件,并且其段内容是位置不相关的,所以在不同的进程中,它的地址会不一样,系统会使用mmap(...)系统调用在动态段区域来为解析器创建段的镜像。所以,一般情况下不用担心解析器的段内容会与待执行文件的内容发生地址冲突。

    • 如果解析器是独立的可执行文件,那么系统就要按照解析器程序的程序头来加载它,在加载的时候就有可能与待执行文件的段相冲突,这种情况下由解析器来负责解决冲突。

    3.3.2 动态链接器

    当创建一个可执行文件时,如果依赖其它的动态链接库,那么链接编辑器会在可执行文件的程序头中加入一个PT_INTERP项,告诉系统这里需要使用动态链接器。

    可执行文件与动态链接器一起创建了进程的镜像,这个过程包含以下活动:

    • 添加可执行文件的段到进程空间;
    • 添加共享目标文件的段到进程空间;
    • 为可执行文件和共享目标文件进行重定位;
    • 如果动态链接器使用了可执行文件的文件描述符,应关闭它;
    • 把控制权交给程序。

    链接编辑器也会为动态链接库组织一些数据,以方便它的链接过程。在“程序头”部分提到过,为了方便在运行的时候访问,这些数据放在可装载的段中。当然具体的数据格式是依处理器而不同的。

    • 类型为SHT_DYNAMIC的.dynamic 节中包含有很多种动态链接信息。在这个节的最开始处有一个结构,其中包含有其它动态链接信息的地址。
    • 类型为SHT_HASH的.hash节中含有符号哈希表。
    • 类型为SHT_PROGBITS的.got和.plt节各包含一张表。

    共享目标所占据的内存地址可能与文件程序头表中所记录的不同。在程序开始执行以前,动态链接器会为内存镜像做重定位,更新绝对地址。当然,库文件在被装载时,如果其内存地址与其文件中描述的完全相同的话,那些引用它们的绝对地址就是对的,不需要更新。但事实上,这种情况很少发生。

    如果进程的环境变量中含有LD_BIND_NOW,而且其值不为空,那么动态连接器就要在程序开始运行之前把所有重定位都处理完。比如,在该环境变量为以下值时,动态连接器都需要这样做:

    • LD_BIND_NOW = 1
    • LD_BIND_NOW = on
    • LD_BIND_NOW = off

    否则,如果LD_BIND_NOW没有出现或者其值为空,动态链接器就可以把处理重定位的工作推后,即只有当一个符号被引用的时候才去重定位它。因为在程序运行过程中,有一些函数并不会被调用到,推后重定位是一种提高效率的方法,可以避免为这些函数做不必要的重定位。

    3.3.3 动态段

    如果一个目标文件参与动态链接的话,它的程序头表中一定会包含一个类型为PT_DYNAMIC的表项,其所对应的段称为动态段(dynamic segment),段的名字为.dynamic(也是.dynamic节)。动态段的作用是提供动态链接器所需要的信息,比如依赖于哪些共享目标文件、动态链接符号表的位置、动态链接重定位表的位置等等。这个动态段中包含有动态节,动态节由符号DYNAMIC所标记,它包含一个由如下结构体组成的数组。

    
    typedef struct {
    	Elf32_Sword    d_tag;
    	union {
    		Elf32_Word d_val;
    		Elf32_Addr d_ptr;
    	} d_un;
    } Elf32_Dyn;
    extern Elf32_Dyn _DYNAMIC[];
    
    typedef struct {
    	Elf64_Sxword    d_tag;
    	union {
    		Elf64_Xword d_val;
    		Elf64_Addr  d_ptr;
    	} d_un;
    } Elf64_Dyn;
    extern Elf64_Dyn _DYNAMIC[];
    

    对于每一个这种类型的目标项,d_tag控制着对d_un的解析:

    1. d_tag标记控制着对d_un的解析。
    2. d_val类型为Elf32_Word/Elf64_Xword的目标项代表的是整型数。
    3. d_ptr类型为Elf32_Addr/Elf64_Addr的目标项代表的是进程空间里的地址。目标项在文件中的地址与其在进程空间内的地址可能会不同。当系统解析到这个动态节中的地址时,动态链接器就可以根据文件地址和内存基地址来计算出实际的内存地址。

    d_tag字段表示当前表项的具体类型,部分可选的枚举值如下:

    #define DT_NULL		0  //用于标记_DYNAMIC 数组的结束
    #define DT_NEEDED	1  //依赖库, DT_STRTAB标记
    #define DT_PLTRELSZ	2  //重定位项的总大小
    #define DT_PLTGOT	3  //GOT表地址
    #define DT_HASH		4  //哈希表地址
    #define DT_STRTAB	5  //字符串表的地址
    #define DT_SYMTAB	6  //符号表的地址
    #define DT_RELA		7  //重定位表的地址
    #define DT_RELASZ	8   //重定位表大小
    #define DT_RELAENT	9  //重定位表项大小
    #define DT_STRSZ	10  //字符串表大小
    #define DT_SYMENT	11  //符号表项大小
    #define DT_INIT		12  //初始化函数
    #define DT_FINI		13  //析构函数
    #define DT_SONAME	14  //别名索引
    #define DT_RPATH 	15  //RPATH
    #define DT_SYMBOLIC	16
    #define DT_REL	        17
    #define DT_RELSZ	18
    #define DT_RELENT	19
    #define DT_PLTREL	20
    #define DT_DEBUG	21
    #define DT_TEXTREL	22
    #define DT_JMPREL	23
    #define DT_ENCODING	32
    

    各个字段解释如下:

    1. DT_NULL : 用于标记_DYNAMIC 数组的结束。
    2. DT_NEEDED : 此元素指明了一个所需的库的名字。不过此元素本身并不是一个字符串,它是一个指向由DT_STRTAB所标记的字符串表中的索引,在表中,此索引处是一个以NULL结尾的字符串,这个字符串就是库的名字。在动态数组中可以包含若干个此类型的项,这些项出现的相对顺序是不能随意调换的。
    3. DT_PLTRELSZ : 此元素含有与函数链接表相关的所有重定位项的总大小,以字节为单位。如果数组中有DT_JMPREL项的话,DT_PLTRELSZ也必须要有。
    4. DT_PLTGOT : 此元素包含与函数链接表或全局偏移量表相应的地址。在Intel架构中,这一项的d_ptr成员给出全局偏移量表中第一项的地址。对于全局偏移量表(GOT)中前三项都是保留的,其中两项用于持有函数链接表信息。
    5. DT_HASH : 此元素含有符号哈希表的地址。这里所指的哈希表与DT_SYMTAB所指的哈希表是同一个。
    6. DT_STRTAB : 此元素包含字符串表的地址,此表中包含符号名、库名等等。
    7. DT_SYMTAB :此元素包含符号表的地址。
    8. DT_RELA : 此元素包含一个重定位表的地址,在重定位表中存储的是显式的加数。在一个目标文件中可以存在多个重定位节,当为可执行文件或共享目标文件创建重定位表的时候,链接器会把这些重定位节连接在一起,最后形成一张大的重定位表。当链接器为一个可执行文件创建进程空间,或者把一个共享目标添加到进程空间中去的时候,它会去读重定位表并执行相应的操作。如果在动态结构中包含有DT_RELA元素的话,就必须同时还包含DT_RELASZDT_RELAENT元素。如果一个文件需要重定位的话,DT_RELADT_REL至少要出现一个。
    9. DT_RELASZ : 此元素持有DT_RELA相应的重定位表的大小,以字节为单位。
    10. DT_RELAENT : 此元素持有DT_RELA相应的重定位表项的大小,以字节为单位。
    11. DT_STRSZ : 此元素持有字符串表的大小,以字节为单位。
    12. DT_SYMENT : 此元素持有符号表项的大小,以字节为单位。
    13. DT_INIT : 此元素持有初始化函数的地址。
    14. DT_FINI : 此元素持有终止函数的地址。
    15. DT_SONAME : 此元素持有一个字符串表中的偏移量,该位置存储了一个以NULL结尾的字符串,是一个共享目标的名字。相应的字符串表由DT_STRTAB指定。
    16. DT_RPATH : 此元素持有一个字符串表中的偏移量,该位置存储了一个以NULL结尾的字符串,是一个用于搜索库文件的路径名。相应的字符串表由DT_STRTAB指定。
    17. DT_SYMBOLIC : 在共享目标文件中,此元素的出现与否决定了动态链接器解析符号时所用的算法。如果此元素不出现的话,动态连接器先搜索可执行文件再搜索库文件;如果此元素出现的话,顺序刚好相反,动态链接器会先从本共享目标文件开始,后搜索可执行文件。
    18. DT_REL : 此元素与 DT_RELA 相似,只是它所指向的重定位表中,“加数”是隐含的而不是显式的。
    19. DT_RELSZ : 此元素持有DT_REL相应的重定位表的大小,以字节为单位。
    20. DT_RELENT : 此元素持有DT_REL相应的重定位表项的大小,以字节为单位。
    21. DT_PLTREL : 本成员指明了函数连接表所引用的重定位项的类型。 d_val成员含有DT_RELDT_RELA。函数连接表中的所有重定位类型都是相同的。
    22. DT_TEXTREL : 如果此元素出现的话,在重定位过程中如果需要修改的是只读段的话,链接器可以做相应的修改;而如果此元素不出现的话,在重定位过程中,即使需要,也不能修改只读段。
    23. DT_JMPREL : 此类型元素如果存在的话,其d_ptr成员含有与函数链接表单独关联的重定位项地址。把多个重定位项分开可以让动态链接器在初始化的时候忽略它们,当然前提条件是“后期绑定”是激活的。如果此元素存在的话,DT_PLTRELSZDT_PLTREL也应该出现。
    24. DT_BIND_NOW : 如果此元素存在的话,动态链接器必须在程序开始执行以前,完成所有包含此项的目标的重定位工作。如果此元素存在,即使程序应用了“后期绑定”,它对于此项所指定的目标也不适用,动态链接器仍需事先做好重定位。

    我们来看一下可执行文件动态段的解析过程,首先我们看到的程序头部信息如下:

    $ readelf -l hello
    
    Elf file type is DYN (Shared object file)
    Entry point 0x1060
    There are 13 program headers, starting at offset 64
    
    Program Headers:
      Type           Offset             VirtAddr           PhysAddr
                     FileSiz            MemSiz              Flags  Align
      PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040
                     0x00000000000002d8 0x00000000000002d8  R      0x8
      INTERP         0x0000000000000318 0x0000000000000318 0x0000000000000318
                     0x000000000000001c 0x000000000000001c  R      0x1
          [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
      LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                     0x00000000000005f8 0x00000000000005f8  R      0x1000
      LOAD           0x0000000000001000 0x0000000000001000 0x0000000000001000
                     0x00000000000001f5 0x00000000000001f5  R E    0x1000
      LOAD           0x0000000000002000 0x0000000000002000 0x0000000000002000
                     0x0000000000000160 0x0000000000000160  R      0x1000
      LOAD           0x0000000000002db8 0x0000000000003db8 0x0000000000003db8
                     0x0000000000000258 0x0000000000000260  RW     0x1000
      DYNAMIC        0x0000000000002dc8 0x0000000000003dc8 0x0000000000003dc8
                     0x00000000000001f0 0x00000000000001f0  RW     0x8
      NOTE           0x0000000000000338 0x0000000000000338 0x0000000000000338
                     0x0000000000000020 0x0000000000000020  R      0x8
      NOTE           0x0000000000000358 0x0000000000000358 0x0000000000000358
                     0x0000000000000044 0x0000000000000044  R      0x4
      GNU_PROPERTY   0x0000000000000338 0x0000000000000338 0x0000000000000338
                     0x0000000000000020 0x0000000000000020  R      0x8
      GNU_EH_FRAME   0x0000000000002014 0x0000000000002014 0x0000000000002014
                     0x0000000000000044 0x0000000000000044  R      0x4
      GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                     0x0000000000000000 0x0000000000000000  RW     0x10
      GNU_RELRO      0x0000000000002db8 0x0000000000003db8 0x0000000000003db8
                     0x0000000000000248 0x0000000000000248  R      0x1
    
     Section to Segment mapping:
      Segment Sections...
       00     
       01     .interp 
       02     .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt 
       03     .init .plt .plt.got .plt.sec .text .fini 
       04     .rodata .eh_frame_hdr .eh_frame 
       05     .init_array .fini_array .dynamic .got .data .bss 
       06     .dynamic 
       07     .note.gnu.property 
       08     .note.gnu.build-id .note.ABI-tag 
       09     .note.gnu.property 
       10     .eh_frame_hdr 
       11     
       12     .init_array .fini_array .dynamic .got 
    

    接着我们可以看到动态段的内容如下:

    $ objdump -s --section=.dynamic hello
    
    hello:     file format elf64-x86-64
    
    Contents of section .dynamic:
     3dc8 01000000 00000000 01000000 00000000  ................
     3dd8 0c000000 00000000 00100000 00000000  ................
     3de8 0d000000 00000000 e8110000 00000000  ................
     3df8 19000000 00000000 b83d0000 00000000  .........=......
     3e08 1b000000 00000000 08000000 00000000  ................
     3e18 1a000000 00000000 c03d0000 00000000  .........=......
     3e28 1c000000 00000000 08000000 00000000  ................
     3e38 f5feff6f 00000000 a0030000 00000000  ...o............
     3e48 05000000 00000000 70040000 00000000  ........p.......
     3e58 06000000 00000000 c8030000 00000000  ................
     3e68 0a000000 00000000 82000000 00000000  ................
     3e78 0b000000 00000000 18000000 00000000  ................
     3e88 15000000 00000000 00000000 00000000  ................
     3e98 03000000 00000000 b83f0000 00000000  .........?......
     3ea8 02000000 00000000 18000000 00000000  ................
     3eb8 14000000 00000000 07000000 00000000  ................
     3ec8 17000000 00000000 e0050000 00000000  ................
     3ed8 07000000 00000000 20050000 00000000  ........ .......
     3ee8 08000000 00000000 c0000000 00000000  ................
     3ef8 09000000 00000000 18000000 00000000  ................
     3f08 1e000000 00000000 08000000 00000000  ................
     3f18 fbffff6f 00000000 01000008 00000000  ...o............
     3f28 feffff6f 00000000 00050000 00000000  ...o............
     3f38 ffffff6f 00000000 01000000 00000000  ...o............
     3f48 f0ffff6f 00000000 f2040000 00000000  ...o............
     3f58 f9ffff6f 00000000 03000000 00000000  ...o............
     3f68 00000000 00000000 00000000 00000000  ................
     3f78 00000000 00000000 00000000 00000000  ................
     3f88 00000000 00000000 00000000 00000000  ................
     3f98 00000000 00000000 00000000 00000000  ................
     3fa8 00000000 00000000 00000000 00000000  ................
    

    虚拟的偏移地址为3dc8,跟我们目标匹配,这里我们看一下DT_NEEDED的数据为:3dc8 01000000 00000000 01000000 00000000;可以知道为DT_STRTAB的索引1的位置,DT_STRTAB的数据如下:3e48 05000000 00000000 70040000 00000000

    查看表信息如下:

    $ objdump -s --section=.dynstr hello
    
    hello:     file format elf64-x86-64
    
    Contents of section .dynstr:
     0470 006c6962 632e736f 2e360070 75747300  .libc.so.6.puts.
     0480 5f5f6378 615f6669 6e616c69 7a65005f  __cxa_finalize._
     0490 5f6c6962 635f7374 6172745f 6d61696e  _libc_start_main
     04a0 00474c49 42435f32 2e322e35 005f4954  .GLIBC_2.2.5._IT
     04b0 4d5f6465 72656769 73746572 544d436c  M_deregisterTMCl
     04c0 6f6e6554 61626c65 005f5f67 6d6f6e5f  oneTable.__gmon_
     04d0 73746172 745f5f00 5f49544d 5f726567  start__._ITM_reg
     04e0 69737465 72544d43 6c6f6e65 5461626c  isterTMCloneTabl
     04f0 6500                                 e.        
    

    注意,上面这些地址信息都是虚拟地址,并不是文件的偏移地址

    3.3.4 共享目标的依赖关系

    当动态链接器为一个目标文件创建内存段的时候,动态结构中的DT_NEEDED项会指明所依赖的库,动态链接器会链接被引用的符号和它们所依赖的库,这个过程会反复地执行,直到一个完整的进程镜像被构建好。当解析一个符号引用的时候,动态链接器以一种“广度优先”的算法来查找符号表。就是说,动态链接器首先查找可执行程序自己的符号表,然后是DT_NEEDED项所指明的库的符号表,再接下来是下一层依赖库的符号表,依次下去。共享目标文件必须是可读的,其它权限没有要求。

    即使一个共享目标在依赖关系中被引用多次,动态链接器也只会链接它一次。在依赖关系列表中的名字,即可以是DT_SONAME字符串,也可以是用于创建目标文件的共享目标文件的路径名。

    如果一个共享目标名字中含有斜线(/)字符,比如/usr/lib/lib2或者directory/file,动态链接就直接把字符串作为路径名。如果名字中没有斜线,比如lib1,需要根据以下三种规则来查找库文件:

    1. 第一,动态数组标记DT_RPATH可能给出了一个含有一系列目录名的字符串,各目录名以冒号:相隔。比如,如果字符串是/home/dir/lib:/home/dir2/lib:,表明动态链接器的查找路径依次是/home/dir/lib/home/dir2/lib和当前目录。

    2. 第二,进程的环境变量中会有一个LD_LIBRARY_PATH变量,它也含有一个目录名列表,各目录名以冒号:相隔,各目录名列表以分号;相隔(LD_LIBRARY_PATH路径的优先级要低于DT_RPATH所指明的路径)。

    3. 第三,如果如上两组路径都无法找到所要的库,动态链接库就搜索/usr/lib

    3.3.5 全局偏移量表

    全局偏移量表(global offset table)在私有数据中包含绝对地址。出于方便共享和重用的考虑,目标文件中的很多内容是“位置无关”的,其映射到进程内存中的什么位置是不一定的,所以只适合使用相对地址,全局偏移量表是一个例外。

    总的来说,位置独立的代码不能含有绝对的虚拟地址。全局偏移量表选择了在私有数据中含有绝对地址,这种办法在没有牺牲位置独立性和可共享性的前提下保存了绝对地址。引用全局偏移量表的程序可以同时使用位置独立的地址和绝对地址,把位置无关的引用重定向到绝对地址上去。

    如果一个程序要求直接访问符号的绝对地址,那么这个符号在全局偏移量表中就必须有一个对应的项。可执行文件和共享目标文件有各自的全局偏移量表,所以一个符号的地址可能会出现在多个表中。动态链接器会在程序开始执行之前,处理好所有全局偏移量表的重定位工作,所以在程序执行的时候,可以保证所有这些符号都有正确的绝对地址。

    全局偏移量表的第 0 项是保留的,它用于持有动态结构的地址,由符号DYNAMIC引用。这样,其它程序,比如动态链接器就可以直接找到其动态结构,而不用借助重定位项。这对于动态链接器来说尤为重要,因为它必须在不依赖于其它程序重定位其内存镜像的情况下初始化自己。在 Intel 架构中,全局偏移量表中的第 1 项和第 2 项也是保留的,它们持有函数连接表的信息

    系统可能为同一个共享目标在不同的程序中选择不同的段地址;甚至也可能每次为同一个程序选择不同的地址。但是,在单次执行中,一旦一个进程的镜像建立起来之后,直到程序退出,内存段的地址都不会再改变了。

    3.3.6 函数地址

    在可执行文件和共享目标文件中,当引用到同一个函数时,函数地址可能并不相同。在共享目标文件中,函数的地址被动态链接器正常地解析为它所在的虚拟地址。但在可执行文件中则不同,但可执行文件引用一个共享库中的函数时,它不是直接指向函数的虚拟地址,而是被动态链接器定向到函数链接表中的一个表项。

    但是,这样的话,来自可执行文件的函数地址和来自共享目标文件的同一函数地址就会不同,为了避免在比较两个函数地址时出现这样的逻辑错误,链接编辑器和动态链接器做了一些特别操作。当可执行文件引用一个在共享目标文件中定义的函数时,链接编辑器就把这个函数的函数链接表项的地址放到其相应的符号表项中去。动态链接器会特别对待这种符号表项。在可执行文件中,如果动态链接器查找一个符号时遇到了这种符号表项,就会按照以下规则行事:

    1. 如果符号表项的st_shndx成员不是SHN_UNDEF,动态链接器就找到了一个符号的定义,把表项的st_value成员作为符号的地址。

    2. 如果符号表项的st_shndx成员是SHN_UNDEF,并且符号类型是STT_FUNCst_value成员又非0的话,动态链接器就认定这是一个特殊的项,把st_value成员作为符号的地址。

    3. 否则,动态链接器认为这个符号是在可执行文件中未定义的。

    有些重定位与函数链接表项有关,这些表项用于给函数调用做定向,而不是引用函数地址。这种重定位不能像上面所描述的那样,用特别的方式去处理函数地址,因为动态链接器不可以把函数链接表项重定向到它们自己。

    3.3.7 函数链接表

    全局偏移量表用于把位置独立的地址重定向到绝对地址,与此功能类似,函数链接表(procedure linkage table)的作用是把位置独立的函数调用重定向到绝对地址。链接编辑器不能解析函数在不同目标文件之间的跳转,那么,它就把对其它目标文件中函数的调用重定向到一个函数链接表项中去。动态链接器决定目标的绝对地址,并且会相应地修改全局偏移量表的内存镜像。这样,动态链接器就可以在不牺牲位置无关性和代码的可共享性条件下,实现到绝对地址的重定位。可执行文件和共享目标文件有各自的函数链接表。

    关于PLT和GOT的关系和动态解析的过程见其他分析文章。

    3.3.8 解析符号

    在以下的这些步骤中,动态链接器与程序合作来解析函数链接表和全局偏移量表中所有的符号引用。

    1. 在一开始创建程序内存镜像的时候,动态链接器把全局偏移量表中的第2和第3个表项设为特定值。

    2. 如果函数连接表是位置独立的,全局偏移量表的地址必须存储在%ebx 中。进程空间中的每一个共享目标文件都有自己的函数连接表,每一个表都是用于本文件内的函数调用。那么,主调函数就要负责在调用函数连接表项之前设置全局偏移量表。

    环境变量LD_BIND_NOW可以改变动态连接器的行为,如果它的值为非NULL,动态连接器在传递控制权给程序之前会估计函数连接表项。否则,如果其值为NULL,这种估计仍然会进行,但并不是在初始化的时候,这个过程会被推后,直到在执行过程中,该函数连接表项被用到才开始。

    “延迟绑定/懒绑定” (lazy binding)一般来说都会提高应用程序的性能,因为这样可以避免用不到的符号在动态连接过程中被解析。但是,在两种情况下,延迟绑定的效果并不理想。

    1. 第一种情况,如果对一个共享目标函数的第一次引用比其后的引用要花更多时间的话,在第一次引用时,程序就要暂停下来,由动态连接器去解析符号,如果应用程序对这种不可预知的暂停比较敏感的话,后期绑定就不适用。

    2. 第二种情况,如果动态连接器解析一个符号失败,程序将会被终止。如果没有打开后期绑定的话,这一切都发生在程序实际得到控制权之前,进程将在初始化过程中被终止。而如果打开了后期绑定的话,错误会发生在程序运行过程中,如果应用程序对这种不可预知的错误敏感的话,后期绑定也不适用。

    3.4 哈希表

    一个 Elf32_Word 目标组成的哈希表支持符号表的访问。下面表示一个哈希表的具体结构

    内容(4字节)
    nbucket
    nchain
    bucket[0]

    bucket[nbucket - 1]
    chain[0]

    chain[nchain - 1]

    Bucket 数组中含有 nbucket 个项, chain 数组中含有 nchain 个项,序号都从 0 开始。 Bucket 和 chain 中包含的都是符号表中的索引。符号表中的项数必须等于nchain,所以符号表中的索引号也可以用来索引 chain 表一个哈希数输入一个符号名,输出一个值用于计算 bucket 索引。如果给出一个符号名,经哈希函数计算得到值 x,那么 x%nbucket 是 bucket 表内的索引, bucket[x%nbucket]给出一个符号表的索引值 y, y 同时也是 chain 表内的索引值。如果符号表内索引值为 y 的元素并不是所要的,那么 chain[y]给出符号表中下一个哈希值相同的项的索引。如果所有哈希值相同的项都不是所要的,最后的一个 chain[y]将包含值STN_UNDEF,说明这个符号表中并不含有此符号。

    tatic unsigned elfhash(const char *_name) {
       const unsigned char *name = (const unsigned char *) _name;
       unsigned h = 0, g;
       while(*name) {
           h = (h << 4) + *name++;
           g = h & 0xf0000000;
           h ^= g;
           h ^= g >> 24;
       }
       return h;
    }
    
    static Elf32_Sym *soinfo_elf_lookup(struct soinfo *si, unsigned hash, const char *name) {
        Elf32_Sym *symtab = si->symtab;
        const char *strtab = si->strtab;
        unsigned n;
    
        for( n = si->bucket[hash % si->nbucket]; n != 0; n = si->chain[n] ) {
            Elf32_Sym *s = symtab + n;
            if( strcmp(strtab + s->st_name, name) == 0 ) {
                return s;
            }
        }
    
        return NULL;
    }
    

    3.5 初始化和终止函数

    当动态链接器构建好进程镜像,并完成重定位后,每一个共享目标都有机会执行一些初始化代码。所有共享目标的初始化都发生在程序开始执行前。

    一个目标的初始化代码被执行以前,必须保证它所依赖的所有目标已经被初始化过。这里所说的“依赖”,即出现在动态结构的DT_NEEDED项里。如果两个目标,它们互相依赖,或者彼此间的依赖关系构成环状的话,哪个应该被先初始化,这里未作定义。例如:

    1. 如果一个目标A依赖于另外一个目标B,而目标B又依赖于目标C的话。当需要对 做初始化时,应先递规地初始化B和C,即先初始化 C,然后是,最后是A。

    2. 如果一个目标A依赖于另处两个目标B和C,而B和C之间没有依赖关系的话,B和C谁先被初始化都可以。

    与初始化过程相似,每一个共享目标还可以有终止函数,将在进程准备终止的时候被调用。动态链接器调用终止函数的顺序正好与初始化过程相反,如果一个目标没有定义初始化函数的话,动态链接器应假设它有一个空的初始化函数并且被调用,并按照相反的顺序来调用其终止函数。

    动态链接器必须保证,无论是初始化函数还是终止函数都不能被重复调用。共享目标把初始化和终止函数分别定义在动态结构的DT_INITDT_FINI项中,初始化和终止函数的代码存放在.init.fini节中。

    我们看一下这两个函数的寻找过程:

    $ objdump  -s --section=.dynamic hello
    
    hello:     file format elf64-x86-64
    
    Contents of section .dynamic:
     3dc8 01000000 00000000 01000000 00000000  ................
     3dd8 0c000000 00000000 00100000 00000000  ................
     3de8 0d000000 00000000 e8110000 00000000  ................
     3df8 19000000 00000000 b83d0000 00000000  .........=......
     3e08 1b000000 00000000 08000000 00000000  ................
     3e18 1a000000 00000000 c03d0000 00000000  .........=......
     3e28 1c000000 00000000 08000000 00000000  ................
     3e38 f5feff6f 00000000 a0030000 00000000  ...o............
     3e48 05000000 00000000 70040000 00000000  ........p.......
     3e58 06000000 00000000 c8030000 00000000  ................
     3e68 0a000000 00000000 82000000 00000000  ................
     3e78 0b000000 00000000 18000000 00000000  ................
     3e88 15000000 00000000 00000000 00000000  ................
     3e98 03000000 00000000 b83f0000 00000000  .........?......
     3ea8 02000000 00000000 18000000 00000000  ................
     3eb8 14000000 00000000 07000000 00000000  ................
     3ec8 17000000 00000000 e0050000 00000000  ................
     3ed8 07000000 00000000 20050000 00000000  ........ .......
     3ee8 08000000 00000000 c0000000 00000000  ................
     3ef8 09000000 00000000 18000000 00000000  ................
     3f08 1e000000 00000000 08000000 00000000  ................
     3f18 fbffff6f 00000000 01000008 00000000  ...o............
     3f28 feffff6f 00000000 00050000 00000000  ...o............
     3f38 ffffff6f 00000000 01000000 00000000  ...o............
     3f48 f0ffff6f 00000000 f2040000 00000000  ...o............
     3f58 f9ffff6f 00000000 03000000 00000000  ...o............
     3f68 00000000 00000000 00000000 00000000  ................
     3f78 00000000 00000000 00000000 00000000  ................
     3f88 00000000 00000000 00000000 00000000  ................
     3f98 00000000 00000000 00000000 00000000  ................
     3fa8 00000000 00000000 00000000 00000000  ................
    

    3dd8 0c000000 00000000 00100000 000000003de8 0d000000 00000000 e8110000 00000000 分别指示了.init.fini的初始化和终止函数,这两个节的反汇编内容如下:

    $ objdump -d --section=.init hello 
    
    hello:     file format elf64-x86-64
    
    
    Disassembly of section .init:
    
    0000000000001000 <_init>:
        1000:	f3 0f 1e fa          	endbr64 
        1004:	48 83 ec 08          	sub    $0x8,%rsp
        1008:	48 8b 05 d9 2f 00 00 	mov    0x2fd9(%rip),%rax        # 3fe8 <__gmon_start__>
        100f:	48 85 c0             	test   %rax,%rax
        1012:	74 02                	je     1016 <_init+0x16>
        1014:	ff d0                	callq  *%rax
        1016:	48 83 c4 08          	add    $0x8,%rsp
        101a:	c3                   	retq   
    
    
    $ objdump -d --section=.fini hello 
    
    hello:     file format elf64-x86-64
    
    
    Disassembly of section .fini:
    
    00000000000011e8 <_fini>:
        11e8:	f3 0f 1e fa          	endbr64 
        11ec:	48 83 ec 08          	sub    $0x8,%rsp
        11f0:	48 83 c4 08          	add    $0x8,%rsp
        11f4:	c3                   	retq   
    
    
    更多相关内容
  • 该库用于使用Go编程语言读取ELF文件。 Go的标准库已经包含与ELF相关的功能,但是这些功能不包含一些有用的功能,这些功能可以直接显示或访问ELF文件的某些方面。 该库支持大端和小端32位和64位ELF文件。 您可以通过...
  • windows中查看elf文件

    2022-05-10 13:31:51
    查看elf文件的工具包,包含有readelf.exe,nm.exe,objdump.exe以及如果不能运行,确实libiconv-2.dll这个组件也包含在里面 除了上述命令行工具,还有一个可视化的FileViewPro
  • 操作流程测试用例介绍:本elf注入的功能是在elf文件执行前,生成helloworld文件并写入内容:helloworld,之后再执行原elf文件功能gcc main.c -o main # 生成注入函数# 测试文件功能:打印 This is the program, ...
  • 如何将elf文件转换为hex文件 elf(Executable and Linkable Format)文件一般是由gcc编译器生成的,在Linux开发环境使用较多,但Windows一般情况下需要使用hex文件来进行烧录,那么如何将elf格式转换为hex格式呢?...
  • 新建的是helloworld工程模板,生成elf文件后右键helloworld工程,选择Run As----Run Configuration,出现的错误是:An internal error occurred during: Launching hello_world_0 Debug .
  • ELF文件格式分析.pdf

    2021-06-24 17:38:11
    ELF文件格式分析
  • elf文件转换为hex文件的小程序,使用前安装arm-none-eabi编译工具链,复制到elf文件所在目录双击执行,若当前文件没有.elf文件,则会提示 No such file,若转换成功则会在目录内生成HexFile.hex文件
  • 适用于Windows,Linux和MacOS的ELF文件查看器/编辑器。 如何在Linux上构建 安装Qt 5.15.2: : 克隆项目:git clone --recursive 编辑build_lin64.bat(检查QT_PATH变量) 运行build_lin64.bat 如何在OSX上构建 ...
  • pyelfwriter 这是一个非常简单的库,用于... 所有其他(pyelftools、pydevtools、pylibelf)python ELF 库更适合读取现有的 ELF 文件,但它们都不允许从头开始创建 ELF 文件。 有关用法,请参阅文件末尾的测试示例。
  • C#的elf文件解析库

    2019-01-10 23:31:19
    nupkg格式的C#库文件,专门用来解析elf文件的,使用的时候先加载安装这个库,怎么安装自行百度C# nupkg, 然后using包含ELFSharp.ELF.XXX的命名空间,然后就可以操作对应的函数了, 实例: var elf = ELFReader....
  • Elf文件解析工具v1.5

    2021-03-10 00:04:05
    支持keil、ccs、gcc等编译器产生的elf文件。支持全部类型elf、coredump文件的program、section、symbol等信息解析。 2021更新内容: 1. 增加rel、rela重定位文件解析。 2. 增加note 程序段解析。 3. 丰富symbol搜索...
  • ELF文件格式分析.pdf》文档,非常不错的elf格式参考文档,参考elf解析过程,能很快掌握elf文件格式
  • 小精灵包解析可执行链接文件(Unix ELF二进制文件)。 有关一般参考,请参见 。 脚本用法elf.py <输入ELF文件名>特征32位和64位ELF解析ELF类,方便抽象ElfBytes类用于字节级操作简要检查动态链接依赖关系查找
  • ELF(Executable and Linkable Format)即可执行连接文件格式,是Linux,SVR4和Solaris2.0默认的目标文件格式,目前标准接口委员...分析elf文件有助于理解一些重要的系统概念,例如程序的编译和链接,程序的加载和运行等
  • 西北工业大学操作系统实验 解析ELF文件
  • 本文 是作者在探索系统软件构件的复用技术的过程中生成的技术笔记,重点分析了 UNIX 类操作系统中普遍采用的目标文件格式 ELF(Executable and Linkable Format),目的是研究操作系统中二进制级软件构件的静态、动态...
  • Elf文件格式

    2017-11-01 11:59:14
    Elf文件格式中文版.Android逆向之旅---SO(ELF)文件格式详解
  • ELF文件信息隐藏.zip

    2019-12-30 22:17:54
    详细分析了Linux平台下标准的可执行文件格式ELF(Executable and Linking Format),并针对ELF文件提出了一系列可以用来信息隐藏的方案,利用MATLAB等工具,通过实验进行验证,并对这些方案进行优缺点分析。...
  • ELF文件格式简介

    千次阅读 2022-05-03 23:47:05
      简单了解下ELF文件的格式。 1 简介   可执行与可链接格式 (Executable and Linkable Format,ELF),常被称为 ELF格式,是一种用于可执行文件、目标代码、共享库和核心转储(core dump)的标准文件格式,...

      简单了解下ELF文件的格式。

    1 简介

      可执行与可链接格式 (Executable and Linkable Format,ELF),常被称为 ELF格式,是一种用于可执行文件、目标代码、共享库和核心转储(core dump)的标准文件格式,一般用于类Unix系统,比如Linux,Macox等。ELF 格式灵活性高、可扩展,并且跨平台。比如它支持不同的字节序和地址范围,所以它不会不兼容某一特别的 CPU 或指令架构。这也使得 ELF 格式能够被运行于众多不同平台的各种操作系统所广泛采纳。
      ELF文件一般由三种类型的文件:

    • 可重定向文件:文件保存着代码和适当的数据,用来和其他的目标文件一起来创建一个可执行文件或者是一个共享目标文件。比如编译的中间产物.o文件;
    • 可执行文件:一个可执行文件;
    • 共享目标文件:共享库。文件保存着代码和合适的数据,用来被下连接编辑器和动态链接器链接。比如linux下的.so文件。

    2 ELF文件格式

      在编译过程中ELF文件格式在链接和程序的运行阶段的格式不同。链接阶段每个.o文件都是一个独立的ELF文件,为了效率和便利性他们的段需要进行合并才能生成对应的可执行文件。
    在这里插入图片描述

      ELF文件包含一个Header描述文件的基本信息;程序头表告诉徐彤如何构建进程的内存镜像,因此只有可执行文件由程序头表;Sections描述了链接过程中的需要的符号表、数据、指令等信息,而在可执行文件中是Segments,是经过合并的Secitons;节/段头表指明了对应section/segment在文件中的偏移,链接阶段的ELF文件必须包含该表头;而每个节/段头描述了对应的section/segment的大小,入口等基本信息。

      下图是32bit系统下面使用的字段的数大小,64bit系统类似,之后不在赘述。
    在这里插入图片描述

    2.1 ELF Header

      ELF文件头描述了ELF文件的基本类型,地址偏移等信息,分为32bit和64bit两个版本,定义于linux源码的/usr/include/elf.h文件中。

    #define EI_NIDENT	16
    
    typedef struct elf32_hdr{
      unsigned char	e_ident[EI_NIDENT];
      Elf32_Half	e_type;
      Elf32_Half	e_machine;
      Elf32_Word	e_version;
      Elf32_Addr	e_entry;  /* Entry point */
      Elf32_Off	e_phoff;
      Elf32_Off	e_shoff;
      Elf32_Word	e_flags;
      Elf32_Half	e_ehsize;
      Elf32_Half	e_phentsize;
      Elf32_Half	e_phnum;
      Elf32_Half	e_shentsize;
      Elf32_Half	e_shnum;
      Elf32_Half	e_shstrndx;
    } Elf32_Ehdr;
    
    typedef struct elf64_hdr {
      unsigned char	e_ident[EI_NIDENT];	/* ELF "magic number" */
      Elf64_Half e_type;
      Elf64_Half e_machine;
      Elf64_Word e_version;
      Elf64_Addr e_entry;		/* Entry point virtual address */
      Elf64_Off e_phoff;		/* Program header table file offset */
      Elf64_Off e_shoff;		/* Section header table file offset */
      Elf64_Word e_flags;
      Elf64_Half e_ehsize;
      Elf64_Half e_phentsize;
      Elf64_Half e_phnum;
      Elf64_Half e_shentsize;
      Elf64_Half e_shnum;
      Elf64_Half e_shstrndx;
    } Elf64_Ehdr;
    

      从上面的结构中能够看出32bit和64bit的区别仅仅是字长的区别,字段上没有实际上的差别。每个字段的含义如下:

    • e_ident:ELF文件的描述,是一个16字节的标识,表明当前文件的数据格式,位数等:
      • [0,3]字节为魔数,即e_ident[EI_MAG0-EI_MAG3],取值为固定的0x7f E L F,标记当前文件为一个ELF文件;
      • [4,4]字节为EI_CLASSe_ident[EI_CLASS],表明当前文件的类别:
        • 0:表示非法的类别;
        • 1:表示32bit;
        • 2:表示64bit;
      • [5,5]字节为EI_DATAe_ident[EI_DATA],表明当期那文件的数据排列方式:
        • 0表示非法;
        • 1表示小端;
        • 2表示大端;
      • [6,6]字节为EI_VERSIONe_ident[EI_VERSION],表明当前文件的版本,目前该取值必须为EV_CURRENT即1;
      • [7,7]字节为EI_PADe_ident[EI_PAD]表明e_ident中未使用的字节的起点(值是相对于e_ident[EI_PAD+1]的偏移),未使用的字节会被初始化为0,解析ELF文件时需要忽略对应的字段;

      EI_MAG0,EI_MAG1,EI_MAG2,EI_MAG3,EI_CLASS,EI_DATA,EI_VERSION,EI_OSABI,EI_PAD是linux源码中定义的宏,取值分别为0-7,分别对应各个字段的下标;下面的宏定义将采用类似EI_MAG0(0)的方式,表示EI_MAG0的值为0。

    • e_type:文件的标识字段标识文件的类型;
      • ET_NONE(0):未知的文件格式;
      • ET_REL(1):可重定位文件,比如目标文件;
      • ET_EXEC(2):可执行文件;
      • ET_DYN(3):共享目标文件;
      • ET_CORE(4):Core转储文件,比如程序crash之后的转储文件;
      • ET_LOPROC(0xff00):特定处理器的文件标识;
      • ET_HIPROC(0xffff):特定处理器的文件标识;
      • [ET_LOPROC,ET_HIPROC]之间的值用来表示特定处理器的文件格式;
    • e_machine:目标文件的体系结构(下面列举了少数处理器架构,具体ELF文件支持的架构在对应的文件中查看即可);
      • ET_NONE(0):未知的处理器架构;
      • EM_M32(1):AT&T WE 32100;
      • EM_SPARC(2):SPARC;
      • EM_386(3):Intel 80386;
      • EM_68K(4):Motorola 68000;
      • EM_88K(5):Motorola 88000;
      • EM_860(6):Intel 80860;
      • EM_MIPS(7):MIPS RS3000大端;
      • EM_MIPS_RS4_BE(10):MIPS RS4000大端;
      • 其他,预留;
    • e_version:当前文件的版本;
      • EV_NONE(0):非法的版本;
      • EV_CURRENT(`):当前版本;
    • e_entry:程序的虚拟入口地址,如果文件没有对应的入口可以为0;
    • e_phoff:文件中程序头表的偏移(bytes),如果文件没有该项,则应该为0;
    • e_shoff:文件中段表/节表的偏移(bytes),如果文件没有该项,则应该为0;
    • e_flags:处理器相关的标志位,宏格式为EF_machine_flag比如EF_MIPS_PIC
    • e_ehsize:ELF文件头的大小(bytes);
    • e_phentsize:程序头表中单项的大小,表中每一项的大小相同;
    • e_phnum:程序头表中的项数,也就是说程序头表的实际大小为ephentsize x e_phnum,如果文件中没有程序头表该项为0;
    • e_shentsize:节表中单项的大小,表中每一项的大小相同;
    • e_shnum:节表中项的数量;
    • e_shstrndx:节表中节名的索引,如果文件没有该表则该项为SHN_UNDEF(0)

    2.2 程序头表(Program Header Table)

      可执行文件或者共享目标文件的程序头部是一个结构数组,每个结构描述了一个段 或者系统准备程序执行所必需的其它信息。程序头表描述了ELF文件中Segment在文件中的布局,描述了OS该如何装载可执行文件到内存。程序头表的表项的描述如下,类似于ELF Header也有32和64位两个版本。

    typedef struct elf32_phdr {
    	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;
    	Elf32_Word p_align;
    } Elf32_Phdr;
    
    typedef struct elf64_phdr {
    	Elf64_Word p_type;
    	Elf64_Word p_flags;
    	Elf64_Off p_offset;	/* Segment file offset */
    	Elf64_Addr p_vaddr;	/* Segment virtual address */
    	Elf64_Addr p_paddr;	/* Segment physical address */
    	Elf64_Xword p_filesz;	/* Segment size in file */
    	Elf64_Xword p_memsz;	/* Segment size in memory */
    	Elf64_Xword p_align;	/* Segment alignment, file & memory */
    } Elf64_Phdr;
    
    • p_type:当前Segment的类型;
      • PT_NULL(0):当前项未使用,项中的成员是未定义的,需要忽略当前项;
      • PT_LOAD(1):当前Segment是一个可装载的Segment,即可以被装载映射到内存中,其大小由p_fileszp_memsz描述。如果p_memsz>p_filesz则剩余的字节被置零,但是p_filesz>p_memsz是非法的。动态库一般包含两个该类型的段:代码段和数据段;
      • PT_DYNAMIC(2):动态段,动态库特有的段,包含了动态链接必须的一些信息,比如需要链接的共享库列表、GOT等等;
      • PT_INTERP(3):当前段用于存储一段以NULL为结尾的字符串,该字符串表明了程序解释器的位置。且当前段仅仅对于可执行文件有实际意义,一个可执行文件中不能出现两个当前段,如果一个文件中包含当前段。比如/lib64/ld-linux-x86-64.so.2
      • PT_NOTE(4):用于保存与特定供应商或者系统相关的附加信息以便于兼容性、一致性检查,但是实际上只保存了操作系统的规范信息;
      • PT_SHLIB(5):保留段;
      • PT_PHDR(6):保存程序头表本身的位置和大小,当前段不能在文件中出现一次以上,且仅仅当程序表头为内存映像的一部分时起作用,它必须在所有加载项目之前;
      • [PT_LPROC(0x70000000),PT_HIPROC(0x7fffffff)]:该范围内的值用作预留;
    • p_offset:当前段相对于文件起始位置的偏移量;
    • p_vaddr:段的第一个字节将被映射到到内存中的虚拟地址;
    • p_paddr:此成员仅用于与物理地址相关的系统中。因为 System V 忽略所有应用程序的物理地址信息,此字段对与可执行文件和共享目标文件而言具体内容是指定的;
    • p_filesz:段在文件映像中所占的字节数,可能为 0;
    • p_memsz:段在内存映像中占用的字节数,可能为 0;
    • p_flags:段相关的标志;
    • p_align:段在文件中和内存中如何对齐。可加载的进程段的p_vaddr和- p_offset取值必须合适,相对于对页面大小的取模而言;
      • 0和1表示不需要对齐;
      • 其他值必须为2的幂次方,且必须 p _ a d d r ∣ p _ a l i g n = = p _ o f f s e t ∣ p a l i g n p\_addr|p\_align==p\_offset| p_align p_addrp_align==p_offsetpalign

    2.3 节头表(Section Header Table)

      节头表描述了ELF文件中的节的基本信息。可执行文件不一定由节头表但是一定有节,节头表可利用特殊的方式去除。

      段和节的区别是:

    • 段包含了程序装载可执行的基本信息,段告诉OS如何装载当前段到虚拟内存以及当前段的权限等和执行相关的信息,一个段可以包含0个或多个节;
    • 节包含了程序的代码和数据等内容,链接器会将多个节合并为一个段。
    typedef struct elf32_shdr {
      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;
    
    typedef struct elf64_shdr {
      Elf64_Word sh_name;		/* Section name, index in string tbl */
      Elf64_Word sh_type;		/* Type of section */
      Elf64_Xword sh_flags;		/* Miscellaneous section attributes */
      Elf64_Addr sh_addr;		/* Section virtual addr at execution */
      Elf64_Off sh_offset;		/* Section file offset */
      Elf64_Xword sh_size;		/* Size of section in bytes */
      Elf64_Word sh_link;		/* Index of 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:值是节名称在字符串表中的索引;
    • sh_type:描述节的类型和语义;
      • SHT_NULL(0):当前节是非活跃的,没有一个对应的具体的节内存;
      • SHT_PROGBITS(1):包含了程序的指令信息、数据等程序运行相关的信息;
      • SHT_SYMTAB(2):保存了符号信息,用于重定位;
        • 此种类型节的sh_link存储相关字符串表的节索引,sh_info存储最后一个局部符号的符号表索引+1;
      • SHT_DYNSYM(11):保存共享库导入动态符号信息;
        • 此种类型节的sh_link存储相关字符串表的节索引,sh_info存储最后一个局部符号的符号表索引+1;
      • SHT_STRTAB(3):一个字符串表,保存了每个节的节名称;
      • SHT_RELA(4):存储可重定位表项,可能会有附加内容,目标文件可能有多个可重定位表项;
        • 此种类型节的sh_link存储相关符号表的节索引,sh_info存储重定位所使用节的索引;
      • SHT_HASH(5):存储符号哈希表,所有参与动态链接的目标只能包含一个哈希表,一个目标文件只能包含一个哈希表;
        • 此种类型节的sh_link存储哈希表所使用的符号表的节索引,sh_info为0;
      • SHT_DYAMIC(6):存储包含动态链接的信息,一个目标文件只能包含一个;
        • 此种类型的节的sh_link存储当前节中使用到的字符串表格的节的索引,sh_info为0;
      • SHT_NOTE(7):存储以某种形式标记文件的信息;
      • SHT_NOBITS(8):这种类型的节不占据文件空间,但是成员sh_offset依然会包含对应的偏移;
      • SHT_REL(9):包含可重定位表项,无附加内容,目标文件可能有多个可重定位表项;
        • 此种类型节的sh_link存储相关符号表的节索引,sh_info存储重定位所使用节的索引;
      • SHT_SHLIB(10):保留区,包含此节的程序与ABI不兼容;
      • [SHT_LOPROC(0x70000000),SHT_HIPROC(0x7fffffff)]:留给处理器专用语义;
      • [SHT_LOUSER(0x80000000),SHT_HIUSER(0xffffffff)]:预留;
    • sh_flags:1bit位的标志位;
      • SHF_WRITE(0x1):当前节包含进程执行过程中可写的数据;
      • SHF_ALLOC(0x2):当前节在运行阶段占据内存;
      • SHF_EXECINSTR(0x4):当前节包含可执行的机器指令;
      • SHF_MASKPROC(0xf0000000):所有包含当前掩码都表示预留给特定处理器的;
    • sh_addr:如果当前节需要被装载到内存,则当前项存储当前节映射到内存的首地址,否则应该为0;
    • sh_offset:当前节的首地址相对于文件的偏移;
    • sh_size:节的大小。但是对于类型为SHT_NOBITS的节,当前值可能不为0但是在文件中不占据任何空间;
    • sh_link:存储节投标中的索引,表示当前节依赖于对应的节。对于特定的节有特定的含义,其他为SHN_UNDEF
    • sh_info:节的附加信息。对于特定的节有特定的含义,其他为0
    • sh_addralign:地址约束对齐,值应该为0或者2的幂次方,0和1表示未进行对齐;
    • sh_entsize:某些节是一个数组,对于这类节当前字段给出数组中每个项的字节数,比如符号表。如果节并不包含对应的数组,值应该为0。

    2.3 一些特殊的节

      ELF文件中有一些预定义的节来保存程序、数据和一些控制信息,这些节被用来链接或者装载程序。每个操作系统都支持一组链接模式,主要分为两类(也就是常说的动态库和静态库):

    • Static:静态绑定的一组目标文件、系统库和库档案(比如静态库),解析包含的符号引用并创建一个完全自包含的可执行文件;
    • Dynamic:一组目标文件、库、系统共享资源和其他共享库链接在一起创建可执行文件。当加载此可执行文件时必须使系统中其他共享资源和动态库可用,程序才能正常运行。

      库文件无论是动态库还是静态库在其文件中都包含对应的节,一些特殊的节其功能如下:

    • .bss,类型SHT_NOBITS,属性SHF_ALLOC|SHF_WRITE:存储未经初始化的数据。根据定义程序开始执行时,系统会将这些数据初始化为0,且此节不占用文件空间;
    • .comment,类型SHT_PROGBITS,属性none:存储版本控制信息;
    • .data,类型SHT_PROGBITS,属性SHF_ALLOC|SHF_WRITE:存放初始化的数据;
    • .data1,类型SHT_PROGBITS,属性SHF_ALLOC|SHF_WRITE:存放初始化的数据;
    • .debug,类型SHT_PROGBITS,属性none:存放用于符号调试的信息;
    • .dynamic,类型SHT_DYNAMIC,属性SHF_ALLOC,是否有属性SHF_WRITE屈居于处理器:包含动态链接的信息,
    • .hash,类型SHT_HASH,属性SHF_ALLOC
    • .line,类型SHT_PROGBITS,属性none:存储调试的行号信息,描述源代码和机器码之间的对应关系;
    • .note,类型SHT_NOTE,属性none
    • .rodata,类型SHT_PROGBITS,属性SHF_ALLOC:存储只读数据;
    • .rodata1,类型SHT_PROGBITS,属性SHF_ALLOC:存储只读数据;
    • .shstrtab,类型SHT_STRTAB,属性none:存储节的名称;
    • .strtab,类型SHT_STRTAB:存储常见的与符号表关联的字符串。如果文件有一个包含符号字符串表的可加载段,则该段的属性将包括 SHF_ALLOC 位; 否则,该位将关闭;
    • .symtab,类型SHT_SYMTAB,属性``````:存储一个符号表。如果文件具有包含符号表的可加载段,则该节的属性将包括 SHF_ALLOC 位;否则,该位将关闭;
    • .text,类型SHT_PROGBITS,属性SHF_ALLOC|SHF_EXECINSTR:存储程序的代码指令;
    • .dynstr,类型SHT_STRTAB,属性SHF_ALLOC:存储动态链接所需的字符串,最常见的是表示与符号表条目关联的名称的字符串;
    • .dynsym,类型SHT_DYNSYM,属性SHF_ALLOC:存储动态链接符号表;
    • .fini,类型SHT_PROGBITS,属性SHF_ALLOC|SHF_EXECINSTR:存储有助于进程终止代码的可执行指令。 当程序正常退出时,系统执行本节代码;
    • .init,类型SHT_PROGBITS,属性SHF_ALLOC|SHF_EXECINSTR:存储有助于进程初始化代码的可执行指令。 当程序开始运行时,系统会在调用主程序入口点(C 程序称为 main)之前执行本节中的代码;
    • .interp,类型SHT_PROGBITS:保存程序解释器的路径名。 如果文件有一个包含该节的可加载段,则该节的属性将包括 SHF_ALLOC 位; 否则,该位将关闭;
    • .relname,类型SHT_REL:包含重定位信息。如果文件具有包含重定位的可加载段,则这些部分的属性将包括 SHF_ALLOC 位;否则,该位将关闭。通常,名称由 重定位适用的部分。因此.text的重定位部分通常具有名称.rel.text.rela.text
    • .relaname,类型SHT_RELA:同relname
    • 其他:对于C++程序有些版本会有.ctors(有时也会是.init_array,见Can’t find .dtors and .ctors in binary)和dtors两个节存储构造和析构相关的代码。

      带有点 (.) 前缀的部分名称是为系统保留的,但如果它们的现有含义令人满意,应用程序可以使用这些部分。 应用程序可以使用不带前缀的名称以避免与系统部分冲突。 目标文件格式允许定义不在上面列表中的部分。 一个目标文件可能有多个同名的部分。

    2.4 字符串表

      字符串表是一个存储字符串的表格,而每个字符串是以NULL也就是\0为结尾的。字符串表格中索引为0处的字符串被定义为空字符串。符号表中保存的字符串是节名和目标文件中使用到的符号。而需要使用对应字符串时,只需要在需要使用的地方指明对应字符在字符串表中的索引即可,使用的字符串就是索引处到第一个\0之间的字符串。
    在这里插入图片描述

    2.5 符号表

      目标文件的符号表包含定位和重定位程序的符号定义和引用所需的信息。符号表索引是该数组的下标。索引0既指定表中的第一个条目,又用作未定义的符号索引。

    typedef struct elf32_sym{
      Elf32_Word	st_name;
      Elf32_Addr	st_value;
      Elf32_Word	st_size;
      unsigned char	st_info;
      unsigned char	st_other;
      Elf32_Half	st_shndx;
    } Elf32_Sym;
    
    typedef struct elf64_sym {
      Elf64_Word st_name;		/* Symbol name, index in string tbl */
      unsigned char	st_info;	/* Type and binding attributes */
      unsigned char	st_other;	/* No defined meaning, 0 */
      Elf64_Half st_shndx;		/* Associated section index */
      Elf64_Addr st_value;		/* Value of the symbol */
      Elf64_Xword st_size;		/* Associated symbol size */
    } Elf64_Sym;
    
    • st_name:存储一个指向字符串表的索引来表示对应符号的名称;
    • st_value:存储对应符号的取值,具体值依赖于上下文,可能是一个指针地址,立即数等。另外,不同对象文件类型的符号表条目对 st_value 成员的解释略有不同:
      • 在重定位文件中在可重定位文件中,st_value保存节索引为SHN_COMMON的符号的对齐约束;
      • 在可重定位文件中,st_value保存已定义符号的节偏移量。 也就是说,st_value是从st_shndx标识的部分的开头的偏移量;
      • 在可执行文件和共享对象文件中,st_value保存一个虚拟地址。 为了使这些文件的符号对动态链接器更有用,节偏移(文件解释)让位于与节号无关的虚拟地址(内存解释)。
    • st_size:符号的大小,具体指为sizeof(instance),如果未知则为0;
    • st_info:指定符号的类型和绑定属性。可以用下面的代码分别解析出bind,type,info三个属性:
    #define ELF32_ST_BIND(i) ((i)>>4) 
    #define ELF32_ST_TYPE(i) ((i)&0xf) 
    #define ELF32_ST_INFO(b,t) (((b)<<4)+((t)&0xf))
    
    • BIND
      • STB_LOCAL(0):局部符号在包含其定义的目标文件之外是不可见的。 同名的本地符号可以存在于多个文件中,互不干扰;
      • STB_GLOBAL(1):全局符号对所有正在组合的目标文件都是可见的。 一个文件对全局符号的定义将满足另一个文件对同一全局符号的未定义引用;
      • STB_WEAK(2):弱符号类似于全局符号,但它们的定义具有较低的优先级;
      • [STB_LOPROC(13),STB_HIPROC(15)]:预留位,用于特殊处理器的特定含义;
    • TYPE
      • STT_NOTYPE(0):符号的类型未指定;
      • STT_OBJECT(1):符号与数据对象相关联,例如变量、数组等;
      • STT_FUNC(2):符号与函数或其他可执行代码相关联;
      • STT_SECTION(3):该符号与一个节相关联。 这种类型的符号表条目主要用于重定位,通常具有STB_LOCALBIND属性;
      • STT_FILE(4):一个有STB_LOCAL的BIND属性的文件符号的节索引为SHN_ABS。并且如果存在其他STB_LOCAL属性的符号,则当前符号应该在其之前;
      • [STT_LOPROC(13),STT_HIPROC(15)]:预留位,用于特殊处理器的特定含义;
    • INFO
      • SHN_ABS:符号有一个绝对值,不会因为重定位而改变;
      • SHN_COMMON:该符号标记尚未分配的公共块。 符号的值给出了对齐约束,类似于节的 sh_addralign 成员。 也就是说,链接编辑器将为符号分配存储空间,该地址是 st_value 的倍数。 符号的大小表明需要多少字节;
      • SHN_UNDEF:此节表索引表示该符号未定义。 当链接编辑器将此对象文件与另一个定义指定符号的文件组合时,此文件对符号的引用将链接到实际定义;
    • st_other:该成员当前持有 0 并且没有定义的含义;
    • st_shndx:每个符号都有属于的节,当前成员存储的就是对应节的索引。

    3 ELF文件示例

      下面是使用下面的代码编译生成动态库libadd.so作为示例:

    //add.h
    int add(int a, int b);
    static int mult(int a, int b);
    
    //add.c
    //编译命令gcc add.c -shared -o libadd.so
    extern int extern_value;
    static int static_value = 1;
    static int static_value1;
    
    int add(int a, int b){
        return 0;
    }
    
    static int mult(int a, int b){
        return 1;
    }
    

    3.1 ELF Header

      使用命令readelf -h <ELF文件名>查看ELF文件的Header。

    //readelf -h libadd.so
    ELF Header:
      Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
      Class:                             ELF64
      Data:                              2's complement, little endian
      Version:                           1 (current)
      OS/ABI:                            UNIX - System V
      ABI Version:                       0
      Type:                              DYN (Shared object file)
      Machine:                           Advanced Micro Devices X86-64
      Version:                           0x1
      Entry point address:               0x4a0
      Start of program headers:          64 (bytes into file)
      Start of section headers:          6000 (bytes into file)
      Flags:                             0x0
      Size of this header:               64 (bytes)
      Size of program headers:           56 (bytes)
      Number of program headers:         7
      Size of section headers:           64 (bytes)
      Number of section headers:         24
      Section header string table index: 23
    

      从上面的Magic Number中能够看出:当前文件类型为64bit的共享库,小端存储,版本为1,机器架构为x86-64,程序头表项有7项,节头表项有24项。

    3.2 Program Header Table

      使用命令readelf -l <ELF文件名>查看程序头表;

    //readelf -l libadd.so
    Elf file type is DYN (Shared object file)
    Entry point 0x4a0
    There are 7 program headers, starting at offset 64
    Program Headers:
      Type           Offset             VirtAddr           PhysAddr           FileSiz            MemSiz              Flags  Align 
      LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000674 0x0000000000000674  R E    0x200000
      LOAD           0x0000000000000e80 0x0000000000200e80 0x0000000000200e80 0x00000000000001a4 0x00000000000001b0  RW     0x200000
      DYNAMIC        0x0000000000000e90 0x0000000000200e90 0x0000000000200e90 0x0000000000000150 0x0000000000000150  RW     0x8
      NOTE           0x00000000000001c8 0x00000000000001c8 0x00000000000001c8 0x0000000000000024 0x0000000000000024  R      0x4
      GNU_EH_FRAME   0x00000000000005a8 0x00000000000005a8 0x00000000000005a8 0x000000000000002c 0x000000000000002c  R      0x4
      GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000  RW     0x10
      GNU_RELRO      0x0000000000000e80 0x0000000000200e80 0x0000000000200e80 0x0000000000000180 0x0000000000000180  R      0x1
    
     Section to Segment mapping:
      Segment Sections...
       00     .note.gnu.build-id .gnu.hash .dynsym .dynstr .rela.dyn .init .plt .plt.got .text .fini .eh_frame_hdr .eh_frame
       01     .init_array .fini_array .dynamic .got .got.plt .data .bss
       02     .dynamic
       03     .note.gnu.build-id
       04     .eh_frame_hdr
       05
       06     .init_array .fini_array .dynamic .got
    

      从上面看出上半部分的内容基本和程序头表项的每个字段基本对应。从下面的Segment Sections可以看出一个Segment是多个Section的集合。

    3.3 Section Header Table

      使用命令readelf -S <ELF文件名>查看节头表的内容。

    ➜  tmp readelf -S libadd.so
    There are 24 section headers, starting at offset 0x1770:
    
    Section Headers:
      [Nr] Name              Type             Address           Offset   Size              EntSize          Flags  Link  Info  Align
      [ 0]                   NULL             0000000000000000  00000000 0000000000000000  0000000000000000           0     0     0
      [ 1] .note.gnu.build-i NOTE             00000000000001c8  000001c8 0000000000000024  0000000000000000   A       0     0     4
      [ 2] .gnu.hash         GNU_HASH         00000000000001f0  000001f0 000000000000003c  0000000000000000   A       3     0     8
      [ 3] .dynsym           DYNSYM           0000000000000230  00000230 0000000000000108  0000000000000018   A       4     1     8
      [ 4] .dynstr           STRTAB           0000000000000338  00000338 000000000000007d  0000000000000000   A       0     0     1
      [ 5] .rela.dyn         RELA             00000000000003b8  000003b8 00000000000000a8  0000000000000018   A       3     0     8
      [ 6] .init             PROGBITS         0000000000000460  00000460 0000000000000017  0000000000000000  AX       0     0     4
      [ 7] .plt              PROGBITS         0000000000000480  00000480 0000000000000010  0000000000000010  AX       0     0     16
      [ 8] .plt.got          PROGBITS         0000000000000490  00000490 0000000000000008  0000000000000008  AX       0     0     8
      [ 9] .text             PROGBITS         00000000000004a0  000004a0 00000000000000fc  0000000000000000  AX       0     0     16
      [10] .fini             PROGBITS         000000000000059c  0000059c 0000000000000009  0000000000000000  AX       0     0     4
      [11] .eh_frame_hdr     PROGBITS         00000000000005a8  000005a8 000000000000002c  0000000000000000   A       0     0     4
      [12] .eh_frame         PROGBITS         00000000000005d8  000005d8 000000000000009c  0000000000000000   A       0     0     8
      [13] .init_array       INIT_ARRAY       0000000000200e80  00000e80 0000000000000008  0000000000000008  WA       0     0     8
      [14] .fini_array       FINI_ARRAY       0000000000200e88  00000e88 0000000000000008  0000000000000008  WA       0     0     8
      [15] .dynamic          DYNAMIC          0000000000200e90  00000e90 0000000000000150  0000000000000010  WA       4     0     8
      [16] .got              PROGBITS         0000000000200fe0  00000fe0 0000000000000020  0000000000000008  WA       0     0     8
      [17] .got.plt          PROGBITS         0000000000201000  00001000 0000000000000018  0000000000000008  WA       0     0     8
      [18] .data             PROGBITS         0000000000201018  00001018 000000000000000c  0000000000000000  WA       0     0     8
      [19] .bss              NOBITS           0000000000201024  00001024 000000000000000c  0000000000000000  WA       0     0     4
      [20] .comment          PROGBITS         0000000000000000  00001024 0000000000000029  0000000000000001  MS       0     0     1
      [21] .symtab           SYMTAB           0000000000000000  00001050 00000000000004c8  0000000000000018          22    41     8
      [22] .strtab           STRTAB           0000000000000000  00001518 0000000000000193  0000000000000000           0     0     1
      [23] .shstrtab         STRTAB           0000000000000000  000016ab 00000000000000c3  0000000000000000           0     0     1
    Key to Flags:
      W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
      L (link order), O (extra OS processing required), G (group), T (TLS),
      C (compressed), x (unknown), o (OS specific), E (exclude),
      l (large), p (processor specific)
    

      从上面看出内容基本和程序头表项的每个字段基本对应。除了上面提到的特殊的节也有一些额外的节,比如.got.plt

    3.4 符号表

      readelf -s <ELF文件名>查看符号表。

    //readelf -s libadd.so
    Symbol table '.dynsym' contains 11 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
         1: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __cxa_finalize
         2: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable
         3: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTab
         4: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
         5: 0000000000201024     0 NOTYPE  GLOBAL DEFAULT   18 _edata
         6: 0000000000201030     0 NOTYPE  GLOBAL DEFAULT   19 _end
         7: 0000000000000460     0 FUNC    GLOBAL DEFAULT    6 _init
         8: 000000000000057a    17 FUNC    GLOBAL DEFAULT    9 add
         9: 0000000000201024     0 NOTYPE  GLOBAL DEFAULT   19 __bss_start
        10: 000000000000059c     0 FUNC    GLOBAL DEFAULT   10 _fini
    
    Symbol table '.symtab' contains 51 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
         1: 00000000000001c8     0 SECTION LOCAL  DEFAULT    1
         2: 00000000000001f0     0 SECTION LOCAL  DEFAULT    2
         3: 0000000000000230     0 SECTION LOCAL  DEFAULT    3
         4: 0000000000000338     0 SECTION LOCAL  DEFAULT    4
         5: 00000000000003b8     0 SECTION LOCAL  DEFAULT    5
         6: 0000000000000460     0 SECTION LOCAL  DEFAULT    6
         7: 0000000000000480     0 SECTION LOCAL  DEFAULT    7
         8: 0000000000000490     0 SECTION LOCAL  DEFAULT    8
         9: 00000000000004a0     0 SECTION LOCAL  DEFAULT    9
        10: 000000000000059c     0 SECTION LOCAL  DEFAULT   10
        11: 00000000000005a8     0 SECTION LOCAL  DEFAULT   11
        12: 00000000000005d8     0 SECTION LOCAL  DEFAULT   12
        13: 0000000000200e80     0 SECTION LOCAL  DEFAULT   13
        14: 0000000000200e88     0 SECTION LOCAL  DEFAULT   14
        15: 0000000000200e90     0 SECTION LOCAL  DEFAULT   15
        16: 0000000000200fe0     0 SECTION LOCAL  DEFAULT   16
        17: 0000000000201000     0 SECTION LOCAL  DEFAULT   17
        18: 0000000000201018     0 SECTION LOCAL  DEFAULT   18
        19: 0000000000201024     0 SECTION LOCAL  DEFAULT   19
        20: 0000000000000000     0 SECTION LOCAL  DEFAULT   20
        21: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c
        22: 00000000000004a0     0 FUNC    LOCAL  DEFAULT    9 deregister_tm_clones
        23: 00000000000004e0     0 FUNC    LOCAL  DEFAULT    9 register_tm_clones
        24: 0000000000000530     0 FUNC    LOCAL  DEFAULT    9 __do_global_dtors_aux
        25: 0000000000201024     1 OBJECT  LOCAL  DEFAULT   19 completed.7698
        26: 0000000000200e88     0 OBJECT  LOCAL  DEFAULT   14 __do_global_dtors_aux_fin
        27: 0000000000000570     0 FUNC    LOCAL  DEFAULT    9 frame_dummy
        28: 0000000000200e80     0 OBJECT  LOCAL  DEFAULT   13 __frame_dummy_init_array_
        29: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS add.c
        30: 0000000000201020     4 OBJECT  LOCAL  DEFAULT   18 static_value
        31: 0000000000201028     4 OBJECT  LOCAL  DEFAULT   19 static_value1
        32: 000000000000058b    17 FUNC    LOCAL  DEFAULT    9 mult
        33: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c
        34: 0000000000000670     0 OBJECT  LOCAL  DEFAULT   12 __FRAME_END__
        35: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS
        36: 0000000000200e90     0 OBJECT  LOCAL  DEFAULT   15 _DYNAMIC
        37: 0000000000201028     0 OBJECT  LOCAL  DEFAULT   18 __TMC_END__
        38: 0000000000201018     0 OBJECT  LOCAL  DEFAULT   18 __dso_handle
        39: 00000000000005a8     0 NOTYPE  LOCAL  DEFAULT   11 __GNU_EH_FRAME_HDR
        40: 0000000000201000     0 OBJECT  LOCAL  DEFAULT   17 _GLOBAL_OFFSET_TABLE_
        41: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __cxa_finalize
        42: 0000000000000460     0 FUNC    GLOBAL DEFAULT    6 _init
        43: 000000000000057a    17 FUNC    GLOBAL DEFAULT    9 add
        44: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable
        45: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTab
        46: 0000000000201024     0 NOTYPE  GLOBAL DEFAULT   19 __bss_start
        47: 000000000000059c     0 FUNC    GLOBAL DEFAULT   10 _fini
        48: 0000000000201024     0 NOTYPE  GLOBAL DEFAULT   18 _edata
        49: 0000000000201030     0 NOTYPE  GLOBAL DEFAULT   19 _end
        50: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
    

      符号表可以看出有两个分别为dynsymsymtabsymtab中包含所有在程序中出现的符号以及一些库函数的符号,而dynsym中的符号是symtab中符号的子集,仅仅出现了外部可以看到的符号(静态函数mult的符号在dynsym就看不到)。这是因为dynsym中的符号只有在动态链接时也就是运行时才能被解析。

    4 参考文献

    展开全文
  • ELF文件格式分析-北京大学操作系统实验室,ELF文件格式分析-北京大学操作系统实验室
  • ELF format / ELF文件格式

    2021-03-16 13:48:38
    2003/06/11 15:14 28,626 ELF文件格式(中文)(一) .txt 2003/09/25 18:17 24,614 ELF文件格式(中文)(三) .txt 2003/06/11 10:41 26,811 ELF文件格式(中文)(二) .txt 2004/10/22 20:42 36,137 从程序员角度看ELF.txt ...
  • ELF文件解析器

    千次阅读 2022-02-11 14:07:03
    前言 最近选了Linux内核原理的选修课,虽然因为课时比较短涉及到的内容只能涵盖Linux知识的一小部分,但是老师的水平确实很高,讲的知识也很深入,这次布置的小作业是编写Linux平台下的...1.ELF文件介绍 在 Linux 系

    全部代码:https://github.com/Kakaluoto/ELFReader

    前言

    最近选了Linux内核原理的选修课,虽然因为课时比较短涉及到的内容只能涵盖Linux知识的一小部分,但是老师的水平确实很高,讲的知识也很深入,这次布置的小作业是编写Linux平台下的C语言程序实现如下功能:

    模仿实现Linux下readelf工具的部分功能,能够对ELF可执行文件进行简单分析。(至少支持readelf工具的-h、-S、-s三个命令选项功能)

    关于原理部分自认为没有能力和大佬们讲得一样透彻清楚,所以参考文章链接贴在了文章的末尾,看了一定就会明白的。

    1.ELF文件介绍

    在 Linux 系统中,一个 ELF 文件主要用来表示 3 种类型的文件:

    • 可执行文件:被操作系统中的加载器从硬盘上读取,载入到内存中去执行;
    • 目标文件:被链接器读取,用来产生一个可执行文件或者共享库文件;
    • 共享库文件:在动态链接的时候,由 ld-linux.so 来读取;
    img

    2.readelf命令

    -a :--all 显示全部信息,等价于 -h -l -S -s -r -d -V -A -I
    -h :--file-header 显示elf文件开始的文件头信息. 
    -l :--program-headers  ;--segments 显示程序头(段头)信息(如果有的话)。 
    -S :--section-headers  ;--sections 显示节头信息(如果有的话)。 
    -g :--section-groups 显示节组信息(如果有的话)。
    -t :--section-details 显示节的详细信息(-S的)。 
    -s :--syms  ;--symbols 显示符号表段中的项(如果有的话)。 
    -e :--headers 显示全部头信息,等价于: -h -l -S 
    -n :--notes 显示note段(内核注释)的信息。 
    -r :--relocs 显示可重定位段的信息。 
    -u :--unwind 显示unwind段信息。当前只支持IA64 ELF的unwind段信息。 
    -d :--dynamic 显示动态段的信息。 
    -V :--version-info 显示版本段的信息。 
    -A :--arch-specific 显示CPU构架信息。 
    -D :--use-dynamic 使用动态段中的符号表显示符号,而不是使用符号段。 
    -x <number or name> :--hex-dump=<number or name> 以16进制方式显示指定段内内容。number指定段表中段的索引,或字符串指定文件中的段名。 
    -w[liaprmfFsoR]或者
    -debugdump[=line,=info,=abbrev,=pubnames,=aranges,
    =macro,=frames,=frames-interp,=str,=loc,=Ranges] 显示调试段中指定的内容。 
    -I :--histogram 显示符号的时候,显示bucket list长度的柱状图。 
    -v :--version 显示readelf的版本信息。 
    -H :--help 显示readelf所支持的命令行选项。 
    -W :--wide 宽行输出。 
    

    3.代码实现

    Kakaluoto/ELFReader (github.com)

    这次我一共分成了5个文件header.h ; main.cpp ; readelf_symbol.cpp ; readelf_Section.cpp ; readelf_header.cpp

    头文件 header.h

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cstdlib>
    #include <elf.h>
    #ifndef ELFREADER_HEADER_H
    #define ELFREADER_HEADER_H
    using namespace std;
    void readelf_h(const char* filename);
    
    void readelf_S(const char* filename);
    
    void readelf_s(const char* filename);
    #endif //ELFREADER_HEADER_H
    
    

    主函数 main.cpp

    #include "header.h"
    
    int main(int argc, char* argv[]) {
        if (argc < 2) { exit(0); }
        //argv[0]是当前可执行文件路径
        //arv[1]是参数,参数以空格作为分隔符
        //argv[2]是被解析的可执行文件名
        if (strcmp(argv[1], "-h") == 0)
            readelf_h(argv[2]);
        else if (strcmp(argv[1], "-S") == 0)
            readelf_S(argv[2]);
        else if (strcmp(argv[1], "-s") == 0)
            readelf_s(argv[2]);
        else
            printf("invalid argument!\n");
        return 0;
    }
    

    模仿readelf -h 的实现readelf_header.cpp

    #include "header.h"
    
    void readelf_h(const char* filename) {
        FILE* fp;//定义文件指针
        Elf64_Ehdr elf_header;//定义elf头用来存储
        fp = fopen(filename, "r");
        if (fp == NULL) { exit(0); }
        fread(&elf_header, sizeof(Elf64_Ehdr), 1, fp);//读header
        if (elf_header.e_ident[0] != 0x7f || elf_header.e_ident[1] != 'E') { exit(0); }//判断是否是elf文件
        printf("ELF Header:\n");
        printf("  Magic:\t");
        for (unsigned char i : elf_header.e_ident) {
            printf("%02x ", i);
        }
        printf("\n");
        printf("  Class:\t\t\t\t");
        switch (elf_header.e_ident[EI_CLASS]) {
            case 1:
                printf("ELF%d\n", 32);
                break;
            case 2:
                printf("ELF%d\n", 64);
                break;
            default:
                printf("Error!\n");
        }
        printf("  Data:\t\t\t\t\t");
        switch (elf_header.e_ident[EI_DATA]) {
            case 1:
                printf("2's complement, little endian\n");
                break;
            case 2:
                printf("2's complement, big endian\n");
                break;
            default:
                printf("Error!\n");
        }
        printf("  Version:\t\t\t\t%d (current)\n", elf_header.e_ident[EI_VERSION]);
        printf("  OS/ABI:\t\t\t\t");
        switch (elf_header.e_ident[EI_OSABI]) {
            case ELFOSABI_NONE:
                printf("UNIX System V ABI\n");
                break;
            case ELFOSABI_HPUX:
                printf("HP-UX\n");
                break;
            case ELFOSABI_NETBSD:
                printf("NetBSD.\n");
                break;
            case ELFOSABI_GNU:
                printf("Object uses GNU ELF extensions.\n");
                break;
            case ELFOSABI_SOLARIS:
                printf("Sun Solaris.\n");
                break;
            case ELFOSABI_AIX:
                printf("IBM AIX.\n");
                break;
            case ELFOSABI_IRIX:
                printf("SGI Irix.\n");
                break;
            case ELFOSABI_FREEBSD:
                printf("FreeBSD.\n");
                break;
            case ELFOSABI_TRU64:
                printf("Compaq TRU64 UNIX.\n");
                break;
            case ELFOSABI_MODESTO:
                printf("Novell Modesto.\n");
                break;
            case ELFOSABI_OPENBSD:
                printf("OpenBSD.\n");
                break;
            case ELFOSABI_ARM_AEABI:
                printf("ARM EABI\n");
                break;
            case ELFOSABI_ARM:
                printf("ARM\n");
                break;
            case ELFOSABI_STANDALONE:
                printf("Standalone (embedded) application\n");
                break;
            default:
                printf("Error!\n");
        }
        printf("  ABI Version:\t\t\t\t%d\n", elf_header.e_ident[EI_ABIVERSION]);
        printf("  Type:\t\t\t\t\t");
        switch (elf_header.e_type) {
            case ET_REL:
                printf("REL (Relocatable file)\n");
                break;
            case ET_EXEC:
                printf("EXEC (Executable file)\n");
                break;
            case ET_DYN:
                printf("DYN (Shared object file)\n");
                break;
            default:
                printf("Error!\n");
        }
        printf("  Machine:\t\t\t\t");
        switch (elf_header.e_machine) {
            case EM_NONE:
                printf("No Machine!\n");
                break;
            case EM_386:
                printf("Intel 80386\n");
                break;
            case EM_860:
                printf("Intel 80860\n");
                break;
            case EM_ARM:
                printf("ARM\n");
                break;
            case EM_X86_64:
                printf("AMD x86-64 architecture\n");
                break;
            case EM_AVR:
                printf("Atmel AVR 8-bit microcontroller\n");
                break;
            case EM_MSP430:
                printf("Texas Instruments msp430\n");
                break;
            case EM_ALTERA_NIOS2:
                printf("Altera Nios II\n");
                break;
            case EM_MICROBLAZE:
                printf("Xilinx MicroBlaze\n");
                break;
            case EM_8051:
                printf("Intel 8051 and variants\n");
                break;
            case EM_STM8:
                printf("STMicroelectronics STM8\n");
                break;
            case EM_CUDA:
                printf("NVIDIA CUDA\n");
                break;
            case EM_AMDGPU:
                printf("AMD GPU\n");
                break;
            case EM_RISCV:
                printf("RISC-V\n");
                break;
            case EM_BPF:
                printf("Linux BPF -- in-kernel virtual machine\n");
                break;
            default:
                printf("Unknown Machine!\n");
        }
        printf("  Version:\t\t\t\t%x\n", elf_header.e_ident[EI_VERSION]);
        printf("  Entry point address:\t\t\t0x%016lx\n", elf_header.e_entry);
        printf("  Start of program headers:\t\t%ld (bytes into file)\n", elf_header.e_phoff);
        printf("  Start of section headers:\t\t%ld (bytes into file)\n", elf_header.e_shoff);
        printf("  Flags:\t\t\t\t0x%x\n", elf_header.e_flags);
        printf("  Size of this header:\t\t\t%d (bytes)\n", elf_header.e_ehsize);
        printf("  Size of program headers:\t\t%d (bytes)\n", elf_header.e_phentsize);
        printf("  Number of program headers:\t\t%d\n", elf_header.e_phnum);
        printf("  Size of section headers:\t\t%d (bytes)\n", elf_header.e_shentsize);
        printf("  Number of section headers:\t\t%d\n", elf_header.e_shnum);
        printf("  Section header string table index:\t%d\n", elf_header.e_shstrndx);
    }
    
    

    模仿readelf -S 的实现readelf_Section.cpp

    #include "header.h"
    
    void readelf_S(const char* filename) {
        FILE* fp;
        Elf64_Ehdr elf_header;
        fp = fopen(filename, "r");
        if (fp == NULL) { exit(0); }
        fread(&elf_header, sizeof(Elf64_Ehdr), 1, fp);
        if (elf_header.e_ident[0] != 0x7f || elf_header.e_ident[1] != 'E') { exit(0); }
        //定义数组用来存储段表里每一个section_header,段的数目:elf_header.e_shnum
        Elf64_Shdr* sec_headers = new Elf64_Shdr[elf_header.e_shnum];
        //将指针移动到段表起始地址,段起始地址elf_header.e_shoff即相对于整个elf文件的偏移量,SEEK_SET从文件起始开始偏移
        fseek(fp, elf_header.e_shoff, SEEK_SET);
        //读section_header,每一个header大小为sizeof(Elf64_Shdr),一共读elf_header.e_shnum个段表头
        fread(sec_headers, sizeof(Elf64_Shdr), elf_header.e_shnum, fp);
        printf("There are %d section headers, starting at offset 0x%lx\n\n", elf_header.e_shnum, elf_header.e_shoff);
        printf("Section Headers:\n");
    
        int str_tab_ind = elf_header.e_shstrndx;//获取字符串表在段表中的索引elf_header.e_shstrndx,用来读取段名
        fseek(fp, sec_headers[str_tab_ind].sh_offset, SEEK_SET);//将指针移动到字符串表
        char* string_table = new char[sec_headers[str_tab_ind].sh_size];//构造字符数组用来存储字符串表里的字符
        fread(string_table, 1, sec_headers[str_tab_ind].sh_size, fp);//将字符串表里面的字符全部读出来
    
        //获取section段的类型,输入类型对应的数值,返回字符串型的类型名
        auto get_sh_type = [](int sh_type, string& sec_header_name) {
            switch (sh_type) {
                case SHT_NULL:
                    sec_header_name = "NULL";
                    break;
                case SHT_PROGBITS:
                    sec_header_name = "PROGBITS";
                    break;
                case SHT_SYMTAB:
                    sec_header_name = "SYMTAB";
                    break;
                case SHT_STRTAB:
                    sec_header_name = "STRTAB";
                    break;
                case SHT_RELA:
                    sec_header_name = "RELA";
                    break;
                case SHT_HASH:
                    sec_header_name = "HASH";
                    break;
                case SHT_DYNAMIC:
                    sec_header_name = "DYNAMIC";
                    break;
                case SHT_NOTE:
                    sec_header_name = "NOTE";
                    break;
                case SHT_NOBITS:
                    sec_header_name = "NOBITS";
                    break;
                case SHT_REL:
                    sec_header_name = "REL";
                    break;
                case SHT_SHLIB:
                    sec_header_name = "SHLIB";
                    break;
                case SHT_DYNSYM:
                    sec_header_name = "DYNSYM";
                    break;
                case SHT_INIT_ARRAY:
                    sec_header_name = "INIT_ARRAY";
                    break;
                case SHT_FINI_ARRAY:
                    sec_header_name = "FINI_ARRAY";
                    break;
                case SHT_PREINIT_ARRAY:
                    sec_header_name = "PREINIT_ARRAY";
                    break;
                case SHT_GROUP:
                    sec_header_name = "GROUP";
                    break;
                case SHT_SYMTAB_SHNDX:
                    sec_header_name = "SYMTAB_SHNDX";
                    break;
                case SHT_NUM:
                    sec_header_name = "NUM";
                    break;
                case SHT_GNU_HASH:
                    sec_header_name = "GNU_HASH";
                    break;
                case SHT_GNU_versym:
                    sec_header_name = "VERSYM";
                    break;
                case SHT_GNU_verneed:
                    sec_header_name = "VERNEED";
                    break;
                default:
                    sec_header_name = "UnknownType";
            }
        };
    
        //获取section段的标志flag,输入类型对应的数值,返回字符串型的flag
        //因为可能同时满足多个flag,所以根据对应位是否为1来判断是否满足对应的flag满足就将flag字符串拼凑
        auto get_sh_flags = [](unsigned int sh_flags, string& sec_header_name) {
            if ((sh_flags & SHF_WRITE) >> 0)
                sec_header_name += "W";
            if ((sh_flags & SHF_ALLOC) >> 1)
                sec_header_name += "A";
            if ((sh_flags & SHF_EXECINSTR) >> 2)
                sec_header_name += "X";
            if ((sh_flags & SHF_MERGE) >> 4)
                sec_header_name += "M";
            if ((sh_flags & SHF_STRINGS) >> 5)
                sec_header_name += "S";
            if ((sh_flags & SHF_INFO_LINK) >> 6)
                sec_header_name += "I";
            if ((sh_flags & SHF_LINK_ORDER) >> 7)
                sec_header_name += "L";
            if ((sh_flags & SHF_OS_NONCONFORMING) >> 8)
                sec_header_name += "O";
            if ((sh_flags & SHF_GROUP) >> 9)
                sec_header_name += "G";
            if ((sh_flags & SHF_TLS) >> 10)
                sec_header_name += "T";
            if ((sh_flags & SHF_COMPRESSED) >> 11)
                sec_header_name += "C";
            //特殊flag因为对应的位和上面的flag对应的位不重叠,所以可以单独处理
            switch (sh_flags) {
                case SHF_MASKOS:
                    sec_header_name = "o";
                    break;
                case SHF_MASKPROC:
                    sec_header_name = "p";
                    break;
                case SHF_EXCLUDE:
                    sec_header_name = "E";
                    break;
            }
        };
    
        printf("  [Nr]\tName\t\t\tType\t\tAddr\t\tOffset\t\tSize\t\t"
               "EntSize\t\tFlags\tLink\tInfo\tAlign\n");
        //遍历section_headers段表里的每个section,输出相应的信息
        for (int i = 0; i < elf_header.e_shnum; i++) {
            printf("  [%2d]\t", i);
            printf("%-24s", &string_table[sec_headers[i].sh_name]);
            string sh_type;
            get_sh_type(sec_headers[i].sh_type, sh_type);
            printf("%-16s", sh_type.data());
            printf("0x%08lx\t", sec_headers[i].sh_addr);
            printf("0x%08lx\t", sec_headers[i].sh_offset);
            printf("0x%08lx\t", sec_headers[i].sh_size);
            printf("0x%08lx\t", sec_headers[i].sh_entsize);
            string sh_flags;
            get_sh_flags(sec_headers[i].sh_flags, sh_flags);
            printf("%-8s", sh_flags.data());
            printf("%-8d", sec_headers[i].sh_link);
            printf("%-8d", sec_headers[i].sh_info);
            printf("%-8ld", sec_headers[i].sh_addralign);
            printf("\n");
        }
        printf("Key to Flags:\n"
               "\tW (write), A (alloc), X (execute), M (merge), S (strings), I (info),\n"
               "\tL (link order), O (extra OS processing required), G (group), T (TLS),\n"
               "\tC (compressed), x (unknown), o (OS specific), E (exclude),\n"
               "\tl (large), p (processor specific)\n");
    
        //释放堆内存
        delete[] string_table;
        delete[] sec_headers;
        fclose(fp);
    }
    
    

    模仿readelf -s 的实现readelf_symbol.cpp

    #include "header.h"
    
    void readelf_s(const char* filename) {
        FILE* fp;
        Elf64_Ehdr elf_header;
        fp = fopen(filename, "r");
        if (fp == NULL) { exit(0); }
        fread(&elf_header, sizeof(Elf64_Ehdr), 1, fp);
        if (elf_header.e_ident[0] != 0x7f || elf_header.e_ident[1] != 'E') { exit(0); }
        Elf64_Shdr* sec_headers = new Elf64_Shdr[elf_header.e_shnum];//存放每个section_header的数组
        fseek(fp, elf_header.e_shoff, SEEK_SET);//移动指针到段表对应的偏移地址
        fread(sec_headers, sizeof(Elf64_Shdr), elf_header.e_shnum, fp);//将段表数据读到开辟的数组sec_headers里
    
        int str_tab_ind = elf_header.e_shstrndx;//获取字符串表.shstrtab在段表中的索引
        fseek(fp, sec_headers[str_tab_ind].sh_offset, SEEK_SET);//移动指针到字符串表.shstrtab对应的偏移地址
        char* string_table = new char[sec_headers[str_tab_ind].sh_size];//开辟堆内存用来存放字符串表.shstrtab
        fread(string_table, 1, sec_headers[str_tab_ind].sh_size, fp);//将字符串表.shstrtab对应地址处的数据读到字符串数组里
    
        int dynsym_ind = -1;//默认.dynsym符号表索引为-1
        int symtab_ind = -1;//默认.symtab符号表索引为-1
        int dynstr_ind = -1;//默认.dynstr字符串表索引为-1
        int strtab_ind = -1;//默认.strtab字符串索引为-1
    
        //遍历段表section_headers获取符号表.dynsym;.symtab;.dynstr;.strtab四张表在段表中的索引
        for (int i = 0; i < elf_header.e_shnum; i++) {
            if (sec_headers[i].sh_type == SHT_DYNSYM)//是.dynsym符号表
                dynsym_ind = i;
            else if (sec_headers[i].sh_type == SHT_SYMTAB)//是.symtab符号表
                symtab_ind = i;
            if (strcmp(&string_table[sec_headers[i].sh_name], ".strtab") == 0)//是.strtab字符串表
                strtab_ind = i;
            else if (strcmp(&string_table[sec_headers[i].sh_name], ".dynstr") == 0)//是.dynstr字符串表
                dynstr_ind = i;
        }
        //获取符号表entry对应的st_info段,用来计算符号类型和绑定信息
        auto get_st_info = [](unsigned int st_info, string& symbol_type, string& symbol_binding) {
            unsigned char st_type = st_info & 0x0000000f;//低4位表示符号类型
            unsigned char st_binding = (st_info & (~0x0000000f)) >> 4;//高28位表示符号绑定信息
            switch (st_binding) {
                case STB_LOCAL:
                    symbol_binding = "LOCAL";
                    break;
                case STB_GLOBAL:
                    symbol_binding = "GLOBAL";
                    break;
                case STB_WEAK:
                    symbol_binding = "WEAK";
                    break;
                case STB_NUM:
                    symbol_binding = "NUM";
                    break;
                case STB_LOOS:
                    symbol_binding = "LOOS";
                    break;
                case STB_HIOS:
                    symbol_binding = "HIOS";
                    break;
                case STB_LOPROC:
                    symbol_binding = "LOPROC";
                    break;
                case STB_HIPROC:
                    symbol_binding = "HIPROC";
                    break;
                default:
                    symbol_binding = "UnknownType";
            }
            switch (st_type) {
                case STT_NOTYPE:
                    symbol_type = "NOTYPE";
                    break;
                case STT_OBJECT:
                    symbol_type = "OBJECT";
                    break;
                case STT_FUNC:
                    symbol_type = "FUNC";
                    break;
                case STT_SECTION:
                    symbol_type = "SECTION";
                    break;
                case STT_FILE:
                    symbol_type = "FILE";
                    break;
                case STT_COMMON:
                    symbol_type = "COMMON";
                    break;
                case STT_TLS:
                    symbol_type = "TLS";
                    break;
                case STT_NUM:
                    symbol_type = "NUM";
                    break;
                case STT_LOOS:
                    symbol_type = "LOOS";
                    break;
                case STT_HIOS:
                    symbol_type = "HIOS";
                    break;
                case STT_LOPROC:
                    symbol_type = "LOPROC";
                    break;
                case STT_HIPROC:
                    symbol_type = "HIPROC";
                    break;
                default:
                    symbol_type = "UnknownBinding";
            }
        };
    
        //获取符号所在的段在段表的索引,并对特殊符号进行特殊处理
        auto get_st_shndx = [](unsigned int st_shndx, string& Ndx) {
            switch (st_shndx) {
                case SHN_UNDEF:
                    Ndx = "UNDEF";break;
                case SHN_COMMON:
                    Ndx = "COMMON";break;
                case SHN_ABS:
                    Ndx = "ABS";break;
                default:
                    Ndx = to_string(st_shndx);
            }
        };
    
        //输出符号表信息,输入符号表在段表中的索引sym_ind,符号表entry数目entry_num,符号表对应的字符串表string_table
        auto show_symbol_table = [&](int sym_ind, unsigned long entry_num, char* string_table) {
            fseek(fp, sec_headers[sym_ind].sh_offset, SEEK_SET);//将指针移动到符号表对应的偏移地址
    
            Elf64_Sym* sym_entries = new Elf64_Sym[entry_num];//开辟堆内存用来存储符号表中所有entry
            fread(sym_entries, sizeof(Elf64_Sym), entry_num, fp);//读符号表
            printf("  Num:\t\tValue\t\tSize\tType\tBind\tVis\t"
                   "Ndx\t\tName\n");
            //遍历符号表里的每个entry,并且输出entry的信息
            for (int i = 0; i < entry_num; i++) {
                printf("  %3d:\t", i);
                printf("0x%016lx:\t", sym_entries[i].st_value);
                printf("%4ld\t", sym_entries[i].st_size);
                string symbol_type;
                string symbol_binding;
                get_st_info(sym_entries[i].st_info, symbol_type, symbol_binding);
                printf("%s\t", symbol_type.data());
                printf("%s\t", symbol_binding.data());
                printf("DEFAULT\t");
                string Ndx;
                get_st_shndx(sym_entries[i].st_shndx, Ndx);
                printf("%4s\t", Ndx.data());
                //根据entry的st_name属性在符号表对应的字符串表表里找到entry的name
                printf("%s", &string_table[sym_entries[i].st_name]);
                printf("\n");
            }
            //释放堆内存
            delete[] sym_entries;
        };
    
        //如果.dynsym段存在,且.dynstr存在
        if ((dynsym_ind != -1) && (dynstr_ind != -1)) {
            //符号表大小sec_headers[dynsym_ind].sh_size每个entry大小sec_headers[dynsym_ind].sh_entsize
            // 计算entry数目entry_num
            unsigned long entry_num = sec_headers[dynsym_ind].sh_size / sec_headers[dynsym_ind].sh_entsize;
            printf("Symbol table '.dynsym' contains %ld entries\n", entry_num);
            fseek(fp, sec_headers[dynstr_ind].sh_offset, SEEK_SET);//将指针移动到.dynstr字符串表对应的偏移地址
            //开辟堆内存用来存储字符串表
            char* dynstr_string_table = new char[sec_headers[dynstr_ind].sh_size];
            //将数据读到字符串表里
            fread(dynstr_string_table, 1, sec_headers[dynstr_ind].sh_size, fp);
            show_symbol_table(dynsym_ind, entry_num, dynstr_string_table);
            //释放字符串表
            delete[] dynstr_string_table;
        } else {
            printf("No Dynamic linker symbol table!\n");
        }
        printf("\n");
        //如果.symtab段存在,且.strtab存在
        if ((symtab_ind != -1) && (strtab_ind != -1)) {
            unsigned long entry_num = sec_headers[symtab_ind].sh_size / sec_headers[symtab_ind].sh_entsize;
            printf("Symbol table '.symtab' contains %ld entries\n", entry_num);
            fseek(fp, sec_headers[strtab_ind].sh_offset, SEEK_SET);
            char* strtab_string_table = new char[sec_headers[strtab_ind].sh_size];
            fread(strtab_string_table, 1, sec_headers[strtab_ind].sh_size, fp);
            show_symbol_table(symtab_ind, entry_num, strtab_string_table);
            delete[] strtab_string_table;
        } else {
            printf("No symbol table!\n");
        }
    
        //释放
        delete[] string_table;
        delete[] sec_headers;
        fclose(fp);
    }
    
    

    4.使用CMake-Make编译并执行

    进入ELFReader所在目录执行如下命令即可

    ./ELFReader -h filename
    
    ./ELFReader -S filename
    
    ./ELFReader -s filename
    

    其中filename是被解析的elf文件所在路径。

    示例:

    在build文件夹下执行cmake … 再make最后工程目录结构如下,ELFReader是elf文件解析器,libMylib.so共享库和helloworld可执行文件是待解析测试文件

    .
    ├── build
    │   ├── bin
    │   │   ├── ELFReader
    │   │   ├── helloworld
    │   │   └── libMylib.so
    ......
    ├── CMakeLists.txt
    ├── readme.md
    └── src
        ├── CMakeLists.txt
        ├── header.h
        ├── main.cpp
        ├── readefl_h.cpp
        ├── readelf_s.cpp
        └── readelf_S.cpp
    

    根目录CMakeLists.txt

    cmake_minimum_required(VERSION 3.10)
    project(ELFReader)
    set(CMAKE_CXX_STANDARD 11)
    ADD_SUBDIRECTORY(./src)
    

    src目录下CMakeLists.txt

    cmake_minimum_required(VERSION 3.10)
    
    SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
    AUX_SOURCE_DIRECTORY(./ DIR_SRCS)
    ADD_EXECUTABLE(ELFReader ${DIR_SRCS})
    
    

    进入bin目录并执行

    cd build/bin/
    ./ELFReader -h helloworld
    

    输出:

    ELF Header:
      Magic:	7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
      Class:				ELF64
      Data:					2's complement, little endian
      Version:				1 (current)
      OS/ABI:				UNIX System V ABI
      ABI Version:				0
      Type:					DYN (Shared object file)
      Machine:				AMD x86-64 architecture
      Version:				1
      Entry point address:			0x00000000000010c0
      Start of program headers:		64 (bytes into file)
      Start of section headers:		36440 (bytes into file)
      Flags:				0x0
      Size of this header:			64 (bytes)
      Size of program headers:		56 (bytes)
      Number of program headers:		13
      Size of section headers:		64 (bytes)
      Number of section headers:		36
      Section header string table index:	35
    

    执行-S命令

    ./ELFReader -S helloworld
    

    输出:

    There are 36 section headers, starting at offset 0x8e58
    
    Section Headers:
      [Nr]	Name			Type		Addr		Offset		Size		EntSize		Flags	Link	Info	Align
      [ 0]	                        NULL            0x00000000	0x00000000	0x00000000	0x00000000	        0       0       0       
      [ 1]	.interp                 PROGBITS        0x00000318	0x00000318	0x0000001c	0x00000000	A       0       0       1       
      [ 2]	.note.gnu.property      NOTE            0x00000338	0x00000338	0x00000020	0x00000000	A       0       0       8       
      [ 3]	.note.gnu.build-id      NOTE            0x00000358	0x00000358	0x00000024	0x00000000	A       0       0       4       
      [ 4]	.note.ABI-tag           NOTE            0x0000037c	0x0000037c	0x00000020	0x00000000	A       0       0       4       
      [ 5]	.gnu.hash               GNU_HASH        0x000003a0	0x000003a0	0x00000028	0x00000000	A       6       0       8       
      [ 6]	.dynsym                 DYNSYM          0x000003c8	0x000003c8	0x00000138	0x00000018	A       7       1       8       
      [ 7]	.dynstr                 STRTAB          0x00000500	0x00000500	0x00000163	0x00000000	A       0       0       1       
      [ 8]	.gnu.version            VERSYM          0x00000664	0x00000664	0x0000001a	0x00000002	A       6       0       2       
      [ 9]	.gnu.version_r          VERNEED         0x00000680	0x00000680	0x00000040	0x00000000	A       7       2       8       
      [10]	.rela.dyn               RELA            0x000006c0	0x000006c0	0x00000120	0x00000018	A       6       0       8       
      [11]	.rela.plt               RELA            0x000007e0	0x000007e0	0x00000060	0x00000018	AI      6       24      8       
      [12]	.init                   PROGBITS        0x00001000	0x00001000	0x0000001b	0x00000000	AX      0       0       4       
      [13]	.plt                    PROGBITS        0x00001020	0x00001020	0x00000050	0x00000010	AX      0       0       16      
      [14]	.plt.got                PROGBITS        0x00001070	0x00001070	0x00000010	0x00000010	AX      0       0       16      
      [15]	.plt.sec                PROGBITS        0x00001080	0x00001080	0x00000040	0x00000010	AX      0       0       16      
      [16]	.text                   PROGBITS        0x000010c0	0x000010c0	0x00000205	0x00000000	AX      0       0       16      
      [17]	.fini                   PROGBITS        0x000012c8	0x000012c8	0x0000000d	0x00000000	AX      0       0       4       
      [18]	.rodata                 PROGBITS        0x00002000	0x00002000	0x00000013	0x00000000	A       0       0       4       
      [19]	.eh_frame_hdr           PROGBITS        0x00002014	0x00002014	0x00000054	0x00000000	A       0       0       4       
      [20]	.eh_frame               PROGBITS        0x00002068	0x00002068	0x00000148	0x00000000	A       0       0       8       
      [21]	.init_array             INIT_ARRAY      0x00003d78	0x00002d78	0x00000010	0x00000008	WA      0       0       8       
      [22]	.fini_array             FINI_ARRAY      0x00003d88	0x00002d88	0x00000008	0x00000008	WA      0       0       8       
      [23]	.dynamic                DYNAMIC         0x00003d90	0x00002d90	0x00000200	0x00000010	WA      7       0       8       
      [24]	.got                    PROGBITS        0x00003f90	0x00002f90	0x00000070	0x00000008	WA      0       0       8       
      [25]	.data                   PROGBITS        0x00004000	0x00003000	0x00000010	0x00000000	WA      0       0       8       
      [26]	.bss                    NOBITS          0x00004040	0x00003010	0x00000118	0x00000000	WA      0       0       64      
      [27]	.comment                PROGBITS        0x00000000	0x00003010	0x0000002a	0x00000001	MS      0       0       1       
      [28]	.debug_aranges          PROGBITS        0x00000000	0x0000303a	0x00000030	0x00000000	        0       0       1       
      [29]	.debug_info             PROGBITS        0x00000000	0x0000306a	0x00002c34	0x00000000	        0       0       1       
      [30]	.debug_abbrev           PROGBITS        0x00000000	0x00005c9e	0x00000649	0x00000000	        0       0       1       
      [31]	.debug_line             PROGBITS        0x00000000	0x000062e7	0x00000406	0x00000000	        0       0       1       
      [32]	.debug_str              PROGBITS        0x00000000	0x000066ed	0x00001b08	0x00000001	MS      0       0       1       
      [33]	.symtab                 SYMTAB          0x00000000	0x000081f8	0x00000780	0x00000018	        34      55      8       
      [34]	.strtab                 STRTAB          0x00000000	0x00008978	0x00000381	0x00000000	        0       0       1       
      [35]	.shstrtab               STRTAB          0x00000000	0x00008cf9	0x0000015a	0x00000000	        0       0       1       
    Key to Flags:
    	W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
    	L (link order), O (extra OS processing required), G (group), T (TLS),
    	C (compressed), x (unknown), o (OS specific), E (exclude),
    	l (large), p (processor specific)
    
    

    执行-s命令

    ./ELFReader -s helloworld
    

    输出:

    Symbol table '.dynsym' contains 13 entries
      Num:		Value		Size	Type	Bind	Vis	Ndx		Name
        0:	0x0000000000000000:	   0	NOTYPE	LOCAL	DEFAULT	UNDEF	
        1:	0x0000000000000000:	   0	FUNC	GLOBAL	DEFAULT	UNDEF	_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
        2:	0x0000000000000000:	   0	FUNC	GLOBAL	DEFAULT	UNDEF	__cxa_atexit
        3:	0x0000000000000000:	   0	FUNC	GLOBAL	DEFAULT	UNDEF	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
        4:	0x0000000000000000:	   0	FUNC	GLOBAL	DEFAULT	UNDEF	_ZNSolsEPFRSoS_E
        5:	0x0000000000000000:	   0	FUNC	GLOBAL	DEFAULT	UNDEF	_ZNSt8ios_base4InitC1Ev
        6:	0x0000000000000000:	   0	NOTYPE	WEAK	DEFAULT	UNDEF	_ITM_deregisterTMCloneTable
        7:	0x0000000000000000:	   0	FUNC	GLOBAL	DEFAULT	UNDEF	__libc_start_main
        8:	0x0000000000000000:	   0	NOTYPE	WEAK	DEFAULT	UNDEF	__gmon_start__
        9:	0x0000000000000000:	   0	NOTYPE	WEAK	DEFAULT	UNDEF	_ITM_registerTMCloneTable
       10:	0x0000000000000000:	   0	FUNC	GLOBAL	DEFAULT	UNDEF	_ZNSt8ios_base4InitD1Ev
       11:	0x0000000000000000:	   0	FUNC	WEAK	DEFAULT	UNDEF	__cxa_finalize
       12:	0x0000000000004040:	 272	OBJECT	GLOBAL	DEFAULT	  26	_ZSt4cout
    
    Symbol table '.symtab' contains 80 entries
      Num:		Value		Size	Type	Bind	Vis	Ndx		Name
        0:	0x0000000000000000:	   0	NOTYPE	LOCAL	DEFAULT	UNDEF	
        1:	0x0000000000000318:	   0	SECTION	LOCAL	DEFAULT	   1	
        2:	0x0000000000000338:	   0	SECTION	LOCAL	DEFAULT	   2	
        3:	0x0000000000000358:	   0	SECTION	LOCAL	DEFAULT	   3	
        4:	0x000000000000037c:	   0	SECTION	LOCAL	DEFAULT	   4	
        5:	0x00000000000003a0:	   0	SECTION	LOCAL	DEFAULT	   5	
        6:	0x00000000000003c8:	   0	SECTION	LOCAL	DEFAULT	   6	
        7:	0x0000000000000500:	   0	SECTION	LOCAL	DEFAULT	   7	
        8:	0x0000000000000664:	   0	SECTION	LOCAL	DEFAULT	   8	
        9:	0x0000000000000680:	   0	SECTION	LOCAL	DEFAULT	   9	
       10:	0x00000000000006c0:	   0	SECTION	LOCAL	DEFAULT	  10	
       11:	0x00000000000007e0:	   0	SECTION	LOCAL	DEFAULT	  11	
       12:	0x0000000000001000:	   0	SECTION	LOCAL	DEFAULT	  12	
       13:	0x0000000000001020:	   0	SECTION	LOCAL	DEFAULT	  13	
       14:	0x0000000000001070:	   0	SECTION	LOCAL	DEFAULT	  14	
       15:	0x0000000000001080:	   0	SECTION	LOCAL	DEFAULT	  15	
       16:	0x00000000000010c0:	   0	SECTION	LOCAL	DEFAULT	  16	
       17:	0x00000000000012c8:	   0	SECTION	LOCAL	DEFAULT	  17	
       18:	0x0000000000002000:	   0	SECTION	LOCAL	DEFAULT	  18	
       19:	0x0000000000002014:	   0	SECTION	LOCAL	DEFAULT	  19	
       20:	0x0000000000002068:	   0	SECTION	LOCAL	DEFAULT	  20	
       21:	0x0000000000003d78:	   0	SECTION	LOCAL	DEFAULT	  21	
       22:	0x0000000000003d88:	   0	SECTION	LOCAL	DEFAULT	  22	
       23:	0x0000000000003d90:	   0	SECTION	LOCAL	DEFAULT	  23	
       24:	0x0000000000003f90:	   0	SECTION	LOCAL	DEFAULT	  24	
       25:	0x0000000000004000:	   0	SECTION	LOCAL	DEFAULT	  25	
       26:	0x0000000000004040:	   0	SECTION	LOCAL	DEFAULT	  26	
       27:	0x0000000000000000:	   0	SECTION	LOCAL	DEFAULT	  27	
       28:	0x0000000000000000:	   0	SECTION	LOCAL	DEFAULT	  28	
       29:	0x0000000000000000:	   0	SECTION	LOCAL	DEFAULT	  29	
       30:	0x0000000000000000:	   0	SECTION	LOCAL	DEFAULT	  30	
       31:	0x0000000000000000:	   0	SECTION	LOCAL	DEFAULT	  31	
       32:	0x0000000000000000:	   0	SECTION	LOCAL	DEFAULT	  32	
       33:	0x0000000000000000:	   0	FILE	LOCAL	DEFAULT	 ABS	crtstuff.c
       34:	0x00000000000010f0:	   0	FUNC	LOCAL	DEFAULT	  16	deregister_tm_clones
       35:	0x0000000000001120:	   0	FUNC	LOCAL	DEFAULT	  16	register_tm_clones
       36:	0x0000000000001160:	   0	FUNC	LOCAL	DEFAULT	  16	__do_global_dtors_aux
       37:	0x0000000000004150:	   1	OBJECT	LOCAL	DEFAULT	  26	completed.8060
       38:	0x0000000000003d88:	   0	OBJECT	LOCAL	DEFAULT	  22	__do_global_dtors_aux_fini_array_entry
       39:	0x00000000000011a0:	   0	FUNC	LOCAL	DEFAULT	  16	frame_dummy
       40:	0x0000000000003d78:	   0	OBJECT	LOCAL	DEFAULT	  21	__frame_dummy_init_array_entry
       41:	0x0000000000000000:	   0	FILE	LOCAL	DEFAULT	 ABS	main.cpp
       42:	0x0000000000002004:	   1	OBJECT	LOCAL	DEFAULT	  18	_ZStL19piecewise_construct
       43:	0x0000000000004151:	   1	OBJECT	LOCAL	DEFAULT	  26	_ZStL8__ioinit
       44:	0x00000000000011e0:	  77	FUNC	LOCAL	DEFAULT	  16	_Z41__static_initialization_and_destruction_0ii
       45:	0x000000000000122d:	  25	FUNC	LOCAL	DEFAULT	  16	_GLOBAL__sub_I_main
       46:	0x0000000000000000:	   0	FILE	LOCAL	DEFAULT	 ABS	crtstuff.c
       47:	0x00000000000021ac:	   0	OBJECT	LOCAL	DEFAULT	  20	__FRAME_END__
       48:	0x0000000000000000:	   0	FILE	LOCAL	DEFAULT	 ABS	
       49:	0x0000000000002014:	   0	NOTYPE	LOCAL	DEFAULT	  19	__GNU_EH_FRAME_HDR
       50:	0x0000000000001000:	   0	FUNC	LOCAL	DEFAULT	  12	_init
       51:	0x0000000000003d90:	   0	OBJECT	LOCAL	DEFAULT	  23	_DYNAMIC
       52:	0x0000000000003d88:	   0	NOTYPE	LOCAL	DEFAULT	  21	__init_array_end
       53:	0x0000000000003d78:	   0	NOTYPE	LOCAL	DEFAULT	  21	__init_array_start
       54:	0x0000000000003f90:	   0	OBJECT	LOCAL	DEFAULT	  24	_GLOBAL_OFFSET_TABLE_
       55:	0x0000000000004010:	   0	NOTYPE	GLOBAL	DEFAULT	  25	_edata
       56:	0x0000000000004000:	   0	NOTYPE	WEAK	DEFAULT	  25	data_start
       57:	0x0000000000002000:	   4	OBJECT	GLOBAL	DEFAULT	  18	_IO_stdin_used
       58:	0x0000000000000000:	   0	FUNC	WEAK	DEFAULT	UNDEF	__cxa_finalize@@GLIBC_2.2.5
       59:	0x00000000000011a9:	  55	FUNC	GLOBAL	DEFAULT	  16	main
       60:	0x0000000000000000:	   0	FUNC	GLOBAL	DEFAULT	UNDEF	_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@@GLIBCXX_3.4
       61:	0x0000000000004008:	   0	OBJECT	GLOBAL	DEFAULT	  25	__dso_handle
       62:	0x00000000000012c8:	   0	FUNC	GLOBAL	DEFAULT	  17	_fini
       63:	0x0000000000000000:	   0	FUNC	GLOBAL	DEFAULT	UNDEF	__cxa_atexit@@GLIBC_2.2.5
       64:	0x00000000000010c0:	  47	FUNC	GLOBAL	DEFAULT	  16	_start
       65:	0x0000000000000000:	   0	FUNC	GLOBAL	DEFAULT	UNDEF	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@@GLIBCXX_3.4
       66:	0x0000000000000000:	   0	FUNC	GLOBAL	DEFAULT	UNDEF	_ZNSolsEPFRSoS_E@@GLIBCXX_3.4
       67:	0x0000000000004010:	   0	OBJECT	GLOBAL	DEFAULT	  25	__TMC_END__
       68:	0x0000000000004040:	 272	OBJECT	GLOBAL	DEFAULT	  26	_ZSt4cout@@GLIBCXX_3.4
       69:	0x0000000000004000:	   0	NOTYPE	GLOBAL	DEFAULT	  25	__data_start
       70:	0x0000000000004158:	   0	NOTYPE	GLOBAL	DEFAULT	  26	_end
       71:	0x0000000000004010:	   0	NOTYPE	GLOBAL	DEFAULT	  26	__bss_start
       72:	0x0000000000000000:	   0	FUNC	GLOBAL	DEFAULT	UNDEF	_ZNSt8ios_base4InitC1Ev@@GLIBCXX_3.4
       73:	0x0000000000001250:	 101	FUNC	GLOBAL	DEFAULT	  16	__libc_csu_init
       74:	0x0000000000000000:	   0	NOTYPE	WEAK	DEFAULT	UNDEF	_ITM_deregisterTMCloneTable
       75:	0x00000000000012c0:	   5	FUNC	GLOBAL	DEFAULT	  16	__libc_csu_fini
       76:	0x0000000000000000:	   0	FUNC	GLOBAL	DEFAULT	UNDEF	__libc_start_main@@GLIBC_2.2.5
       77:	0x0000000000000000:	   0	NOTYPE	WEAK	DEFAULT	UNDEF	__gmon_start__
       78:	0x0000000000000000:	   0	NOTYPE	WEAK	DEFAULT	UNDEF	_ITM_registerTMCloneTable
       79:	0x0000000000000000:	   0	FUNC	GLOBAL	DEFAULT	UNDEF	_ZNSt8ios_base4InitD1Ev@@GLIBCXX_3.4
    
    

    参考文章

    ELF文件结构描述 - yooooooo - 博客园 (cnblogs.com)

    Linux系统中编译、链接的基石-ELF文件:扒开它的层层外衣,从字节码的粒度来探索 (qq.com)

    C/C++ 实现ELF结构解析工具 - lyshark - 博客园 (cnblogs.com)

    C语言实现ELF文件解析_Yuhan的博客-CSDN博客_elf文件读取

    《程序员的自我修养——链接、装载与库》俞甲子,石凡,潘爱民

    展开全文
  • 一、ELF 文件简介、 二、ELF 文件头、 三、ELF 文件头标志、 四、ELF 文件位数、 五、ELF 文件大小端格式





    一、ELF 文件简介



    在上一篇博客 【Android 逆向】ELF 文件格式 ( 安装 010 Editor 二进制查看工具的 ELF.bt 插件模板 | 安装 ELF.bt 模板 | 打开 ELF 文件 ) 中 , 准备 ELF 文件解析环境 , 在 010 Editor 中安装了 ELF.bt 模板 ;

    在这里插入图片描述





    二、ELF 文件头



    ELF 文件头区域如下 :

    在这里插入图片描述

    在这里插入图片描述

    前 16 字节是 ELF 的标志 ,

    在这里插入图片描述





    三、ELF 文件头标志



    0 ~ 3 字节 : 是 0x7F 和 ELF 的 ASCII 码 ; 这是 ELF 文件的特征 ;

    在这里插入图片描述





    四、ELF 文件位数



    4 字节 : 表示该 ELF 文件的位数 32 位还是 64 位 ; 值为 01 , 表示该 ELF 文件是 32 位 文件 ;

    在这里插入图片描述





    五、ELF 文件大小端格式



    5 字节 : 表示 有效位 格式 , 取值 LSB / MSB ; 此处值为 1 , LSB 格式 ; 这个值由编译器决定 ; 有些 CPU 加载 LSB 值快 , 有些 CPU 加载 MSB 值快 ; 大部分 程序 使用 LSB 格式 ;

    LSB 表示最低有效位 ( 小端格式 ) , MSB 表示最高有效位 ( 大端格式 ) ;

    在这里插入图片描述

    在这里插入图片描述

    注意与 大端格式 / 小端格式 区分 , 概念不同 ; LSB 最低有效位 , 某种程度上等效于 小端格式 ;
    在这里插入图片描述

    展开全文
  • Linux下的ELF文件格式简介.pdf
  • 一、ELF 文件简介、 二、ELF 文件结构
  • ELF 文件格式

    2019-02-22 10:27:22
    Elf文件最详细的介绍。也是最好的一个介绍elf文件格式的资料。 看过都说好。
  • Linux系统下的ELF文件分析.pdf

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 84,314
精华内容 33,725
关键字:

elf文件