ELF 格式详解

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。

$ readelf -h hello.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: 672 (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: 13
Section header string table index: 10

 

ELF文件结构示意图中定义的Elf_Ehdr的各个成员的含义与readelf具有对应关系。如下表所示:

成员含义
e_identMagic: 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_typeType: REL (Relocatable file)
 ELF文件类型
e_machineMachine: Advanced Micro Devices X86-64
 ELF文件的CPI平台属性
e_versionVersion: 0x1
 ELF版本号。一般为常数1
e_entryEntry point address: 0x0
 入口地址,规定ELF程序的入口虚拟地址,操作系统在加载完该程序后从这个地址开始执行进程的指令。可重定位指令一般没有入口地址,则该值为0
e_phoffStart of program headers: 0(bytes into file)
e_shoffStart of section headers: 672 (bytes into file)
 Section Header Table 在文件中的偏移
e_wordFlags: 0x0
 ELF标志位,用来标识一些ELF文件平台相关的属性。
e_ehsizeSize of this header: 64 (bytes)
 ELF Header本身的大小
e_phentsizeSize of program headers: 0 (bytes)
e_phnumNumber of program headers: 0
e_shentsizeSize of section headers: 64 (bytes)
 单个Section Header大小
e_shnumNumber of section headers: 13
 Section Header的数量
e_shstrndxSection header string table index: 10
 Section Header字符串表在Section Header Table中的索引

ELF魔数

每种可执行文件的格式的开头几个字节都是很特殊的,特别是开头4个字节,通常被称为魔数(Magic Number)。通过对魔数的判断可以确定文件的格式和类型。如:ELF的可执行文件格式的头4个字节为0x7Felf;Java的可执行文件格式的头4个字节为cafe;如果被执行的是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工具来查看节头表。

$ readelf -S hello.o
There are 13 section headers, starting at offset 0x2a0:
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
0000000000000015 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 000001f0
0000000000000030 0000000000000018 I 11 1 8
[ 3] .data PROGBITS 0000000000000000 00000055
0000000000000000 0000000000000000 WA 0 0 1
[ 4] .bss NOBITS 0000000000000000 00000055
0000000000000000 0000000000000000 WA 0 0 1
[ 5] .rodata PROGBITS 0000000000000000 00000055
000000000000000d 0000000000000000 A 0 0 1
[ 6] .comment PROGBITS 0000000000000000 00000062
0000000000000035 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 00000097
0000000000000000 0000000000000000 0 0 1
[ 8] .eh_frame PROGBITS 0000000000000000 00000098
0000000000000038 0000000000000000 A 0 0 8
[ 9] .rela.eh_frame RELA 0000000000000000 00000220
0000000000000018 0000000000000018 I 11 8 8
[10] .shstrtab STRTAB 0000000000000000 00000238
0000000000000061 0000000000000000 0 0 1
[11] .symtab SYMTAB 0000000000000000 000000d0
0000000000000108 0000000000000018 12 9 8
[12] .strtab STRTAB 0000000000000000 000001d8
0000000000000013 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)

 

ELF文件结构示意图中定义的Elf_Shdr的各个成员的含义与readelf具有对应关系。如下表所示:

成员含义
sh_name节名
 节名是一个字符串,保存在一个名为.shstrtab的字符串表(可通过Section Header索引到)。sh_name的值实际上是其节名字符串在.shstrtab中的偏移值
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_NULL0无效节
SHT_PROGBITS1程序节。代码节、数据节都是这种类型。
SHT_SYMTAB2符号表
SHT_STRTAB3字符串表
SHT_RELA4重定位表。该节包含了重定位信息。
SHT_HASH5符号表的哈希表
SHT_DYNAMIC6动态链接信息
SHT_NOTE7提示性信息
SHT_NOBITS8表示该节在文件中没有内容。如.bss
SHT_REL9该节包含了重定位信息
SHT_SHLIB10保留
SHT_DNYSYM11动态链接的符号表

节标志位(sh_flag)

节标志位表示该节在进程虚拟地址空间中的属性。如是否可写、是否可执行等。相关常量以SHF_开头。常见的节标志位如下表所示:

常量含义
SHF_WRITE1表示该节在进程空间中可写
SHF_ALLOC2表示该节在进程空间中需要分配空间。有些包含指示或控制信息的节不需要在进程空间中分配空间,就不会有这个标志。
SHF_EXECINSTR4表示该节在进程空间中可以被执行

节链接信息(sh_link、sh_info)

如果节的类型是与链接相关的(无论是动态链接还是静态链接),如重定位表、符号表、等,则sh_linksh_info两个成员所包含的意义如下所示。其他类型的节,这两个成员没有意义。

sh_typesh_linksh_info
SHT_DYNAMIC该节所使用的字符串表在节头表中的下标0
SHT_HASH该节所使用的符号表在节头表中的下标0
SHT_REL该节所使用的相应符号表在节头表中的下标该重定位表所作用的节在节头表中的下标
SHT_RELA该节所使用的相应符号表在节头表中的下标该重定位表所作用的节在节头表中的下标
SHT_SYMTAB操作系统相关操作系统相关
SHT_DYNSYM操作系统相关操作系统相关
otherSHN_UNDEF0

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节保存的是符号字符串表,表中的内容会被.symtabElfN_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_other0
st_shndx符号所在的节
st_info符号类型及绑定属性

使用readelf工具我们也能够看到符号表的相关信息。

$ readelf -s hello.o
Symbol table '.symtab' contains 11 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 SECTION LOCAL DEFAULT 7
7: 0000000000000000 0 SECTION LOCAL DEFAULT 8
8: 0000000000000000 0 SECTION LOCAL DEFAULT 6
9: 0000000000000000 21 FUNC GLOBAL DEFAULT 1 main
10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND puts

 

字符串表

类似于符号表,在大多数共享库和动态链接可执行文件中,也存在两个字符串表。即.dynstr.strtab,分别对应于.dynsymsymtab。此外,还有一个.shstrtab的节头字符串表,用于保存节头表中用到的字符串,可通过sh_name进行索引。

ELF文件中所有字符表的结构基本一致,如上图所示。

重定位表

重定位就是将符号定义和符号引用进行连接的过程。可重定位文件需要包含描述如何修改节内容的相关信息,从而使可执行文件和共享目标文件能够保存进程的程序镜像所需要的正确信息。

重定位表是进行重定位的重要依据。我们可以使用objdump工具查看目标文件的重定位表:

$ objdump -r hello.o
hello.o: file format elf64-x86-64
RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
0000000000000005 R_X86_64_32 .rodata
000000000000000a R_X86_64_PC32 puts-0x0000000000000004
RELOCATION RECORDS FOR [.eh_frame]:
OFFSET TYPE VALUE
0000000000000020 R_X86_64_PC32 .text

 

重定位表是一个Elf_Rel类型的数组结构,每一项对应一个需要进行重定位的项。
其成员含义如下表所示:

成员含义
r_offset重定位入口的偏移。
 对于可重定位文件来说,这个值是该重定位入口所要修正的位置的第一个字节相对于节起始的偏移
 对于可执行文件或共享对象文件来说,这个值是该重定位入口所要修正的位置的第一个字节的虚拟地址
r_info重定位入口的类型和符号
 因为不同处理器的指令系统不一样,所以重定位所要修正的指令地址格式也不一样。每种处理器都有自己的一套重定位入口的类型。
 对于可执行文件和共享目标文件来说,它们的重定位入口是动态链接类型的。

重定位是目标文件链接成为可执行文件的关键。我们将在后面的进行介绍。

 

 

来自:http://cxd2014.github.io/2015/12/02/elf/

ELF简介

ELF(Executableand Linking Format 可执行和链接格式),ELF文件格式是一个开放标准,各种UNIX系统的可执行文件都采用ELF格式,它有三种不同的类型:

  1. 可重定位的目标文件(Relocatable)也就是通常称的目标文件,后缀为.o
  2. 可执行文件(Executable)
  3. 共享库(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文件为链接程序和执行程序分别提供了不同的视角。如下图:

Figure_1

  • 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类型的说明:

  1. .bss该节保存着未初始化的数据,这些数据存在于程序内存映象中。通过定义,当程序开始运行,系统初始化那些数据为0。该节不占文件空间,正如它的节类型SHT_NOBITS指示的一样。

  2. .comment该节保存着版本控制信息。

  3. .data.data1这些节保存着初始化了的数据,那些数据存在于程序内存映象中。

  4. .debug该节保存着为符号调试的信息。这些内容不明确。

  5. .dynamic该节保存着动态连接的信息。SHF_ALLOC位指定该节的属性。设置SHF_WRITE位时表示是处理器特定的。

  6. .dynstr该节保存着动态连接时需要的字符串,一般情况下表示和一个符号表项相对应的名字

  7. .dynsym该节保存着动态链接符号表,如Symbol Table的描述。

  8. .fini该节保存着可执行指令,它包含了进程的终止代码。当一个程序正常退出时,系统安排执行这个节的中的代码。

  9. .got该节保存着全局的偏移量表。

  10. .hash该节保存着符号哈希表。

  11. .init该节保存着可执行指令,它构成了进程的初始化代码。当一个程序开始运行时,在main函数被调用之前(c语言中称为main),系统安排执行这个节的中的代码。

  12. .interp该节保存了程序解释器(program interpreter)的路径。假如在这个节中有一个可装载的节,那么该节属性的SHF_ALLOC位将被设置;否则,该位不会被设置。

  13. .line该节包含源文件中的行数信息用于符号调试,它描述源程序与机器代码之间的对应关系。该节内容不明确的。

  14. .note该节保存Part 2讨论的Note Section节的格式信息。

  15. .plt该节保存过程链接表(Procedure Linkage Table)。

  16. .rel<name>.rela<name>这些节保存着重定位的信息。如果文件包含了一个可装载的节需要重定位,那么该节属性中的SHF_ALLOC位会被设置;否则该位被关闭。通常,由需要重定的那个节来提供。比如一个需要重定位的节`.text`,那么该节名字为`.rel.text`或者`.rela.text`。

  17. .rodata.rodata1这些节保存着只读数据,放在进程映象中的只读节。

  18. .shstrtab该节保存着节名称。

  19. .strtab该节保存着字符串,一般表示和一个符号表项相对应的名字。假如文件有一个可装载的节,并且该节包括了符号字符表,那么该节属性的SHF_ALLOC位将被设置;否则不设置。

  20. .symtab该节保存着一个符号表。假如文件有一个可装载的节,并且该节包含了符号表,那么该节属性的SHF_ALLOC位将被设置;否则不设置。

  21. .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 fileCore 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

  • 6
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
elf格式解析工具是一种用于分析和解析可执行和可链接格式(Executable and Linkable Format,ELF)文件的工具。ELF是一种用于在Unix和类Unix系统中表示可执行文件、可重定位目标文件、共享库和核心转储文件的标准文件格式。 在解析ELF格式文件时,解析工具需要读取文件的头部、节区头部、程序头部和符号表等部分的信息。这些信息包含了文件的基本结构和属性,通过解析这些信息,可以获得文件的节区布局、程序入口点、依赖库等重要信息。 解析ELF格式文件的源码通常会包含以下步骤: 1. 打开文件:首先,源码会通过系统调用或文件操作函数打开要解析的ELF格式文件。 2. 读取头部信息:源码会读取ELF文件的头部信息,并解析其中的属性,如ELF文件类型、机器类型、入口点地址等。 3. 读取节区头部信息:源码会读取ELF文件的节区头部信息,并解析其中的属性,如节区的偏移量、大小、对齐方式等。 4. 读取程序头部信息:源码会读取ELF文件的程序头部信息,并解析其中的属性,如程序头部表的偏移量、大小等。程序头部表描述了ELF文件执行时要用到的程序段的布局和属性。 5. 读取符号表信息:源码会读取ELF文件的符号表信息,并解析其中的属性,如符号的名称、类型、地址等。符号表包含了代码和数据中定义和引用的符号信息,可以用于调试和符号重定位等操作。 解析ELF格式文件的源码需要对ELF文件格式有一定的了解,并能够通过读取文件中的字节流并解析文件中的结构来提取文件的各种属性和信息。具体实现方式可以通过使用C或C++等编程语言来实现,使用系统调用或文件操作函数来读取文件内容。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值