ELF 全称 “Executable and Linkable Format”,即可执行可链接文件格式,目前常见的Linux、 Android可执行文件、共享库(.so)、目标文件( .o)以及Core 文件(吐核)均为此格式。
文件布局
常见的ELF文件大致结构如下:
来自:
http://chuquan.me/2018/05/21/elf-introduce/
前文
结尾说到编译器编译源代码后生成的文件叫做
目标文件
,而目标文件经过编译器链接之后得到的就是
可执行文件
。那么目标文件到底是什么?它和可执行文件又有什么区别?链接到底又做了什么呢?接下来,我们将探索一下目标文件的本质。
目标文件的格式
目前,PC平台流行的
可执行文件格式(Executable)
主要包含如下两种,它们都是
COFF(Common File Format)
格式的变种。
-
Windows下的
PE(Portable Executable)
-
Linux下的
ELF(Executable Linkable Format)
目标文件就是源代码经过编译后但未进行连接的那些中间文件(Windows的
.obj
和Linux的
.o
),它与可执行文件的格式非常相似,所以一般跟可执行文件格式一起采用同一种格式存储
。在Windows下采用
PE-COFF
文件格式;Linux下采用
ELF
文件格式。
事实上,除了
可执行文件
外,
动态链接库(DDL,Dynamic Linking Library)
、
静态链接库(Static Linking Library)
均采用可执行文件格式存储。它们在Window下均按照PE-COFF格式存储;Linux下均按照ELF格式存储。只是文件名后缀不同而已。
-
动态链接库:Windows的
.dll
、Linux的
.so
-
静态链接库:Windows的
.lib
、Linux的
.a
下面,我们将以ELF文件为例进行介绍。
注意:段(
Segment
)与节(
Section
)的区别。很多地方对两者有所混淆。段是程序执行的必要组成,当多个目标文件链接成一个可执行文件时,会将相同权限的节合并到一个段中。相比而言,节的粒度更小。
如图所示,为ELF文件的基本结构,其主要由四部分组成:
- ELF Header
- ELF Program Header Table (或称Program Headers、程序头)
- ELF Section Header Table (或称Section Headers、节头表)
- ELF Sections
从图中,我们就能看出它们各自的数据结构以及相互之间的索引关系。下面我们依次进行介绍。
ELF Header
我们可以使用readelf工具来查看ELF Header。
|
|
ELF文件结构示意图中定义的
Elf_Ehdr
的各个成员的含义与readelf具有对应关系。如下表所示:
成员 | 含义 |
---|---|
e_ident | Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |
Class: ELF32 | |
Data: 2’s complement, little end | |
Version: 1(current) | |
OS/ABI: UNIX – System V | |
ABI Version: 0 | |
e_type | Type: REL (Relocatable file) |
ELF文件类型 | |
e_machine | Machine: Advanced Micro Devices X86-64 |
ELF文件的CPI平台属性 | |
e_version | Version: 0x1 |
ELF版本号。一般为常数1 | |
e_entry | Entry point address: 0x0 |
入口地址,规定ELF程序的入口虚拟地址,操作系统在加载完该程序后从这个地址开始执行进程的指令。可重定位指令一般没有入口地址,则该值为0 |
|
e_phoff | Start of program headers: 0(bytes into file) |
e_shoff | Start of section headers: 672 (bytes into file) |
Section Header Table 在文件中的偏移 | |
e_word | Flags: 0x0 |
ELF标志位,用来标识一些ELF文件平台相关的属性。 | |
e_ehsize | Size of this header: 64 (bytes) |
ELF Header本身的大小 | |
e_phentsize | Size of program headers: 0 (bytes) |
e_phnum | Number of program headers: 0 |
e_shentsize | Size of section headers: 64 (bytes) |
单个Section Header大小 | |
e_shnum | Number of section headers: 13 |
Section Header的数量 | |
e_shstrndx | Section header string table index: 10 |
Section Header字符串表在Section Header Table中的索引 |
ELF魔数
每种可执行文件的格式的开头几个字节都是很特殊的,特别是开头4个字节,通常被称为
魔数(Magic Number)
。通过对魔数的判断可以确定文件的格式和类型。如:ELF的可执行文件格式的头4个字节为
0x7F
、
e
、
l
、
f
;Java的可执行文件格式的头4个字节为
c
、
a
、
f
、
e
;如果被执行的是Shell脚本或perl、python等解释型语言的脚本,那么它的第一行往往是
#!/bin/sh
或
#!/usr/bin/perl
或
#!/usr/bin/python
,此时前两个字节
#
和
!
就构成了魔数,系统一旦判断到这两个字节,就对后面的字符串进行解析,以确定具体的解释程序路径。
ELF文件类型
ELF文件主要有三种类型,可以通过ELF Header中的
e_type
成员进行区分。
-
可重定位文件(Relocatable File)
:
ETL_REL
。一般为
.o
文件。可以被链接成可执行文件或共享目标文件。静态链接库属于可重定位文件。 -
可执行文件(Executable File)
:
ET_EXEC
。可以直接执行的程序。 -
共享目标文件(Shared Object File)
:
ET_DYN
。一般为
.so
文件。有两种情况可以使用。- 链接器将其与其他可重定位文件、共享目标文件链接成新的目标文件;
- 动态链接器将其与其他共享目标文件、结合一个可执行文件,创建进程映像。
ELF Section Header Table
ELF 节头表是一个节头数组。每一个节头都描述了其所对应的节的信息,如节名、节大小、在文件中的偏移、读写权限等。
编译器、链接器、装载器都是通过节头表来定位和访问各个节的属性的。
我们可以使用readelf工具来查看节头表。
|
|
ELF文件结构示意图中定义的
Elf_Shdr
的各个成员的含义与readelf具有对应关系。如下表所示:
成员 | 含义 |
---|---|
sh_name | 节名 |
节名是一个字符串,保存在一个名为
的字符串表(可通过Section Header索引到)。sh_name的值实际上是其节名字符串在
中的偏移值 |
|
sh_type | 节类型 |
sh_flags | 节标志位 |
sh_addr | 节地址:节的虚拟地址 |
如果该节可以被加载,则sh_addr为该节被加载后在进程地址空间中的虚拟地址;否则sh_addr为0 | |
sh_offset | 节偏移 |
如果该节存在于文件中,则表示该节在文件中的偏移;否则无意义,如sh_offset对于BSS 节来说是没有意义的 |
|
sh_size | 节大小 |
sh_link、sh_info | 节链接信息 |
sh_addralign | 节地址对齐方式 |
sh_entsize | 节项大小 |
有些节包含了一些固定大小的项,如符号表,其包含的每个符号所在的大小都一样的,对于这种节,sh_entsize表示每个项的大小。 如果为0,则表示该节不包含固定大小的项。 |
节类型(sh_type)
节名是一个字符串,只是在链接和编译过程中有意义,但它并不能真正地表示节的类型。对于编译器和链接器来说,主要决定节的属性是节的类型(
sh_type
)和节的标志位(
sh_flags
)。
节的类型相关常量以
SHT_
开头,上述
readelf -S
命令执行的结果省略了该前缀。常见的节类型如下表所示:
常量 | 值 | 含义 |
---|---|---|
SHT_NULL | 0 | 无效节 |
SHT_PROGBITS | 1 |
程序节 。代码节、数据节都是这种类型。 |
SHT_SYMTAB | 2 |
符号表 |
SHT_STRTAB | 3 |
字符串表 |
SHT_RELA | 4 |
重定位表 。该节包含了重定位信息。 |
SHT_HASH | 5 |
符号表的哈希表 |
SHT_DYNAMIC | 6 | 动态链接信息 |
SHT_NOTE | 7 | 提示性信息 |
SHT_NOBITS | 8 |
表示该节在文件中没有内容。如
节 |
SHT_REL | 9 | 该节包含了重定位信息 |
SHT_SHLIB | 10 | 保留 |
SHT_DNYSYM | 11 |
动态链接的符号表 |
节标志位(sh_flag)
节标志位表示该节在进程虚拟地址空间中的属性。如是否可写、是否可执行等。相关常量以
SHF_
开头。常见的节标志位如下表所示:
常量 | 值 | 含义 |
---|---|---|
SHF_WRITE | 1 | 表示该节在进程空间中可写 |
SHF_ALLOC | 2 | 表示该节在进程空间中需要分配空间。有些包含指示或控制信息的节不需要在进程空间中分配空间,就不会有这个标志。 |
SHF_EXECINSTR | 4 | 表示该节在进程空间中可以被执行 |
节链接信息(sh_link、sh_info)
如果节的类型是与链接相关的(无论是动态链接还是静态链接),如
重定位表、符号表、
等,则
sh_link
、
sh_info
两个成员所包含的意义如下所示。其他类型的节,这两个成员没有意义。
sh_type | sh_link | sh_info |
---|---|---|
SHT_DYNAMIC |
该节所使用的 字符串表 在节头表中的下标 |
0 |
SHT_HASH |
该节所使用的 符号表 在节头表中的下标 |
0 |
SHT_REL |
该节所使用的 相应符号表 在节头表中的下标 |
该重定位表所作用的节在节头表中的下标 |
SHT_RELA |
该节所使用的 相应符号表 在节头表中的下标 |
该重定位表所作用的节在节头表中的下标 |
SHT_SYMTAB | 操作系统相关 | 操作系统相关 |
SHT_DYNSYM | 操作系统相关 | 操作系统相关 |
other | SHN_UNDEF | 0 |
ELF Sections
节的分类
上述ELF Section Header Table部分已经简单介绍了节类型。接下来我们来介绍详细一些比较重要的节。
.text节
.text
节是保存了程序代码指令的
代码节
。
一段可执行程序,如果存在Phdr,则
.text
节就会存在于
text
段中
。由于
.text
节保存了程序代码,所以节类型为
SHT_PROGBITS
。
.rodata节
rodata
节保存了只读的数据,如一行C语言代码中的字符串。由于
.rodata
节是只读的,所以只能存在于一个可执行文件的
只读段
中。因此,只能在
text
段(不是
data
段)中找到
.rodata
节。由于
.rodata
节是只读的,所以节类型为
SHT_PROGBITS
。
.plt节(过程链接表)
.plt
节也称为
过程链接表(Procedure Linkage Table)
,
其包含了动态链接器调用从共享库导入的函数所必需的相关代码
。由于
.plt
节保存了代码,所以节类型为
SHT_PROGBITS
。
.data节
.data
节存在于
data
段中,
其保存了初始化的全局变量等数据
。由于
.data
节保存了程序的变量数据,所以节类型为
SHT_PROGBITS
。
.bss节
.bss
节存在于
data
段中,占用空间不超过4字节,仅表示这个节本省的空间。
.bss
节保存了未进行初始化的全局数据
。程序加载时数据被初始化为0,在程序执行期间可以进行赋值。由于
.bss
节未保存实际的数据,所以节类型为
SHT_NOBITS
。
.got.plt节(全局偏移表-过程链接表)
.got
节保存了
全局偏移表
。
.got
节和
.plt
节一起提供了对导入的共享库函数的访问入口,由动态链接器在运行时进行修改
。由于
.got.plt
节与程序执行有关,所以节类型为
SHT_PROGBITS
。
.dynsym节(动态链接符号表)
.dynsym
节保存在
text
段中。
其保存了从共享库导入的动态符号表
。节类型为
SHT_DYNSYM
。
.dynstr节(动态链接字符串表)
.dynstr
保存了动态链接字符串表,表中存放了一系列字符串,这些字符串代表了符号名称,以空字符作为终止符。
.rel.*节(重定位表)
重定位表保存了重定位相关的信息,
这些信息描述了如何在链接或运行时,对ELF目标文件的某部分或者进程镜像进行补充或修改
。由于重定位表保存了重定位相关的数据,所以节类型为
SHT_REL
。
.hash节
.hash
节也称为
.gnu.hash
,其保存了一个用于查找符号的散列表。
.symtab节(符号表)
.symtab
节是一个
ElfN_Sym
的数组,保存了符号信息。节类型为
SHT_SYMTAB
。
.strtab节(字符串表)
.strtab
节保存的是符号字符串表,表中的内容会被
.symtab
的
ElfN_Sym
结构中的
st_name
引用。节类型为
SHT_STRTAB
。
.ctors节和.dtors节
.ctors
(
构造器
)节和
.dtors
(
析构器
)节分别保存了指向构造函数和析构函数的函数指针,
构造函数是在main函数执行之前需要执行的代码;析构函数是在main函数之后需要执行的代码
。
符号表
节的分类中我们介绍了
.dynsym
节和
.symtab
节,两者都是符号表。那么它们到底有什么区别呢?存在什么关系呢?
符号是对某些类型的数据或代码(如全局变量或函数)的符号引用,函数名或变量名就是符号名
。例如,
printf()
函数会在动态链接符号表
.dynsym
中存有一个指向该函数的符号项(以
Elf_Sym
数据结构表示)。在大多数共享库和动态链接可执行文件中,存在两个符号表。即
.dynsym
和
.symtab
。
.dynsym
保存了引用来自外部文件符号的全局符号
。如
printf
库函数。
.dynsym
保存的符号是
.symtab
所保存符合的子集,
.symtab
中还保存了可执行文件的本地符号
。如全局变量,代码中定义的本地函数等。
既然
.dynsym
是
.symtab
的子集,那为何要同时存在两个符号表呢?
通过
readelf -S
命令可以查看可执行文件的输出,一部分节标志位(
sh_flags
)被标记为了
A(ALLOC)、WA(WRITE/ALLOC)、AX(ALLOC/EXEC)
。其中,
.dynsym
被标记为ALLOC,而
.symtab
则没有标记。
ALLOC表示有该标记的节会在运行时分配并装载进入内存,而
.symtab
不是在运行时必需的,因此不会被装载到内存中。
.dynsym
保存的符号只能在运行时被解析,因此是运行时动态链接器所需的唯一符号
。
.dynsym
对于动态链接可执行文件的执行是必需的,而
.symtab
只是用来进行调试和链接的。
上图所示为通过符号表索引字符串表的示意图。符号表中的每一项都是一个
Elf_Sym
结构,对应可以在字符串表中索引得到一个字符串。该数据结构中成员的含义如下表所示:
成员 | 含义 |
---|---|
st_name | 符号名。该值为该符号名在字符串表中的偏移地址。 |
st_value | 符号对应的值。存放符号的值(可能是地址或位置偏移量)。 |
st_size | 符号的大小。 |
st_other | 0 |
st_shndx | 符号所在的节 |
st_info | 符号类型及绑定属性 |
使用readelf工具我们也能够看到符号表的相关信息。
|
|
字符串表
类似于符号表,在大多数共享库和动态链接可执行文件中,也存在两个字符串表。即
.dynstr
和
.strtab
,分别对应于
.dynsym
和
symtab
。此外,还有一个
.shstrtab
的节头字符串表,用于保存节头表中用到的字符串,可通过
sh_name
进行索引。
ELF文件中所有字符表的结构基本一致,如上图所示。
重定位表
重定位就是将符号定义和符号引用进行连接的过程
。可重定位文件需要包含描述如何修改节内容的相关信息,从而使可执行文件和共享目标文件能够保存进程的程序镜像所需要的正确信息。
重定位表是进行重定位的重要依据。我们可以使用objdump工具查看目标文件的重定位表:
|
|
重定位表是一个
Elf_Rel
类型的数组结构,每一项对应一个需要进行重定位的项。
其成员含义如下表所示:
成员 | 含义 |
---|---|
r_offset | 重定位入口的偏移。 |
对于 可重定位文件 来说,这个值是该重定位入口所要修正的位置的第一个字节相对于节起始的偏移 |
|
对于 可执行文件或共享对象文件 来说,这个值是该重定位入口所要修正的位置的第一个字节的虚拟地址 |
|
r_info | 重定位入口的类型和符号 |
因为不同处理器的指令系统不一样,所以重定位所要修正的指令地址格式也不一样。每种处理器都有自己的一套重定位入口的类型。 | |
对于 可执行文件和共享目标文件 来说,它们的重定位入口是动态链接类型的。 |
重定位是目标文件链接成为可执行文件的关键。我们将在后面的进行介绍。
来自:
http://cxd2014.github.io/2015/12/02/elf/
ELF简介
ELF(Executableand Linking Format 可执行和链接格式),ELF文件格式是一个开放标准,各种UNIX系统的可执行文件都采用ELF格式,它有三种不同的类型:
-
可重定位的目标文件(Relocatable)也就是通常称的目标文件,后缀为
.o
- 可执行文件(Executable)
-
共享库(Shared Object)共享文件:也就是通常称的库文件,后缀为
.so
注1: Linux中的
readelf
命令可以查看ELF文件的详细信息
注2:
ELF
文件只能在操作系统环境下运行,裸机环境运行的是
BIN
文件;编译器默认输出的文件格式是ELF格式,可以使用
objcopy
命令转化为BIN文件:
/* 将name.elf转化为name.bin文件 */
arm-linux-objcopy -O binary name.elf name.bin
ELF组成
Object
文件参与程序链接(编译程序)和程序执行(运行程序)。为了方便和效率,
Object
文件为链接程序和执行程序分别提供了不同的视角。如下图:
-
ELF header
保存在文件最顶端它记录了整个文件的基本信息; -
Sections
包含了链接视角中每个节里的信息:指令、数据、符号表、重定位信息等等; -
section header table
包含描述文件节的信息,每个节对应表中的一项,包含节名、节的大小信息等等; -
segment
包含了执行视角中的每个段(就是程序数据和指令)文本段、数据段等等; -
program header table
描述了一个段的信息或者系统准备程序运行环境时所需要的其他信息;
ELF header
ELF header
的数据结构:
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; /* 程序进入点 */
Elf32_Off e_phoff; /* 程序头部偏移量 */
Elf32_Off e_shoff; /* 节头部偏移量 */
Elf32_Word e_flags; /* 处理器特定标志 */
Elf32_Half e_ehsize; /* ELF头部长度 */
Elf32_Half e_phentsize; /* 程序头部中一个条目的长度 */
Elf32_Half e_phnum; /* 程序头部条目个数 */
Elf32_Half e_shentsize; /* 节头部中一个条目的长度 */
Elf32_Half e_shnum; /* 节头部条目个数 */
Elf32_Half e_shstrndx; /* 节头部字符表索引 */
} Elf32_Ehdr;
-
e_ident[0~3]
包含了ELF文件的魔数,依次是0x7f、’E’、’L’、’F’ -
e_ident[4]
表示硬件系统的位数,1代表32位,2代表64位 -
e_ident[5]
表示数据编码方式,1代表小端格式,2代表大端格式 -
e_ident[6]
指定ELF头部的版本,当前必须为1 -
e_ident[7~15]
是填充符,通常是0 -
e_ident[16]
e_ident[]数组的大小
一个实际程序的
ELF Header
信息:
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: ARM
Version: 0x1
Entry point address: 0x858c
Start of program headers: 52 (bytes into file)
Start of section headers: 4500 (bytes into file)
Flags: 0x5000002, has entry point, Version5 EABI
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 10
Size of section headers: 40 (bytes)
Number of section headers: 29
Section header string table index: 26
section header table
section header
的数据结构:
typedef struct elf32_shdr {
Elf32_Wordsh_name;
Elf32_Wordsh_type;
Elf32_Wordsh_flags;
Elf32_Addrsh_addr;
Elf32_Off sh_offset;
Elf32_Wordsh_size;
Elf32_Wordsh_link;
Elf32_Wordsh_info;
Elf32_Wordsh_addralign;
Elf32_Wordsh_entsize;
} Elf32_Shdr;
一个实际程序的
section header table
信息:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 00008174 000174 000013 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 00008188 000188 000020 00 A 0 0 4
[ 3] .hash HASH 000081a8 0001a8 000054 04 A 4 0 4
[ 4] .dynsym DYNSYM 000081fc 0001fc 000100 10 A 5 1 4
[ 5] .dynstr STRTAB 000082fc 0002fc 0000ee 00 A 0 0 1
[ 6] .gnu.version VERSYM 000083ea 0003ea 000020 02 A 4 0 2
[ 7] .gnu.version_r VERNEED 0000840c 00040c 000040 00 A 5 2 4
[ 8] .rel.dyn REL 0000844c 00044c 000008 08 A 4 0 4
[ 9] .rel.plt REL 00008454 000454 000070 08 A 4 11 4
[10] .init PROGBITS 000084c4 0004c4 00000c 00 AX 0 0 4
[11] .plt PROGBITS 000084d0 0004d0 0000bc 04 AX 0 0 4
[12] .text PROGBITS 0000858c 00058c 0002d4 00 AX 0 0 4
[13] .fini PROGBITS 00008860 000860 000008 00 AX 0 0 4
[14] .rodata PROGBITS 00008868 000868 00001c 00 A 0 0 4
[15] .ARM.exidx ARM_EXIDX 00008884 000884 000008 00 AL 12 0 4
[16] .eh_frame PROGBITS 0000888c 00088c 000004 00 A 0 0 4
[17] .init_array INIT_ARRAY 00010f04 000f04 000004 00 WA 0 0 4
[18] .fini_array FINI_ARRAY 00010f08 000f08 000004 00 WA 0 0 4
[19] .jcr PROGBITS 00010f0c 000f0c 000004 00 WA 0 0 4
[20] .dynamic DYNAMIC 00010f10 000f10 0000f0 08 WA 5 0 4
[21] .got PROGBITS 00011000 001000 000048 04 WA 0 0 4
[22] .data PROGBITS 00011048 001048 000008 00 WA 0 0 4
[23] .bss NOBITS 00011050 001050 000008 00 WA 0 0 4
[24] .ARM.attributes ARM_ATTRIBUTES 00000000 001050 000036 00 0 0 1
[25] .comment PROGBITS 00000000 001086 00001b 01 MS 0 0 1
[26] .shstrtab STRTAB 00000000 0010a1 0000f3 00 0 0 1
[27] .symtab SYMTAB 00000000 00161c 000770 10 28 82 4
[28] .strtab STRTAB 00000000 001d8c 000399 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
所有
section
类型的说明:
-
.bss
该节保存着未初始化的数据,这些数据存在于程序内存映象中。通过定义,当程序开始运行,系统初始化那些数据为0。该节不占文件空间,正如它的节类型
SHT_NOBITS
指示的一样。 -
.comment
该节保存着版本控制信息。 -
.data
和
.data1
这些节保存着初始化了的数据,那些数据存在于程序内存映象中。 -
.debug
该节保存着为符号调试的信息。这些内容不明确。 -
.dynamic
该节保存着动态连接的信息。
SHF_ALLOC
位指定该节的属性。设置
SHF_WRITE
位时表示是处理器特定的。 -
.dynstr
该节保存着动态连接时需要的字符串,一般情况下表示和一个符号表项相对应的名字 -
.dynsym
该节保存着动态链接符号表,如
Symbol Table
的描述。 -
.fini
该节保存着可执行指令,它包含了进程的终止代码。当一个程序正常退出时,系统安排执行这个节的中的代码。 -
.got
该节保存着全局的偏移量表。 -
.hash
该节保存着符号哈希表。 -
.init
该节保存着可执行指令,它构成了进程的初始化代码。当一个程序开始运行时,在
main
函数被调用之前(c语言中称为main),系统安排执行这个节的中的代码。 -
.interp
该节保存了程序解释器(program interpreter)的路径。假如在这个节中有一个可装载的节,那么该节属性的
SHF_ALLOC
位将被设置;否则,该位不会被设置。 -
.line
该节包含源文件中的行数信息用于符号调试,它描述源程序与机器代码之间的对应关系。该节内容不明确的。 -
.note
该节保存Part 2讨论的
Note Section
节的格式信息。 -
.plt
该节保存过程链接表(Procedure Linkage Table)。 -
.rel<name>
和
.rela<name>
这些节保存着重定位的信息。如果文件包含了一个可装载的节需要重定位,那么该节属性中的
SHF_ALLOC
位会被设置;否则该位被关闭。通常,由需要重定的那个节来提供。比如一个需要重定位的节`.text`,那么该节名字为`.rel.text`或者`.rela.text`。 -
.rodata
和
.rodata1
这些节保存着只读数据,放在进程映象中的只读节。 -
.shstrtab
该节保存着节名称。 -
.strtab
该节保存着字符串,一般表示和一个符号表项相对应的名字。假如文件有一个可装载的节,并且该节包括了符号字符表,那么该节属性的
SHF_ALLOC
位将被设置;否则不设置。 -
.symtab
该节保存着一个符号表。假如文件有一个可装载的节,并且该节包含了符号表,那么该节属性的
SHF_ALLOC
位将被设置;否则不设置。 -
.text
该节保存着程序的
text
或者说是程序的可执行指令。
Symbol Table
符号表保存着程序定义和引用的符号(全局变量和函数)信息。一个符号表的索引是数组的下标。第0项既指定这个表的起始项也作为未定义符号的索引。
Symbol Table
的数据结构:
typedef struct elf32_sym{
Elf32_Wordst_name;
Elf32_Addrst_value;
Elf32_Wordst_size;
unsigned char st_info;
unsigned char st_other;
Elf32_Halfst_shndx;
} Elf32_Sym;
一个实际程序的
Symbol Table
信息:
Symbol table '.dynsym' contains 16 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 000084e4 0 FUNC GLOBAL DEFAULT UND abort@GLIBC_2.4 (2)
2: 000084f0 0 FUNC GLOBAL DEFAULT UND pthread_exit@GLIBC_2.4 (3)
3: 000084fc 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.4 (2)
4: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
5: 00000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
6: 00008514 0 FUNC GLOBAL DEFAULT UND fclose@GLIBC_2.4 (2)
7: 00008520 0 FUNC GLOBAL DEFAULT UND fopen@GLIBC_2.4 (2)
8: 0000852c 0 FUNC GLOBAL DEFAULT UND pthread_getspecific@GLIBC_2.4 (3)
9: 00008538 0 FUNC GLOBAL DEFAULT UND pthread_create@GLIBC_2.4 (3)
10: 00008544 0 FUNC GLOBAL DEFAULT UND pthread_setspecific@GLIBC_2.4 (3)
11: 00008550 0 FUNC GLOBAL DEFAULT UND fprintf@GLIBC_2.4 (2)
12: 0000855c 0 FUNC GLOBAL DEFAULT UND pthread_self@GLIBC_2.4 (3)
13: 00008568 0 FUNC GLOBAL DEFAULT UND sprintf@GLIBC_2.4 (2)
14: 00008574 0 FUNC GLOBAL DEFAULT UND pthread_join@GLIBC_2.4 (3)
15: 00008580 0 FUNC GLOBAL DEFAULT UND pthread_key_create@GLIBC_2.4 (3)
Program Header table
program header
的数据结构:
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;
一个实际程序的
program header table
信息:
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
EXIDX 0x000884 0x00008884 0x00008884 0x00008 0x00008 R 0x4
PHDR 0x000034 0x00008034 0x00008034 0x00140 0x00140 R E 0x4
INTERP 0x000174 0x00008174 0x00008174 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.3]
LOAD 0x000000 0x00008000 0x00008000 0x00890 0x00890 R E 0x8000
LOAD 0x000f04 0x00010f04 0x00010f04 0x0014c 0x00154 RW 0x8000
DYNAMIC 0x000f10 0x00010f10 0x00010f10 0x000f0 0x000f0 RW 0x4
NOTE 0x000188 0x00008188 0x00008188 0x00020 0x00020 R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x4
GNU_RELRO 0x000f04 0x00010f04 0x00010f04 0x000fc 0x000fc R 0x1
LOOS+5041580 0x000000 0x00000000 0x00000000 0x00000 0x00000 0x4
参考文献
汇编语言———数据段、程序段、栈段
我们注意到,“段地址”这个名称中包含着“段”的概念。这种说法可能对一些学习者产生了误导,使人误以为内存被划分成一个一个的段,每一个段都有一个地址。如果我们在一开始形成了这种认知,将影响以后对汇编语言的深入理解和灵活应用。
其实,内存并没有分段,段的划分来自于CPU。由于8086CPU用“基础地址(段地址×16)+偏移地址=物理地址”的方式给出内存单元的物理地址,使得我们可以用分段的方式来管理内存。
如下图所示,我们可以认为:地址10000H~100FFH的内存单元组成一个段,该段的起始地址(基础地址)为10000H,段地址为1000H,大小为100H;我们也可以认为地址10000H~1007F、10080H~100FHH的内存单元组成两个段,它们的起始地址(基础地址)为:10000H和10080H,段地址为:1000H和1008H,大小都为80H
汇编的段:
数据段、程序段、栈段
数据段:存放数据的段。使用时候,用CS寄存器。
程序段:用来存放程序的段。使用的时候,用CS和IP寄存器。
栈段:是一个栈。使用时,初始设置SS和SP寄存器。
实战操作一波:把10000H~1000FH的空间当作栈;把20000H和20002H中的数据通过栈进行对换。
objdump readelf
目标文件只是ELF文件的
可重定位文件(Relocatable file)
,ELF文件一共有
4种类型
:
Relocatable file、Executable file、Shared object file
和
Core Dump file
coredump
objdump
是在
类Unix
操作系统
上显示关于
目标文件
的各种信息的
命令行
程序。例如,它可用作
反汇编器
来以汇编代码形式查看
可执行文件
。
objdump -S helloworld
go tool objdump
readelf -h main.o
-h等价于
–file-header
readelf -S main -S等价于–section-headers或者–sections
查看
符号表
:$readelf -s main.o ,-s等价于–syms或者–symbols。
同样$objdump -d -j .data main可反汇编查看date区内容。
.data区保存的是初始化的全局变量。
使用objdump反汇编查看.text的内容:
$objdump -d -j .text main
-d选项告诉objdump反汇编机器码,-j选项告诉objdump只关心.text区。 //输出内容太多,省略了一些次要的
反汇编应用程序
objdump -d main.o
显示文件头信息
objdump -f main.o
显示制定section段信息(comment段)
objdump -s -j .comment main.o
转载:
https://zhuanlan.zhihu.com/p/73114831
http://chuquan.me/2018/05/21/elf-introduce/
http://cxd2014.github.io/2015/12/02/elf/
https://www.jianshu.com/p/863b279c941e