ELF是linux动态库,可执行文件的格式.具体介绍可参阅wiki Executable and Linkable Format。可以类比到windows下exe的格式。
首先推荐一个写的不错文档ELF格式
我们知道程序需要加载内存后才能运行。但是ELF文件加载到内存后布局会变化和原始ELF文件相比,加载器会将相同的节属性(比如只读)合并一个段。所以ELF也就有了两种视图,一种未加载前静态视图,另一种是加载后的动态视图。
我们首先了解静态视图下ELF文件格式如下:
你可以把ELF内容大致分为四个部分:
(1) ELF头部
(2) 节
(3) 节表头
(4) 程序头
- ELF头部固定在ELF文件开始
- 需要留意程序头和节表头可以位于ELF任意位置,他们位置被ELF头部中的属性指定
- 节分有很多种格式需要根据节类别区分,比如重定义节 与代码节
本文根据以下代码作为示例
#includestatic int mystaticVar = 3 ; int myglobalvar=3; //函数来自test.so extern void testfun(); int main(){ int *inp= 0x00; *inp=2; testfun(); static int myLocalVar1 = 3 ; static int myUnintLocallvar; printf("hello world %d rn",mystaticVar); return 0; } void hell(){ testfun(); }
目标文件 main.o
可执行文件main.out
我们以main.o 举例
我们用file查看文件类别
ELF 64-bit 告诉我们这个文件是一个64位系统下的ELF文件
LSB是least significant bit缩写表示第一个字节是多字节中最低有效位,简而言之就是小端模式
x86-64 是指该文件运行在那个处理器的ABI下
version 1(SYSV)是该ELF标准是UNIX_System_V具体参阅SYSV
not stripped表示该ELF存在符号表
relocatable 表示该文件是可重定位,因为main.o是目标文件而不是可执行文件,部分代码地址是不确定的
上面信息其实file程序读取该文件的elf头部得到。我们使用readelf -h文件头查看更详细的信息
数据结构如下所示
typedef struct
{
unsigned char e_ident[EI_NIDENT];
Elf64_Half e_type;
Elf64_Half e_machine;
Elf64_Word e_version;
Elf64_Addr e_entry;
Elf64_Off e_phoff;
Elf64_Off e_shoff;
Elf64_Word e_flags;
Elf64_Half e_ehsize;
Elf64_Half e_phentsize;
Elf64_Half e_phnum;
Elf64_Half e_shentsize;
Elf64_Half e_shnum;
Elf64_Half e_shstrndx;
} Elf64_Ehdr;
e_ident
一个16字节数组大小
完整参数参阅请参阅 ELF Header
在elf.h有EI_XXX表示上面下标位置
//elf.h #define EI_NIDENT (16) #define EI_MAG0 0 #define ELFMAG0 0x7f #define EI_MAG1 1 #define ELFMAG1 'E' #define EI_MAG2 2 #define ELFMAG2 'L' #define EI_MAG3 3 #define ELFMAG3 'F' #define ELFMAG "177ELF" #define SELFMAG 4 #define EI_CLASS 4 #define ELFCLASSNONE 0 #define ELFCLASS32 1 #define ELFCLASS64 2 #define ELFCLASSNUM 3 #define EI_DATA 5 #define ELFDATANONE 0 #define ELFDATA2LSB 1 #define ELFDATA2MSB 2 #define ELFDATANUM 3 #define EI_VERSION 6 #define EI_OSABI 7 #define ELFOSABI_NONE 0 #define ELFOSABI_SYSV 0 #define ELFOSABI_HPUX 1 #define ELFOSABI_NETBSD 2 #define ELFOSABI_GNU 3 #define ELFOSABI_LINUX ELFOSABI_GNU #define ELFOSABI_SOLARIS 6 #define ELFOSABI_AIX 7 #define ELFOSABI_IRIX 8 #define ELFOSABI_FREEBSD 9 #define ELFOSABI_TRU64 10 #define ELFOSABI_MODESTO 11 #define ELFOSABI_OPENBSD 12 #define ELFOSABI_ARM_AEABI 64 #define ELFOSABI_ARM 97 #define ELFOSABI_STANDALONE 255 #define EI_ABIVERSION 8 #define EI_PAD 9
| Name | 下标范围 | Purpose |
|---|---|---|
| ELF魔数 | 0-4 | 固定为0x7f+ELF |
| EI_CLASS | 5 | 表示文件时32位还是64位 1是32 2是64 |
| EI_DATA | 6 | 指定大小端 1小端 2大端 |
| EI_VERSION | 7 | ELF规范版本当前规定是1 |
| EI_OSABI | 8 | ELF启用一些基于操作系统或者cpu特性一般为0 |
| EI_ABIVERSION | 9 | 一般为0 指定当前当ABI版本配合EI_OSABI使用 |
| EI_PAD | 10-F | 预留 |
| EI_NIDENT | F | e_ident[]数组大小 |
表示当前ELF文件类型,下面举例常见的类型
| 名字 | 数值 | 寓意 |
|---|---|---|
| ET_NONE | 0 | 非文件类型 |
| ET_REL | 1 | 可重定位文件 |
| ET_EXEC | 2 | 可执行文件 |
| ET_DYN | 3 | 共享库 |
| ET_CORE | 4 | Core file |
制定当前ELF运行的CPU架构
下面举例常见的类型
| 名字 | 数值 | 寓意 |
|---|---|---|
| EM_X86_64 | 62 | AMD-x86-64 |
用于指定ELF版本一般都为1
e_entryelf 代码运行的入口
e_flags在e_machine指定的处理器下的一些特性
节头表相关字段e_shoff
节头表在文件的偏移
e_shentsize
节头表中每个条目的大小
e_shnum
节头表中条目的数目
e_phoff
程序头在文件中的偏移
e_phentsize
指定程序头中每个条目的大小
e_phnum
指定程序头中每个条目的个数
每个节头都一个名称,这些名称都存储一个特殊节中。而e_shstrndx 指定这个特殊的节所在节头表的下标
我们先看看这个程序中所有节如下:
一共13个节,其中.shstrtab表示的存储字符串节 。
.shstrtab是section head string table
我们查看这个节内容如下所示:
大致结构如下:
本例中我们依旧使用main.o我们可以到节头表信息如下:
节头表的第一项固定为空节不存储实际内容
每个节头数据结构如下:
typedef struct
{
Elf64_Word sh_name;
Elf64_Word sh_type;
Elf64_Xword sh_flags;
Elf64_Addr sh_addr;
Elf64_Off sh_offset;
Elf64_Xword sh_size;
Elf64_Word sh_link;
Elf64_Word sh_info;
Elf64_Xword sh_addralign;
Elf64_Xword sh_entsize;
} Elf64_Shdr;
sh_nameelf节头规则详细文档 https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.sheader.html
节名在字符串节中下标,本例中字符串节名称为.shstrtab.我们举例其中一个节.text
我们看到.text节的sh_name为20h也就是十进制32.我们看下.shstrtab指向的字节数组的32位
这个字段根据节的内容(content)和语义(semantics)对节进行分类。
分类类型有很多种,我们只举例其中比较常见的类型。
| 名称 | 数值 | 解释 |
|---|---|---|
| SHT_PROGBITS | 1 | 一般存放代码或者数据类型 |
| SHT_STRTAB | 3 | 存放字符串表类型 |
| SHT_NOBITS | 8 | 表示这个节不存储信息在文件中,比如未初始化的数据 |
.text与.data一般就是SHT_PROGBITS (text存储代码 data存储数据)
.shstrtab一般是SHT_STRTAB
.bss一般是SHT_NOBITS (存储全局未初始化数据等)
字段标记是否可读可写可执行等,以及是否在内存中分配内存(SHF_ALLOC)
下图为枚举值表:
这个节被加载后对应VA地址
sh_offset这个节在文件中的偏移
sh_size节大小(不是指节头大小哦)
sh_link一般用于关联节所在节头表的数组下标,一般为0
举例说明:
我们节中有一个专门用于重定位的节如.rela.text 就是用来重定位代码段部分代码的。
sh_link表示这个节所使用的的符号表节在节头表的下标
sh_info表示哪个节需要重定向。这个值指向在节头表中的索引。
如下图所示 :
一般用于关联节所在节头表的数组下标,一般为0
sh_addralign对其数值。如果为0或者1表示不对齐。
sh_addr必须为0或者对其sh_addralign取模
ent是entry缩写。
部分节内部存储是固定数据结构条目数组,针对这类别节sh_entsize指代的是每个条目的字节大小。
举例说明:
符号表节名为.symtab,它存储若干固定结构的符号信息。如下图所示
符号表每个条目数据结构如下所示
typedef struct {
Elf32_Word st_name;
Elf32_Addr st_value;
Elf32_Word st_size;
unsigned char st_info;
unsigned char st_other;
Elf32_Half st_shndx;
} Elf32_Sym;//32位
typedef struct {
Elf64_Word st_name;
unsigned char st_info;
unsigned char st_other;
Elf64_Half st_shndx;
Elf64_Addr st_value;
Elf64_Xword st_size;
} Elf64_Sym;//64位
sh_entsize指的就是Elf64_Sym或者Elf32_Sym的大小
参考ELF Header



