前言
ELF
是linux
动态库,可执行文件的格式.具体介绍可参阅wiki Executable and Linkable Format。可以类比到windows下exe的格式。
首先推荐一个写的不错文档ELF格式
我们知道程序
需要加载内存后才能运行。但是ELF文件加载到内存后布局会变化和原始ELF文件相比,加载器
会将相同的节属性(比如只读)合并一个段。所以ELF
也就有了两种视图,一种未加载前静态视图,另一种是加载后的动态视图。
我们首先了解静态视图
下ELF
文件格式如下:
你可以把ELF内容大致分为四个部分:
(1) ELF头部
(2) 节
(3) 节表头
(4) 程序头
ELF头部
固定在ELF文件开始- 需要留意程序头和节表头可以位于ELF任意位置,他们位置被
ELF头部
中的属性指定- 节分有很多种格式需要根据节类别区分,比如重定义节 与代码节
本文根据以下代码作为示例
#include <stdio.h>
static 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 \r\n",mystaticVar);
return 0;
}
void hell(){
testfun();
}
目标文件 main.o
可执行文件main.out
ELF头部
我们以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]; /* Magic number and other info */
Elf64_Half e_type; /* Object file type */
Elf64_Half e_machine; /* Architecture */
Elf64_Word e_version; /* Object file version */
Elf64_Addr e_entry; /* Entry point virtual address */
Elf64_Off e_phoff; /* Program header table file offset */
Elf64_Off e_shoff; /* Section header table file offset */
Elf64_Word e_flags; /* Processor-specific flags */
Elf64_Half e_ehsize; /* ELF header size in bytes */
Elf64_Half e_phentsize; /* Program header table entry size */
Elf64_Half e_phnum; /* Program header table entry count */
Elf64_Half e_shentsize; /* Section header table entry size */
Elf64_Half e_shnum; /* Section header table entry count */
Elf64_Half e_shstrndx; /* Section header string table index */
} Elf64_Ehdr;
e_ident
一个16
字节数组大小
完整参数参阅请参阅 ELF Header
在elf.h
有EI_XXX
表示上面下标位置
//elf.h
#define EI_NIDENT (16)
#define EI_MAG0 0 /* File identification byte 0 index */
#define ELFMAG0 0x7f /* Magic number byte 0 */
#define EI_MAG1 1 /* File identification byte 1 index */
#define ELFMAG1 'E' /* Magic number byte 1 */
#define EI_MAG2 2 /* File identification byte 2 index */
#define ELFMAG2 'L' /* Magic number byte 2 */
#define EI_MAG3 3 /* File identification byte 3 index */
#define ELFMAG3 'F' /* Magic number byte 3 */
/* Conglomeration of the identification bytes, for easy testing as a word. */
#define ELFMAG "\177ELF"
#define SELFMAG 4
#define EI_CLASS 4 /* File class byte index */
#define ELFCLASSNONE 0 /* Invalid class */
#define ELFCLASS32 1 /* 32-bit objects */
#define ELFCLASS64 2 /* 64-bit objects */
#define ELFCLASSNUM 3
#define EI_DATA 5 /* Data encoding byte index */
#define ELFDATANONE 0 /* Invalid data encoding */
#define ELFDATA2LSB 1 /* 2's complement, little endian */
#define ELFDATA2MSB 2 /* 2's complement, big endian */
#define ELFDATANUM 3
#define EI_VERSION 6 /* File version byte index */
/* Value must be EV_CURRENT */
#define EI_OSABI 7 /* OS ABI identification */
#define ELFOSABI_NONE 0 /* UNIX System V ABI */
#define ELFOSABI_SYSV 0 /* Alias. */
#define ELFOSABI_HPUX 1 /* HP-UX */
#define ELFOSABI_NETBSD 2 /* NetBSD. */
#define ELFOSABI_GNU 3 /* Object uses GNU ELF extensions. */
#define ELFOSABI_LINUX ELFOSABI_GNU /* Compatibility alias. */
#define ELFOSABI_SOLARIS 6 /* Sun Solaris. */
#define ELFOSABI_AIX 7 /* IBM AIX. */
#define ELFOSABI_IRIX 8 /* SGI Irix. */
#define ELFOSABI_FREEBSD 9 /* FreeBSD. */
#define ELFOSABI_TRU64 10 /* Compaq TRU64 UNIX. */
#define ELFOSABI_MODESTO 11 /* Novell Modesto. */
#define ELFOSABI_OPENBSD 12 /* OpenBSD. */
#define ELFOSABI_ARM_AEABI 64 /* ARM EABI */
#define ELFOSABI_ARM 97 /* ARM */
#define ELFOSABI_STANDALONE 255 /* Standalone (embedded) application */
#define EI_ABIVERSION 8 /* ABI version */
#define EI_PAD 9 /* Byte index of padding bytes */
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[]数组大小 |
e_type
表示当前ELF文件类型,下面举例常见的类型
名字 | 数值 | 寓意 |
---|---|---|
ET_NONE | 0 | 非文件类型 |
ET_REL | 1 | 可重定位文件 |
ET_EXEC | 2 | 可执行文件 |
ET_DYN | 3 | 共享库 |
ET_CORE | 4 | Core file |
e_machine
制定当前ELF运行的CPU架构
下面举例常见的类型
名字 | 数值 | 寓意 |
---|---|---|
EM_X86_64 | 62 | AMD-x86-64 |
e_version
用于指定ELF版本一般都为1
e_entry
elf 代码运行的入口
e_flags
在e_machine指定的处理器下的一些特性
节头表相关字段
e_shoff
节头表在文件的偏移
e_shentsize
节头表中每个条目的大小
e_shnum
节头表中条目的数目
程序头相关字段
e_phoff
程序头在文件中的偏移
e_phentsize
指定程序头中每个条目的大小
e_phnum
指定程序头中每个条目的个数
e_shstrndx
每个节头都一个名称,这些名称都存储一个特殊节中。而e_shstrndx 指定这个特殊的节所在节头表的下标
我们先看看这个程序中所有节如下:
一共13个节,其中.shstrtab
表示的存储字符串节 。
.shstrtab
是section head string table
我们查看这个节内容如下所示:
大致结构如下:
节头
本例中我们依旧使用main.o
我们可以到节头表信息如下:
节头表的第一项固定为空节不存储实际内容
每个节头数据结构如下:
typedef struct
{
Elf64_Word sh_name; /* Section name (string tbl index) */
Elf64_Word sh_type; /* Section type */
Elf64_Xword sh_flags; /* Section flags */
Elf64_Addr sh_addr; /* Section virtual addr at execution */
Elf64_Off sh_offset; /* Section file offset */
Elf64_Xword sh_size; /* Section size in bytes */
Elf64_Word sh_link; /* Link to another section */
Elf64_Word sh_info; /* Additional section information */
Elf64_Xword sh_addralign; /* Section alignment */
Elf64_Xword sh_entsize; /* Entry size if section holds table */
} Elf64_Shdr;
elf节头规则详细文档 https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.sheader.html
sh_name
节名在字符串节中下标,本例中字符串节名称为.shstrtab
.我们举例其中一个节.text
我们看到.text节的sh_name为20h也就是十进制32.我们看下.shstrtab
指向的字节数组的32位
sh_type
这个字段根据节的内容(content)和语义(semantics)对节进行分类。
分类类型有很多种,我们只举例其中比较常见的类型。
名称 | 数值 | 解释 |
---|---|---|
SHT_PROGBITS | 1 | 一般存放代码或者数据类型 |
SHT_STRTAB | 3 | 存放字符串表类型 |
SHT_NOBITS | 8 | 表示这个节不存储信息在文件中,比如未初始化的数据 |
.text
与.data
一般就是SHT_PROGBITS
(text存储代码 data存储数据)
.shstrtab
一般是SHT_STRTAB
.bss
一般是SHT_NOBITS
(存储全局未初始化数据等)
sh_flags
字段标记是否可读可写可执行等,以及是否在内存中分配内存(SHF_ALLOC
)
下图为枚举值表:
sh_addr
这个节被加载后对应VA地址
sh_offset
这个节在文件中的偏移
sh_size
节大小(不是指节头大小哦)
sh_link
一般用于关联节所在节头表的数组下标,一般为0
举例说明:
我们节中有一个专门用于重定位的节如.rela.text
就是用来重定位代码段部分代码的。
sh_link
表示这个节所使用的的符号表节在节头表的下标
sh_info
表示哪个节需要重定向。这个值指向在节头表中的索引。
如下图所示 :
sh_info
一般用于关联节所在节头表的数组下标,一般为0
sh_addralign
对其数值。如果为0或者1表示不对齐。
sh_addr必须为0或者对其sh_addralign取模
sh_entsize
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的大小