ELF文件格式详解

前情提要

需求想写不落地加载dex的壳,但是想要加载dex的同时还可以oat就必须涉及到hook了,于是决定彻底扫一遍hook。但看了几篇文后发现….ELF文件格式都忘了差不多了、linker与exec还没看…..
还是因为当初看的时候手太懒了没有动手写啊……….

于是先从一篇完整的elf文件格式解析写起
readelf真的好用……

hook基本都是c++写的,因此解析也用c++写好点
因为我的机子都是64位的,因此解析android arm-64位下的so为目的

由于许多书籍对elf中的名词描述不同,因此采用如下翻译:

汉语 英语
ELF文件头 ELF header
基地址 base address
动态连接器 dynamic linker
全局偏移量表 global offset table
初始化函数 initialization function
函数连接表 procedure linkage table
程序头 program header
section
segment

此外本篇将地址区分为三种:
逻辑地址:指由程序产生的与段相关的偏移地址部分。也就是段*16+的那个值。
虚拟地址/线性地址:为进程虚拟的地址空间
物理地址:实际地址空间

现在从逻辑地址到虚拟地址还要经过一次保护模式分段机制。简述就是保护模式将段的信息存于GDT与LDT中,段寄存器存其表的索引值。
32位Linux中逻辑地址等于线性地址,因为Linux所有的段(用户代码段、用户数据段、内核代码段、内核数据段)的线性地址都是从 0x00000000 开始,长度4G,这样 线性地址=逻辑地址+ 0x00000000,也就是说逻辑地址等于线性地址了。
现在先认为elf中的都是虚拟地址。等搞清保护模式机制时阅读exec与linker再分析地址问题。64位下的机制貌似很不一样…….

ps:elf不是精灵的意思么….之后根据linker写elf壳的时候就叫darkelf好了….

概述

由于考虑到对各种不同硬件和操作系统的适用性和扩展性,ELF(v1.2)规范在制 定时,把 ELF格式分为了三个层次。
第一层是基本的部分,即格式中通用的部 分,这部分在各种处理器架构和操作系统上都是相同的;
第二层是处理器的扩展部 分,这部分会因处理器架构的不同而不同,规范中只定义了针对 Intel i386架构的内容,针对其它的处理器的内容由处理器厂商自己提供;
第三层是操作系统的扩展 部分,这部分内容在不同的操作系统下面也可能是不同的,规范中只定义了针对 UNIX System V Release 4操作系统的内容。
90年代发布的ELF1.2标准,因此本篇参考该标准解析。由于该文件只是规范,并且有根据不同厂商扩展的部分,实际一切以现有的ARM64-android为分析源

文件类型

ELF文件的类型:可重定位文件(.o .a)、可执行文件、共享文件(.so)、转存文件

可重定位文件

用于与其它目标文件进行连接以构建可执行文件或动态链接库。可重定位文件就是常说的目标文件,由源文件编译而成,但还 没有连接成可执行文件。在 UNIX系统下,一般有扩展名”.o”。之所以称其为“可 重定位”,是因为在这些文件中,如果引用到其它目标文件或库文件中定义的符号 (变量或者函数)的话,只是给出一个名字,这里还并不知道这个符号在哪里,其具体的地址是什么。需要在连接的过程中,把对这些外部符号的引用重新定位到其 真正定义的位置上,所以称目标文件为“可重定位”或者“待重定位”的。 .a是.o的集合

共享目标文件

即动态连接库文件。它在以下两种情况下被使用:第一,在连接过程中与其它动态链接库或可重定位文件一起构建新的目标 文件;第二,在可执行文件被加载的过程中,被动态链接到新的进程中,成为运行 代码的一部分。

可执行文件

经过连接的,可以执行的程序文件。

文件格式概观

ELF文件根据用途不同可被看成的内容不同

用于构建程序或者用于运行程序

注意实际上elf文件只有文件头的位置是固定的,其它内容的位置都可以变化

ELF文件头

位于文件的开始处,包含有整个文件的结构信息。

节(section)

是专用于连接过程而言的,在每个“节”中包含有指令数据、符号数据、重 定位数据等等。

程序头表(program header table)

在运行过程中是必须的,在连接过程中是可选的,因为它的作用是告诉系统 如何创建进程的镜像。

节头表 (section header table)

包含有文件中所有“节”的信息。在连接视图中,“节头表”是必须存在 的,文件里的每一个“节”都需要在“节头表”中有一个对应的注册项,这个注册 项描述了节的名字、大小等等。

数据类型

无符号:

自定义 字节
Elf_Byte 1
Elf64_Quarter 2
Elf64_Half 4
Elf64_Word 4
Elf64_Addr 8
Elf64_Off 8
Elf64_Xword 8

有符号:

自定义 字节
Elf64_Shalf 4
Elf64_Sword 4
Elf64_Sxword 8

不管平台如何,x86还是arm。linux还是win。编译或者加载64位elf的时候ELF文件都是按这个类型大小定义的。因此只要保证头文件数据类型的大小一致性就能移植编译使用
该字长由ELF自己定义,与主机无关

ELF的字符集采用ASCII,支持其它编码格式

ELF文件头

文件头描述整个文件的结构信息,描述可能与文件中实际不同,具体处理需要参考具体实现源码-linker与exec。加壳时寻找其bug

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#define EI_NIDENT 16 /* Size of e_ident[] */
typedef struct {
unsigned char e_ident[EI_NIDENT]; /* Id bytes */
Elf64_Quarter e_type; /* file type */
Elf64_Quarter e_machine; /* machine type */
Elf64_Half e_version; /* version number */
Elf64_Addr e_entry; /* entry point */
Elf64_Off e_phoff; /* Program hdr offset */
Elf64_Off e_shoff; /* Section hdr offset */
Elf64_Half e_flags; /* Processor flags */
Elf64_Quarter e_ehsize; /* sizeof ehdr */
Elf64_Quarter e_phentsize; /* Program header entry size */
Elf64_Quarter e_phnum; /* Number of program headers */
Elf64_Quarter e_shentsize; /* Section header entry size */
Elf64_Quarter e_shnum; /* Number of section headers */
Elf64_Quarter e_shstrndx; /* String table index */
} Elf64_Ehdr;

ident

前16个字节用于识别最基本的意义。
ELF文件开始的这一部分的格式是固定并通用的,在所有平台上都一样。 所有处理器都可能用固定的格式去读取这一部分的内容,从而获知这个 ELF文件 中接下来的内容应该如何读取和解析。

定义ident数组索引功能的宏:

1
2
3
4
5
6
7
8
9
10
11
12
//e_ident[] identification indexes
#define EI_MAG0 0 /* file ID 文件标志*/
#define EI_MAG1 1 /* file ID */
#define EI_MAG2 2 /* file ID */
#define EI_MAG3 3 /* file ID */
#define EI_CLASS 4 /* file class 文件类别*/
#define EI_DATA 5 /* data encoding 编码格式*/
#define EI_VERSION 6 /* ELF header version 文件版本*/
#define EI_OSABI 7 /* OS/ABI ID */
#define EI_ABIVERSION 8 /* ABI version */
#define EI_PAD 9 /* start of pad bytes 补齐*/
#define EI_NIDENT 16 /* Size of e_ident[] */

MAG0-3

魔数部分,用于标识ELF文件,这四个字节内容固定

1
2
3
4
5
6
7
/* e_ident[] magic number */
#define ELFMAG0 0x7f /* e_ident[EI_MAG0] */
#define ELFMAG1 'E' /* e_ident[EI_MAG1] */
#define ELFMAG2 'L' /* e_ident[EI_MAG2] */
#define ELFMAG3 'F' /* e_ident[EI_MAG3] */
#define ELFMAG "\177ELF" /* magic */
#define SELFMAG 4 /* size of magic */

实际结果就是这个

CLASS

这个字节用于指明文件的类型

1
2
3
4
5
/* e_ident[] file class */
#define ELFCLASSNONE 0 /* invalid */
#define ELFCLASS32 1 /* 32-bit objs */
#define ELFCLASS64 2 /* 64-bit objs */
#define ELFCLASSNUM 3 /* number of classes 这里可选的种类数*/

实际结果为2

DATA

指明了目标文件中的数据编码格式

1
2
3
4
5
/* e_ident[] data encoding */
#define ELFDATANONE 0 /* invalid */
#define ELFDATA2LSB 1 /* Little-Endian */
#define ELFDATA2MSB 2 /* Big-Endian */
#define ELFDATANUM 3 /* number of data encode defines 可选种类数*/

决定大端小端的。
实际为小端,因此arm64下android原生为小端编码-方便与机器

VERSION

指明 ELF文件头的版本

1
2
3
4
5
typedef enum <uchar> {
E_NONE = 0x0,
E_CURRENT = 0x1,
E_NUM = 0x2
} ei_version_e;

current:当前
实际为1

OSABI

指明可运行的ABI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* e_ident[] Operating System/ABI */
#define ELFOSABI_SYSV 0 /* UNIX System V ABI */
#define ELFOSABI_HPUX 1 /* HP-UX operating system */
#define ELFOSABI_NETBSD 2 /* NetBSD */
#define ELFOSABI_LINUX 3 /* GNU/Linux */
#define ELFOSABI_HURD 4 /* GNU/Hurd */
#define ELFOSABI_86OPEN 5 /* 86Open common IA32 ABI */
#define ELFOSABI_SOLARIS 6 /* Solaris */
#define ELFOSABI_MONTEREY 7 /* Monterey */
#define ELFOSABI_IRIX 8 /* IRIX */
#define ELFOSABI_FREEBSD 9 /* FreeBSD */
#define ELFOSABI_TRU64 10 /* TRU64 UNIX */
#define ELFOSABI_MODESTO 11 /* Novell Modesto */
#define ELFOSABI_OPENBSD 12 /* OpenBSD */
#define ELFOSABI_ARM 97 /* ARM */
#define ELFOSABI_STANDALONE 255 /* Standalone (embedded) application */

实际并没使用…为0

ABIVERSION

实际为0

PAD

从 e-ident[EI-PAD]到 e-ident[EI-NIDENT-1]之间的7个字节目前暂时不使 用,留作以后扩展,在实际的文件中应被填 0补充,其它程序在读取 ELF文 件头时应该忽略这些字节。如果以后 ELF文件头的内容被扩展,这 9个字节 中有一些被使用起来的话,EI_PAD将被定义得更大

type

此字段表明本目标文件属于哪种类型。

1
2
3
4
5
6
7
8
9
/* e_type */
#define ET_NONE 0 /* No file type */
#define ET_REL 1 /* relocatable file 可重定位文件*/
#define ET_EXEC 2 /* executable file 可执行文件*/
#define ET_DYN 3 /* shared object file 动态连接库文件*/
#define ET_CORE 4 /* core file core文件*/
#define ET_NUM 5 /* number of types 种类的数量*/
#define ET_LOPROC 0xff00 /* reserved range for processor 特定处理器文件扩展下边界*/
#define ET_HIPROC 0xffff /* specific e_type 特定处理器扩展上边界*/

ET-LOPROC ~ ET-HIPROC (0xff00 ~ 0xffff)这一范围内的文件类型是为特定 处理器而保留的,如果需要为某种处理器专门设定文件格式,可以从这一范围内选 取一个做为标识。
在以上已定义范围外的文件类型均为保留类型,留做以后可能的扩展。

这个实际上arm64android下不管so还是加不加pie的可执行文件都是3

machine

此字段用于指定该文件适用的处理器体系结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* e_machine */
#define EM_NONE 0 /* No Machine */
#define EM_M32 1 /* AT&T WE 32100 */
#define EM_SPARC 2 /* SPARC */
#define EM_386 3 /* Intel 80386 */
#define EM_68K 4 /* Motorola 68000 */
#define EM_88K 5 /* Motorola 88000 */
#define EM_486 6 /* Intel 80486 - unused? */
#define EM_860 7 /* Intel 80860 */
#define EM_MIPS 8 /* MIPS R3000 Big-Endian only */
/*
* Don't know if EM_MIPS_RS4_BE,
* EM_SPARC64, EM_PARISC,
* or EM_PPC are ABI compliant
*/
#define EM_MIPS_RS4_BE 10 /* MIPS R4000 Big-Endian */
#define EM_SPARC64 11 /* SPARC v9 64-bit unoffical */
#define EM_PARISC 15 /* HPPA */
#define EM_SPARC32PLUS 18 /* Enhanced instruction set SPARC */
#define EM_PPC 20 /* PowerPC */
#define EM_ARM 40 /* Advanced RISC Machines ARM */
#define EM_ALPHA 41 /* DEC ALPHA */
#define EM_SPARCV9 43 /* SPARC version 9 */
#define EM_ALPHA_EXP 0x9026 /* DEC ALPHA */
#define EM_AMD64 62 /* AMD64 architecture */
#define EM_VAX 75 /* DEC VAX */
#define EM_NUM 15 /* number of machine types */

实际这里的值是B7。应该是android自己定的…

version

此字段指明目标文件的版本

1
2
3
4
/* Version */
#define EV_NONE 0 /* Invalid */
#define EV_CURRENT 1 /* Current */
#define EV_NUM 2 /* number of versions */

实际为1,可以根据目标文件的版本变动

entry

指明程序入口的虚拟地址,对于非可执行文件该值为0

实际上so与可执行都有这个值…用ida分析后发现该值指向的文件偏移量就是start函数地址。
文件中的虚拟地址应该都是相对文件0的偏移。

phoff

此字段指明程序头表(program header table)开始处在文件中的偏移量。如果没 有程序头表,该值应设为 0
这些偏移都是从0开始的,并且偏移处就是开始。如果把整个文件看成数组,file[off]就是目标的开始第一个字节,说成数组/文件的第几个字节就是off+1
数数从1开始数,数组从0开始标

shoff

此字段指明节头表(section header table)开始处在文件中的偏移量。如果没有节头表,该值应设为 0。

flags

此字段含有处理器特定的标志位。标志的名字符合”EF-machine-flag”的格式。
实际为0

ehsize

此字段表明 ELF文件头的大小,以字节为单位。

phentsize

此字段表明在程序头表中每一个表项的大小,以字节为单位。

phnum

此字段表明程序头表中总共有多少个表项。如果一个目标文件中没有程序头 表,该值应设为 0

shentsize

此字段表明在节头表中每一个表项的大小,以字节为单位。

shnum

此字段表明节头表中总共有多少个表项。如果一个目标文件中没有节头表, 该值应设为 0

shstrndx

节头表中与节名字表相对应的表项的索引。如果文件没有节名字表,此值应 设置为 SHN-UNDEF。
这个就是节名字表在节表的下标

节头表

在目标文件中可以包含很多“节”(section),所有这些“节”都登记在一张称为 “节头表”(section header table)的数组里。节头表的每一个表项是一个 Elf64-Shdr 结构,通过每一个表项可以定位到对应的节

elf文件头中给出了其表头位置、表项数量、表项大小

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

1
2
3
4
5
6
7
8
/* Special Section Indexes */
#define SHN_UNDEF 0 /* undefined 它表示一个未定义的、不存在的节的索引。 */
#define SHN_LORESERVE 0xff00 /* lower bounds of reserved indexes 被保留索引号区间的下限。*/
#define SHN_LOPROC 0xff00 /* reserved range for processor 为处理器定制节所保留的索引号区间的下限。 */
#define SHN_HIPROC 0xff1f /* specific section indexes 为处理器定制节所保留的索引号区间的上限。 */
#define SHN_ABS 0xfff1 /* absolute value 此节中所定义的符号有绝对的值,这个值不会因重定位而改变。*/
#define SHN_COMMON 0xfff2 /* common symbol 此节中所定义的符号是公共的,比如 FORTRAN COMMON符号或者未分配的C外部变量。*/
#define SHN_HIRESERVE 0xffff /* upper bounds of reserved indexes 被保留索引号区间的上限*/

通常,目标文件中含有众多的“节”,“节”区是文件中大的部分,它们需要满足下列这些条件:

  1. 目标文件中的每一个节一定对应有一个节头(section header),节头中有对节的描述信息;但有的节头可以没有对应的节,而只是一个空的头。
  2. 每一个节所占用的空间是连续的。
  3. 各个节之间不能互相重叠。
  4. 在目标文件中,节与节之间可能会存在一些多余的字节,这些字节不属于任何节。
1
2
3
4
5
6
7
8
9
10
11
12
typedef struct {
Elf64_Half sh_name; /* section name */
Elf64_Half sh_type; /* section type */
Elf64_Xword sh_flags; /* section flags */
Elf64_Addr sh_addr; /* virtual address */
Elf64_Off sh_offset; /* file offset */
Elf64_Xword sh_size; /* section size */
Elf64_Half sh_link; /* link to another */
Elf64_Half sh_info; /* misc info */
Elf64_Xword sh_addralign; /* memory alignment */
Elf64_Xword sh_entsize; /* table entry size */
} Elf64_Shdr;

name

本节的名字。整个名字的字符串并不存储在这里,它仅是一个索引号,指向 “字符串表”节中的某个位置,那里存储了一个以’\0’结尾的字符串

有一些定义好的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* Section names */
#define ELF_BSS ".bss" /* uninitialized data */
#define ELF_DATA ".data" /* initialized data */
#define ELF_DEBUG ".debug" /* debug */
#define ELF_DYNAMIC ".dynamic" /* dynamic linking information */
#define ELF_DYNSTR ".dynstr" /* dynamic string table */
#define ELF_DYNSYM ".dynsym" /* dynamic symbol table */
#define ELF_FINI ".fini" /* termination code */
#define ELF_GOT ".got" /* global offset table */
#define ELF_HASH ".hash" /* symbol hash table */
#define ELF_INIT ".init" /* initialization code */
#define ELF_REL_DATA ".rel.data" /* relocation data */
#define ELF_REL_FINI ".rel.fini" /* relocation termination code */
#define ELF_REL_INIT ".rel.init" /* relocation initialization code */
#define ELF_REL_DYN ".rel.dyn" /* relocaltion dynamic link info */
#define ELF_REL_RODATA ".rel.rodata" /* relocation read-only data */
#define ELF_REL_TEXT ".rel.text" /* relocation code */
#define ELF_RODATA ".rodata" /* read-only data */
#define ELF_SHSTRTAB ".shstrtab" /* section header string table */
#define ELF_STRTAB ".strtab" /* string table */
#define ELF_SYMTAB ".symtab" /* symbol table */
#define ELF_TEXT ".text" /* code */

功能见下特殊节

type

本节的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* sh_type */
#define SHT_NULL 0 /* inactive */
#define SHT_PROGBITS 1 /* program defined information */
#define SHT_SYMTAB 2 /* symbol table section */
#define SHT_STRTAB 3 /* string table section */
#define SHT_RELA 4 /* relocation section with addends*/
#define SHT_HASH 5 /* symbol hash table section */
#define SHT_DYNAMIC 6 /* dynamic section */
#define SHT_NOTE 7 /* note section */
#define SHT_NOBITS 8 /* no space section */
#define SHT_REL 9 /* relation section without addends */
#define SHT_SHLIB 10 /* reserved - purpose unknown */
#define SHT_DYNSYM 11 /* dynamic symbol table section */
#define SHT_NUM 12 /* number of section types 节种类的数量 */
#define SHT_LOPROC 0x70000000 /* reserved range for processor */
#define SHT_HIPROC 0x7fffffff /* specific section header types */
#define SHT_LOUSER 0x80000000 /* reserved range for application */
#define SHT_HIUSER 0xffffffff /* specific indexes */

0号的表头项不表达实际的内容,只是一个空的表项

NULL

此值表明本节头是一个无效的(非活动的)节头,它也没有对应的节。本节头中的其它成员的值也都是没有意义的。

PROGBITS

此值表明本节所含有的信息是由程序定义的,本节内容的格式和含义都由程序来决定。

SYMTAB

静态符号表
一般来说,SHT_SYMTAB提供的符号用于在创建目标文件的时候编辑连接,在运行期间也有可能会用于动态连接。SHT-SYMTAB包含完整的符号表,它往往会包含很多在运行期间(动态连接)用不到的符号。所以一个目标文件可以再有一个 SHT_DYNSYM节,它含有一个较小的符号表,专门用于动态连接。

STRTAB

此值表明本节是字符串表。目标文件中可以包含多个字符串表节。

RELA

重定位节,含加数

HASH

哈希表,连接使用,便于快速找到符号

DYNAMIC

动态连接信息的节

NOTE

此值表明本节包含的信息用于以某种方式来标记本文件。

NOBITS

此值表明这一节的内容是空的,节并不占用实际的文件空间。

REL

重定位节,不含加数

SHLIB

保留值,未定义

DYNSYM

动态符号表

LOPROC

为特殊处理器保留的节类型索引值的下边界。

HIPROC

为特殊处理器保留的节类型索引值的上边界。

LOUSER

为应用程序保留节类型索引值的下边界。

HIUSER

为应用程序保留节类型索引值的下边界。

flags

本节的一些属性,由一系列标志比特位组成,各个比特定义了节的不同属性,当某种属性被设置时相应的标志位被设为1,反之则设为0。未定义的标志位被全部置0。

1
2
3
4
5
6
/* Section Attribute Flags - sh_flags */
#define SHF_WRITE 0x1 /* Writable */
#define SHF_ALLOC 0x2 /* occupies memory */
#define SHF_EXECINSTR 0x4 /* executable */
#define SHF_MASKPROC 0xf0000000 /* reserved bits for processor */
/* specific section attributes */

WRITE

如果此标志被设置,表示本节所包含的内容在进程运行过程中是可写的

ALLOC

如果此标志被设置,表示本节内容在进程运行过程中要占用内存单元。并不是所有节都会占用实际的内存,有一些起控制作用的节,在目标文件映射到进程空间时,并不需要占用内存。

EXECINSTR

如果此标志被设置,表示此节内容是指令代码。

MASKPROC

所有被此值所覆盖的位都是保留做特殊处理器扩展用的。

addr

如果本节的内容需要映射到进程空间中去,此成员指定映射的起始地址;如果不需要映射,此值为0。
即指定虚拟地址。
实际上该值和文件偏移可能一样可能不同,但该值也是相对基址0的,在虚拟地址中可以相对装入,在文件中节连续但在内存中节之间可能留有很大空隙

offset

指明了本节所在的位置,该值是节的第一个字节在文件中的位置,即相对于文件开头的偏移量。单位是字节。如果该节的类型为 SHT-NOBITS的话,表明这一节的内容是空的,节并不占用实际的空间,这时 sh_offset只代表一个逻辑上的位置概念,并不代表实际的内容。

size

指明节的大小,单位是字节。如果该节的类型为 SHT-NOBITS,此值仍然可能为非零,但没有实际的意义。
指明的是该节在内存与文件中的大小,但是可被 NOBITS表示为仅指明内存中的大小。

此成员是一个索引值,指向节头表中本节所对应的位置。根据节的类型不同,本成员的意义也有所不同。

info

此成员含有此节的附加信息,根据节的类型不同,本成员的意义也有所不同
对于某些节类型来说,sh-link和 sh-info含有特殊的信息:

addralign

此成员指明本节内容如何对齐字节,即该节的地址应该向多少个字节对齐。
保证addr%(2 addralign )=0 为0或1表示没有对齐要求

entsize

有一些节的内容是一张表,其中每一个表项的大小是固定的,比如符号表。对于这种表来说,本成员指定其每一个表项的大小。如果此值为 0则表明本节内容不是这种表格

特殊节

在ELF文件中有一些特定的节是预定义好的,其内容是指令代码或者控制信息。这些节专门为操作系统使用,对于不同的操作系统,这些节的类型和属性有所不同。
.开头表示为系统保留的。应用程序可以构造自己的节,但最好不要重名。节名不具有唯一性,可以重复。

比如但不限于:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* Section names */
#define ELF_BSS ".bss" /* uninitialized data */
#define ELF_DATA ".data" /* initialized data */
#define ELF_DEBUG ".debug" /* debug */
#define ELF_DYNAMIC ".dynamic" /* dynamic linking information */
#define ELF_DYNSTR ".dynstr" /* dynamic string table */
#define ELF_DYNSYM ".dynsym" /* dynamic symbol table */
#define ELF_FINI ".fini" /* termination code */
#define ELF_GOT ".got" /* global offset table */
#define ELF_HASH ".hash" /* symbol hash table */
#define ELF_INIT ".init" /* initialization code */
#define ELF_REL_DATA ".rel.data" /* relocation data */
#define ELF_REL_FINI ".rel.fini" /* relocation termination code */
#define ELF_REL_INIT ".rel.init" /* relocation initialization code */
#define ELF_REL_DYN ".rel.dyn" /* relocaltion dynamic link info */
#define ELF_REL_RODATA ".rel.rodata" /* relocation read-only data */
#define ELF_REL_TEXT ".rel.text" /* relocation code */
#define ELF_RODATA ".rodata" /* read-only data */
#define ELF_SHSTRTAB ".shstrtab" /* section header string table */
#define ELF_STRTAB ".strtab" /* string table */
#define ELF_SYMTAB ".symtab" /* symbol table */
#define ELF_TEXT ".text" /* code */

bss

本节中包含目标文件中未初始化的全局变量。一般情况下,可执行程序在开始运行的时候,系统会把这一段内容清零。但是,在运行期间的 bss段是由系统初始化而成的,在目标文件中.bss节并不包含任何内容,其长度为 0,所以它的节类型为 SHT-NOBITS。
其也指明了size

comment

版本控制信息

可见其中包含了编译器信息,没有alloc表示不在内存中分配空间

data/data1

这两个节用于存放程序中被初始化过的全局变量。在目标文件中,它们是占用实际的存储空间的,与.bss节不同。

debug

本节中含有调试信息,内容格式没有统一规定。所有以”.debug”为前缀的节名 字都是保留的

dynamic

本节包含动态连接信息,并且可能有 SHF-ALLOC和 SHF-WRITE等属性。 是否具有SHF-WRITE属性取决于操作系统和处理器。
保存了动态连接的基本信息,依赖对象、动态连接符号表、动态连接重定位表等

dynstr

此节含有用于动态连接的字符串,一般是那些与符号表相关的名字。

里面含有动态连接符号的字串

dynsym

此节含有动态连接符号表

fini

此节包含进程终止时要执行的程序指令。当程序正常退出时,系统会执行这一节中的代码。

got

此节包含全局偏移量表。

hash

本节包含一张符号哈希表。

init

此节包含进程初始化时要执行的程序指令。当程序开始运行时,系统会在进 入主函数之前执行这一节中的代码。so中含有也会用于初始化

interp

此节含有 ELF程序解析器的路径名。如果此节被包含在某个可装载的段中,那么本节的属性中应置 SHF-ALLOC标志位,否则不置此标志

line

本节也是一个用于调试的节,它包含那些调试符号的行号,为程序指令码与源文件的行号建立起联系。其内容格式没有统一规定。

note

注释节

plt

函数连接表

relname/relaname

这两个节含有重定位信息。如果此节被包含在某个可装载的段中,那么本节的属性中应置 SHF-ALLOC标志位,否则不置此标志。注意,这两个节的名字 中”name”是可替换的部分,执照惯例,对哪一节做重定位就把”name”换成哪一节 的名字。比如,.text节的重定位节的名字将是.rel.text或.rela.text。

重定位是把符号引用和符号定义连接在一起的过程。使程序知道该跳转到哪里去

1
2
3
4
5
6
7
8
9
10
typedef struct {
Elf64_Xword r_offset; /* where to do it */
Elf64_Xword r_info; /* index & type of relocation */
} Elf64_Rel;
typedef struct {
Elf64_Xword r_offset; /* where to do it */
Elf64_Xword r_info; /* index & type of relocation */
Elf64_Sxword r_addend; /* adjustment value */
} Elf64_Rela;

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

  1. 在重定位文件中,r-offset成员含有一个节偏移量。也就是说,重定位节本身描述的是如何修改文件中的另一个节的内容,重定位偏移量 (r-offset)指向了另一个节中的一个存储单元地址。
  2. 在可执行文件或共享目标文件中,r-offset含有的是符号定义在进程空间中的虚拟地址。可执行文件和共享目标文件是用于运行程序而不是构建程序的,所以对它们来说更有用的信息是运行期的内存虚拟地址,而不是某个符号定义在文件中的位置。

表明了从哪个符号表找到符号去哪里如何修改地址

offset

本数据成员给出重定位所作用的位置。对于重定位文件来说,此值是受重定位作用的存储单元在节中的字节偏移量;对于可执行文件或共享目标文件来说,此值是受重定位作用的存储单元的虚拟地址。

info

1
2
3
#define ELF64_R_SYM(info) ((info) >> 32)
#define ELF64_R_TYPE(info) ((info) & 0xFFFFFFFF)
#define ELF64_R_INFO(s,t) (((s) << 32) + (__uint32_t)(t))

低32位表示重定位类型,高32位表示重定位符号在符号表的下标。使用的符号表用link表示,作用的段用info表示
比如:
GLOB-DAT: 这种重定位类型用于把指定的符号地址设置为一个全局偏移量表项。这种重 定位类型在符号与全局偏移量表项之间建立起了联系。

addend

本成员指定了一个加数,这个加数用于计算需要重定位的域的值

rodata/rodata1

本节包含程序中的只读数据,在程序装载时,它们一般会被装入进程空间中 那些只读的段中去。

shstrtab

本节是“节名字表”,含有所有其它节的名字,属于字符串表的一种,它包含所有节的名字。每一个节头的 sh-name成员应该是一个索引值,它指向节名字表中的一个位置,从这个位置开始到接下来第一个’null’字符为止的这个字符串,正是这个节的名字。

可见并不用分配内存空间

strtab

本节用于存放字符串,主要是那些符号表项的名字。如果一个目标文件有一个可装载的段,并且其中含有符号表,那么本节的属性中应该有SHF-ALLOC。
以null开头,该节种存放多个以null结尾的字符序列。
索引时提供的序号不是字符串的序号。如果把整个字符串节看做字节数组的话该序号是数组的下标。从下标到null为被指向的字串。因此可以引用每个字符串靠后的部分。
实际上so与可执行中没有看见这个节,这个节一般在可重定位文件中(.o.a)

symtab

本节用于存放符号表。如果一个目标文件有一个可载入的段,并且其中含有 符号表,那么本节的属性中应该有 SHF-ALLOC。
目标文件中的“符号表(symbol table)”所包含的信息用于定位和重定位程序中的符号定义和引用。目标文件的其它部分通过一个符号在这个表中的索引值来使用该符号。索引值从0开始计数,但值为0的表项(即第一项)并没有实际的意义,它表示未定义的符号。这里用常量STN-UNDEF来表示未定义的符号。

定义:

1
2
3
4
5
6
7
8
typedef struct {
Elf64_Half st_name; /* Symbol name index in str table */
Elf_Byte st_info; /* type / binding attrs */
Elf_Byte st_other; /* unused */
Elf64_Quarter st_shndx; /* section index of symbol */
Elf64_Xword st_value; /* value of symbol */
Elf64_Xword st_size; /* size of symbol */
} Elf64_Sym;

该结构体32位与64位排列不同。64位的是:411288;32位的是:444112。应该是为了保证地址对齐
符号表的首项全0。实际上so与可执行文件中并没有此节,该节主要用于静态连接的

name

符号的名字。但它并不是一个字符串,而是一个指向字符串表的索引值,在字符串表中对应位置上的字符串就是该符号名字的实际文本。如果此值为非0,它代表符号名在字符串表中的索引值。如果此值为0,那么此符号无名字。

info

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

1
2
3
#define ELF64_ST_BIND(x) ((x) >> 4)
#define ELF64_ST_TYPE(x) (((unsigned int) x) & 0xf)
#define ELF64_ST_INFO(b,t) (((b) << 4) + ((t) & 0xf))//合并用

符号绑定(Symbol Binding)

4位:

1
2
3
4
5
6
7
/* Symbol Binding - ELF32_ST_BIND - st_info */
#define STB_LOCAL 0 /* Local symbol */
#define STB_GLOBAL 1 /* Global symbol */
#define STB_WEAK 2 /* like global - lower precedence */
#define STB_NUM 3 /* number of symbol bindings 种类数*/
#define STB_LOPROC 13 /* reserved range for processor */
#define STB_HIPROC 15 /* specific symbol bindings */

LOCAL
表明本符号是一个本地符号。它只出现在本文件中,在本文件外该符号无效。所以在不同的文件中可以定义相同的符号名,它们之间不会互相影响。

GLOBAL
表明本符号是一个全局符号。当有多个文件被连接在一起时,在所有文 件中该符号都是可见的。正常情况下,在一个文件中定义的全局符号,一定是在其它文件中需要被引用,否则无须定义为全局。

WEAK
类似于全局符号,但是相对于STB-GLOBAL,它们的优先级更低。
多个可重定位文件链接时,同名的GLOBAL不许出现多次,但当GLOBAL存在时,同名弱符号可以存在但会被忽略。比其优先级高的还有COMMON符号

LOPROC-HIPROC
为特殊处理器保留的属性区间。

在符号表中,不同属性的符号所在位置也有不同,本地符号(STB-LOCAL)排在前面,全局符号(STB-GLOBAL/STB-WEAK)排在后面。

符号类型(Symbol Types)
1
2
3
4
5
6
7
8
9
/* Symbol type - ELF32_ST_TYPE - st_info */
#define STT_NOTYPE 0 /* not specified */
#define STT_OBJECT 1 /* data object */
#define STT_FUNC 2 /* function */
#define STT_SECTION 3 /* section */
#define STT_FILE 4 /* file */
#define STT_NUM 5 /* number of symbol types */
#define STT_LOPROC 13 /* reserved range for processor */
#define STT_HIPROC 15 /* specific symbol types */

NOTYPE
本符号类型未指定。

OBJECT
本符号是一个数据对象,比如变量、数组等。

FUNC
本符号是一个函数,或者其它的可执行代码。函数符号在共享目标文件中有特殊的意义。当另外一个目标文件引用一个共享目标文件中的函数符号时,连接编辑器为被引用符号自动创建一个连接表项。非STT-FUNC类型的共享目标符号不会通过这种连接表项被自动引用。

SECTION
本符号与一个节相关联,用于重定位,通常具有STB_LOCAL属性。

FILE
本符号是一个文件名符号,它具有STB-LOCAL属性,它的节索引值是 SHN-ABS。在符号表中如果存在本类符号的话,它会出现在所有 STB-LOCAL类符号的前部。

L-H
特殊处理器保留

other

本数据成员目前暂未使用,在目标文件中一律赋值为 0。

shndx

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

ABS

符号的值是绝对的,具有常量性,在重定位过程中,此值不需要改变。

COMMON

本符号所关联的是一个还没有分配的公共节,本符号的值规定了其内容的字节对齐规则,与sh-addralign相似。也就是说,连接器会为本符号分配存储空间,而且其起始地址是向st-value对齐的。本符号的值指明了要分配的字节数。

UNDEF

当一个符号指向第 1节(SHN-UNDEF)时,表明本符号在当前目标文件中未定义,在连接过程中,连接器会找到此符号被定义的文件,并把这些文件连接在一起。本文件中对该符号的引用会被连接到实际的定义上去。

value

符号的值。这个值其实没有固定的类型,它可能代表一个数值,也可以是一个地址,具体是什么要看上下文。

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

这样的设计是为了在不同类型文件中访问数据更有效

size

符号的大小。各种符号的大小各不相同,比如一个对象的大小就是它实际占用的字节数。

text

本节包含程序指令代码

要执行一个程序,系统要先把相应的可执行文件和动态连接库装载到进程空间中,这样形成一个可运行的进程的内存空间布局,也可以称它为“进程镜像”。
权限相同的节分别映射容易浪费页,因为映射必须以页为单位才可以方便进行权限管理、换入换出等操作(段式现往往用于在页式前划分空间),因此将权限相同的节合并为段一起映射,用于加载执行。
这也就是前面看节表时发现如下具有W权限的节明显新分配到了一个页

文件映射到这俩个段时邻界部分会被映射俩次,文件是紧凑连续的,并作为源,分页映射到内存会按文件中段的要求映射内存,因此邻界区会有一页的重复
总体上看,文件在内存中被拉长了,但每个段内的相对位置不会变,内存内段内某个位置-程序头指定VA=文件中的这个位置-程序头指定文件偏移
举例:
dynamic中的:pltgot指定的VA为0x11f98
根据段头表指定的分段位于7号 GNU Read-only After Relocation段。该段头指定了:

节头中指定了:

因此满足:0x11f98-0x11D80=0x1f98-0x1d80

因此程序头实际上就是指定了应该把文件中的那一部分映射到内存中的那一部分。其中文件的部分用文件偏移表示,内存中的部分用VA表示,文件偏移相对0,内存VA也是相对基地址的(默认为0)。
操作系统先将文件按程序头的规定映射到内存中(实际是形成页表),再按程序头中的内容解析整个elf文件,因为此时解析都是读取的内存中的数据(从文件取太慢太多次了)因此程序头中的内容按VA表示更方便定位。所以elf可执行与so中的地址数据都是VA的!!!!!!!!
具体看exec的源码分析,其中有几个段指明的部分是重复的,是为了指明信息,如动态段的位置、连接器的路径、栈信息等

程序头

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

1
2
3
4
5
6
7
8
9
10
typedef struct {
Elf64_Half p_type; /* entry type类型 */
Elf64_Half p_flags; /* flags 标志*/
Elf64_Off p_offset; /* offset 文件偏移*/
Elf64_Addr p_vaddr; /* virtual address 虚拟地址*/
Elf64_Addr p_paddr; /* physical address 物理地址*/
Elf64_Xword p_filesz; /* file size 文件中大小*/
Elf64_Xword p_memsz; /* memory size 镜像中大小*/
Elf64_Xword p_align; /* memory & file alignment对齐 */
} Elf64_Phdr;

有些程序头的内容是描述进程内的段,而有的是给出一些辅助信息,并不直接成为段的内容

type

此数据成员说明了本程序头所描述的段的类型,或者如何解析本程序头的信息。
除非有特别要求,否则所有程序头的段类型域 p-type 都是可选项,不是必须存在的。在所有程序头都不指定段类型的情况下,程序头表中所有的表项都不代表任何特别的类型,而只是作为一种索引,表明其相应的段的大小和位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/* Segment types - p_type */
#define PT_NULL 0 /* unused */
#define PT_LOAD 1 /* loadable segment */
#define PT_DYNAMIC 2 /* dynamic linking section */
#define PT_INTERP 3 /* the RTLD */
#define PT_NOTE 4 /* auxiliary information */
#define PT_SHLIB 5 /* reserved - purpose undefined */
#define PT_PHDR 6 /* program header */
#define PT_NUM 7 /* Number of segment types */
#define PT_LOOS 0x60000000 /* reserved range for OS */
#define PT_HIOS 0x6fffffff /* specific segment types */
#define PT_LOPROC 0x70000000 /* reserved range for processor */
#define PT_HIPROC 0x7fffffff /* specific segment types */
010edit模板中的:
// Program Header Types
typedef enum <Elf32_Word> {
PT_NULL = 0x0,
PT_LOAD = 0x1,
PT_DYNAMIC = 0x2,
PT_INERP = 0x3,
PT_NOTE = 0x4,
PT_SHLIB = 0x5,
PT_PHDR = 0x6,
PT_TLS = 0x7,
PT_NUM = 0x8,
PT_LOOS = 0x60000000,
PT_GNU_EH_FRAME = 0x6474e550,
PT_GNU_STACK = 0x6474e551,
PT_GNU_RELRO = 0x6474e552,
PT_LOSUNW = 0x6ffffffa,
PT_SUNWBSS = 0x6ffffffa,
PT_SUNWSTACK = 0x6ffffffb,
PT_HISUNW = 0x6fffffff,
PT_HIOS = 0x6fffffff,
PT_LOPROC = 0x70000000,
PT_HIPROC = 0x7fffffff,
// ARM Sections
PT_SHT_ARM_EXIDX = 0x70000001,
PT_SHT_ARM_PREEMPTMAP = 0x70000002,
PT_SHT_ARM_ATTRIBUTES = 0x70000003,
PT_SHT_ARM_DEBUGOVERLAY = 0x70000004,
PT_SHT_ARM_OVERLAYSECTION = 0x70000005
} p_type32_e;
typedef p_type32_e p_type64_e;

NULL

此类型表明本程序头是未使用的,本程序头内的其它成员值均无意义。具有此种类型的程序头应该被忽略。

LOAD

此类型表明本程序头指向一个可装载的段。段的内容会被从文件中拷贝到内存中。段在文件中的大小是p-filesz,在内存中的大小是p-memsz。如果 p-memsz大于 p-filesz,在内存中多出的存储空间应填0补充,也就是说,段在内存中可以比在文件中占用空间更大;而相反,p-filesz 永远不应该比 p-memsz大,因为这样的话,内存中就将无法完整地映射段的内容。在程序头表中,所有 PT-LOAD类型的程序头按照 p-vaddr的值做升序排列。

DYNAMIC

此类型表明本段指明了动态连接的信息。

INTERP

本段指向了一个以”null”结尾的字符串,这个字符串是一个 ELF解析器的路径。这种段类型只对可执行程序有意义,当它出现在共享目标文件中时, 是一个无意义的多余项。在一个ELF文件中它多只能出现一次,而且必须出现在其它可装载段的表项之前。

NOTE

本段指向了一个以”null”结尾的字符串,这个字符串包含一些附加的信息。

SHLIB

该段类型是保留的,而且未定义语法。

PHDR

此类型的程序头如果存在的话,它表明的是其自身所在的程序头表在文件或内存中的位置和大小。这样的段在文件中可以不存在,只有当所在程序头表所覆盖的段只是整个程序的一部分时,才会出现一次这种表项,而且这种表项一定出现在其它可装载段的表项之前。

L-H

系统与处理器保留

offset

此数据成员给出本段内容在文件中的位置,即段内容的开始位置相对于文件开头的偏移量。

vaddr

此数据成员给出本段内容的开始位置在进程空间中的虚拟地址。
在被加载到进程空间里时,尽管“段”会被分配到一个不确定的地址,但是不同的段之间会有确定的“相对位置(relative position)”。也就是说,在目标文件中存储的两个段,它们的位置之间有多少偏移,当它们被加载到内存中时,这两个段的位置之间仍然保持这么大的偏移(距离)。映射在不同进程中的同一so段间距离相等。
文件中写的虚拟地址与实际内存中的虚拟地址之差为基地址。基地址计算方法见源码分析

paddr

此数据成员给出本段内容的开始位置在进程空间中的物理地址。对于目前大多数现代操作系统而言,应用程序中段的物理地址事先是不可知的,所以目前这个成员多数情况下保留不用,或者被操作系统改作它用。

filesz

此数据成员给出本段内容在文件中的大小,单位是字节,可以是 0。

memsz

此数据成员给出本段内容在内容镜像中的大小,单位是字节,可以是 0。

flags

此数据成员给出了本段内容的属性。

1
2
3
4
5
6
/* Segment flags - p_flags */
#define PF_X 0x1 /* Executable 执行*/
#define PF_W 0x2 /* Writable 写*/
#define PF_R 0x4 /* Readable 读*/
#define PF_MASKPROC 0xf0000000 /* reserved bits for processor处理器保留 */
/* specific segment flags */

实际的权限依据系统的内存管理器处理,可能比给的权限大

align

对于可装载的段来说,其 p-vaddr和 p-offset的值至少要向内存页面大小对 齐。此数据成员指明本段内容如何在内存和文件中对齐。如果该值为 0或 1,表明 没有对齐要求;否则,p-align应该是一个正整数,并且是 2 的幂次数。p-vaddr和 p-offset在对 p-align取模后应该相等。

段内容

一个段可以包含多个节,方便装载。程序头只关心段

注释段NOTE

NOTE的段往往会包含类型为 NOTE的节,NOTE节可 以为目标文件提供一些特别的信息,用于给其它的程序检查目标文件的一致性和兼容性。

1
2
3
4
5
typedef struct {
Elf64_Half namesz;
Elf64_Half descsz;
Elf64_Half type;
} Elf64_Note;

注释节中包含三种信息:名字 (name/namesz)、类型(type)和描述(desc/descsz)。
名字用于区别不同的信息提供者, 比如不同的厂商,”ABC Computer Company”或”XYZ Computer Company”。
一个信息提供者可能会提供很多种信息,为了区别,把信息分类,即给每一种 信息加一个 ID,或者说是类型 type。
有了 name和 type的约束,desc的内容才有意义,才知道它所描述的是什么。

程序解析器/动调连接器INTERP

这个串指明了一个 ELF程序解析器,系统会转去初始化该解析器的进程镜像。也就是,在这时系统会暂停原来的工作,不是用待执行文件的段内容去初始化进程空间,而是把进程空间暂时“借”给解析器程序使用。然后,解析器程序将从系统手中接过控制权继续执行。
在 Intel架构下,系统中自带一种符合 ELF规范的程序解析器: /usr/lib/libc.so.1。

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

通过dynamic段取到需要的so,在程序加载到内存执行前完成加载so并重定位。

动态段DYNAMIC

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

1
2
3
4
5
6
7
8
typedef struct {
Elf64_Xword d_tag; /* controls meaning of d_val */
union {
Elf64_Addr d_ptr; //地址是VA的,相对基址
//这个位置是相对程序的装载基址的,不同权限的段会被加载到不同的页,因此不同的俩项该地址差距可能很大
Elf64_Xword d_val;
} d_un;
} Elf64_Dyn;

对于每一项,tag控制着对un的解析

tag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/* Dynamic Array Tags - d_tag */
名称 值 un 可执行文件 共享目标文件
#define DT_NULL 0 /*忽略 必须 必须 */
#define DT_NEEDED 1 /*val 可选 可选 */
#define DT_PLTRELSZ 2 /*val 可选 可选 */
#define DT_PLTGOT 3 /*ptr 可选 可选 */
#define DT_HASH 4 /*ptr 必须 必须 */
#define DT_STRTAB 5 /*ptr 必须 必须 */
#define DT_SYMTAB 6 /*ptr 必须 必须 */
#define DT_RELA 7 /*ptr 必须 可选 */
#define DT_RELASZ 8 /*val 必须 可选 */
#define DT_RELAENT 9 /*val 必须 可选 */
#define DT_STRSZ 10 /*val 必须 必须 */
#define DT_SYMENT 11 /*val 必须 必须*/
#define DT_INIT 12 /*ptr 可选 可选 */
#define DT_FINI 13 /*ptr 可选 可选 */
#define DT_SONAME 14 /*val 忽略 可选 */
#define DT_RPATH 15 /*val 可选 忽略 */
#define DT_SYMBOLIC 16 /*忽略 忽略 可选 */
#define DT_REL 17 /*ptr 必须 可选 */
#define DT_RELSZ 18 /*val 必须 可选 */
#define DT_RELENT 19 /*val 必须 可选 */
#define DT_PLTREL 20 /*val 可选 可选 */
#define DT_DEBUG 21 /*ptr 可选 可选 */
#define DT_TEXTREL 22 /*忽略 可选 可选 */
#define DT_JMPREL 23 /*ptr 可选 可选 */
#define DT_BIND_NOW 24 /*忽略 可选 可选 */
#define DT_NUM 25 /*数量 */
#define DT_LOPROC 0x70000000 /* */
#define DT_HIPROC 0x7fffffff /* */


图:dynamic-so

图:dynamic-exe

NULL
用于标记DYNAMIC数组的结束。

NEEDED
此元素指明了一个所需的库的名字。不过此元素本身并不是一个字符串,它是一个指向由”DT-STRTAB”所标记的字符串表中的索引,在表中,此索引处是一个以’null’结尾的字符串,这个字符串就是库的名字。在动态数组中可以包含若干个此类型的项,这些项出现的相对顺序是不能随意调换的。

PLTRELSZ
此元素含有与函数连接表相关的所有重定位项的总大小,以字节为单位。如果数组中有DT-JMPREL项的话,DT-PLTRELSZ也必须要有。

PLTGOT
此元素包含与函数连接表或全局偏移量表相应的地址。

HASH
此元素含有符号哈希表的地址。

STRTAB
此元素包含字符串表的地址,此表中包含符号名、库名等等。

SYMTAB
此元素包含符号表的地址。

RELA
此元素包含一个重定位表的地址。就是前面的Rela,表明了从哪个符号表找到符号去哪里如何修改地址

RELASZ
此元素持有DT-RELA相应的重定位表的大小,以字节为单位。

RELAENT
此元素持有DT-RELA相应的重定位表项的大小,以字节为单位。

STRSZ
此元素持有字符串表的大小,以字节为单位。

SYMENT
此元素持有符号表项的大小,以字节为单位。

INIT
此元素持有初始化函数的地址。

FINI
此元素持有终止函数的地址。

SONAME
此元素持有一个字符串表中的偏移量,该位置存储了一个以’null’结尾的字符串,是一个共享目标的名字。相应的字符串表由DT-STRTAB指定。
自己的名字

RPATH
此元素持有一个字符串表中的偏移量,该位置存储了一个以’null’结尾的字符串,是一个用于搜索库文件的路径名。相应的字符串表由DT-STRTAB指定。

SYMBOLIC
在共享目标文件中,此元素的出现与否决定了动态连接器解析符号时所用的算法。先搜素可执行文件还是库文件

REL
此元素与DT-RELA相似,只是它所指向的重定位表中,“加数”是隐含的而不是显式的。与静态中的相同

RELSZ
此元素持有DT-REL相应的重定位表的大小,以字节为单位。

RELENT
此元素持有DT-REL相应的重定位表项的大小,以字节为单位。

PLTREL
本成员指明了函数连接表所引用的重定位项的类型。d-val成员含有DT-REL或DT-RELA。函数连接表中的所有重定位类型都是相同的。

DEBUG
本成员用于调试,格式未明确定义。

TEXTREL
如果此元素出现的话,在重定位过程中如果需要修改的是只读段的话,连接编辑器可以做相应的修改。

JMPREL
此类型元素如果存在的话,其d-ptr成员含有与函数连接表单独关联的重定位项地址。把多个重定位项分开可以让动态连接器在初始化的时候忽略它们,当然前提条件是“后期绑定”是激活的。如果此元素存在的话,DT-PLTRELSZ和DT-PLTREL也应该出现。

BIND-NOW
如果此元素存在的话,动态连接器必须在程序开始执行以前,完成所有包含此项的目标的重定位工作。如果此元素存在,即使程序应用了“后期绑定”,它对于此项所指定的目标也不适用,动态连接器仍需事先做好重定位。

L-H
处理器保留

共享目标的依赖关系

静态连接下,连接器提取库的成员拷贝到输出文件中。动态连接下需要找到so库并添加到进程中。详见linker源码分析
当动态连接器为一个目标文件创建内存段的时候,动态结构中的 DT-NEEDED 项会指明所依赖的库,动态连接器会连接被引用的符号和它们所依赖的库,这个过程会反复地执行,直到一个完整的进程镜像被构建好。当解析一个符号引用的时候,动态连接器以一种“广度优先”的算法来查找符号表。就是说,动态连接器首先查找可执行程序自己的符号表,然后是 DT-NEEDED项所指明的库的符号表, 再接下来是下一层依赖库的符号表,依次下去。共享目标文件必须是可读的,其它权限没有要求。
共享库加载后会记录其名字,只引用一次,名字可以是SONAME也可以是共享文件的路径名。
寻找时,如果共享库名字中含有/,则直接把字符串作为路径名,如果没有,则按如下顺序查找:

  1. RPATH可能给出了一个含有一系列目录名的字符串,各目录名以冒号”:”相隔。在这些目录下依次查找
  2. 进程的环境变量中会有一个 LD_LIBRARY_PATH变量,它也含有一个目录名列表,各目录名以冒号”:”相隔,各目录名列表以分号”;” 相隔。
  3. 如果如上两组路径都无法找到所要的库,动态连接库就搜索/usr/lib。

全局偏移量表got

该表不通过处理器架构格式与解析方式不同。
可执行文件和共享目标文件有各自的全局偏移量表
详细分析见下文

函数连接表plt

该表不同处理器下实现不同
可执行文件和共享目标文件有各自的函数连接表。
详细分析见下文

解析符号

具体见linker分析

  1. 在一开始创建程序内存镜像的时候,动态连接器把全局偏移量表中的第 2和第 3个表项设为特定值。下面的步骤中会解析所设置的值。
  2. 如果函数连接表是位置独立的,全局偏移量表的地址必须存储在 %ebx中。进程空间中的每一个共享目标文件都有自己的函数连接表,每一个表都是用于本文件内的函数调用。那么,主调函数就要负责在调用函数连接表项之前设置全局偏移量表。
  3. 这里做个示例,假设程序要调用函数 name1,与之相应的函数连接表是.PLT1。
  4. 第一条指令跳转到 name1所在全局偏移量表项中的地址。一开始, 全局偏移量表中持有的是”push1”指令的地址,而不是”name1”的地址。
  5. 接下来,程序把一个重定位偏移量压入堆栈。重定位偏移量是一个 32位的非负数,它指向重定位表内的一项。被指定的重定位项类型为 R-386-JMP-SLOT,它的”offset”指定了前一个”jmp”指令所用到的一个全局 偏移表项。重定位项也包含一个符号表索引,为动态连接器指明了正被引用 的是什么符号,在本例是即”name1”。
  6. 把重定位偏移量压栈以后,程序就跳到.PLT0,即函数连接表的第一项。”push1”指令把全局偏移量表的第 2项(got-plus-4 or 4(%ebx))压入栈 顶,因此给了动态连接器一个字的确认信息。接下来,程序跳转到全局偏移 量表的第 3项(got-plus-8 or 8(%ebx))中的地址,控制权就又交给了动态连接器。
  7. 动态连接器接过控制权后,它弹出栈顶数据,查找指定的重定位项,找到符号的值,把”name1”真正的地址存储在全局偏移量表项中,并把控制权传给指定的目标。
  8. 接下来,函数连接表会把控制权直接转到 name1,不需要动态连接 器再次介入。也就是说,.PLT1处的 jmp指令会跳转到 name1。

这就是后期绑定

哈希表


图hash

不是规范的一部分,可能有不同实现

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,说明这个符号表中并不含有此符号。

初始化和终止函数

当动态连接器构建好进程镜像,并完成重定位后,每一个共享目标都有机会执 行一些初始化代码。所有共享目标的初始化都发生在程序开始执行前。
依赖需要先初始化,动态连接器保证其只调用一次,动态连接器只负责共享目标文件中的初始化和终止化,并不负责可执行文件。

示例

一个so的解析

源:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include<stdio.h>
#include <jni.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
struct NativeWorkerArgs
{
jint id;
jint iterations;
};
static jmethodID gOnNativeMessage = NULL;
// Java VM interface pointer
static JavaVM* gVm = NULL;
// Global reference to object
static jobject gObj = NULL;
int a;
int b=3;
extern int g_int1, g_int2;
char s[]="123456";
jint JNI_OnLoad (JavaVM* vm, void* reserved)
{
// Cache the JavaVM interface pointer
gVm = vm;
return JNI_VERSION_1_4;
}
extern "C" JNIEXPORT
JNIEXPORT void JNICALL
Java_com_example_imbaya_native1_MainActivity_nativeFree(JNIEnv *env, jobject obj) {
// If object global reference is set
if (NULL != gObj)
{
// Delete the global reference
env->DeleteGlobalRef(gObj);
gObj = NULL;
}
}
static void* nativeWorkerThread (void* args)
{
JNIEnv* env = NULL;
// Attach current thread to Java virtual machine
// and obrain JNIEnv interface pointer
if (0 == gVm->AttachCurrentThread(&env, NULL))
{
// Get the native worker thread arguments
NativeWorkerArgs* nativeWorkerArgs = (NativeWorkerArgs*) args;
// Run the native worker in thread context
Java_com_example_imbaya_native1_MainActivity_nativeWorker(env,
gObj,
nativeWorkerArgs->id,
nativeWorkerArgs->iterations);
// Free the native worker thread arguments
delete nativeWorkerArgs;
// Detach current thread from Java virtual machine
gVm->DetachCurrentThread();
}
return (void*) 1;
}
......
extern "C"
JNIEXPORT void JNICALL
Java_com_example_imbaya_native1_MainActivity_posixThreads2(JNIEnv *env, jobject instance,
jint threads, jint iterations) {
// TODO
}

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
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: AArch64
Version: 0x1
Entry point address: 0x9d0
Start of program headers: 64 (bytes into file)
Start of section headers: 8528 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 8
Size of section headers: 64 (bytes)
Number of section headers: 23
Section header string table index: 22
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 0000000000000200 00000200
0000000000000024 0000000000000000 A 0 0 4
[ 2] .hash HASH 0000000000000228 00000228
00000000000000bc 0000000000000004 A 3 0 8
[ 3] .dynsym DYNSYM 00000000000002e8 000002e8
00000000000002a0 0000000000000018 A 4 3 8
[ 4] .dynstr STRTAB 0000000000000588 00000588
0000000000000230 0000000000000000 A 0 0 1
[ 5] .gnu.version VERSYM 00000000000007b8 000007b8
0000000000000038 0000000000000002 A 3 0 2
[ 6] .gnu.version_r VERNEED 00000000000007f0 000007f0
0000000000000040 0000000000000000 A 4 2 8
[ 7] .rela.dyn RELA 0000000000000830 00000830
0000000000000018 0000000000000018 A 3 0 8
[ 8] .rela.plt RELA 0000000000000848 00000848
00000000000000d8 0000000000000018 AI 3 18 8
[ 9] .plt PROGBITS 0000000000000920 00000920
00000000000000b0 0000000000000010 AX 0 0 16
[10] .text PROGBITS 00000000000009d0 000009d0
0000000000000514 0000000000000000 AX 0 0 4
[11] .rodata PROGBITS 0000000000000ee4 00000ee4
0000000000000087 0000000000000001 AMS 0 0 1
[12] .eh_frame_hdr PROGBITS 0000000000000f6c 00000f6c
000000000000004c 0000000000000000 A 0 0 4
[13] .eh_frame PROGBITS 0000000000000fb8 00000fb8
0000000000000138 0000000000000000 A 0 0 8
[14] .note.android.ide NOTE 00000000000010f0 000010f0
0000000000000098 0000000000000000 A 0 0 4
[15] .init_array INIT_ARRAY 0000000000011d80 00001d80
0000000000000008 0000000000000008 WA 0 0 1
[16] .fini_array FINI_ARRAY 0000000000011d88 00001d88
0000000000000010 0000000000000008 WA 0 0 8
[17] .dynamic DYNAMIC 0000000000011d98 00001d98
0000000000000200 0000000000000010 WA 4 0 8
[18] .got PROGBITS 0000000000011f98 00001f98
0000000000000068 0000000000000008 WA 0 0 8
[19] .data PROGBITS 0000000000012000 00002000
0000000000000013 0000000000000000 WA 0 0 8
[20] .bss NOBITS 0000000000012018 00002013
0000000000000020 0000000000000000 WA 0 0 8
[21] .comment PROGBITS 0000000000000000 00002013
0000000000000064 0000000000000001 MS 0 0 1
[22] .shstrtab STRTAB 0000000000000000 00002077
00000000000000d8 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),
p (processor specific)
There are no section groups in this file.
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000001188 0x0000000000001188 R E 0x10000
LOAD 0x0000000000001d80 0x0000000000011d80 0x0000000000011d80
0x0000000000000293 0x00000000000002b8 RW 0x10000
DYNAMIC 0x0000000000001d98 0x0000000000011d98 0x0000000000011d98
0x0000000000000200 0x0000000000000200 RW 0x8
NOTE 0x0000000000000200 0x0000000000000200 0x0000000000000200
0x0000000000000024 0x0000000000000024 R 0x4
NOTE 0x00000000000010f0 0x00000000000010f0 0x00000000000010f0
0x0000000000000098 0x0000000000000098 R 0x4
GNU_EH_FRAME 0x0000000000000f6c 0x0000000000000f6c 0x0000000000000f6c
0x000000000000004c 0x000000000000004c R 0x4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x10
GNU_RELRO 0x0000000000001d80 0x0000000000011d80 0x0000000000011d80
0x0000000000000280 0x0000000000000280 R 0x1
Section to Segment mapping:
Segment Sections...
00 .note.gnu.build-id .hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .plt .text .rodata .eh_frame_hdr .eh_frame .note.android.ident
01 .init_array .fini_array .dynamic .got .data .bss
02 .dynamic
03 .note.gnu.build-id
04 .note.android.ident
05 .eh_frame_hdr
06
07 .init_array .fini_array .dynamic .got
Dynamic section at offset 0x1d98 contains 28 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libstdc++.so]
0x0000000000000001 (NEEDED) Shared library: [libc.so]
0x0000000000000001 (NEEDED) Shared library: [libm.so]
0x0000000000000001 (NEEDED) Shared library: [libdl.so]
0x000000000000000e (SONAME) Library soname: [libtest.so]
0x0000000000000019 (INIT_ARRAY) 0x11d80
0x000000000000001b (INIT_ARRAYSZ) 8 (bytes)
0x000000000000001a (FINI_ARRAY) 0x11d88
0x000000000000001c (FINI_ARRAYSZ) 16 (bytes)
0x0000000000000004 (HASH) 0x228
0x0000000000000005 (STRTAB) 0x588
0x0000000000000006 (SYMTAB) 0x2e8
0x000000000000000a (STRSZ) 560 (bytes)
0x000000000000000b (SYMENT) 24 (bytes)
0x0000000000000003 (PLTGOT) 0x11f98
0x0000000000000002 (PLTRELSZ) 216 (bytes)
0x0000000000000014 (PLTREL) RELA
0x0000000000000017 (JMPREL) 0x848
0x0000000000000007 (RELA) 0x830
0x0000000000000008 (RELASZ) 24 (bytes)
0x0000000000000009 (RELAENT) 24 (bytes)
0x0000000000000018 (BIND_NOW)
0x000000006ffffffb (FLAGS_1) Flags: NOW
0x000000006ffffffe (VERNEED) 0x7f0
0x000000006fffffff (VERNEEDNUM) 2
0x000000006ffffff0 (VERSYM) 0x7b8
0x000000006ffffff9 (RELACOUNT) 1
0x0000000000000000 (NULL) 0x0
Relocation section '.rela.dyn' at offset 0x830 contains 1 entry:
Offset Info Type Sym. Value Sym. Name + Addend
000000011d88 000000000403 R_AARCH64_RELATIV 9d0
Relocation section '.rela.plt' at offset 0x848 contains 9 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000011fb0 000300000402 R_AARCH64_JUMP_SL 0000000000000000 sleep@LIBC + 0
000000011fb8 000500000402 R_AARCH64_JUMP_SL 0000000000000000 pthread_create@LIBC + 0
000000011fc0 000700000402 R_AARCH64_JUMP_SL 0000000000000000 __cxa_finalize@LIBC + 0
000000011fc8 000c00000402 R_AARCH64_JUMP_SL 0000000000000000 _ZdlPv@LIBC_O + 0
000000011fd0 000e00000402 R_AARCH64_JUMP_SL 0000000000000000 __stack_chk_fail@LIBC + 0
000000011fd8 000f00000402 R_AARCH64_JUMP_SL 0000000000000000 _Znwm@LIBC_O + 0
000000011fe0 001300000402 R_AARCH64_JUMP_SL 0000000000000000 sprintf@LIBC + 0
000000011fe8 001a00000402 R_AARCH64_JUMP_SL 0000000000000c1c _ZN7_JNIEnv14CallVoidM + 0
000000011ff0 001b00000402 R_AARCH64_JUMP_SL 0000000000000000 __cxa_atexit@LIBC + 0
The decoding of unwind sections for machine type AArch64 is not currently supported.
Symbol table '.dynsym' contains 28 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000000009d0 0 SECTION LOCAL DEFAULT 10
2: 0000000000012000 0 SECTION LOCAL DEFAULT 19
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sleep@LIBC (2)
4: 0000000000000ee0 4 FUNC GLOBAL DEFAULT 10 Java_com_example_imbaya_n
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_create@LIBC (2)
6: 0000000000012038 0 NOTYPE GLOBAL DEFAULT ABS _bss_end__
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __cxa_finalize@LIBC (2)
8: 0000000000012018 4 OBJECT GLOBAL DEFAULT 20 a
9: 0000000000012008 4 OBJECT GLOBAL DEFAULT 19 b
10: 000000000001200c 7 OBJECT GLOBAL DEFAULT 19 s
11: 0000000000000b04 52 FUNC GLOBAL DEFAULT 10 Java_com_example_imbaya_n
12: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _ZdlPv@LIBC_O (3)
13: 0000000000000a0c 24 FUNC GLOBAL DEFAULT 10 JNI_OnLoad
14: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __stack_chk_fail@LIBC (2)
15: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _Znwm@LIBC_O (3)
16: 0000000000000cbc 232 FUNC GLOBAL DEFAULT 10 Java_com_example_imbaya_n
17: 0000000000012013 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
18: 0000000000012038 0 NOTYPE GLOBAL DEFAULT ABS __end__
19: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sprintf@LIBC (2)
20: 0000000000012013 0 NOTYPE GLOBAL DEFAULT ABS __bss_start__
21: 0000000000000b38 228 FUNC GLOBAL DEFAULT 10 Java_com_example_imbaya_n
22: 0000000000000a24 224 FUNC GLOBAL DEFAULT 10 Java_com_example_imbaya_n
23: 0000000000012013 0 NOTYPE GLOBAL DEFAULT ABS _edata
24: 0000000000012038 0 NOTYPE GLOBAL DEFAULT ABS __bss_end__
25: 0000000000012038 0 NOTYPE GLOBAL DEFAULT ABS _end
26: 0000000000000c1c 160 FUNC WEAK DEFAULT 10 _ZN7_JNIEnv14CallVoidMeth
27: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __cxa_atexit@LIBC (2)
Histogram for bucket list length (total of 17 buckets):
Length Number % of total Coverage
0 4 ( 23.5%)
1 5 ( 29.4%) 20.0%
2 5 ( 29.4%) 60.0%
3 2 ( 11.8%) 84.0%
4 1 ( 5.9%) 100.0%
Version symbols section '.gnu.version' contains 28 entries:
Addr: 00000000000007b8 Offset: 0x0007b8 Link: 3 (.dynsym)
000: 0 (*local*) 0 (*local*) 0 (*local*) 2 (LIBC)
004: 1 (*global*) 2 (LIBC) 1 (*global*) 2 (LIBC)
008: 1 (*global*) 1 (*global*) 1 (*global*) 1 (*global*)
00c: 3 (LIBC_O) 1 (*global*) 2 (LIBC) 3 (LIBC_O)
010: 1 (*global*) 1 (*global*) 1 (*global*) 2 (LIBC)
014: 1 (*global*) 1 (*global*) 1 (*global*) 1 (*global*)
018: 1 (*global*) 1 (*global*) 1 (*global*) 2 (LIBC)
Version needs section '.gnu.version_r' contains 2 entries:
Addr: 0x00000000000007f0 Offset: 0x0007f0 Link: 4 (.dynstr)
000000: Version: 1 File: libstdc++.so Cnt: 1
0x0010: Name: LIBC_O Flags: none Version: 3
0x0020: Version: 1 File: libc.so Cnt: 1
0x0030: Name: LIBC Flags: none Version: 2
Displaying notes found in: .note.gnu.build-id
Owner Data size Description
GNU 0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring)
Build ID: 2712b618a779fca3051582467c360adff5c8acd5
Displaying notes found in: .note.android.ident
Owner Data size Description
Android 0x00000084 NT_VERSION (version)
description data: 15 00 00 00 72 31 36 62 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 34 34 37 39 34 39 39 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

一个可执行文件的解析

源:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
int dsad=1;
void f(int abort)
{
printf("%d",abort);
}
int main(void)
{
int abort=3;
printf("Hello");
f(abort);
}

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
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: AArch64
Version: 0x1
Entry point address: 0x660
Start of program headers: 64 (bytes into file)
Start of section headers: 4440 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 9
Size of section headers: 64 (bytes)
Number of section headers: 24
Section header string table index: 23
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 0000000000000238 00000238
0000000000000015 0000000000000000 A 0 0 1
[ 2] .note.android.ide NOTE 0000000000000250 00000250
0000000000000098 0000000000000000 A 0 0 4
[ 3] .note.gnu.build-i NOTE 00000000000002e8 000002e8
0000000000000024 0000000000000000 A 0 0 4
[ 4] .hash HASH 0000000000000310 00000310
0000000000000048 0000000000000004 A 5 0 8
[ 5] .dynsym DYNSYM 0000000000000358 00000358
0000000000000138 0000000000000018 A 6 3 8
[ 6] .dynstr STRTAB 0000000000000490 00000490
0000000000000099 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 000000000000052a 0000052a
000000000000001a 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000000548 00000548
0000000000000020 0000000000000000 A 6 1 8
[ 9] .rela.dyn RELA 0000000000000568 00000568
0000000000000060 0000000000000018 A 5 0 8
[10] .rela.plt RELA 00000000000005c8 000005c8
0000000000000048 0000000000000018 AI 5 20 8
[11] .plt PROGBITS 0000000000000610 00000610
0000000000000050 0000000000000010 AX 0 0 16
[12] .text PROGBITS 0000000000000660 00000660
00000000000000a8 0000000000000000 AX 0 0 4
[13] .rodata PROGBITS 0000000000000708 00000708
0000000000000009 0000000000000001 AMS 0 0 1
[14] .eh_frame_hdr PROGBITS 0000000000000714 00000714
0000000000000014 0000000000000000 A 0 0 4
[15] .eh_frame PROGBITS 0000000000000728 00000728
0000000000000038 0000000000000000 A 0 0 8
[16] .preinit_array PREINIT_ARRAY 0000000000010d58 00000d58
0000000000000010 0000000000000008 WA 0 0 8
[17] .init_array INIT_ARRAY 0000000000010d68 00000d68
0000000000000010 0000000000000008 WA 0 0 8
[18] .fini_array FINI_ARRAY 0000000000010d78 00000d78
0000000000000010 0000000000000008 WA 0 0 8
[19] .dynamic DYNAMIC 0000000000010d88 00000d88
0000000000000220 0000000000000010 WA 6 0 8
[20] .got PROGBITS 0000000000010fa8 00000fa8
0000000000000058 0000000000000008 WA 0 0 8
[21] .data PROGBITS 0000000000011000 00001000
0000000000000008 0000000000000000 WA 0 0 8
[22] .comment PROGBITS 0000000000000000 00001008
0000000000000064 0000000000000001 MS 0 0 1
[23] .shstrtab STRTAB 0000000000000000 0000106c
00000000000000ea 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),
p (processor specific)
There are no section groups in this file.
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040
0x00000000000001f8 0x00000000000001f8 R E 0x8
INTERP 0x0000000000000238 0x0000000000000238 0x0000000000000238
0x0000000000000015 0x0000000000000015 R 0x1
[Requesting program interpreter: /system/bin/linker64]
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000760 0x0000000000000760 R E 0x10000
LOAD 0x0000000000000d58 0x0000000000010d58 0x0000000000010d58
0x00000000000002b0 0x00000000000002b0 RW 0x10000
DYNAMIC 0x0000000000000d88 0x0000000000010d88 0x0000000000010d88
0x0000000000000220 0x0000000000000220 RW 0x8
NOTE 0x0000000000000250 0x0000000000000250 0x0000000000000250
0x00000000000000bc 0x00000000000000bc R 0x4
GNU_EH_FRAME 0x0000000000000714 0x0000000000000714 0x0000000000000714
0x0000000000000014 0x0000000000000014 R 0x4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x10
GNU_RELRO 0x0000000000000d58 0x0000000000010d58 0x0000000000010d58
0x00000000000002a8 0x00000000000002a8 R 0x1
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.android.ident .note.gnu.build-id .hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .plt .text .rodata .eh_frame_hdr .eh_frame
03 .preinit_array .init_array .fini_array .dynamic .got .data
04 .dynamic
05 .note.android.ident .note.gnu.build-id
06 .eh_frame_hdr
07
08 .preinit_array .init_array .fini_array .dynamic .got
Dynamic section at offset 0xd88 contains 30 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libstdc++.so]
0x0000000000000001 (NEEDED) Shared library: [libc.so]
0x0000000000000001 (NEEDED) Shared library: [libm.so]
0x0000000000000001 (NEEDED) Shared library: [libdl.so]
0x0000000000000020 (PREINIT_ARRAY) 0x10d58
0x0000000000000021 (PREINIT_ARRAYSZ) 0x10
0x0000000000000019 (INIT_ARRAY) 0x10d68
0x000000000000001b (INIT_ARRAYSZ) 16 (bytes)
0x000000000000001a (FINI_ARRAY) 0x10d78
0x000000000000001c (FINI_ARRAYSZ) 16 (bytes)
0x0000000000000004 (HASH) 0x310
0x0000000000000005 (STRTAB) 0x490
0x0000000000000006 (SYMTAB) 0x358
0x000000000000000a (STRSZ) 153 (bytes)
0x000000000000000b (SYMENT) 24 (bytes)
0x0000000000000015 (DEBUG) 0x0
0x0000000000000003 (PLTGOT) 0x10fa8
0x0000000000000002 (PLTRELSZ) 72 (bytes)
0x0000000000000014 (PLTREL) RELA
0x0000000000000017 (JMPREL) 0x5c8
0x0000000000000007 (RELA) 0x568
0x0000000000000008 (RELASZ) 96 (bytes)
0x0000000000000009 (RELAENT) 24 (bytes)
0x0000000000000018 (BIND_NOW)
0x000000006ffffffb (FLAGS_1) Flags: NOW PIE
0x000000006ffffffe (VERNEED) 0x548
0x000000006fffffff (VERNEEDNUM) 1
0x000000006ffffff0 (VERSYM) 0x52a
0x000000006ffffff9 (RELACOUNT) 4
0x0000000000000000 (NULL) 0x0
Relocation section '.rela.dyn' at offset 0x568 contains 4 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000010fe0 000000000403 R_AARCH64_RELATIV 6d8
000000010fe8 000000000403 R_AARCH64_RELATIV 10d58
000000010ff0 000000000403 R_AARCH64_RELATIV 10d78
000000010ff8 000000000403 R_AARCH64_RELATIV 10d68
Relocation section '.rela.plt' at offset 0x5c8 contains 3 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000010fc0 000700000402 R_AARCH64_JUMP_SL 0000000000000000 printf@LIBC + 0
000000010fc8 000b00000402 R_AARCH64_JUMP_SL 0000000000000000 __libc_init@LIBC + 0
000000010fd0 000c00000402 R_AARCH64_JUMP_SL 0000000000000000 __cxa_atexit@LIBC + 0
The decoding of unwind sections for machine type AArch64 is not currently supported.
Symbol table '.dynsym' contains 13 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000660 0 SECTION LOCAL DEFAULT 12
2: 0000000000011000 0 SECTION LOCAL DEFAULT 21
3: 0000000000011008 0 NOTYPE LOCAL DEFAULT ABS __bss_start
readelf: Warning: local symbol 3 found at index >= .dynsym's sh_info value of 3
4: 0000000000011008 0 NOTYPE LOCAL DEFAULT ABS _edata
readelf: Warning: local symbol 4 found at index >= .dynsym's sh_info value of 3
5: 0000000000011008 0 NOTYPE LOCAL DEFAULT ABS _end
readelf: Warning: local symbol 5 found at index >= .dynsym's sh_info value of 3
6: 00000000000006d8 48 FUNC GLOBAL DEFAULT 12 main
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@LIBC (2)
8: 0000000000010d58 8 OBJECT GLOBAL DEFAULT 16 __PREINIT_ARRAY__
9: 0000000000010d78 8 OBJECT GLOBAL DEFAULT 18 __FINI_ARRAY__
10: 0000000000010d68 8 OBJECT GLOBAL DEFAULT 17 __INIT_ARRAY__
11: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_init@LIBC (2)
12: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __cxa_atexit@LIBC (2)
Histogram for bucket list length (total of 3 buckets):
Length Number % of total Coverage
0 0 ( 0.0%)
1 1 ( 33.3%) 10.0%
2 0 ( 0.0%) 10.0%
3 0 ( 0.0%) 10.0%
4 1 ( 33.3%) 50.0%
5 1 ( 33.3%) 100.0%
Version symbols section '.gnu.version' contains 13 entries:
Addr: 000000000000052a Offset: 0x00052a Link: 5 (.dynsym)
000: 0 (*local*) 0 (*local*) 0 (*local*) 1 (*global*)
004: 1 (*global*) 1 (*global*) 1 (*global*) 2 (LIBC)
008: 1 (*global*) 1 (*global*) 1 (*global*) 2 (LIBC)
00c: 2 (LIBC)
Version needs section '.gnu.version_r' contains 1 entry:
Addr: 0x0000000000000548 Offset: 0x000548 Link: 6 (.dynstr)
000000: Version: 1 File: libc.so Cnt: 1
0x0010: Name: LIBC Flags: none Version: 2
Displaying notes found in: .note.android.ident
Owner Data size Description
Android 0x00000084 NT_VERSION (version)
description data: 15 00 00 00 72 31 36 62 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 34 34 37 39 34 39 39 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 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: 510605f6988e76596585654d74ba4dabd91c00c4

实现一个解析器

github:
https://github.com/imbaya2466/XFile

参考

程序员的自我修养
ELF格式解析-v1.2 赵 凤 阳
ELF 文件格式分析 -滕启明
ELF for the ARM® 64-bit Architecture (AArch64)
ELF for the ARM® Architecture
CSAPP