在上一篇文章中我们编写了Allocator
和File
来简化对内存管理和文件的操作,在本章中我们编写ELF文件加载功能在本章中我们需要了解一下ELF文件的相关知识
ELF文件基本介绍
ELF文件有三种不同的类型,可重定位文件,可执行文件和共享目标文件。其中可重定位文件是由编译器和汇编器创建,在运行之前需要经过链接器处理
- 可重定位文件: 该文件包含代码和数据,用于连接成可执行文件或共享目标文件,静态链接库也属于该类,例如在Linux中
.a
和.o
文件 - 可执行文件: 包含二进制代码和数据,可以直接复制到内存并执行,例如Linux中
/bin
/usr/bin
目录下的文件 - 共享目标文件: 一种特殊类型的可重定位文件,可以加载或运行时被动态加载到内存并链接,例如Linux中的
.so
文件(动态链接库)
在Linux中我们可以使用file
命令来查看文件的类型,例如我们file
命令查看我们编译出的内核文件
$ file kernel
kernel: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, with debug_info, not stripped
我们可以看到kernel文件的信息为 ELF 64位可执行文件,架构为x86-64,已经完成链接,包含调试信息
如果要查看elf文件的详细信息可以使用readelf
命令,在使用readelf
命令时必须要使用参数,参数的功能如下(常用)
-h
:仅显示ELF文件的文件头信息-l
: 仅显示ELF文件的程序段信息-S
: 仅显示ELF文件的section信息-A
: 仅显示ELF运行的目标架构-V
: 仅显示ELF文件版本信息-a
: 等同于-h
-l
-S
-s
-r
-d
-V
-A
-I
等参数组合
ELF文件基本结构
在本章中我们只关注不含有动态链接的可执行ELF文件(主要是分析编译出的内核文件),ELF文件按照不同的视角分为链接视角和可执行视角
ELF头部
ELF头部描述了该ELF文件的自身信息,并提供寻找文件其他段的方法
编译出的内核文件使用readelf -h kernel
能获取到以下信息
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: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x202060
Start of program headers: 64 (bytes into file)
Start of section headers: 34808 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 7
Size of section headers: 64 (bytes)
Number of section headers: 11
Section header string table index: 9
显示的内容中
- Magic称为幻数,用于标识文件的类型,
7F 45 4C 46
是ELF文件的幻数,Windows 的PE文件的幻数为4D 5A
- Class表示该ELF文件的类别,分为ELF32和ELF64
- Data表示ELF文件使用的字节序,在本例中为小端序
- Version表示ELF文件的版本,目前只有一个版本,因此固定为1,
- Entry point address 表示程序的入口地址,当将elf文件内容加载到内存后,代码执行开始的地址
- Start of program headers 表示程序头的起始位置(相对于文件而言),在本例中程序头起始位置在该文件的第64字节处
- Start of section headers表示程序段的起始位置(相对于文件而言),在本例中程序头起始位置在该文件的第34808 字节处
- Flags: 特定于处理器的标志,32位和64位Intel架构都没有定义标志,因此eflags的值是0
- Size of this header 表示ELF文件头部所占用的大小
- Size of program headers 表示所有的ELF程序头所占用的大小
- Number of program headers表示ELF程序头的个数
- Size of section headers 表示ELF段头所占用大小
- Number of section headers表示ELF段头的个数
- Section header string table index表示段头字符串表的索引
在rust中我们将ELF头部结构定义如下
#[repr(C)]
pub struct ElfHeader64 {
pub magic: [u8; 4],
pub class: u8,
pub endianness: u8,
pub header_version: u8,
pub abi: u8,
pub abi_version: u8,
pub unused: [u8; 7],
pub elftype: u16,
pub machine: u16,
pub elf_version: u32,
pub entry: <Self as GenElfHeader>::Word, // u64 in elf 64 file
pub phoff: <Self as GenElfHeader>::Word, // u64 in elf 64 file
pub shoff: <Self as GenElfHeader>::Word, // u64 in elf 64 file
pub flags: u32,
pub ehsize: u16,
pub phentsize: u16,
pub phnum: u16,
pub shentsize: u16,
pub shnum: u16,
pub shstrndx: u16,
}
其中<Self as GenElfHeader>::Word
是针对每一个ELF文件位数不同而制定的,例如32位ELF文件中<Self as GenElfHeader>::Word
的值就是u32
,64位ELF文件的值就是u64
以下是我们编译出来的内核文件二进制内容(节选的开头部分)每一列表示一个字节例如7F
就是一个字节大小为u8
,2个字节大小为u1