The ELF Binary Format
1. ELF file types
一个ELF文件可以被标记为以下几种类型之一。
ET_NONE
: 未知类型,不确定或未定义ET_REL
:重定位文件, ELF被标记为relocatable意味者该文件被标记一段可重定位的代码,或目标文件。可重定位目标文件通常是还未被链接到可执行程序的一段位置独立的代码Position independent code (PIC)。编译完代码后可以看到.o格式的文件,这种文件包含了创建可执行文件所需要的代码和数据。ET_EXEC
: 可执行文件, ELF类型为executable,意味着该文件被标记为可执行文件。 这些类型的文件也称为程序,是一个进程开始执行的入口。ET_DYN
: 共享目标文件,ELF类型为dynamic,意味着该文件被标记为了一个动态的可链接的目标文件,也称为共享库。这类共享库会在程序运行时被装载并链接到程序的进程镜像中。ET_CORE
: 核心文件,在程序崩溃或者进程传递了一个SIGSEGV信号(分段违规)时,会在核心文件中记录整个进程的镜像消息,可以用GDB读取这类文件来辅助调试并查找程序崩溃的原因。
使用readelf -h 可以查看ELF文件,查看原始的ELF文件头。ELF文件头从文件的0偏移开始,是除了文件头之后剩余部分文件的一个映射。随便找了一个在BSD编辑的EXEC文件,readelf -h 之后如下图
ELF头部的结构定义如下:
#define EI_NIDENT 16
typedef struct {
unsigned char e_ident[EI_NIDENT];
uint16_t e_type;
uint16_t e_machine;
uint32_t e_version;
ElfN_Addr e_entry;
ElfN_Off e_phoff;
ElfN_Off e_shoff;
uint32_t e_flags;
uint16_t e_ehsize;
uint16_t e_phentsize;
uint16_t e_phnum;
uint16_t e_shentsize;
uint16_t e_shnum;
uint16_t e_shstrndx;
} ElfN_Ehdr;
ps:readelf命令
readelf -S <object> //查询节头表
readelf -l <object> //查询程序头表
readelf -s <object> //查询符号表
readelf -e <object> //查询ELF文件头数据
readelf -r <object> //查询重定位入口
readelf -d <object> //查询动态段
2. ELF program headers
ELF程序头是对二进制文件中段的描述,是程序装载必需的一部分。段(segment)是在内存装载时被解析的,描述了磁盘上可执行文件的内存布局以及如何映射到内存中。
程序头描述了可执行文件(包括共享库)中段及其类型(为那种类型的数据或代码而保留的段)。
//ELF32_Phdr结构,构成了32位ELF可执行文件程序头表的一个程序头条目。 program header table
typedef struct {
uint32_t p_type; (segment type)
Elf32_Off p_offset; (segment offset)
Elf32_Addr p_vaddr; (segment virtual address)
Elf32_Addr p_paddr; (segment physical address)
uint32_t p_filesz; (size of segment in the file)
uint32_t p_memsz; (size of segment in memory)
uint32_t p_flags; (segment flags, I.E execute|read|read)
uint32_t p_align; (segment alignment in memory)
} Elf32_Phdr;
// 这些变量描述了段在文件和内存中的布局
下面讨论5个常见的程序头类型。
1.PT_LOAD
一个可执行文件至少有一个PT_LOAD类型的段。
这类程序头描述的是可装载的段(a loadable segment),即这种类型的段将被装载或映射到内存中。
一个需要动态链接的ELF可执行文件通常包含以下两个可装载的段(类型为PT_LOAD)
- 存放程序代码的text段;
- 存放全局变量和动态链接信息的data段。
上面的两个段将会被映射到内存中,并根据p_align中存放的值在内存中对齐。
2.PT_DYNAMIC-动态段的Phdr
动态段是动态链接可执行文件所特有的,包含了动态链接器所必需的一些信息。在动态段中包含了一些标记值和指针,包括但不限于以下内容:
- 运行时需要链接的共享库列表;
- 全局偏移表(GOT,Global offset table)的地址;
- 重定位条目的相关信息。
Following is complete list of the tag names:完整的标记名列表
Tag name/标记名 | 描述 |
---|---|
DT_HASH | 符号散列表的地址 |
DT_STRTAB | 字符串表的地址 |
DT_SYMTAB | 符号表的地址 |
DT_RELA | 相对地址重定位表的地址 |
DT_RELASZ | Rela表的字节大小 |
DT_RELAENT | Rela表条目的字节大小 |
DT_STRSZ | 字符串表的字节大小 |
DT_SYMENT | 符号表条目的字节大小 |
DT_INIT | 初始化函数的地址 |
DT_FINT | 终止函数的地址 |
DT_SONAME | 共享目标文件名的字符串表偏移量 |
DT_RPATH | 库搜索路径的字符串表偏移量 |
DT_SYMBOLIC | 修改链接器,在可执行文件之前的共享目标文件中搜索符号 |
DT_REL | Rel relocs表的地址 |
DT_RELSZ | Rel表的字节大小 |
DT_RELENT | Rel表条目的字节大小 |
DT_PLTREL | PLT引用的reloc类型(Rela或Rel) |
DT_DEBUG | 还未进行定义,为测试保留 |
DT_TEXTREL | 缺少此项表明重定位只能应用于可写段 |
DT_JMPREL | 仅用于PLT的重定位条目地址 |
DT_BIND_NOW | 指示动态链接器在将控制权交给可执行文件之前处理所有的重定位 |
DT_RUNPATH | 库搜索路径的字符串表偏移量 |
动态段包含了一些结构体,在这些结构中存放着与动态链接相关的信息。
//32位ELF文件的动态段结构体:d_tag成员变量控制着d_un的含义。
typedef struct {
Elf32_Sword d_tag;
union {
Elf32_Word d_val;
Elf32_Addr d_ptr;
} d_un;
} Elf32_Dyn;
extern Elf32_Dyn _DYNAMIC[];
3. PT_NOTE
PT_NOTE类型的段可能保存了与特定供应商或者系统相关的附加消息。
//ELF规范中的定义
Sometimes a vendor or system builder needs to mark an object file with special information that other programs will check for conformance, compatibility, and so on. Sections of type SHT_NOTE and program header elements of type PT_NOTE can be used for this purpose. (SHT_NOTE类型的节section和PT_NOTE类型的程序头元素可以用于这一目的)The note information in sections and program header elements holds any number of entries, each of which is an array of 4-byte words in the format of the target processor. Labels appear below to help explain note information organization, but they are not part of the specification.
这一段只保存了操作系统的规范信息,在可执行文件运行时是不需要这个段的。(since the system will just assume the executable is native either way)。 容易成为病毒感染的一个地方。
NOTE段病毒(http://vxheavens.com/lib/vhe06.html)
4. PT_INTERP
PT_INTERP段只将位置和大小信息存放在一个以null为终止符的字符串中,是对程序解释器位置的描述。例如 /lib/linux-ld