2019-06-26

[toc]

网址

深入理解程序构造

LMA与VMA总结

ELF文件解析和加载(附代码)

Linux及安全实践四——ELF文件格式分析

程序员的自我修养:(1)目标文件

ELF文件格式

ELF文件解析和加载(附代码)

ELF中与动态链接相关的段

strtab symtab shstrtab

ELF Format 笔记(十一)—— 程序头结构

说明

Executable and Linkable Format,可执行和可链接格式。

本文使用的cpp源码

//  test.cpp

int fang() {
    return 1;
}

int main() {
    fang();
    return 0;
}

elf文件标准的文件类型

可执行文件(ET_EXEC)

1. linux 下 的/bin/bash

2. windows 下的 .exe

可重定位文件(ET_REL)

类型标记为:relocatable

主要包含数据和代码,

多个(包含一个)可重定位文件可用来链接成可执行文件、共享库,然后其机器码中的地址就会重定位。

1. 静态库(.a)

这个有点区别,它基本上就是把许多目标文件捆绑在一起打包,类似tar命令, 再加上一些索引。

2. 目标文件(.o)

在linux中是常见的中间文件,其格式与可执行文件相似。

3. windows.obj

共享库(.so,又称共享目标文件,ET_DYN)

类型标记为:dynamic

该文件被标记为了一个动态的可链接的目标文件,也称为共享库。这类共享库会在程序运行时被装载链接到程序的进程镜像中。

主要包含代码和数据。

用途

  1. 第一种用途可以与其它文件链接生成可重定位或者共享目标文件
  2. 再者直接链接到可执行文件,作为进程映象的一部分动态执行。

1. Linux下的.so

2. Windows下的.dll

核心转储格式文件(Core dumpET_CORE)

又称核心文件

这个格式调试bug时很有用,进程意外终止时产生的,保留程序终止时进程的信息,Linux下的Core dump

载程序崩溃或者进程传递了一个SIGSEGV信号(分段违规)时,会在核心文件中记录整个进程的镜像信息。可以使用GDB读取这类文件来辅助调试并查找程序崩溃的原因。

未知类型(ET_NONE)

组成

1. ELF header

2. Program header table (程序头表)

3. Section (节、段)

4. Section header table (节头表、段头表、段表)

除了ELF header位置是固定的,其他部分的位置、大小由ELF header中的值决定。

ELF header

ELF header 结构定义(/usr/include/elf.h 中有)

typedef uint16_t Elf64_Half;    //  16位,16位值的类型

typedef uint32_t Elf64_Word;    //  32位,用于有符号和无符号的32位值的类型

typedef uint64_t Elf64_Addr;    // 地址类型,64位

typedef uint64_t Elf64_Off;     //  偏移的类型,64位

/* The ELF file header.  This appears at the start of every ELF file.  */

#define EI_NIDENT (16)  

typedef struct
{
  unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */   //  16字节
  Elf64_Half    e_type;         /* Object file type */
  Elf64_Half    e_machine;      /* Architecture */
  Elf64_Word    e_version;      /* Object file 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;        /* Processor-specific flags */
  Elf64_Half    e_ehsize;       /* ELF header size in bytes */
  Elf64_Half    e_phentsize;        /* Program header table entry size */
  Elf64_Half    e_phnum;        /* Program header table entry count */
  Elf64_Half    e_shentsize;        /* Section header table entry size */
  Elf64_Half    e_shnum;        /* Section header table entry count */
  Elf64_Half    e_shstrndx;     /* Section header string table index */
} Elf64_Ehdr;   //  大小总共64个字节,即在ELF文件的 前 0x40 字节。 Ehdr 即为 ELF header 

命令行查看 ELF header

[root@localhost src]# readelf -h test.o
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:                              REL (Relocatable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          0 (bytes into file)
  Start of section headers:          1648 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           0 (bytes)
  Number of program headers:         0
  Size of section headers:           64 (bytes)
  Number of section headers:         20
  Section header string table index: 19

hexdump -C test.o查看16进制,下面提取出 ELF header

00000000  7f 45 4c 46 02 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  01 00 3e 00 01 00 00 00  00 00 00 00 00 00 00 00  |..>.............|
00000020  00 00 00 00 00 00 00 00  70 06 00 00 00 00 00 00  |........p.......|
00000030  00 00 00 00 40 00 00 00  00 00 40 00 14 00 13 00  |....@.....@.....|

identification 即 e_ident

  1. 第一部分7f 45 4c 46 -> .ELF:4字节,表示是一个ELF对象
  2. 第二部分02:1字节,02表示是64位对象
  3. 第三部分01:1字节,01表示小端
  4. 第四部分0.:1字节,01表示头文件版本
  5. 其余默认0

information

e_type

两个字节,01 00表示是一个重定位文件,02 00表示可执行文件,即上面的elf文件标准的文件类型

e_machine

两个字节,3e 00表示是intel80386处理器体系结构。

e_version

四个字节,01 00 00 00表示是当前版本。

e_entry

八个字节,00 00 00 00 00 00 00 00表示当前程序没有入口点。

e_phoff

八个字节,00 00 00 00 00 00 00 00表示没有程序头表。

e_shoff

八个字节,70 06 00 00 00 00 00 00表示段表的偏移地址在00 00 00 00 00 00 06 70处,这里可以看出是小端。

e_flags

四个字节,00 00 00 00表示未知处理器特定标志#define EF_SH_UNKNOWN 0x0

e_ehsize

两个字节,40 00表示elf文件头大小为00 40(64个字节)

e_phentsize

两个字节,00 00表示重定位文件没有程序头表。

e_phnum

两个字节,00 00表示重定位文件没有程序头表。和上面重复了

e_ehentsize

两个字节,40 00 表示段头大小为00 40(64字节),section header table中每个元素(即header)的大小

e_shnum

两个字节,14 00表示段表入口有20个,即段表有20段。

e_shstrndx

两个字节,13 00 表示段表字符串在段表中的索引号,.shstrab段的段表索引号为00 13,即19

段头表(Section header table)相关字段

  1. 段头表偏移:e_shoff
  2. 段头表中各元素长度:e_ehentsize
  3. 段头表个数:e_shnum

通过ELF header找到各section

[root@localhost src]# readelf -S test.o
There are 20 section headers, starting at offset 0x670:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000000000  00000040
       000000000000001b  0000000000000000  AX       0     0     1
  [ 2] .rela.text        RELA             0000000000000000  00000430
       0000000000000018  0000000000000018   I      17     1     8
  [ 3] .data             PROGBITS         0000000000000000  0000005b
       0000000000000000  0000000000000000  WA       0     0     1
  [ 4] .bss              NOBITS           0000000000000000  0000005b
       0000000000000000  0000000000000000  WA       0     0     1
  [ 5] .debug_info       PROGBITS         0000000000000000  0000005b
       0000000000000073  0000000000000000           0     0     1
  [ 6] .rela.debug_info  RELA             0000000000000000  00000448
       0000000000000108  0000000000000018   I      17     5     8
  [ 7] .debug_abbrev     PROGBITS         0000000000000000  000000ce
       0000000000000051  0000000000000000           0     0     1
  [ 8] .debug_aranges    PROGBITS         0000000000000000  0000011f
       0000000000000030  0000000000000000           0     0     1
  [ 9] .rela.debug_arang RELA             0000000000000000  00000550
       0000000000000030  0000000000000018   I      17     8     8
  [10] .debug_line       PROGBITS         0000000000000000  0000014f
       0000000000000041  0000000000000000           0     0     1
  [11] .rela.debug_line  RELA             0000000000000000  00000580
       0000000000000018  0000000000000018   I      17    10     8
  [12] .debug_str        PROGBITS         0000000000000000  00000190
       000000000000008d  0000000000000001  MS       0     0     1
  [13] .comment          PROGBITS         0000000000000000  0000021d
       000000000000002e  0000000000000001  MS       0     0     1
  [14] .note.GNU-stack   PROGBITS         0000000000000000  0000024b
       0000000000000000  0000000000000000           0     0     1
  [15] .eh_frame         PROGBITS         0000000000000000  00000250
       0000000000000058  0000000000000000   A       0     0     8
  [16] .rela.eh_frame    RELA             0000000000000000  00000598
       0000000000000030  0000000000000018   I      17    15     8
  [17] .symtab           SYMTAB           0000000000000000  000002a8
       0000000000000168  0000000000000018          18    13     8
  [18] .strtab           STRTAB           0000000000000000  00000410
       0000000000000019  0000000000000000           0     0     1
  [19] .shstrtab         STRTAB           0000000000000000  000005c8
       00000000000000a8  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)

找到段表

文件头的e_shoff是段表偏移00 00 00 00 00 00 06 70,即相对于文件首地址的偏移。

e_shnum*e_ehentsize+e_shoff=00000b70,即为段表尾部(不包含)。

//  第一段:全为0,不表示任何段
00000670  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
//  第二段
//  段名:.text
//  类型:PROGBITS
//  标志:表示alloc和execute
//  相对于文件头偏移:0x40
//  大小:0x1b
000006b0  20 00 00 00 01 00 00 00  06 00 00 00 00 00 00 00  | ...............|
000006c0  00 00 00 00 00 00 00 00  40 00 00 00 00 00 00 00  |........@.......|
000006d0  1b 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000006e0  01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
//  第三段
//  段名:.rela.text
//  类型:RELA
//  标志:info
//  相对于文件头偏移:0x0430
//  大小:0x18
000006f0  1b 00 00 00 04 00 00 00  40 00 00 00 00 00 00 00  |........@.......|
00000700  00 00 00 00 00 00 00 00  30 04 00 00 00 00 00 00  |........0.......|
00000710  18 00 00 00 00 00 00 00  11 00 00 00 01 00 00 00  |................|
00000720  08 00 00 00 00 00 00 00  18 00 00 00 00 00 00 00  |................|
//  第四段
//  段名:.data
//  类型:PROGBITS
//  标志:write、alloc
//  相对于文件头偏移:0x5b
//  大小:0
00000730  26 00 00 00 01 00 00 00  03 00 00 00 00 00 00 00  |&...............|
00000740  00 00 00 00 00 00 00 00  5b 00 00 00 00 00 00 00  |........[.......|
00000750  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000760  01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
//  第五段
//  段名:.bss
//  类型:NOBITS
//  标志:write、alloc
//  相对于文件头偏移:0x5b
//  大小:0
00000770  2c 00 00 00 08 00 00 00  03 00 00 00 00 00 00 00  |,...............|
00000780  00 00 00 00 00 00 00 00  5b 00 00 00 00 00 00 00  |........[.......|
00000790  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000007a0  01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
//  第六段
//  段名:.debug_info
//  类型:PROGBITS
//  标志:无
//  相对于文件头偏移:0x5b
//  大小:0x73
000007b0  36 00 00 00 01 00 00 00  00 00 00 00 00 00 00 00  |6...............|
000007c0  00 00 00 00 00 00 00 00  5b 00 00 00 00 00 00 00  |........[.......|
000007d0  73 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |s...............|
000007e0  01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
//  第七段
//  段名:.rela.debug_info
//  类型:RELA
//  标志:info
//  相对于文件头偏移:0x0448
//  大小:0x0108
000007f0  31 00 00 00 04 00 00 00  40 00 00 00 00 00 00 00  |1.......@.......|
00000800  00 00 00 00 00 00 00 00  48 04 00 00 00 00 00 00  |........H.......|
00000810  08 01 00 00 00 00 00 00  11 00 00 00 05 00 00 00  |................|
00000820  08 00 00 00 00 00 00 00  18 00 00 00 00 00 00 00  |................|
//  第八段
//  段名:.debug_abbrev
//  类型:PROGBITS
//  标志:无
//  相对于文件头偏移:0xce
//  大小:0x51
00000830  42 00 00 00 01 00 00 00  00 00 00 00 00 00 00 00  |B...............|
00000840  00 00 00 00 00 00 00 00  ce 00 00 00 00 00 00 00  |................|
00000850  51 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |Q...............|
00000860  01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
//  第九段
//  段名:.debug_aranges
//  类型:PROGBITS
//  标志:无
//  相对于文件头偏移:0x011f
//  大小:0x30
00000870  55 00 00 00 01 00 00 00  00 00 00 00 00 00 00 00  |U...............|
00000880  00 00 00 00 00 00 00 00  1f 01 00 00 00 00 00 00  |................|
00000890  30 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |0...............|
000008a0  01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
//  第十段
//  段名:.rela.debug_aranges
//  类型:RELA
//  标志:info
//  相对于文件头偏移:0x0550
//  大小:0x30
000008b0  50 00 00 00 04 00 00 00  40 00 00 00 00 00 00 00  |P.......@.......|
000008c0  00 00 00 00 00 00 00 00  50 05 00 00 00 00 00 00  |........P.......|
000008d0  30 00 00 00 00 00 00 00  11 00 00 00 08 00 00 00  |0...............|
000008e0  08 00 00 00 00 00 00 00  18 00 00 00 00 00 00 00  |................|
//  第十一段
//  段名:.debug_line
//  类型:PROGBITS
//  标志:无
//  相对于文件头偏移:0x014f
//  大小:0x41
000008f0  69 00 00 00 01 00 00 00  00 00 00 00 00 00 00 00  |i...............|
00000900  00 00 00 00 00 00 00 00  4f 01 00 00 00 00 00 00  |........O.......|
00000910  41 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |A...............|
00000920  01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
//  第十二段
//  段名:.rela.debug_line
//  类型:RELA
//  标志:info
//  相对于文件头偏移:0x0580
//  大小:0x18
00000930  64 00 00 00 04 00 00 00  40 00 00 00 00 00 00 00  |d.......@.......|
00000940  00 00 00 00 00 00 00 00  80 05 00 00 00 00 00 00  |................|
00000950  18 00 00 00 00 00 00 00  11 00 00 00 0a 00 00 00  |................|
00000960  08 00 00 00 00 00 00 00  18 00 00 00 00 00 00 00  |................|
//  第十三段
//  段名:.debug_str
//  类型:PROGBITS
//  标志:merge、strings
//  相对于文件头偏移:0x0190
//  大小:0x8d
00000970  75 00 00 00 01 00 00 00  30 00 00 00 00 00 00 00  |u.......0.......|
00000980  00 00 00 00 00 00 00 00  90 01 00 00 00 00 00 00  |................|
00000990  8d 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000009a0  01 00 00 00 00 00 00 00  01 00 00 00 00 00 00 00  |................|
//  第十四段
//  段名:.comment
//  类型:PROGBITS
//  标志:merge、strings
//  相对于文件头偏移:0x021d
//  大小:0x2e
000009b0  80 00 00 00 01 00 00 00  30 00 00 00 00 00 00 00  |........0.......|
000009c0  00 00 00 00 00 00 00 00  1d 02 00 00 00 00 00 00  |................|
000009d0  2e 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000009e0  01 00 00 00 00 00 00 00  01 00 00 00 00 00 00 00  |................|
//  第十五段
//  段名:.note.GNU-stack
//  类型:PROGBITS
//  标志:无
//  相对于文件头偏移:0x024b
//  大小:0
000009f0  89 00 00 00 01 00 00 00  00 00 00 00 00 00 00 00  |................|
00000a00  00 00 00 00 00 00 00 00  4b 02 00 00 00 00 00 00  |........K.......|
00000a10  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000a20  01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
//  第十六段
//  段名:.eh_frame
//  类型:PROGBITS
//  标志:alloc
//  相对于文件头偏移:0x0250
//  大小:0x58
00000a30  9e 00 00 00 01 00 00 00  02 00 00 00 00 00 00 00  |................|
00000a40  00 00 00 00 00 00 00 00  50 02 00 00 00 00 00 00  |........P.......|
00000a50  58 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |X...............|
00000a60  08 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
//  第十七段
//  段名:.rela.eh_frame
//  类型:RELA
//  标志:info
//  相对于文件头偏移:0x0598
//  大小:0x30
00000a70  99 00 00 00 04 00 00 00  40 00 00 00 00 00 00 00  |........@.......|
00000a80  00 00 00 00 00 00 00 00  98 05 00 00 00 00 00 00  |................|
00000a90  30 00 00 00 00 00 00 00  11 00 00 00 0f 00 00 00  |0...............|
00000aa0  08 00 00 00 00 00 00 00  18 00 00 00 00 00 00 00  |................|
//  第十八段
//  段名:.symtab
//  类型:STRTAB
//  标志:无
//  相对于文件头偏移:0x02a8
//  大小:0x0168
00000ab0  01 00 00 00 02 00 00 00  00 00 00 00 00 00 00 00  |................|
00000ac0  00 00 00 00 00 00 00 00  a8 02 00 00 00 00 00 00  |................|
00000ad0  68 01 00 00 00 00 00 00  12 00 00 00 0d 00 00 00  |h...............|
00000ae0  08 00 00 00 00 00 00 00  18 00 00 00 00 00 00 00  |................|
//  第十九段
//  段名:.strtab
//  类型:STRTAB
//  标志:无
//  相对于文件头偏移:0x0410
//  大小:0x19
00000af0  09 00 00 00 03 00 00 00  00 00 00 00 00 00 00 00  |................|
00000b00  00 00 00 00 00 00 00 00  10 04 00 00 00 00 00 00  |................|
00000b10  19 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000b20  01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
//  第二十段
//  段名:.shstrtab
//  类型:STRTAB
//  标志:无
//  相对于文件头偏移:0x05c8
//  大小:0xa8
00000b30  11 00 00 00 03 00 00 00  00 00 00 00 00 00 00 00  |................|
00000b40  00 00 00 00 00 00 00 00  c8 05 00 00 00 00 00 00  |................|
00000b50  a8 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000b60  01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000b70

Section header table

Section header table 结构定义(/usr/include/elf.h 中有)

typedef struct
{
  Elf64_Word    sh_name;        /* Section name (string tbl index) */
  Elf64_Word    sh_type;        /* Section type */
  Elf64_Xword   sh_flags;       /* Section flags */
  Elf64_Addr    sh_addr;        /* Section virtual addr at execution */
  Elf64_Off sh_offset;      /* Section file offset */
  Elf64_Xword   sh_size;        /* Section size in bytes */
  Elf64_Word    sh_link;        /* Link to another section */
  Elf64_Word    sh_info;        /* Additional section information */
  Elf64_Xword   sh_addralign;       /* Section alignment */
  Elf64_Xword   sh_entsize;     /* Entry size if section holds table */
} Elf64_Shdr;

分析段表

第一段:全为0,不表示任何段

00000670  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*

注意这里*是省略了3行0值,这个从下一个地址000006b0可以看出。

第二段

000006b0  20 00 00 00 01 00 00 00  06 00 00 00 00 00 00 00  | ...............|
000006c0  00 00 00 00 00 00 00 00  40 00 00 00 00 00 00 00  |........@.......|
000006d0  1b 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000006e0  01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

sh_name

四个字节,20 00 00 00表示该段名称在.shstrtab中偏移量,是字符串,以\0结尾,这里为.text节。

sh_type

四个字节,01 00 00 00表示这个段拥有程序所定义的信息,其格式和含义完全由该程序确定,这里表示PROGBITS

02 00 00 00表示STRTAB,即字符串表。

sh_flags

八个字节,06 00 00 00 00 00 00 00表示allocexecute

sh_addr

八个字节,00 00 00 00 00 00 00 00表示是section在内存中的虚拟地址,.o文件不需要执行,这里都是0。

sh_offset

八个字节,40 00 00 00 00 00 00 00表示是section与文件头之间的偏移,即00 00 00 00 00 00 00 40。由于ELF header0x40字节,因此它的下一个地址就是本section header tableheader指向的section

sh_size

八个字节,1b 00 00 00 00 00 00 00表示文件里面section占用的大小,即00 00 00 00 00 00 00 1b

sh_link

四个字节,00 00 00 00表示没有链接信息。

sh_info

四个字节,00 00 00 00表示没有辅助信息。

sh_addralign

八个字节,01 00 00 00 00 00 00 00表示字节对齐长度。

sh_entsize

八个字节,00 00 00 00 00 00 00 00表示没有入口。

其他...

section

可用objdump -s -d test.o把各段信息提取出来

.text:本节中是可执行指令的集合

通过section header table中的信息,可以找到指令:

00000040  55 48 89 e5 b8 01 00 00  00 5d c3 55 48 89 e5 e8  |UH.......].UH...|
00000050  00 00 00 00 b8 00 00 00  00 5d c3

通过反汇编查看:

[root@localhost src]# objdump -d test.o

test.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <_Z4fangv>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   b8 01 00 00 00          mov    $0x1,%eax
   9:   5d                      pop    %rbp
   a:   c3                      retq   

000000000000000b 
: b: 55 push %rbp c: 48 89 e5 mov %rsp,%rbp f: e8 00 00 00 00 callq 14 14: b8 00 00 00 00 mov $0x0,%eax 19: 5d pop %rbp 1a: c3 retq

.comment:本节用来存放编译器版本信息

00000210                                          00 47 43  |             .GC|
00000220  43 3a 20 28 47 4e 55 29  20 34 2e 38 2e 35 20 32  |C: (GNU) 4.8.5 2|
00000230  30 31 35 30 36 32 33 20  28 52 65 64 20 48 61 74  |0150623 (Red Hat|
00000240  20 34 2e 38 2e 35 2d 33  36 29 00                 | 4.8.5-36).     |

.symtab:符号表

Symbol Table,本节存放所有section中定义的符号名字,一般是变量、函数

000002a0                           00 00 00 00 00 00 00 00  |        ........|
000002b0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000002c0  01 00 00 00 04 00 f1 ff  00 00 00 00 00 00 00 00  |................|
000002d0  00 00 00 00 00 00 00 00  00 00 00 00 03 00 01 00  |................|
000002e0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000002f0  00 00 00 00 03 00 03 00  00 00 00 00 00 00 00 00  |................|
00000300  00 00 00 00 00 00 00 00  00 00 00 00 03 00 04 00  |................|
00000310  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000320  00 00 00 00 03 00 05 00  00 00 00 00 00 00 00 00  |................|
00000330  00 00 00 00 00 00 00 00  00 00 00 00 03 00 07 00  |................|
00000340  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000350  00 00 00 00 03 00 08 00  00 00 00 00 00 00 00 00  |................|
00000360  00 00 00 00 00 00 00 00  00 00 00 00 03 00 0a 00  |................|
00000370  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000380  00 00 00 00 03 00 0c 00  00 00 00 00 00 00 00 00  |................|
00000390  00 00 00 00 00 00 00 00  00 00 00 00 03 00 0e 00  |................|
000003a0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000003b0  00 00 00 00 03 00 0f 00  00 00 00 00 00 00 00 00  |................|
000003c0  00 00 00 00 00 00 00 00  00 00 00 00 03 00 0d 00  |................|
000003d0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000003e0  0b 00 00 00 12 00 01 00  00 00 00 00 00 00 00 00  |................|
000003f0  0b 00 00 00 00 00 00 00  14 00 00 00 12 00 01 00  |................|
00000400  0b 00 00 00 00 00 00 00  10 00 00 00 00 00 00 00  |................|

.shstrtab:本节存放 sh_name

Section Header String Table,段表字符串表,针对段表的sh_name

000005c0                           00 2e 73 79 6d 74 61 62  |        ..symtab|
000005d0  00 2e 73 74 72 74 61 62  00 2e 73 68 73 74 72 74  |..strtab..shstrt|
000005e0  61 62 00 2e 72 65 6c 61  2e 74 65 78 74 00 2e 64  |ab..rela.text..d|
000005f0  61 74 61 00 2e 62 73 73  00 2e 72 65 6c 61 2e 64  |ata..bss..rela.d|
00000600  65 62 75 67 5f 69 6e 66  6f 00 2e 64 65 62 75 67  |ebug_info..debug|
00000610  5f 61 62 62 72 65 76 00  2e 72 65 6c 61 2e 64 65  |_abbrev..rela.de|
00000620  62 75 67 5f 61 72 61 6e  67 65 73 00 2e 72 65 6c  |bug_aranges..rel|
00000630  61 2e 64 65 62 75 67 5f  6c 69 6e 65 00 2e 64 65  |a.debug_line..de|
00000640  62 75 67 5f 73 74 72 00  2e 63 6f 6d 6d 65 6e 74  |bug_str..comment|
00000650  00 2e 6e 6f 74 65 2e 47  4e 55 2d 73 74 61 63 6b  |..note.GNU-stack|
00000660  00 2e 72 65 6c 61 2e 65  68 5f 66 72 61 6d 65 00  |..rela.eh_frame.|

.strtab:本节是段表的字符串表

String Table,字符串表,用于存储ELF文件中用到的各种字符串。

shstrtabsymtab经常引用strtab中的字符串。

00000410  00 74 65 73 74 62 2e 63  70 70 00 5f 5a 34 66 61  |.testb.cpp._Z4fa|
00000420  6e 67 76 00 6d 61 69 6e  00                       |ngv.main.       |

0分割出3部分,为testb.cpp_Z4fangvmain

特殊符号

ld链接脚本中将会定义很多特殊的符号,这些符号并没有在你的程序中定义,但是你可以直接声明并应用,我们称之为特殊符号

对于这些特殊符号,我们不必定义它,只需声明引用即可使用。链接器会在将程序最终链接成可执行文件的时候将其解析成正确的值,只有
在使用ld链接生成最终可执行文件的时候这些符号才会存在。

查看链接默认脚本

ld -verbose

.rodata:只读数据段

保存常量,比如调用printf时会用一个字符串常量"%d\n"用来定义格式化输出,它是一种只读数据,所以保存在.rodata段,

只读数据类型

  1. 在只读变量
  2. 字符串常量

单独设置.rodata段的好处

支持了C里面的关键字const, 而且操作系统加载程序时自动将只读变量加载到只读存储区,或者映射成只读,这样任何修改操作都会被认为非法操作,保证了程序的安全性。

.data:数据段

保存的是那些已经初始化的全局静态变量和局部静态变量。

.bss

存放:

  1. 未初始化的全局变量
  2. 局部静态变量
  3. 初始化为0的变量

这些变量可读可写。

未初始化的全局变量和局部静态变量的处理方式

与不同的语言和不同的编译器实现有关

  1. 放入.bss
  2. 使用未定义的COMMON符号,直到链接成可执行文件时才在.bss段分配空间。

与动态链接相关的段

在Linux 下,动态链接器 ld.so是一个共享对象,操作系统同样通过映射的方式将其加载到进程的地址空间中。操作系统在加载完动态链接后,将控制权交给动态链接器的入口地址,动态链接器执行一系列自身的初始化操作,而后根据当前的环境参数,对可执行文件进行动态链接工作,完成后将操作系统的控制权交给可执行文件的入口地址。

.interp

动态链接器在操作系统中的位置不是由系统配置决定,也不是由环境参数指定,而是由 ELF 文件中的 .interp 段指定。

该段里保存的是一个字符串,这个字符串就是可执行文件所需要的动态链接器的位置,常位于 /lib/ld-linux.so.2。(通常是软链接)

.dynamic

该段中保存了动态链接器所需要的基本信息,是一个结构数组,可以看做动态链接下 ELF 文件的“文件头”。存储了动态链接会用到的各个表的位置等信息。

.dynsym

该段与 .symtab段类似,但只保存了与动态链接相关的符号,很多时候,ELF文件同时拥有 .symtab.synsym段,其中 .symtab 将包含 .synsym 中的符号。

该符号表中记录了动态链接符号在动态符号字符串表中的偏移,与.symtab中记录对应。

.dynstr

该段是 .dynsym 段的辅助段,.dynstr.dynsym 的关系,类比与 .symtab.strtab 的关系

.hash

在动态链接下,需要在程序运行时查找符号,为了加快符号查找过程,增加了辅助的符号哈希表,功能与 .dynstr 类似

.rel.dyn

对数据引用的修正,其所修正的位置位于 .got以及数据段(类似重定位段 .rel.data

.rel.plt

对函数引用的修正,其所修正的位置位于 .got.plt

其他常量段

常用的段名 说明
.rodata1 Read Only Data,这种段里存放的是只读数据,比如字符串常量,全局const变量,和".rodata"一样
.debug 调试信息
.hash 符号哈希表
.line 调试时的行号表,即源代码行号和编译后指令的对应表
.note 额外的编译器信息。比如程序的公司名,发布版本号
,plt .got 动态链接的跳转表和全局入口表
.init .finit 程序初始化与终结代码段

Program header table (程序头表)

ELF中把权限相同、又连在一起的段(section)叫做segment,操作系统正是按照segment来映射可执行文件的。

描述这些segment的结构叫做程序头,它描述了elf文件该如何被操作系统映射到内存空间中。

Program header table 结构定义

typedef struct
{
  Elf64_Word    p_type;         /* Segment type */
  Elf64_Word    p_flags;        /* Segment 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 */
} Elf64_Phdr;

p_type

段类型,如:PT_LOAD (可加载段)、PT_DYNAMIC (动态段)。

p_offset

段数据的第一个字节相对于文件开头的偏移量。

p_vaddr

段数据的第一个字节在内存中的虚拟地址 (只是一个偏移)。

p_paddr

暂时不用关心。

p_filesz

段数据在文件中的的字节大小,可以是 0。

p_memsz

段数据在内存映像中的字节大小,可以是 0。

p_flags

段内存映像的访问权限:PF_X (可执行)、PF_W (可写)、PF_R (可读)。

p_align

如果为 0 或 1,表示不需要对齐。否则,p_align 应该是 2 的正整数幂,p_vaddr 和 p_offset 在对 p_align 取模后应相等。

5种常见程序头类型

1. PT_LOAD

一个可执行文件只是一个PT_LOAD类型的段。

这类程序头描述的是可装载的段。

一般一个需要动态链接的ELF可执行文件通常由两个可装载的段:

  1. 存放程序代码的text段
  2. 存放全局变量和动态链接的data段。

上面两个段会被映射到内存,根据p_align中存放的值在内存中对齐。

程序头主要描述程序执行时在内存中的布局。

2. PT_DYNAMIC

动态段是动态链接可执行文件持有的,包含动态链接器所必有的一些信息,包含了一些标记值和指针,比如运行是需要链接的共享库列表,全局偏移表,重定位条目的相关信息。

3. PT_NOTE

存了与特定供应商或者系统相关的附加信息。

4. PT_INTERP

将信息二号位置存放在一个NULL为终止符的字符串中,对程序解释器位置的描述。

5. PT_LOAD

段保存了程序头标本身的位置和大小。Phdr表保存了所有的Phdr对文件(以及内存镜像)中段的描述信息。

readelf获取程序头表(目标文件没有程序头表)

[root@localhost src]# readelf -l test   

Elf file type is EXEC (Executable file)
Entry point 0x4005c0
There are 9 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040
                 0x00000000000001f8 0x00000000000001f8  R E    8
  INTERP         0x0000000000000238 0x0000000000400238 0x0000000000400238
                 0x000000000000001c 0x000000000000001c  R      1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x000000000000096c 0x000000000000096c  R E    200000
  LOAD           0x0000000000000e38 0x0000000000600e38 0x0000000000600e38
                 0x000000000000020c 0x0000000000000220  RW     200000
  DYNAMIC        0x0000000000000e50 0x0000000000600e50 0x0000000000600e50
                 0x00000000000001b0 0x00000000000001b0  RW     8
  NOTE           0x0000000000000254 0x0000000000400254 0x0000000000400254
                 0x0000000000000044 0x0000000000000044  R      4
  GNU_EH_FRAME   0x00000000000007d8 0x00000000004007d8 0x00000000004007d8
                 0x000000000000004c 0x000000000000004c  R      4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     10
  GNU_RELRO      0x0000000000000e38 0x0000000000600e38 0x0000000000600e38
                 0x00000000000001c8 0x00000000000001c8  R      1

 Section to Segment mapping:
  Segment Sections...
   00     
   01     .interp 
   02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame 
   03     .init_array .fini_array .jcr .dynamic .got.plt .data .bss 
   04     .dynamic 
   05     .note.ABI-tag .note.gnu.build-id 
   06     .eh_frame_hdr 
   07     
   08     .init_array .fini_array .jcr .dynamic 

只有LOAD类型的segment需要被映射。

我们需要做的是找到这些segment在文件中的位置,并将其加载到对应的内存空间,对于memsiz大于filesiz的部分全部填充为0,加载完之后让程序跳转到入口地址。

节 (section) 和段 (segment)

节 (section) 和段 (segment) 分别是从链接视图和执行视图两个不同的角度的来划分的,它们有一个对应关系 (每个段“包含”一个或者多个节):

[root@localhost src]# readelf -l test   

...

Section to Segment mapping:
  Segment Sections...
   00     
   01     .interp 
   02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame 
   03     .init_array .fini_array .jcr .dynamic .got.plt .data .bss 
   04     .dynamic 
   05     .note.ABI-tag .note.gnu.build-id 
   06     .eh_frame_hdr 
   07     
   08     .init_array .fini_array .jcr .dynamic 

你可能感兴趣的:(2019-06-26)