Linux之ELF文件初探
对比windowsPE文件与概述
在windows中可执行文件是pe文件格式,Linux中可执行文件是ELF文件,其文件格式是ELF文件格式,在Linux下的ELF文件除了可执行文件(Excutable File),可重定位目标文件(RellocatableObject File)、共享目标文件(SharedObjectFile)、核心转储文件(Core DumpFile)也都是ELF格式文件。
一个典型的ELF文件大致的结构如下
| 文件头(ELF Header) |
|---|
| 程序头表(Program Header Table) |
| 代码段(.text) |
| 数据段(.data) |
| bss段(.bss) |
| 段表字符串表(.shstrtab) |
| 段表(Section Header Table) |
| 符号表(.symtab) |
| 字符串表(.strtab) |
| 重定位表(.rel.text) |
| 重定位表(.rel.data) |
使用Linux下专用工具readelf来查看elf文件信息

- 查看readelf中的源码

FLF文件组成
文件头:
用于记录一个ELF文件的信息(多少位?能够运行的CPU平台是什么?程序的入口点在哪里)
查看ELF头

在readelf的源码中变量类型Elf_Internal_Ehdr_,定义在internal头文件中
#define EI_NIDENT 16 /* Size of e_ident[] */
typedef struct elf_internal_ehdr {
unsigned char e_ident[EI_NIDENT]; /* ELF "magic number" */
bfd_vma e_entry; /* Entry point virtual address */
bfd_size_type e_phoff; /* Program header table file offset */
bfd_size_type e_shoff; /* Section header table file offset */
unsigned long e_version; /* Identifies object file version */
unsigned long e_flags; /* Processor-specific flags */
unsigned short e_type; /* Identifies object file type */
unsigned short e_machine; /* Specifies required architecture */
unsigned int e_ehsize; /* ELF header size in bytes */
unsigned int e_phentsize; /* Program header table entry size */
unsigned int e_phnum; /* Program header table entry count */
unsigned int e_shentsize; /* Section header table entry size */
unsigned int e_shnum; /* Section header table entry count */
unsigned int e_shstrndx; /* Section header string table index */
} Elf_Internal_Ehdr;
- 在Linux自带的头文件中查看

#define EI_NIDENT (16)
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
} Elf32_Ehdr;
程序头表:
记录了每个Segment的相关信息,比如类型、对应文件的偏移、大小、属性等,
程序头表和段头表相对独立,它们是由ELF文件头统一管理,程序头表管理ELF文件加载后,ELF文件内可加载段到内存映像的映射关系,一般只有可执行文件中,包含程序头表。程序头表包含多个程序头表项,程序头表描述的对象称为“Segment”,Segment描述的是ELF文件加载后的数据块,段(Section)描述的是ELF文件加载前的数据块。一般来说,来说两者会存在一定的对应关系,比如代码段.text的加载信息保存在程序头表项对应存放代码的Segment中,数据段.data的加载信息保存在程序头表项对应存放数据的Segment中。有时候为了简化程序头表项的个数,会把同类型的多个段,设置整个ELF文件作为一个Segment

程序头表的数据结构
/* Program segment header. */
typedef struct
{
Elf32_Word p_type; /* Segment type */
Elf32_Off p_offset; /* Segment file offset Segment对应的内容在文件的偏移*/
Elf32_Addr p_vaddr; /* Segment virtual address Segment在内存中的线性地址*/
Elf32_Addr p_paddr; /* Segment physical address */
Elf32_Word p_filesz; /* Segment size in file */
Elf32_Word p_memsz; /* Segment size in memory */
Elf32_Word p_flags; /* Segment flags */
Elf32_Word p_align; /* Segment alignment */
} Elf32_Phdr;
#define PT_NULL 0 /* Program header table entry unused */
#define PT_LOAD 1 /* Loadable program segment */
#define PT_DYNAMIC 2 /* Dynamic linking information */
#define PT_INTERP 3 /* Program interpreter */
#define PT_NOTE 4 /* Auxiliary information */
#define PT_SHLIB 5 /* Reserved */
#define PT_PHDR 6 /* Entry for header table itself */
#define PT_TLS 7 /* Thread-local storage segment */
#define PT_NUM 8 /* Number of defined types */
p_flag权限属性标志
| 值 | 说明 | 宏 |
|---|---|---|
| 1 | 可执行 | PE_X |
| 2 | 可写 | PE_W |
| 3 | 可读 | PE_R |
区段头表:用于记录ELF文件的主要的数据
查看区段

区段头表的数据结构
/* Section header. */ typedef struct
{
Elf32_Word sh_name; /* Section name (string tbl index) */
Elf32_Word sh_type; /* Section type */
Elf32_Word sh_flags; /* Section flags */
Elf32_Addr sh_addr; /* Section virtual addr at execution */
Elf32_Off sh_offset; /* Section file offset */
Elf32_Word sh_size; /* Section size in bytes */
Elf32_Word sh_link; /* Link to another section */
Elf32_Word sh_info; /* Additional section information */
Elf32_Word sh_addralign; /* Section alignment */
Elf32_Word sh_entsize; /* Entry size if section holds table */
} Elf32_Shdr;
区段头表一共有10个字段,含义如下
(1)sh_name段名,是一个是一个4字节的偏移,记录了段名字符串在段表字符串表(“.shstrtab”段)内的偏移。段表字符串并非表的形式,而是一个文件块,保存了所有的段表字符串内容,存储在“.shstrtab”的段中,根据“.shstrtab”的偏移,加上sh_name便可以访问到每个段对应的段名字符串。


起始地址是000017ac,第一个段表项全0,sh_name在段表项中的偏移是001b,由上图可以得到“.shstrtab”段的偏移是0016ae,所以,计算段名的偏移应该是0x0000001b + 0x000016ae = 0x000016c9
根据计算的结果,查看0x000016c9处:

(2)sh_type,表示段的类型。段的类型有很多,常见的有SHT_PROGBITS,表示程序数据,SHT_SYMTAB表示符号表,SHT_STRTAB表示字符串表,还有专门存放构造函数数组段SHT_INIT_ARRAY,析构函数数组段SHT_FINI_ARRAY。
.txt 代码段
.data 数据段
.radata记录常量数据
.symtab记录符号表(相当于PE文件的导出表)的数据
.strtab 串表段
.shstrtab 有段表 字符串表段
.rel .plt记录某个区段的重定位内容(相当于PE文件的导入表)
对应的宏如下:
/* Legal values for sh_type (section type). */ #define SHT_NULL 0 /* Section header table entry unused */
#define SHT_PROGBITS 1 /* Program data */
#define SHT_SYMTAB 2 /* Symbol table */
#define SHT_STRTAB 3 /* String table */
#define SHT_RELA 4 /* Relocation entries with addends */
#define SHT_HASH 5 /* Symbol hash table */
#define SHT_DYNAMIC 6 /* Dynamic linking information */
#define SHT_NOTE 7 /* Notes */
#define SHT_NOBITS 8 /* Program space with no data (bss) */
#define SHT_REL 9 /* Relocation entries, no addends */
#define SHT_SHLIB 10 /* Reserved */
#define SHT_DYNSYM 11 /* Dynamic linker symbol table */
#define SHT_INIT_ARRAY 14 /* Array of constructors */
#define SHT_FINI_ARRAY 15 /* Array of destructors */
#define SHT_PREINIT_ARRAY 16 /* Array of pre-constructors */
(3)sh_flags,表示段标志,记录段的属性。其中0表示默认属性,1表示段可写,取值位SHF_WRITE。2表示段加载后需要为之分配内存空间,取值为SHF_ALLOC。4表示可执行,取值为SHF_EXECINSTR,段标志属性可以叠加。
(4)sh_addr,表示段加载后的线性地址
(5)sh_offset,表示段在文件内的偏移,根据此偏移可确定段的位置,读取段的内容。
(6)sh_size,表示段的大小,单位为字节。需要注意的是,如果段类型为SHT_NOBITS,段内没有数据,那么段的大小并非指文件块的大小,而是指段加载后占用内存的大小。

(7~8)sh_link和sh_info表示段的链接信息,一般用于描述符号表段和重定位表段的链接信息。对于符号表段(SHT_SYMTAB),sh_link记录的是符号表使用的串表所在段(一般是,.strtab)对应段表项在段表内的索引。

sh_info 记录的是符号表最后一个局部符号的符号表项在符号表内的索引加1,一般恰好是第一个全局符号的符号表项索引,这样可以帮助连接器更快的地定位到第一个全局符号。如下图:段中符号表段的信息sh_info,刚好是局部符号+1的索引。

对于重定位表表段(段类型是SHT_REL),sh_link记录重定位所作用的符号表段表项早段内的索引,而sh_info记录重定位所作用的段对应的段表项在段表中的索引。

| sh_type | sh_link | sh_info |
|---|---|---|
| SHT_DYNAMIC | 此表项中条目所用到的字符串表在段表中的索引 | |
| SHT_HASH | 此哈希表所适用的符号表的段表索引 | |
| SHT_REL | 相关符号表的段表索引 | 重定位所使用的段的段表索引 |
| SHT_RELA | 相关联的字符串表的段表索引 | 最后一个局部符号的符号表索引值+1 |
| 其它 | SHN_UNDEF | 0 |
(9)sh_addralign,表示段的对齐方式,对齐规则为 sh_offset % sh_addralign = 0,即段的文件偏移必须是sh_addralign的整数倍,sh_addralign的取值必须是2的整数倍,入1、2、4、8等。
| 对齐值 | 对齐方式 | 说明 |
|---|---|---|
| 0 | 无对齐要求 | |
| 1 | 无对齐要求 | |
| 4 | 对齐4 | 满足sh_iffset % 4 = 0 |
| 16 | 对齐16 | 满足sh_iffset % 16 = 0 |
| 32 | 对齐32 | 满足sh_iffset % 32 = 0 |

(10) sh_entsize,一般用于保存注入符号表段,重定位表段时,表示段内保存表的表项大小。例如符号表段“.symtab”内保存的符号表的表项大小为sizeof(Elf32——sym)= 16字节,重定位表段“.rel.plt”内保存的重定位表的表项大小为sizeof(Elf32_rel)= 8字节。
ELF符号表(Symbol Table**)
ELF文件的符号表保存了程序中的符号信息,包括程序中的文件名、函数名、全局变量名等,符号表一般保存在名为“.strtab”的段内,该段对应段表项的类型为SHT_SYMTAB。符号表包含多个符号表项,每个符号表项记录了符号的名称、位置、类型等信息。符号表象的数据结构:
typedef struct
{
Elf32_Word st_name; /* Symbol name (string tbl index) */
Elf32_Addr st_value; /* Symbol value */
Elf32_Word st_size; /* Symbol size */
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility */
Elf32_Section st_shndx; /* Section index */
} Elf32_Sym;

ELF重定位表(Reloc Table)
重定位表常见于可重定位目标文件内,对于静态链接生成的可可执行文件,一般不包括重定位表,动态链接生成的可执行文件暂时不讨论。重定位表一般保存在以名为“.rel”开头的段内,该段对应段表项的类型为SHT_REL,ELF文件需要重定位的段,一般都对应一个重定位表,比如代码段“.txt”的重定位表保持在“.rel.text”内,数据段“.data”的重定位表保持在“.rel.data”内。
重定位表包含多个重定位表项,每个重定位表项记录一条重定位信息,包括重定位的符号、位置、类型等。
/* Relocation table entry without addend (in section of type SHT_REL). */ typedef struct
{
Elf32_Addr r_offset; /* Address */
Elf32_Word r_info; /* Relocation type and symbol index */
} Elf32_Rel;

ELF串表(String Table)
ELF文件内的段表和符号表需要记录段名和符号名,这些名称都是字符串。然而,段表项和符号表项都是固定长度的数据结构,无法存储不定长的字符串。因此FLE文件将名称字符串内容集中存放在一个段内,称为串表。这些段表项和符号表项只需记录段名字符串或符号名字符串在对应串表项的位置即可。
虽然虽然存储的字符串表的内容称为串表,但是并非表的形式,而是一个文件区域。

Linux之ELF文件初探的更多相关文章
- linux 修改 elf 文件的dynamic linker 和 rpath
linux 修改 elf 文件的dynamic linker 和 rpath https://nixos.org/patchelf.html 下载地址 https://nixos.org/releas ...
- 利用linux判断elf文件是64位还是32位
readelf 命令,参数为-h 例如 文件名为python >>>readelf -h python 得到的是ELF Header中的项Magic 第五个数 02时为64位,01时 ...
- ELF文件的加载过程(load_elf_binary函数详解)--Linux进程的管理与调度(十三)
加载和动态链接 从编译/链接和运行的角度看,应用程序和库程序的连接有两种方式. 一种是固定的.静态的连接,就是把需要用到的库函数的目标代码(二进制)代码从程序库中抽取出来,链接进应用软件的目标映像中: ...
- Linux 可执行文件 ELF结构 及程序载入执行
Linux下ELF文件类型分为以下几种: 1.可重定位文件,比如SimpleSection.o: 2.可运行文件,比如/bin/bash. 3.共享目标文件,比如/lib/libc.so. 在Linu ...
- linux实践之ELF文件分析
linux实践之ELF文件分析 下面开始elf文件的分析. 我们首先编写一个简单的C代码. 编译链接生成可执行文件. 首先,查看scn15elf.o文件的详细信息. 以16进制形式查看scn15elf ...
- 实例分析ELF文件动态链接
参考文献: <ELF V1.2> <程序员的自我修养---链接.装载与库>第6章 可执行文件的装载与进程 第7章 动态链接 <Linux GOT与PLT> 开发平台 ...
- 实例分析ELF文件静态链接
参考文献: <ELF V1.2> <程序员的自我修养---链接.装载与库>第4章 静态链接 开发平台: [thm@tanghuimin static_link]$ uname ...
- ELF文件数据布局探索(1)
作为一名Linux小白,第一次看到a.out这个名字,感觉实在是奇怪,搜了一下才知道这是编译器输出的默认可执行文件名 然后vi一下,哇,各种乱码,仔细看看,发现了三个清晰的字符ELF.继续搜索, 第一 ...
- bin文件和elf文件
ELF文件格式是一个开放标准,各种UNIX系统的可执行文件都采用ELF格式,它有三种不同的类型: 可重定位的目标文件(Relocatable,或者Object File) 可执行文件(Executab ...
随机推荐
- Ubuntu 搭建Zookeeper服务
1.下载安装包 官方下载地址http://apache.fayea.com/zookeeper/ 2.安装 安装前确保系统已安装过JDK,JDK安装过程可参照 2.1 解压下载好的tar.gz安装包到 ...
- jQuery常用方法(一)-基础
$("p").addClass(css中定义的样式类型); 给某个元素添加样式 $("img").attr({src:"test.jpg", ...
- Kotlin基本语法和使用技巧
基本语法 val value: String? = "HelloWorld" val name: String = getName() ?: return //如果是null就re ...
- YiShaAdmin,基于.NET Core Web开源的后台快速开发框架
YiShaAdmin YiShaAdmin 基于.NET Core Web开发,借鉴了很多开源项目的优点,让你开发Web管理系统和移动端Api更简单,所以我也把她开源了. 她可以用于所有的Web应用程 ...
- 夯实Java基础系列18:深入理解Java内部类及其实现原理
本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...
- redis 漏洞造成服务器被入侵-CPU飙升
前言 前几天在自己服务器上搭了redis,准备想着大展身手一番,昨天使用redis-cli命令的时候,10s后,显示进程已杀死.然后又试了几次,都是一样的结果,10s时间,进程被杀死.这个时候我还 ...
- node.js操作数据库之MongoDB+mongoose篇
前言 node.js的出现,使得用前端语法(javascript)开发后台服务成为可能,越来越多的前端因此因此接触后端,甚至转向全栈发展.后端开发少不了数据库的操作.MongoDB是一个基于分布式文件 ...
- java 远程方法调用(RMI)
什么是RMI? 维基百科:一种用于实现远程过程调用的应用程序编程接口.它使客户机上运行的程序可以调用远程服务器上的对象. 什么是序列化及反序列化 (1)序列化:把对象转换为字节序列的过程称为对象的序列 ...
- Vue入门教程 第二篇 (数据绑定与响应式)
数据绑定 Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统: <div id="app"> {{ message }} </ ...
- redhat5配置网络源
最近适配了一堆linux系统, Redhat4/5/6, ubuntu 12/14/16, Suse 10/11/12 其中适配到Red5 时候配置网络源 # The mirror system us ...